Newer
Older
mbed-os / storage / blockdevice / source / SFDP.cpp
/*
 * Copyright (c) 2020, Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <algorithm>
#include <memory>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "platform/mbed_error.h"
#include "blockdevice/internal/SFDP.h"

#if (DEVICE_SPI || DEVICE_QSPI || DEVICE_OSPI)

#include "mbed-trace/mbed_trace.h"
#define TRACE_GROUP "SFDP"

namespace {

/* Extracts Parameter ID MSB from the second DWORD of a parameter header */
inline uint8_t sfdp_get_param_id_msb(uint32_t dword2)
{
    return (dword2 & 0xFF000000) >> 24;
}

/* Extracts Parameter Table Pointer from the second DWORD of a parameter header */
inline uint32_t sfdp_get_param_tbl_ptr(uint32_t dword2)
{
    return dword2 & 0x00FFFFFF;
}
}

namespace mbed {

// Erase Types Params
constexpr int SFDP_BASIC_PARAM_TABLE_ERASE_TYPE_1_BYTE = 29; ///< Erase Type 1 Instruction
constexpr int SFDP_BASIC_PARAM_TABLE_ERASE_TYPE_2_BYTE = 31; ///< Erase Type 2 Instruction
constexpr int SFDP_BASIC_PARAM_TABLE_ERASE_TYPE_3_BYTE = 33; ///< Erase Type 3 Instruction
constexpr int SFDP_BASIC_PARAM_TABLE_ERASE_TYPE_4_BYTE = 35; ///< Erase Type 4 Instruction
constexpr int SFDP_BASIC_PARAM_TABLE_ERASE_TYPE_1_SIZE_BYTE = 28; ///< Erase Type 1 Size
constexpr int SFDP_BASIC_PARAM_TABLE_ERASE_TYPE_2_SIZE_BYTE = 30; ///< Erase Type 2 Size
constexpr int SFDP_BASIC_PARAM_TABLE_ERASE_TYPE_3_SIZE_BYTE = 32; ///< Erase Type 3 Size
constexpr int SFDP_BASIC_PARAM_TABLE_ERASE_TYPE_4_SIZE_BYTE = 34; ///< Erase Type 4 Size
constexpr int SFDP_BASIC_PARAM_TABLE_4K_ERASE_TYPE_BYTE = 1; ///< 4 Kilobyte Erase Instruction

constexpr int SFDP_ERASE_BITMASK_TYPE_4K_ERASE_UNSUPPORTED = 0xFF;

/** SFDP Header */
struct sfdp_hdr {
    uint8_t SIG_B0; ///< SFDP Signature, Byte 0
    uint8_t SIG_B1; ///< SFDP Signature, Byte 1
    uint8_t SIG_B2; ///< SFDP Signature, Byte 2
    uint8_t SIG_B3; ///< SFDP Signature, Byte 3
    uint8_t R_MINOR; ///< SFDP Minor Revision
    uint8_t R_MAJOR; ///< SFDP Major Revision
    uint8_t NPH; ///< Number of parameter headers (0-based, 0 indicates 1 parameter header)
    uint8_t ACP; ///< SFDP Access Protocol
};

/** SFDP Parameter header */
struct sfdp_prm_hdr {
    uint8_t PID_LSB; ///< Parameter ID LSB
    uint8_t P_MINOR; ///< Parameter Minor Revision
    uint8_t P_MAJOR; ///< Parameter Major Revision
    uint8_t P_LEN;   ///< Parameter length in DWORDS
    uint32_t DWORD2; ///< Parameter ID MSB + Parameter Table Pointer
};

/** Parse SFDP Header
 * @param sfdp_hdr_ptr Pointer to memory holding an SFDP header
 * @return Number of Parameter Headers on success, -1 on failure
 */
int sfdp_parse_sfdp_header(sfdp_hdr *sfdp_hdr_ptr)
{
    if (!(memcmp(sfdp_hdr_ptr, "SFDP", 4) == 0 && sfdp_hdr_ptr->R_MAJOR == 1)) {
        tr_error("Verify SFDP signature and version Failed");
        return -1;
    }

    tr_debug("Verified SFDP Signature and version successfully");

    int hdr_cnt = sfdp_hdr_ptr->NPH + 1;
    tr_debug("Number of parameter headers: %d", hdr_cnt);

    return hdr_cnt;
}

/** Parse Parameter Header
 * @param phdr_ptr Pointer to memory holding a single SFDP Parameter header
 * @param hdr_info Reference to a Parameter Table structure where info about the table is written
 * @return 0 on success, -1 on failure
 */
int sfdp_parse_single_param_header(sfdp_prm_hdr *phdr_ptr, sfdp_hdr_info &hdr_info)
{
    if (phdr_ptr->P_MAJOR != 1) {
        tr_error("Parameter header: Major Version must be 1!");
        return -1;
    }

    int param_id_msb = sfdp_get_param_id_msb(phdr_ptr->DWORD2);

    /* MSB JEDEC ID */
    if (param_id_msb == 0xFF) {

        /* LSB JEDEC ID */
        switch (phdr_ptr->PID_LSB) {
            case 0x0:
                tr_debug("Parameter header: JEDEC Basic Flash - Revision %" PRIX8 ".%" PRIX8 "",
                         phdr_ptr->P_MAJOR,
                         phdr_ptr->P_MINOR);
                hdr_info.bptbl.addr = sfdp_get_param_tbl_ptr(phdr_ptr->DWORD2);
                hdr_info.bptbl.size = std::min((phdr_ptr->P_LEN * 4), SFDP_BASIC_PARAMS_TBL_SIZE);
                break;
            case 0x81:
                tr_info("Parameter header: Sector Map");
                hdr_info.smptbl.addr = sfdp_get_param_tbl_ptr(phdr_ptr->DWORD2);
                hdr_info.smptbl.size = phdr_ptr->P_LEN * 4;
                break;
            case 0x84:
                tr_info("Parameter header: 4-byte Address Instruction");
                hdr_info.fbatbl.addr = sfdp_get_param_tbl_ptr(phdr_ptr->DWORD2);
                hdr_info.fbatbl.size = phdr_ptr->P_LEN * 4;
                break;
            /* Unsupported */
            case 0x03:
                tr_info("UNSUPPORTED:Parameter header: Replay Protected Monotonic Counters");
                break;
            case 0x05:
                tr_info("UNSUPPORTED:Parameter header: eXtended Serial Peripheral Interface (xSPI) Profile 1.0");
                break;
            case 0x06:
                tr_info("UNSUPPORTED:Parameter header: eXtended Serial Peripheral Interface (xSPI) Profile 2.0");
                break;
            case 0x87:
                tr_info("UNSUPPORTED:Parameter header: SCCR Map for SPI Memory Devices");
                break;
            case 0x88:
                tr_info("UNSUPPORTED:Parameter header: SCCR Map Offsets for Multi-Chip SPI Memory Devices");
                break;
            case 0x09:
                tr_info("UNSUPPORTED:Parameter header: SCCR Map for xSPI Profile 2.0 Memory Devices");
                break;
            case 0x0A:
                tr_info("UNSUPPORTED:Parameter header: Command Sequences to Change to Octal DDR (8D-8D-8D) mode");
                break;
            case 0x0C:
                tr_info("UNSUPPORTED:Parameter header: x4 Quad IO with DS");
                break;
            case 0x8D:
                tr_info("UNSUPPORTED:Parameter header: Command Sequences to Change to Quad DDR (4S-4D-4D) mode");
                break;
            default:
                tr_debug("Parameter header: unknown JEDEC header. Parameter ID LSB: 0x%" PRIX8 "; MSB: 0x%" PRIX8 "",
                         phdr_ptr->PID_LSB,
                         sfdp_get_param_id_msb(phdr_ptr->DWORD2));
        }
    } else if (param_id_msb >= 0x80) { // MSB JEDEC ID
        tr_debug("Parameter header: unknown JEDEC header. Parameter ID LSB: 0x%" PRIX8 "; MSB: 0x%" PRIX8 "",
                 phdr_ptr->PID_LSB,
                 sfdp_get_param_id_msb(phdr_ptr->DWORD2));
    } else { // MSB Vendor ID
        tr_info("Parameter header: vendor specific header. Parameter ID LSB: 0x%" PRIX8 "; MSB: 0x%" PRIX8 "",
                phdr_ptr->PID_LSB,
                sfdp_get_param_id_msb(phdr_ptr->DWORD2));
    }

    return 0;
}

int sfdp_parse_headers(Callback<int(bd_addr_t, sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info)
{
    bd_addr_t addr = 0x0;
    int number_of_param_headers = 0;
    size_t data_length;

    {
        data_length = SFDP_HEADER_SIZE;
        uint8_t sfdp_header[SFDP_HEADER_SIZE];

        int status = sfdp_reader(
                         addr,
                         SFDP_READ_CMD_ADDR_TYPE,
                         SFDP_READ_CMD_INST,
                         SFDP_READ_CMD_DUMMY_CYCLES,
                         sfdp_header,
                         data_length
                     );
        if (status < 0) {
            tr_error("Retrieving SFDP Header failed");
            return -1;
        }

        number_of_param_headers = sfdp_parse_sfdp_header((sfdp_hdr *)sfdp_header);
        if (number_of_param_headers < 0) {
            return number_of_param_headers;
        }
    }

    addr += SFDP_HEADER_SIZE;

    {
        data_length = SFDP_HEADER_SIZE;
        uint8_t param_header[SFDP_HEADER_SIZE];
        int status;
        int hdr_status;

        // Loop over Param Headers and parse them (currently supports Basic Param Table and Sector Region Map Table)
        for (int idx = 0; idx < number_of_param_headers; idx++) {
            status = sfdp_reader(
                         addr,
                         SFDP_READ_CMD_ADDR_TYPE,
                         SFDP_READ_CMD_INST,
                         SFDP_READ_CMD_DUMMY_CYCLES,
                         param_header,
                         data_length
                     );
            if (status < 0) {
                tr_error("Retrieving a parameter header %d failed", idx + 1);
                return -1;
            }

            hdr_status = sfdp_parse_single_param_header((sfdp_prm_hdr *)param_header, sfdp_info);
            if (hdr_status < 0) {
                return hdr_status;
            }

            addr += SFDP_HEADER_SIZE;
        }
    }

    return 0;
}

static constexpr size_t min_descriptor_size = 8; // two DWORDs

static inline bool is_last_descriptor(const uint8_t *descriptor)
{
    // Last descriptor of the current type (detection command/sector map)
    MBED_ASSERT(nullptr != descriptor);
    return descriptor[0] & 0x01;
}

static inline bool is_sector_map_descriptor(const uint8_t *descriptor)
{
    // true - sector map descriptor
    // false - configuration detection command descriptor
    MBED_ASSERT(nullptr != descriptor);
    return descriptor[0] & 0x02;
}

static int sfdp_detect_sector_map_configuration(
    Callback<int(bd_addr_t, sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader,
    sfdp_hdr_info &sfdp_info,
    uint8_t *&descriptor,
    const uint8_t *table_end,
    uint8_t &config)
{
    config = 0;

    // If the table starts with a sector map descriptor instead of a configuration
    // detection command descriptor, this device has only one configuration (i.e. is
    // not configurable) with ID equal to 0.
    if (is_sector_map_descriptor(descriptor)) {
        return 0;
    }

    // Loop through all configuration detection descriptors and run detection commands
    while (!is_sector_map_descriptor(descriptor) && (descriptor + min_descriptor_size <= table_end)) {
        uint8_t instruction = descriptor[1];
        uint8_t dummy_cycles = descriptor[2] & 0x0F;
        auto addr_size = static_cast<sfdp_cmd_addr_size_t>(descriptor[2] >> 6);
        uint8_t mask = descriptor[3];
        uint32_t cmd_addr;
        memcpy(&cmd_addr, &descriptor[4], sizeof(cmd_addr)); // last 32 bits of the descriptor

        uint8_t rx;
        int status = sfdp_reader(cmd_addr, addr_size, instruction, dummy_cycles, &rx, sizeof(rx));
        if (status < 0) {
            tr_error("Sector Map: Configuration detection command failed");
            return -1;
        }

        // Shift existing bits to the left, so we can add the newly detected bit
        config <<= 1;

        // The mask may apply to any bit of rx, so we can't directly combine
        // (rx & mask) with config. Instead, treat (rx & mask) as a boolean.
        if (rx & mask) {
            config |= 0x01;
        }

        if (is_last_descriptor(descriptor)) {
            // We've processed the last configuration detection command descriptor
            descriptor += min_descriptor_size; // Increment the descriptor for the caller
            return 0;
        }
        descriptor += min_descriptor_size; // next descriptor
    }

    tr_error("Sector Map: Incomplete configuration detection command descriptors");
    return -1;
}

static int sfdp_locate_sector_map_by_config(
    const uint8_t config,
    sfdp_hdr_info &sfdp_info,
    uint8_t *&descriptor,
    const uint8_t *table_end)
{
    // The size of a sector map descriptor depends on the number of regions. Before
    // the number of regions is calculated, use the minimum possible size in the a loop condition.
    while (is_sector_map_descriptor(descriptor) && (descriptor + min_descriptor_size <= table_end)) {
        size_t regions = descriptor[2] + 1; // Region ID starts at 0
        size_t current_descriptor_size = (1 /*header*/ + regions) * 4 /*DWORD size*/;
        if (descriptor + current_descriptor_size > table_end) {
            tr_error("Sector Map: Incomplete sector map descriptor at the end of the table");
            return -1;
        }

        if (descriptor[1] == config) {
            // matching sector map found
            return 0;
        }

        if (is_last_descriptor(descriptor)) {
            // We've processed the last sector map descriptor
            tr_error("Sector Map: Failed to find a sector map that matches the current configuration");
            return -1;
        }

        descriptor += current_descriptor_size; // next descriptor
    }

    tr_error("Sector Map: Incomplete sector map descriptors");
    return -1;
}

int sfdp_parse_sector_map_table(Callback<int(bd_addr_t, sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info)
{
    uint32_t tmp_region_size = 0;
    uint8_t type_mask;
    int prev_boundary = 0;
    // Default set to all type bits 1-4 are common
    int min_common_erase_type_bits = SFDP_ERASE_BITMASK_ALL;

    if (!sfdp_info.smptbl.addr || !sfdp_info.smptbl.size) {
        tr_debug("No Sector Map Table");

        // If there's no sector map, we have a single region sized the entire device size
        sfdp_info.smptbl.region_cnt = 1;
        sfdp_info.smptbl.region_size[0] = sfdp_info.bptbl.device_size_bytes;
        sfdp_info.smptbl.region_high_boundary[0] = sfdp_info.bptbl.device_size_bytes - 1;

        return MBED_SUCCESS;
    }

    /* The number of
     * - sector map configuration detection commands
     * - configurations
     * - regions in each configuration
     * are variable -> the size of this table is variable
     */
    auto smptbl_buff = std::unique_ptr<uint8_t[]>(new (std::nothrow) uint8_t[sfdp_info.smptbl.size]);
    if (!smptbl_buff) {
        tr_error("Failed to allocate memory");
        return -1;
    }

    tr_debug("Parsing Sector Map Table - addr: 0x%" PRIx32 ", Size: %d", sfdp_info.smptbl.addr, sfdp_info.smptbl.size);

    int status = sfdp_reader(
                     sfdp_info.smptbl.addr,
                     SFDP_READ_CMD_ADDR_TYPE,
                     SFDP_READ_CMD_INST,
                     SFDP_READ_CMD_DUMMY_CYCLES,
                     smptbl_buff.get(),
                     sfdp_info.smptbl.size
                 );
    if (status < 0) {
        tr_error("Sector Map: Table retrieval failed");
        return -1;
    }

    uint8_t *table = smptbl_buff.get();
    uint8_t *descriptor = table;

    // Detect which configuration is in use
    uint8_t active_config_id = 0x00;
    status = sfdp_detect_sector_map_configuration(sfdp_reader, sfdp_info, descriptor, table + sfdp_info.smptbl.size, active_config_id);
    if (status != 0) {
        tr_error("Failed to detect sector map configuration");
        return status;
    }

    // Locate the sector map for the configuration
    status = sfdp_locate_sector_map_by_config(active_config_id, sfdp_info, descriptor, table + sfdp_info.smptbl.size);
    if (status != 0) {
        tr_error("Failed to locate a matching sector map");
        return status;
    }

    // Find the number of regions from the sector map
    sfdp_info.smptbl.region_cnt = descriptor[2] + 1;
    if (sfdp_info.smptbl.region_cnt > SFDP_SECTOR_MAP_MAX_REGIONS) {
        tr_error("Sector Map: Supporting up to %d regions, current setup to %d regions - fail",
                 SFDP_SECTOR_MAP_MAX_REGIONS,
                 sfdp_info.smptbl.region_cnt);
        return -1;
    }

    // Loop through the regions and set for each one: size, supported erase types, high boundary offset
    // Calculate the minimum common erase type for all regions
    for (auto idx = 0; idx < sfdp_info.smptbl.region_cnt; idx++) {
        tmp_region_size = ((*((uint32_t *)&descriptor[(idx + 1) * 4])) >> 8) & 0x00FFFFFF; // bits 9-32
        sfdp_info.smptbl.region_size[idx] = (tmp_region_size + 1) * 256; // Region size is 0 based multiple of 256 bytes;

        sfdp_info.smptbl.region_erase_types_bitfld[idx] = descriptor[(idx + 1) * 4] & 0x0F; // bits 1-4

        min_common_erase_type_bits &= sfdp_info.smptbl.region_erase_types_bitfld[idx];

        sfdp_info.smptbl.region_high_boundary[idx] = (sfdp_info.smptbl.region_size[idx] - 1) + prev_boundary;

        prev_boundary = sfdp_info.smptbl.region_high_boundary[idx] + 1;
    }

    // Calc minimum Common Erase Size from min_common_erase_type_bits
    type_mask = SFDP_ERASE_BITMASK_TYPE1;
    // If no common erase type is found between regions
    sfdp_info.smptbl.regions_min_common_erase_size = 0;
    for (auto idx = 0; idx < 4; idx++) {
        if (min_common_erase_type_bits & type_mask) {
            sfdp_info.smptbl.regions_min_common_erase_size = sfdp_info.smptbl.erase_type_size_arr[idx];
            break;
        }
        type_mask = type_mask << 1;
    }

    return 0;
}

size_t sfdp_detect_page_size(uint8_t *basic_param_table_ptr, size_t basic_param_table_size)
{
    constexpr int SFDP_BASIC_PARAM_TABLE_PAGE_SIZE = 40;
    constexpr int SFDP_DEFAULT_PAGE_SIZE = 256;

    unsigned int page_size = SFDP_DEFAULT_PAGE_SIZE;

    if (basic_param_table_size > SFDP_BASIC_PARAM_TABLE_PAGE_SIZE) {
        // Page Size is specified by 4 Bits (N), calculated by 2^N
        int page_to_power_size = ((int)basic_param_table_ptr[SFDP_BASIC_PARAM_TABLE_PAGE_SIZE]) >> 4;
        page_size = 1 << page_to_power_size;
        tr_debug("Detected Page Size: %d", page_size);
    } else {
        tr_debug("Using Default Page Size: %d", page_size);
    }
    return page_size;
}

int sfdp_detect_erase_types_inst_and_size(uint8_t *bptbl_ptr, sfdp_hdr_info &sfdp_info)
{
    uint8_t bitfield = 0x01;

    // Erase 4K Inst is taken either from param table legacy 4K erase or superseded by erase Instruction for type of size 4K
    if (sfdp_info.bptbl.size > SFDP_BASIC_PARAM_TABLE_ERASE_TYPE_1_SIZE_BYTE) {
        // Loop Erase Types 1-4
        for (int idx = 0; idx < 4; idx++) {
            sfdp_info.smptbl.erase_type_inst_arr[idx] = -1; // Default for unsupported type
            sfdp_info.smptbl.erase_type_size_arr[idx] = 1
                                                        << bptbl_ptr[SFDP_BASIC_PARAM_TABLE_ERASE_TYPE_1_SIZE_BYTE + 2 * idx]; // Size is 2^N where N is the table value
            tr_debug("Erase Type(A) %d - Inst: 0x%xh, Size: %d", (idx + 1), sfdp_info.smptbl.erase_type_inst_arr[idx],
                     sfdp_info.smptbl.erase_type_size_arr[idx]);
            if (sfdp_info.smptbl.erase_type_size_arr[idx] > 1) {
                // if size==1 type is not supported
                sfdp_info.smptbl.erase_type_inst_arr[idx] = bptbl_ptr[SFDP_BASIC_PARAM_TABLE_ERASE_TYPE_1_BYTE
                                                                      + 2 * idx];

                if ((sfdp_info.smptbl.erase_type_size_arr[idx] < sfdp_info.smptbl.regions_min_common_erase_size)
                        || (sfdp_info.smptbl.regions_min_common_erase_size == 0)) {
                    //Set default minimal common erase for signal region
                    sfdp_info.smptbl.regions_min_common_erase_size = sfdp_info.smptbl.erase_type_size_arr[idx];
                }
                sfdp_info.smptbl.region_erase_types_bitfld[0] |= bitfield; // If there's no region map, set region "0" types bitfield as default
            }

            tr_debug("Erase Type %d - Inst: 0x%xh, Size: %d", (idx + 1), sfdp_info.smptbl.erase_type_inst_arr[idx],
                     sfdp_info.smptbl.erase_type_size_arr[idx]);
            bitfield = bitfield << 1;
        }
    } else {
        tr_debug("Erase types are not available - falling back to legacy 4k erase instruction");

        sfdp_info.bptbl.legacy_erase_instruction = bptbl_ptr[SFDP_BASIC_PARAM_TABLE_4K_ERASE_TYPE_BYTE];
        if (sfdp_info.bptbl.legacy_erase_instruction == SFDP_ERASE_BITMASK_TYPE_4K_ERASE_UNSUPPORTED) {
            tr_error("Legacy 4k erase instruction not supported");
            return -1;
        }
    }

    return 0;
}

int sfdp_find_addr_region(bd_addr_t offset, const sfdp_hdr_info &sfdp_info)
{
    if ((offset >= sfdp_info.bptbl.device_size_bytes) || (sfdp_info.smptbl.region_cnt == 0)) {
        return -1;
    }

    if (sfdp_info.smptbl.region_cnt == 1) {
        return 0;
    }

    for (int idx = 0; idx < sfdp_info.smptbl.region_cnt; idx++) {

        if (offset <= sfdp_info.smptbl.region_high_boundary[idx]) {
            return idx;
        }
    }
    return -1;

}

int sfdp_iterate_next_largest_erase_type(uint8_t bitfield,
                                         bd_size_t size,
                                         bd_addr_t offset,
                                         int region,
                                         const sfdp_smptbl_info &smptbl)
{
    uint8_t type_mask = SFDP_ERASE_BITMASK_TYPE4;
    unsigned int erase_size;
    for (int idx = 3; idx >= 0; idx--) {
        if (bitfield & type_mask) {
            erase_size = smptbl.erase_type_size_arr[idx];
            // Criteria:
            // * offset is aligned to the type's erase size
            // * erase size is no larger than the requested size,
            // * erase range does not exceed the region boundary
            if ((offset % erase_size == 0) && (size >= erase_size) &&
                    (offset + erase_size - 1 <= smptbl.region_high_boundary[region])) {
                return idx;
            }
        }
        type_mask = type_mask >> 1;
    }

    tr_error("No erase type was found for current region addr");
    return -1;
}

int sfdp_detect_device_density(uint8_t *bptbl_ptr, sfdp_bptbl_info &bptbl_info)
{
    // stored in bits - 1
    uint32_t density_bits = (
                                (bptbl_ptr[7] << 24) |
                                (bptbl_ptr[6] << 16) |
                                (bptbl_ptr[5] << 8) |
                                bptbl_ptr[4]);

    bptbl_info.device_size_bytes = (density_bits + 1) / 8;

    tr_info("Density bits: %" PRIu32 " , device size: %llu bytes", density_bits, bptbl_info.device_size_bytes);

    return 0;
}

#if (DEVICE_QSPI || DEVICE_OSPI)
int sfdp_detect_addressability(uint8_t *bptbl_ptr, sfdp_bptbl_info &bptbl_info)
{
    // Check that density is not greater than 4 gigabits (i.e. that addressing beyond 4 bytes is not required)
    if ((bptbl_ptr[7] & 0x80) != 0) {
        return -1;
    }

    return 0;
}
#elif DEVICE_SPI
int sfdp_detect_addressability(uint8_t *bptbl_ptr, sfdp_bptbl_info &bptbl_info)
{
    // Check address size, currently only supports 3byte addresses
    if ((bptbl_ptr[2] & 0x4) != 0 || (bptbl_ptr[7] & 0x80) != 0) {
        return -1;
    }

    return 0;
}
#endif

} /* namespace mbed */
#endif /* (DEVICE_SPI || DEVICE_QSPI || DEVICE_OSPI) */