Mistake on this page?
Report an issue in GitHub or email us

Storage

The two storage APIs are BlockDevice and FileSystem. These APIs provide filesystem and file operations on a block device, as well as an interface for access to block-based storage.

Block Devices

The block device API provides an interface for access to block-based storage. You can use a block device to back a full file system or write to it directly.

BlockDevice class reference

Public Member Functions
virtual ~BlockDevice ()
virtual int init ()=0
virtual int deinit ()=0
virtual int read (void *buffer, bd_addr_t addr, bd_size_t size)=0
virtual int program (const void *buffer, bd_addr_t addr, bd_size_t size)=0
virtual int erase (bd_addr_t addr, bd_size_t size)
virtual int trim (bd_addr_t addr, bd_size_t size)
virtual bd_size_t get_read_size () const =0
virtual bd_size_t get_program_size () const =0
virtual bd_size_t get_erase_size () const
virtual bd_size_t size () const =0
bool is_valid_read (bd_addr_t addr, bd_size_t size) const
bool is_valid_program (bd_addr_t addr, bd_size_t size) const
bool is_valid_erase (bd_addr_t addr, bd_size_t size) const

C++ API Reference

SD card block device example

Block device for SD cards.

/* mbed Microcontroller Library
 * Copyright (c) 2006-2013 ARM Limited
 *
 * 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.
 */

/* Introduction
 * ------------
 * SD and MMC cards support a number of interfaces, but common to them all
 * is one based on SPI. Since we already have the mbed SPI Interface, it will
 * be used for SD cards.
 *
 * The main reference I'm using is Chapter 7, "SPI Mode" of:
 *  http://www.sdcard.org/developers/tech/sdcard/pls/Simplified_Physical_Layer_Spec.pdf
 *
 * SPI Startup
 * -----------
 * The SD card powers up in SD mode. The start-up procedure is complicated
 * by the requirement to support older SDCards in a backwards compatible
 * way with the new higher capacity variants SDHC and SDHC.
 *
 * The following figures from the specification with associated text describe
 * the SPI mode initialisation process:
 *  - Figure 7-1: SD Memory Card State Diagram (SPI mode)
 *  - Figure 7-2: SPI Mode Initialization Flow
 *
 * Firstly, a low initial clock should be selected (in the range of 100-
 * 400kHZ). After initialisation has been completed, the switch to a
 * higher clock speed can be made (e.g. 1MHz). Newer cards will support
 * higher speeds than the default _transfer_sck defined here.
 *
 * Next, note the following from the SDCard specification (note to
 * Figure 7-1):
 *
 *  In any of the cases CMD1 is not recommended because it may be difficult for the host
 *  to distinguish between MultiMediaCard and SD Memory Card
 *
 * Hence CMD1 is not used for the initialisation sequence.
 *
 * The SPI interface mode is selected by asserting CS low and sending the
 * reset command (CMD0). The card will respond with a (R1) response.
 * In practice many cards initially respond with 0xff or invalid data
 * which is ignored. Data is read until a valid response is received
 * or the number of re-reads has exceeded a maximim count. If a valid
 * response is not received then the CMD0 can be retried. This
 * has been found to successfully initialise cards where the SPI master
 * (on MCU) has been reset but the SDCard has not, so the first
 * CMD0 may be lost.
 *
 * CMD8 is optionally sent to determine the voltage range supported, and
 * indirectly determine whether it is a version 1.x SD/non-SD card or
 * version 2.x. I'll just ignore this for now.
 *
 * ACMD41 is repeatedly issued to initialise the card, until "in idle"
 * (bit 0) of the R1 response goes to '0', indicating it is initialised.
 *
 * You should also indicate whether the host supports High Capicity cards,
 * and check whether the card is high capacity - i'll also ignore this
 *
 * SPI Protocol
 * ------------
 * The SD SPI protocol is based on transactions made up of 8-bit words, with
 * the host starting every bus transaction by asserting the CS signal low. The
 * card always responds to commands, data blocks and errors.
 *
 * The protocol supports a CRC, but by default it is off (except for the
 * first reset CMD0, where the CRC can just be pre-calculated, and CMD8)
 * I'll leave the CRC off I think!
 *
 * Standard capacity cards have variable data block sizes, whereas High
 * Capacity cards fix the size of data block to 512 bytes. I'll therefore
 * just always use the Standard Capacity cards with a block size of 512 bytes.
 * This is set with CMD16.
 *
 * You can read and write single blocks (CMD17, CMD25) or multiple blocks
 * (CMD18, CMD25). For simplicity, I'll just use single block accesses. When
 * the card gets a read command, it responds with a response token, and then
 * a data token or an error.
 *
 * SPI Command Format
 * ------------------
 * Commands are 6-bytes long, containing the command, 32-bit argument, and CRC.
 *
 * +---------------+------------+------------+-----------+----------+--------------+
 * | 01 | cmd[5:0] | arg[31:24] | arg[23:16] | arg[15:8] | arg[7:0] | crc[6:0] | 1 |
 * +---------------+------------+------------+-----------+----------+--------------+
 *
 * As I'm not using CRC, I can fix that byte to what is needed for CMD0 (0x95)
 *
 * All Application Specific commands shall be preceded with APP_CMD (CMD55).
 *
 * SPI Response Format
 * -------------------
 * The main response format (R1) is a status byte (normally zero). Key flags:
 *  idle - 1 if the card is in an idle state/initialising
 *  cmd  - 1 if an illegal command code was detected
 *
 *    +-------------------------------------------------+
 * R1 | 0 | arg | addr | seq | crc | cmd | erase | idle |
 *    +-------------------------------------------------+
 *
 * R1b is the same, except it is followed by a busy signal (zeros) until
 * the first non-zero byte when it is ready again.
 *
 * Data Response Token
 * -------------------
 * Every data block written to the card is acknowledged by a byte
 * response token
 *
 * +----------------------+
 * | xxx | 0 | status | 1 |
 * +----------------------+
 *              010 - OK!
 *              101 - CRC Error
 *              110 - Write Error
 *
 * Single Block Read and Write
 * ---------------------------
 *
 * Block transfers have a byte header, followed by the data, followed
 * by a 16-bit CRC. In our case, the data will always be 512 bytes.
 *
 * +------+---------+---------+- -  - -+---------+-----------+----------+
 * | 0xFE | data[0] | data[1] |        | data[n] | crc[15:8] | crc[7:0] |
 * +------+---------+---------+- -  - -+---------+-----------+----------+
 */

/* If the target has no SPI support then SDCard is not supported */
#ifdef DEVICE_SPI

#include "SDBlockDevice.h"
#include "mbed_debug.h"
#include <errno.h>

/* Required version: 5.9.0 and above */
#if defined(MBED_MAJOR_VERSION) && MBED_MAJOR_VERSION >= 5
#if (MBED_VERSION < MBED_ENCODE_VERSION(5,9,0))
#error "Incompatible mbed-os version detected! Required 5.9.0 and above"
#endif
#else
#warning "mbed-os version 5.9.0 or above required"
#endif

/* Started from version 5.10.0 SDBlockDevice external repo is deprecated.
   please use the SDBlockDevice component inside mbed-os.*/
#if defined(MBED_MAJOR_VERSION) && MBED_MAJOR_VERSION >= 5 && (MBED_VERSION >= MBED_ENCODE_VERSION(5,10,0))
#error "Started from version 5.10.0 SDBlockDevice external repo is deprecated. please use the SDBlockDevice component inside mbed-os."
#endif

#ifndef MBED_CONF_SD_CMD_TIMEOUT
#define MBED_CONF_SD_CMD_TIMEOUT                 5000   /*!< Timeout in ms for response */
#endif

#ifndef MBED_CONF_SD_CMD0_IDLE_STATE_RETRIES
#define MBED_CONF_SD_CMD0_IDLE_STATE_RETRIES     5      /*!< Number of retries for sending CMDO */
#endif

#ifndef MBED_CONF_SD_INIT_FREQUENCY
#define MBED_CONF_SD_INIT_FREQUENCY              100000 /*!< Initialization frequency Range (100KHz-400KHz) */
#endif


#define SD_COMMAND_TIMEOUT                       MBED_CONF_SD_CMD_TIMEOUT
#define SD_CMD0_GO_IDLE_STATE_RETRIES            MBED_CONF_SD_CMD0_IDLE_STATE_RETRIES
#define SD_DBG                                   0      /*!< 1 - Enable debugging */
#define SD_CMD_TRACE                             0      /*!< 1 - Enable SD command tracing */

#define SD_BLOCK_DEVICE_ERROR_WOULD_BLOCK        -5001  /*!< operation would block */
#define SD_BLOCK_DEVICE_ERROR_UNSUPPORTED        -5002  /*!< unsupported operation */
#define SD_BLOCK_DEVICE_ERROR_PARAMETER          -5003  /*!< invalid parameter */
#define SD_BLOCK_DEVICE_ERROR_NO_INIT            -5004  /*!< uninitialized */
#define SD_BLOCK_DEVICE_ERROR_NO_DEVICE          -5005  /*!< device is missing or not connected */
#define SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED    -5006  /*!< write protected */
#define SD_BLOCK_DEVICE_ERROR_UNUSABLE           -5007  /*!< unusable card */
#define SD_BLOCK_DEVICE_ERROR_NO_RESPONSE        -5008  /*!< No response from device */
#define SD_BLOCK_DEVICE_ERROR_CRC                -5009  /*!< CRC error */
#define SD_BLOCK_DEVICE_ERROR_ERASE              -5010  /*!< Erase error: reset/sequence */
#define SD_BLOCK_DEVICE_ERROR_WRITE              -5011  /*!< SPI Write error: !SPI_DATA_ACCEPTED */

#define BLOCK_SIZE_HC                            512    /*!< Block size supported for SD card is 512 bytes  */
#define WRITE_BL_PARTIAL                         0      /*!< Partial block write - Not supported */
#define SPI_CMD(x) (0x40 | (x & 0x3f))

/* R1 Response Format */
#define R1_NO_RESPONSE          (0xFF)
#define R1_RESPONSE_RECV        (0x80)
#define R1_IDLE_STATE           (1 << 0)
#define R1_ERASE_RESET          (1 << 1)
#define R1_ILLEGAL_COMMAND      (1 << 2)
#define R1_COM_CRC_ERROR        (1 << 3)
#define R1_ERASE_SEQUENCE_ERROR (1 << 4)
#define R1_ADDRESS_ERROR        (1 << 5)
#define R1_PARAMETER_ERROR      (1 << 6)

// Types
#define SDCARD_NONE              0           /**< No card is present */
#define SDCARD_V1                1           /**< v1.x Standard Capacity */
#define SDCARD_V2                2           /**< v2.x Standard capacity SD card */
#define SDCARD_V2HC              3           /**< v2.x High capacity SD card */
#define CARD_UNKNOWN             4           /**< Unknown or unsupported card */

/* SIZE in Bytes */
#define PACKET_SIZE              6           /*!< SD Packet size CMD+ARG+CRC */
#define R1_RESPONSE_SIZE         1           /*!< Size of R1 response */
#define R2_RESPONSE_SIZE         2           /*!< Size of R2 response */
#define R3_R7_RESPONSE_SIZE      5           /*!< Size of R3/R7 response */

/* R1b Response */
#define DEVICE_BUSY             (0x00)

/* R2 Response Format */
#define R2_CARD_LOCKED          (1 << 0)
#define R2_CMD_FAILED           (1 << 1)
#define R2_ERROR                (1 << 2)
#define R2_CC_ERROR             (1 << 3)
#define R2_CC_FAILED            (1 << 4)
#define R2_WP_VIOLATION         (1 << 5)
#define R2_ERASE_PARAM          (1 << 6)
#define R2_OUT_OF_RANGE         (1 << 7)

/* R3 Response : OCR Register */
#define OCR_HCS_CCS             (0x1 << 30)
#define OCR_LOW_VOLTAGE         (0x01 << 24)
#define OCR_3_3V                (0x1 << 20)

/* R7 response pattern for CMD8 */
#define CMD8_PATTERN             (0xAA)

/*  CRC Enable  */
#define CRC_ENABLE               (0)         /*!< CRC 1 - Enable 0 - Disable */

/* Control Tokens   */
#define SPI_DATA_RESPONSE_MASK   (0x1F)
#define SPI_DATA_ACCEPTED        (0x05)
#define SPI_DATA_CRC_ERROR       (0x0B)
#define SPI_DATA_WRITE_ERROR     (0x0D)
#define SPI_START_BLOCK          (0xFE)      /*!< For Single Block Read/Write and Multiple Block Read */
#define SPI_START_BLK_MUL_WRITE  (0xFC)      /*!< Start Multi-block write */
#define SPI_STOP_TRAN            (0xFD)      /*!< Stop Multi-block write */

#define SPI_DATA_READ_ERROR_MASK (0xF)       /*!< Data Error Token: 4 LSB bits */
#define SPI_READ_ERROR           (0x1 << 0)  /*!< Error */
#define SPI_READ_ERROR_CC        (0x1 << 1)  /*!< CC Error*/
#define SPI_READ_ERROR_ECC_C     (0x1 << 2)  /*!< Card ECC failed */
#define SPI_READ_ERROR_OFR       (0x1 << 3)  /*!< Out of Range */

SDBlockDevice::SDBlockDevice(PinName mosi, PinName miso, PinName sclk, PinName cs, uint64_t hz, bool crc_on)
    : _sectors(0), _spi(mosi, miso, sclk), _cs(cs), _is_initialized(0),
      _crc_on(crc_on), _init_ref_count(0), _crc16(0, 0, false, false)
{
    _cs = 1;
    _card_type = SDCARD_NONE;

    // Set default to 100kHz for initialisation and 1MHz for data transfer
    MBED_STATIC_ASSERT(((MBED_CONF_SD_INIT_FREQUENCY >= 100000) && (MBED_CONF_SD_INIT_FREQUENCY <= 400000)),
                       "Initialization frequency should be between 100KHz to 400KHz");
    _init_sck = MBED_CONF_SD_INIT_FREQUENCY;
    _transfer_sck = hz;

    // Only HC block size is supported.
    _block_size = BLOCK_SIZE_HC;
    _erase_size = BLOCK_SIZE_HC;
}

SDBlockDevice::~SDBlockDevice()
{
    if (_is_initialized) {
        deinit();
    }
}

int SDBlockDevice::_initialise_card()
{
    // Detail debugging is for commands
    _dbg = SD_DBG ? SD_CMD_TRACE : 0;
    int32_t status = BD_ERROR_OK;
    uint32_t response, arg;

    // Initialize the SPI interface: Card by default is in SD mode
    _spi_init();

    // The card is transitioned from SDCard mode to SPI mode by sending the CMD0 + CS Asserted("0")
    if (_go_idle_state() != R1_IDLE_STATE) {
        debug_if(SD_DBG, "No disk, or could not put SD card in to SPI idle state\n");
        return SD_BLOCK_DEVICE_ERROR_NO_DEVICE;
    }

    // Send CMD8, if the card rejects the command then it's probably using the
    // legacy protocol, or is a MMC, or just flat-out broken
    status = _cmd8();
    if (BD_ERROR_OK != status && SD_BLOCK_DEVICE_ERROR_UNSUPPORTED != status) {
        return status;
    }

    if (_crc_on) {
        // Enable CRC
        status = _cmd(CMD59_CRC_ON_OFF, _crc_on);
    }

    // Read OCR - CMD58 Response contains OCR register
    if (BD_ERROR_OK != (status = _cmd(CMD58_READ_OCR, 0x0, 0x0, &response))) {
        return status;
    }

    // Check if card supports voltage range: 3.3V
    if (!(response & OCR_3_3V)) {
        _card_type = CARD_UNKNOWN;
        status = SD_BLOCK_DEVICE_ERROR_UNUSABLE;
        return status;
    }

    // HCS is set 1 for HC/XC capacity cards for ACMD41, if supported
    arg = 0x0;
    if (SDCARD_V2 == _card_type) {
        arg |= OCR_HCS_CCS;
    }

    /* Idle state bit in the R1 response of ACMD41 is used by the card to inform the host
     * if initialization of ACMD41 is completed. "1" indicates that the card is still initializing.
     * "0" indicates completion of initialization. The host repeatedly issues ACMD41 until
     * this bit is set to "0".
     */
    _spi_timer.start();
    do {
        status = _cmd(ACMD41_SD_SEND_OP_COND, arg, 1, &response);
    } while ((response & R1_IDLE_STATE) && (_spi_timer.read_ms() < SD_COMMAND_TIMEOUT));
    _spi_timer.stop();

    // Initialization complete: ACMD41 successful
    if ((BD_ERROR_OK != status) || (0x00 != response)) {
        _card_type = CARD_UNKNOWN;
        debug_if(SD_DBG, "Timeout waiting for card\n");
        return status;
    }

    if (SDCARD_V2 == _card_type) {
        // Get the card capacity CCS: CMD58
        if (BD_ERROR_OK == (status = _cmd(CMD58_READ_OCR, 0x0, 0x0, &response))) {
            // High Capacity card
            if (response & OCR_HCS_CCS) {
                _card_type = SDCARD_V2HC;
                debug_if(SD_DBG, "Card Initialized: High Capacity Card \n");
            } else {
                debug_if(SD_DBG, "Card Initialized: Standard Capacity Card: Version 2.x \n");
            }
        }
    } else {
        _card_type = SDCARD_V1;
        debug_if(SD_DBG, "Card Initialized: Version 1.x Card\n");
    }

    if (!_crc_on) {
        // Disable CRC
        status = _cmd(CMD59_CRC_ON_OFF, _crc_on);
    }
    return status;
}


int SDBlockDevice::init()
{
    int err;

    lock();

    if (!_is_initialized) {
        _init_ref_count = 0;
    }

    _init_ref_count++;

    if (_init_ref_count != 1) {
        goto end;
    }

    err = _initialise_card();
    _is_initialized = (err == BD_ERROR_OK);
    if (!_is_initialized) {
        debug_if(SD_DBG, "Fail to initialize card\n");
        unlock();
        return err;
    }
    debug_if(SD_DBG, "init card = %d\n", _is_initialized);
    _sectors = _sd_sectors();
    // CMD9 failed
    if (0 == _sectors) {
        unlock();
        return BD_ERROR_DEVICE_ERROR;
    }

    // Set block length to 512 (CMD16)
    if (_cmd(CMD16_SET_BLOCKLEN, _block_size) != 0) {
        debug_if(SD_DBG, "Set %d-byte block timed out\n", _block_size);
        unlock();
        return BD_ERROR_DEVICE_ERROR;
    }

    // Set SCK for data transfer
    err = _freq();
    if (err) {
        unlock();
        return err;
    }

end:
    unlock();
    return BD_ERROR_OK;
}

int SDBlockDevice::deinit()
{
    lock();

    if (!_is_initialized) {
        _init_ref_count = 0;
        goto end;
    }

    _init_ref_count--;

    if (_init_ref_count) {
        goto end;
    }

    _is_initialized = false;
    _sectors = 0;

end:
    unlock();
    return BD_ERROR_OK;
}


int SDBlockDevice::program(const void *b, bd_addr_t addr, bd_size_t size)
{
    if (!is_valid_program(addr, size)) {
        return SD_BLOCK_DEVICE_ERROR_PARAMETER;
    }

    lock();
    if (!_is_initialized) {
        unlock();
        return SD_BLOCK_DEVICE_ERROR_NO_INIT;
    }

    const uint8_t *buffer = static_cast<const uint8_t *>(b);
    int status = BD_ERROR_OK;
    uint8_t response;

    // Get block count
    bd_addr_t blockCnt = size / _block_size;

    // SDSC Card (CCS=0) uses byte unit address
    // SDHC and SDXC Cards (CCS=1) use block unit address (512 Bytes unit)
    if (SDCARD_V2HC == _card_type) {
        addr = addr / _block_size;
    }

    // Send command to perform write operation
    if (blockCnt == 1) {
        // Single block write command
        if (BD_ERROR_OK != (status = _cmd(CMD24_WRITE_BLOCK, addr))) {
            unlock();
            return status;
        }

        // Write data
        response = _write(buffer, SPI_START_BLOCK, _block_size);

        // Only CRC and general write error are communicated via response token
        if (response != SPI_DATA_ACCEPTED) {
            debug_if(SD_DBG, "Single Block Write failed: 0x%x \n", response);
            status = SD_BLOCK_DEVICE_ERROR_WRITE;
        }
    } else {
        // Pre-erase setting prior to multiple block write operation
        _cmd(ACMD23_SET_WR_BLK_ERASE_COUNT, blockCnt, 1);

        // Multiple block write command
        if (BD_ERROR_OK != (status = _cmd(CMD25_WRITE_MULTIPLE_BLOCK, addr))) {
            unlock();
            return status;
        }

        // Write the data: one block at a time
        do {
            response = _write(buffer, SPI_START_BLK_MUL_WRITE, _block_size);
            if (response != SPI_DATA_ACCEPTED) {
                debug_if(SD_DBG, "Multiple Block Write failed: 0x%x \n", response);
                break;
            }
            buffer += _block_size;
        } while (--blockCnt);     // Receive all blocks of data

        /* In a Multiple Block write operation, the stop transmission will be done by
         * sending 'Stop Tran' token instead of 'Start Block' token at the beginning
         * of the next block
         */
        _spi.write(SPI_STOP_TRAN);
    }

    _deselect();
    unlock();
    return status;
}

int SDBlockDevice::read(void *b, bd_addr_t addr, bd_size_t size)
{
    if (!is_valid_read(addr, size)) {
        return SD_BLOCK_DEVICE_ERROR_PARAMETER;
    }

    lock();
    if (!_is_initialized) {
        unlock();
        return SD_BLOCK_DEVICE_ERROR_PARAMETER;
    }

    uint8_t *buffer = static_cast<uint8_t *>(b);
    int status = BD_ERROR_OK;
    bd_addr_t blockCnt =  size / _block_size;

    // SDSC Card (CCS=0) uses byte unit address
    // SDHC and SDXC Cards (CCS=1) use block unit address (512 Bytes unit)
    if (SDCARD_V2HC == _card_type) {
        addr = addr / _block_size;
    }

    // Write command ro receive data
    if (blockCnt > 1) {
        status = _cmd(CMD18_READ_MULTIPLE_BLOCK, addr);
    } else {
        status = _cmd(CMD17_READ_SINGLE_BLOCK, addr);
    }
    if (BD_ERROR_OK != status) {
        unlock();
        return status;
    }

    // receive the data : one block at a time
    while (blockCnt) {
        if (0 != _read(buffer, _block_size)) {
            status = SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
            break;
        }
        buffer += _block_size;
        --blockCnt;
    }
    _deselect();

    // Send CMD12(0x00000000) to stop the transmission for multi-block transfer
    if (size > _block_size) {
        status = _cmd(CMD12_STOP_TRANSMISSION, 0x0);
    }
    unlock();
    return status;
}

bool SDBlockDevice::_is_valid_trim(bd_addr_t addr, bd_size_t size)
{
    return (
               addr % _erase_size == 0 &&
               size % _erase_size == 0 &&
               addr + size <= this->size());
}

int SDBlockDevice::trim(bd_addr_t addr, bd_size_t size)
{
    if (!_is_valid_trim(addr, size)) {
        return SD_BLOCK_DEVICE_ERROR_PARAMETER;
    }

    lock();
    if (!_is_initialized) {
        unlock();
        return SD_BLOCK_DEVICE_ERROR_NO_INIT;
    }
    int status = BD_ERROR_OK;

    size -= _block_size;
    // SDSC Card (CCS=0) uses byte unit address
    // SDHC and SDXC Cards (CCS=1) use block unit address (512 Bytes unit)
    if (SDCARD_V2HC == _card_type) {
        size = size / _block_size;
        addr = addr / _block_size;
    }

    // Start lba sent in start command
    if (BD_ERROR_OK != (status = _cmd(CMD32_ERASE_WR_BLK_START_ADDR, addr))) {
        unlock();
        return status;
    }

    // End lba = addr+size sent in end addr command
    if (BD_ERROR_OK != (status = _cmd(CMD33_ERASE_WR_BLK_END_ADDR, addr + size))) {
        unlock();
        return status;
    }
    status = _cmd(CMD38_ERASE, 0x0);
    unlock();
    return status;
}

bd_size_t SDBlockDevice::get_read_size() const
{
    return _block_size;
}

bd_size_t SDBlockDevice::get_program_size() const
{
    return _block_size;
}

bd_size_t SDBlockDevice::size() const
{
    return _block_size * _sectors;
}

const char *SDBlockDevice::get_type() const
{
    return "SD";
}

void SDBlockDevice::debug(bool dbg)
{
    _dbg = dbg;
}

int SDBlockDevice::frequency(uint64_t freq)
{
    lock();
    _transfer_sck = freq;
    int err = _freq();
    unlock();
    return err;
}

// PRIVATE FUNCTIONS
int SDBlockDevice::_freq(void)
{
    // Max frequency supported is 25MHZ
    if (_transfer_sck <= 25000000) {
        _spi.frequency(_transfer_sck);
        return 0;
    } else {  // TODO: Switch function to be implemented for higher frequency
        _transfer_sck = 25000000;
        _spi.frequency(_transfer_sck);
        return -EINVAL;
    }
}

uint8_t SDBlockDevice::_cmd_spi(SDBlockDevice::cmdSupported cmd, uint32_t arg)
{
    uint8_t response;
    char cmdPacket[PACKET_SIZE];
    uint32_t crc;

    // Prepare the command packet
    cmdPacket[0] = SPI_CMD(cmd);
    cmdPacket[1] = (arg >> 24);
    cmdPacket[2] = (arg >> 16);
    cmdPacket[3] = (arg >> 8);
    cmdPacket[4] = (arg >> 0);

    if (_crc_on) {
        _crc7.compute((void *)cmdPacket, 5, &crc);
        cmdPacket[5] = (char)(crc | 0x01);
    } else {
        // CMD0 is executed in SD mode, hence should have correct CRC
        // CMD8 CRC verification is always enabled
        switch (cmd) {
            case CMD0_GO_IDLE_STATE:
                cmdPacket[5] = 0x95;
                break;
            case CMD8_SEND_IF_COND:
                cmdPacket[5] = 0x87;
                break;
            default:
                cmdPacket[5] = 0xFF;    // Make sure bit 0-End bit is high
                break;
        }
    }

    // send a command
    for (int i = 0; i < PACKET_SIZE; i++) {
        _spi.write(cmdPacket[i]);
    }

    // The received byte immediataly following CMD12 is a stuff byte,
    // it should be discarded before receive the response of the CMD12.
    if (CMD12_STOP_TRANSMISSION == cmd) {
        _spi.write(SPI_FILL_CHAR);
    }

    // Loop for response: Response is sent back within command response time (NCR), 0 to 8 bytes for SDC
    for (int i = 0; i < 0x10; i++) {
        response = _spi.write(SPI_FILL_CHAR);
        // Got the response
        if (!(response & R1_RESPONSE_RECV)) {
            break;
        }
    }
    return response;
}

int SDBlockDevice::_cmd(SDBlockDevice::cmdSupported cmd, uint32_t arg, bool isAcmd, uint32_t *resp)
{
    int32_t status = BD_ERROR_OK;
    uint32_t response;

    // Select card and wait for card to be ready before sending next command
    // Note: next command will fail if card is not ready
    _select();

    // No need to wait for card to be ready when sending the stop command
    if (CMD12_STOP_TRANSMISSION != cmd) {
        if (false == _wait_ready(SD_COMMAND_TIMEOUT)) {
            debug_if(SD_DBG, "Card not ready yet \n");
        }
    }

    // Re-try command
    for (int i = 0; i < 3; i++) {
        // Send CMD55 for APP command first
        if (isAcmd) {
            response = _cmd_spi(CMD55_APP_CMD, 0x0);
            // Wait for card to be ready after CMD55
            if (false == _wait_ready(SD_COMMAND_TIMEOUT)) {
                debug_if(SD_DBG, "Card not ready yet \n");
            }
        }

        // Send command over SPI interface
        response = _cmd_spi(cmd, arg);
        if (R1_NO_RESPONSE == response) {
            debug_if(SD_DBG, "No response CMD:%d \n", cmd);
            continue;
        }
        break;
    }

    // Pass the response to the command call if required
    if (NULL != resp) {
        *resp = response;
    }

    // Process the response R1  : Exit on CRC/Illegal command error/No response
    if (R1_NO_RESPONSE == response) {
        _deselect();
        debug_if(SD_DBG, "No response CMD:%d response: 0x%x\n", cmd, response);
        return SD_BLOCK_DEVICE_ERROR_NO_DEVICE;         // No device
    }
    if (response & R1_COM_CRC_ERROR) {
        _deselect();
        debug_if(SD_DBG, "CRC error CMD:%d response 0x%x \n", cmd, response);
        return SD_BLOCK_DEVICE_ERROR_CRC;                // CRC error
    }
    if (response & R1_ILLEGAL_COMMAND) {
        _deselect();
        debug_if(SD_DBG, "Illegal command CMD:%d response 0x%x\n", cmd, response);
        if (CMD8_SEND_IF_COND == cmd) {                  // Illegal command is for Ver1 or not SD Card
            _card_type = CARD_UNKNOWN;
        }
        return SD_BLOCK_DEVICE_ERROR_UNSUPPORTED;      // Command not supported
    }

    debug_if(_dbg, "CMD:%d \t arg:0x%x \t Response:0x%x \n", cmd, arg, response);
    // Set status for other errors
    if ((response & R1_ERASE_RESET) || (response & R1_ERASE_SEQUENCE_ERROR)) {
        status = SD_BLOCK_DEVICE_ERROR_ERASE;            // Erase error
    } else if ((response & R1_ADDRESS_ERROR) || (response & R1_PARAMETER_ERROR)) {
        // Misaligned address / invalid address block length
        status = SD_BLOCK_DEVICE_ERROR_PARAMETER;
    }

    // Get rest of the response part for other commands
    switch (cmd) {
        case CMD8_SEND_IF_COND:             // Response R7
            debug_if(_dbg, "V2-Version Card\n");
            _card_type = SDCARD_V2;
        // Note: No break here, need to read rest of the response
        case CMD58_READ_OCR:                // Response R3
            response  = (_spi.write(SPI_FILL_CHAR) << 24);
            response |= (_spi.write(SPI_FILL_CHAR) << 16);
            response |= (_spi.write(SPI_FILL_CHAR) << 8);
            response |= _spi.write(SPI_FILL_CHAR);
            debug_if(_dbg, "R3/R7: 0x%x \n", response);
            break;

        case CMD12_STOP_TRANSMISSION:       // Response R1b
        case CMD38_ERASE:
            _wait_ready(SD_COMMAND_TIMEOUT);
            break;

        case ACMD13_SD_STATUS:             // Response R2
            response = _spi.write(SPI_FILL_CHAR);
            debug_if(_dbg, "R2: 0x%x \n", response);
            break;

        default:                            // Response R1
            break;
    }

    // Pass the updated response to the command
    if (NULL != resp) {
        *resp = response;
    }

    // Do not deselect card if read is in progress.
    if (((CMD9_SEND_CSD == cmd) || (ACMD22_SEND_NUM_WR_BLOCKS == cmd) ||
            (CMD24_WRITE_BLOCK == cmd) || (CMD25_WRITE_MULTIPLE_BLOCK == cmd) ||
            (CMD17_READ_SINGLE_BLOCK == cmd) || (CMD18_READ_MULTIPLE_BLOCK == cmd))
            && (BD_ERROR_OK == status)) {
        return BD_ERROR_OK;
    }
    // Deselect card
    _deselect();
    return status;
}

int SDBlockDevice::_cmd8()
{
    uint32_t arg = (CMD8_PATTERN << 0);         // [7:0]check pattern
    uint32_t response = 0;
    int32_t status = BD_ERROR_OK;

    arg |= (0x1 << 8);  // 2.7-3.6V             // [11:8]supply voltage(VHS)

    status = _cmd(CMD8_SEND_IF_COND, arg, 0x0, &response);
    // Verify voltage and pattern for V2 version of card
    if ((BD_ERROR_OK == status) && (SDCARD_V2 == _card_type)) {
        // If check pattern is not matched, CMD8 communication is not valid
        if ((response & 0xFFF) != arg) {
            debug_if(SD_DBG, "CMD8 Pattern mismatch 0x%x : 0x%x\n", arg, response);
            _card_type = CARD_UNKNOWN;
            status = SD_BLOCK_DEVICE_ERROR_UNUSABLE;
        }
    }
    return status;
}

uint32_t SDBlockDevice::_go_idle_state()
{
    uint32_t response;

    /* Reseting the MCU SPI master may not reset the on-board SDCard, in which
     * case when MCU power-on occurs the SDCard will resume operations as
     * though there was no reset. In this scenario the first CMD0 will
     * not be interpreted as a command and get lost. For some cards retrying
     * the command overcomes this situation. */
    for (int i = 0; i < SD_CMD0_GO_IDLE_STATE_RETRIES; i++) {
        _cmd(CMD0_GO_IDLE_STATE, 0x0, 0x0, &response);
        if (R1_IDLE_STATE == response) {
            break;
        }
        wait_ms(1);
    }
    return response;
}

int SDBlockDevice::_read_bytes(uint8_t *buffer, uint32_t length)
{
    uint16_t crc;

    // read until start byte (0xFE)
    if (false == _wait_token(SPI_START_BLOCK)) {
        debug_if(SD_DBG, "Read timeout\n");
        _deselect();
        return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
    }

    // read data
    for (uint32_t i = 0; i < length; i++) {
        buffer[i] = _spi.write(SPI_FILL_CHAR);
    }

    // Read the CRC16 checksum for the data block
    crc = (_spi.write(SPI_FILL_CHAR) << 8);
    crc |= _spi.write(SPI_FILL_CHAR);

    if (_crc_on) {
        uint32_t crc_result;
        // Compute and verify checksum
        _crc16.compute((void *)buffer, length, &crc_result);
        if ((uint16_t)crc_result != crc) {
            debug_if(SD_DBG, "_read_bytes: Invalid CRC received 0x%x result of computation 0x%x\n",
                     crc, crc_result);
            _deselect();
            return SD_BLOCK_DEVICE_ERROR_CRC;
        }
    }

    _deselect();
    return 0;
}

int SDBlockDevice::_read(uint8_t *buffer, uint32_t length)
{
    uint16_t crc;

    // read until start byte (0xFE)
    if (false == _wait_token(SPI_START_BLOCK)) {
        debug_if(SD_DBG, "Read timeout\n");
        _deselect();
        return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
    }

    // read data
    _spi.write(NULL, 0, (char *)buffer, length);

    // Read the CRC16 checksum for the data block
    crc = (_spi.write(SPI_FILL_CHAR) << 8);
    crc |= _spi.write(SPI_FILL_CHAR);

    if (_crc_on) {
        uint32_t crc_result;
        // Compute and verify checksum
        _crc16.compute((void *)buffer, length, &crc_result);
        if ((uint16_t)crc_result != crc) {
            debug_if(SD_DBG, "_read_bytes: Invalid CRC received 0x%x result of computation 0x%x\n",
                     crc, crc_result);
            return SD_BLOCK_DEVICE_ERROR_CRC;
        }
    }

    return 0;
}

uint8_t SDBlockDevice::_write(const uint8_t *buffer, uint8_t token, uint32_t length)
{

    uint32_t crc = (~0);
    uint8_t response = 0xFF;

    // indicate start of block
    _spi.write(token);

    // write the data
    _spi.write((char *)buffer, length, NULL, 0);

    if (_crc_on) {
        // Compute CRC
        _crc16.compute((void *)buffer, length, &crc);
    }

    // write the checksum CRC16
    _spi.write(crc >> 8);
    _spi.write(crc);


    // check the response token
    response = _spi.write(SPI_FILL_CHAR);

    // Wait for last block to be written
    if (false == _wait_ready(SD_COMMAND_TIMEOUT)) {
        debug_if(SD_DBG, "Card not ready yet \n");
    }

    return (response & SPI_DATA_RESPONSE_MASK);
}

static uint32_t ext_bits(unsigned char *data, int msb, int lsb)
{
    uint32_t bits = 0;
    uint32_t size = 1 + msb - lsb;
    for (uint32_t i = 0; i < size; i++) {
        uint32_t position = lsb + i;
        uint32_t byte = 15 - (position >> 3);
        uint32_t bit = position & 0x7;
        uint32_t value = (data[byte] >> bit) & 1;
        bits |= value << i;
    }
    return bits;
}

bd_size_t SDBlockDevice::_sd_sectors()
{
    uint32_t c_size, c_size_mult, read_bl_len;
    uint32_t block_len, mult, blocknr;
    uint32_t hc_c_size;
    bd_size_t blocks = 0, capacity = 0;

    // CMD9, Response R2 (R1 byte + 16-byte block read)
    if (_cmd(CMD9_SEND_CSD, 0x0) != 0x0) {
        debug_if(SD_DBG, "Didn't get a response from the disk\n");
        return 0;
    }
    uint8_t csd[16];
    if (_read_bytes(csd, 16) != 0) {
        debug_if(SD_DBG, "Couldn't read csd response from disk\n");
        return 0;
    }

    // csd_structure : csd[127:126]
    int csd_structure = ext_bits(csd, 127, 126);
    switch (csd_structure) {
        case 0:
            c_size = ext_bits(csd, 73, 62);              // c_size        : csd[73:62]
            c_size_mult = ext_bits(csd, 49, 47);         // c_size_mult   : csd[49:47]
            read_bl_len = ext_bits(csd, 83, 80);         // read_bl_len   : csd[83:80] - the *maximum* read block length
            block_len = 1 << read_bl_len;                // BLOCK_LEN = 2^READ_BL_LEN
            mult = 1 << (c_size_mult + 2);               // MULT = 2^C_SIZE_MULT+2 (C_SIZE_MULT < 8)
            blocknr = (c_size + 1) * mult;               // BLOCKNR = (C_SIZE+1) * MULT
            capacity = blocknr * block_len;              // memory capacity = BLOCKNR * BLOCK_LEN
            blocks = capacity / _block_size;
            debug_if(SD_DBG, "Standard Capacity: c_size: %d \n", c_size);
            debug_if(SD_DBG, "Sectors: 0x%x : %llu\n", blocks, blocks);
            debug_if(SD_DBG, "Capacity: 0x%x : %llu MB\n", capacity, (capacity / (1024U * 1024U)));

            // ERASE_BLK_EN = 1: Erase in multiple of 512 bytes supported
            if (ext_bits(csd, 46, 46)) {
                _erase_size = BLOCK_SIZE_HC;
            } else {
                // ERASE_BLK_EN = 1: Erase in multiple of SECTOR_SIZE supported
                _erase_size = BLOCK_SIZE_HC * (ext_bits(csd, 45, 39) + 1);
            }
            break;

        case 1:
            hc_c_size = ext_bits(csd, 69, 48);            // device size : C_SIZE : [69:48]
            blocks = (hc_c_size + 1) << 10;               // block count = C_SIZE+1) * 1K byte (512B is block size)
            debug_if(SD_DBG, "SDHC/SDXC Card: hc_c_size: %d \n", hc_c_size);
            debug_if(SD_DBG, "Sectors: 0x%x : %llu\n", blocks, blocks);
            debug_if(SD_DBG, "Capacity: %llu MB\n", (blocks / (2048U)));
            // ERASE_BLK_EN is fixed to 1, which means host can erase one or multiple of 512 bytes.
            _erase_size = BLOCK_SIZE_HC;
            break;

        default:
            debug_if(SD_DBG, "CSD struct unsupported\r\n");
            return 0;
    };
    return blocks;
}

// SPI function to wait till chip is ready and sends start token
bool SDBlockDevice::_wait_token(uint8_t token)
{
    _spi_timer.reset();
    _spi_timer.start();

    do {
        if (token == _spi.write(SPI_FILL_CHAR)) {
            _spi_timer.stop();
            return true;
        }
    } while (_spi_timer.read_ms() < 300);       // Wait for 300 msec for start token
    _spi_timer.stop();
    debug_if(SD_DBG, "_wait_token: timeout\n");
    return false;
}

// SPI function to wait till chip is ready
// The host controller should wait for end of the process until DO goes high (a 0xFF is received).
bool SDBlockDevice::_wait_ready(uint16_t ms)
{
    uint8_t response;
    _spi_timer.reset();
    _spi_timer.start();
    do {
        response = _spi.write(SPI_FILL_CHAR);
        if (response == 0xFF) {
            _spi_timer.stop();
            return true;
        }
    } while (_spi_timer.read_ms() < ms);
    _spi_timer.stop();
    return false;
}

// SPI function to wait for count
void SDBlockDevice::_spi_wait(uint8_t count)
{
    for (uint8_t i = 0; i < count; ++i) {
        _spi.write(SPI_FILL_CHAR);
    }
}

void SDBlockDevice::_spi_init()
{
    _spi.lock();
    // Set to SCK for initialization, and clock card with cs = 1
    _spi.frequency(_init_sck);
    _spi.format(8, 0);
    _spi.set_default_write_value(SPI_FILL_CHAR);
    // Initial 74 cycles required for few cards, before selecting SPI mode
    _cs = 1;
    _spi_wait(10);
    _spi.unlock();
}

void SDBlockDevice::_select()
{
    _spi.lock();
    _spi.write(SPI_FILL_CHAR);
    _cs = 0;
}

void SDBlockDevice::_deselect()
{
    _cs = 1;
    _spi.write(SPI_FILL_CHAR);
    _spi.unlock();
}

#endif  /* DEVICE_SPI */

Heap block device example

Block device the heap backs for quick testing.

/* mbed Microcontroller Library
 * Copyright (c) 2017 ARM Limited
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#ifndef MBED_MEM_BLOCK_DEVICE_H
#define MBED_MEM_BLOCK_DEVICE_H

#include "BlockDevice.h"
#include "mbed.h"


/** Lazily allocated heap-backed block device
 *
 * Useful for simulating a block device and tests
 *
 * @code
 * #include "mbed.h"
 * #include "HeapBlockDevice.h"
 *
 * #define BLOCK_SIZE 512
 *
 * HeapBlockDevice bd(2048, BLOCK_SIZE); // 2048 bytes with a block size of 512 bytes
 * uint8_t block[BLOCK_SIZE] = "Hello World!\n";
 *
 * int main() {
 *     bd.init();
 *     bd.erase(0, BLOCK_SIZE);
 *     bd.program(block, 0, BLOCK_SIZE);
 *     bd.read(block, 0, BLOCK_SIZE);
 *     printf("%s", block);
 *     bd.deinit();
 * }
 * @endcode
 */
class HeapBlockDevice : public BlockDevice
{
public:

    /** Lifetime of the memory block device
     *
     * @param size      Size of the Block Device in bytes
     * @param block     Block size in bytes. Minimum read, program, and erase sizes are
     *                  configured to this value
     */
    HeapBlockDevice(bd_size_t size, bd_size_t block=512);
    /** Lifetime of the memory block device
     *
     * @param size      Size of the Block Device in bytes
     * @param read      Minimum read size required in bytes
     * @param program   Minimum program size required in bytes
     * @param erase     Minimum erase size required in bytes
     */
    HeapBlockDevice(bd_size_t size, bd_size_t read, bd_size_t program, bd_size_t erase);
    virtual ~HeapBlockDevice();

    /** Initialize a block device
     *
     *  @return         0 on success or a negative error code on failure
     */
    virtual int init();

    /** Deinitialize a block device
     *
     *  @return         0 on success or a negative error code on failure
     */
    virtual int deinit();

    /** Read blocks from a block device
     *
     *  @param buffer   Buffer to read blocks into
     *  @param addr     Address of block to begin reading from
     *  @param size     Size to read in bytes, must be a multiple of read block size
     *  @return         0 on success, negative error code on failure
     */
    virtual int read(void *buffer, bd_addr_t addr, bd_size_t size);

    /** Program blocks to a block device
     *
     *  The blocks must have been erased prior to being programmed
     *
     *  @param buffer   Buffer of data to write to blocks
     *  @param addr     Address of block to begin writing to
     *  @param size     Size to write in bytes, must be a multiple of program block size
     *  @return         0 on success, negative error code on failure
     */
    virtual int program(const void *buffer, bd_addr_t addr, bd_size_t size);

    /** Erase blocks on a block device
     *
     *  The state of an erased block is undefined until it has been programmed
     *
     *  @param addr     Address of block to begin erasing
     *  @param size     Size to erase in bytes, must be a multiple of erase block size
     *  @return         0 on success, negative error code on failure
     */
    virtual int erase(bd_addr_t addr, bd_size_t size);

    /** Get the size of a readable block
     *
     *  @return         Size of a readable block in bytes
     */
    virtual bd_size_t get_read_size() const;

    /** Get the size of a programable block
     *
     *  @return         Size of a programable block in bytes
     */
    virtual bd_size_t get_program_size() const;

    /** Get the size of a eraseable block
     *
     *  @return         Size of a eraseable block in bytes
     */
    virtual bd_size_t get_erase_size() const;

    /** Get the total size of the underlying device
     *
     *  @return         Size of the underlying device in bytes
     */
    virtual bd_size_t size() const;

private:
    bd_size_t _read_size;
    bd_size_t _program_size;
    bd_size_t _erase_size;
    bd_size_t _count;
    uint8_t **_blocks;
};


#endif

Block Device NOR-based SPI example

Block device for NOR-based SPI flash devices.

/* mbed Microcontroller Library
 * Copyright (c) 2018 ARM Limited
 *
 * 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 "SPIFBlockDevice.h"
#include "mbed_critical.h"

#include <string.h>
#include "mbed_wait_api.h"

#include "mbed_trace.h"
#define TRACE_GROUP "SPIF"

/* Started from version 5.10.0 SPIFBlockDevice external repo is deprecated. 
   please use the SPIFBlockDevice component inside mbed-os.*/
#if defined(MBED_MAJOR_VERSION) && MBED_MAJOR_VERSION >= 5 && (MBED_VERSION >= MBED_ENCODE_VERSION(5,10,0))
#error "Started from version 5.10.0 SPIFBlockDevice external repo is deprecated. please use the SPIFBlockDevice component inside mbed-os."
#endif

using namespace mbed;

/* Default SPIF Parameters */
/****************************/
#define SPIF_DEFAULT_READ_SIZE  1
#define SPIF_DEFAULT_PROG_SIZE  1
#define SPIF_DEFAULT_PAGE_SIZE  256
#define SPIF_DEFAULT_SE_SIZE    4096
#define SPI_MAX_STATUS_REGISTER_SIZE 2
#ifndef UINT64_MAX
#define UINT64_MAX -1
#endif
#define SPI_NO_ADDRESS_COMMAND UINT64_MAX
// Status Register Bits
#define SPIF_STATUS_BIT_WIP	0x1 //Write In Progress
#define SPIF_STATUS_BIT_WEL	0x2 // Write Enable Latch

/* SFDP Header Parsing */
/***********************/
#define SPIF_SFDP_HEADER_SIZE 8
#define SPIF_PARAM_HEADER_SIZE 8

/* Basic Parameters Table Parsing */
/**********************************/
#define SFDP_DEFAULT_BASIC_PARAMS_TABLE_SIZE_BYTES 64 /* 16 DWORDS */
//READ Instruction support according to BUS Configuration
#define SPIF_BASIC_PARAM_TABLE_FAST_READ_SUPPORT_BYTE 2
#define SPIF_BASIC_PARAM_TABLE_QPI_READ_SUPPORT_BYTE 16
#define SPIF_BASIC_PARAM_TABLE_222_READ_INST_BYTE 23
#define SPIF_BASIC_PARAM_TABLE_122_READ_INST_BYTE 15
#define SPIF_BASIC_PARAM_TABLE_112_READ_INST_BYTE 13
#define SPIF_BASIC_PARAM_TABLE_PAGE_SIZE_BYTE 40
// Address Length
#define SPIF_ADDR_SIZE_3_BYTES 3
// Erase Types Params
#define SPIF_BASIC_PARAM_ERASE_TYPE_1_BYTE 29
#define SPIF_BASIC_PARAM_ERASE_TYPE_2_BYTE 31
#define SPIF_BASIC_PARAM_ERASE_TYPE_3_BYTE 33
#define SPIF_BASIC_PARAM_ERASE_TYPE_4_BYTE 35
#define SPIF_BASIC_PARAM_ERASE_TYPE_1_SIZE_BYTE 28
#define SPIF_BASIC_PARAM_ERASE_TYPE_2_SIZE_BYTE 30
#define SPIF_BASIC_PARAM_ERASE_TYPE_3_SIZE_BYTE 32
#define SPIF_BASIC_PARAM_ERASE_TYPE_4_SIZE_BYTE 34
#define SPIF_BASIC_PARAM_4K_ERASE_TYPE_BYTE 1

// Erase Types Per Region BitMask
#define ERASE_BITMASK_TYPE4 0x08
#define ERASE_BITMASK_TYPE1 0x01
#define ERASE_BITMASK_NONE  0x00
#define ERASE_BITMASK_ALL   0x0F

#define IS_MEM_READY_MAX_RETRIES 10000

enum spif_default_instructions {
    SPIF_NOP = 0x00, // No operation
    SPIF_PP = 0x02, // Page Program data
    SPIF_READ = 0x03, // Read data
    SPIF_SE   = 0x20, // 4KB Sector Erase
    SPIF_SFDP = 0x5a, // Read SFDP
    SPIF_WRSR = 0x01, // Write Status/Configuration Register
    SPIF_WRDI = 0x04, // Write Disable
    SPIF_RDSR = 0x05, // Read Status Register
    SPIF_WREN = 0x06, // Write Enable
    SPIF_RSTEN = 0x66, // Reset Enable
    SPIF_RST = 0x99, // Reset
    SPIF_RDID = 0x9f, // Read Manufacturer and JDEC Device ID
};

// Mutex is used for some SPI Driver commands that must be done sequentially with no other commands in between
// e.g. (1)Set Write Enable, (2)Program, (3)Wait Memory Ready
SingletonPtr<PlatformMutex> SPIFBlockDevice::_mutex;

// Local Function
static unsigned int local_math_power(int base, int exp);

//***********************
// SPIF Block Device APIs
//***********************
SPIFBlockDevice::SPIFBlockDevice(
    PinName mosi, PinName miso, PinName sclk, PinName csel, int freq)
    : _spi(mosi, miso, sclk), _cs(csel), _device_size_bytes(0), _is_initialized(false), _init_ref_count(0)
{
    _address_size = SPIF_ADDR_SIZE_3_BYTES;
    // Initial SFDP read tables are read with 8 dummy cycles
    // Default Bus Setup 1_1_1 with 0 dummy and mode cycles
    _read_dummy_and_mode_cycles = 8;
    _write_dummy_and_mode_cycles = 0;
    _dummy_and_mode_cycles = _read_dummy_and_mode_cycles;

    _min_common_erase_size = 0;
    _regions_count = 1;
    _region_erase_types_bitfield[0] = ERASE_BITMASK_NONE;

    if (SPIF_BD_ERROR_OK != _spi_set_frequency(freq)) {
        tr_error("ERROR: SPI Set Frequency Failed");
    }

    _cs = 1;
}

int SPIFBlockDevice::init()
{
    uint8_t vendor_device_ids[4];
    size_t data_length = 3;
    int status = SPIF_BD_ERROR_OK;
    uint32_t basic_table_addr = 0;
    size_t basic_table_size = 0;
    uint32_t sector_map_table_addr = 0;
    size_t sector_map_table_size = 0;
    spif_bd_error spi_status = SPIF_BD_ERROR_OK;

    _mutex->lock();

    if (!_is_initialized) {
        _init_ref_count = 0;
    }

    _init_ref_count++;

    if (_init_ref_count != 1) {
        goto exit_point;
    }

    // Soft Reset
    if ( -1 == _reset_flash_mem()) {
        tr_error("ERROR: init - Unable to initialize flash memory, tests failed\n");
        status = SPIF_BD_ERROR_DEVICE_ERROR;
        goto exit_point;
    } else {
        tr_info("INFO: Initialize flash memory OK\n");
    }


    /* Read Manufacturer ID (1byte), and Device ID (2bytes)*/
    spi_status = _spi_send_general_command(SPIF_RDID, SPI_NO_ADDRESS_COMMAND, NULL, 0, (char *)vendor_device_ids,
                                           data_length);
    if (spi_status != SPIF_BD_ERROR_OK) {
        tr_error("ERROR: init - Read Vendor ID Failed");
        status = SPIF_BD_ERROR_DEVICE_ERROR;
        goto exit_point;
    }

    switch (vendor_device_ids[0]) {
        case 0xbf:
            // SST devices come preset with block protection
            // enabled for some regions, issue write disable instruction to clear
            _set_write_enable();
            _spi_send_general_command(SPIF_WRDI, SPI_NO_ADDRESS_COMMAND, NULL, 0, NULL, 0);
            break;
    }

    //Synchronize Device
    if ( false == _is_mem_ready()) {
        tr_error("ERROR: init - _is_mem_ready Failed");
        status = SPIF_BD_ERROR_READY_FAILED;
        goto exit_point;
    }

    /**************************** Parse SFDP Header ***********************************/
    if ( 0 != _sfdp_parse_sfdp_headers(basic_table_addr, basic_table_size, sector_map_table_addr, sector_map_table_size)) {
        tr_error("ERROR: init - Parse SFDP Headers Failed");
        status = SPIF_BD_ERROR_PARSING_FAILED;
        goto exit_point;
    }


    /**************************** Parse Basic Parameters Table ***********************************/
    if ( 0 != _sfdp_parse_basic_param_table(basic_table_addr, basic_table_size) ) {
        tr_error("ERROR: init - Parse Basic Param Table Failed");
        status = SPIF_BD_ERROR_PARSING_FAILED;
        goto exit_point;
    }

    /**************************** Parse Sector Map Table ***********************************/
    _region_size_bytes[0] =
        _device_size_bytes; // If there's no region map, we have a single region sized the entire device size
    _region_high_boundary[0] = _device_size_bytes - 1;

    if ( (sector_map_table_addr != 0) && (0 != sector_map_table_size) ) {
        tr_info("INFO: init - Parsing Sector Map Table - addr: 0x%lxh, Size: %d", sector_map_table_addr,
                sector_map_table_size);
        if (0 != _sfdp_parse_sector_map_table(sector_map_table_addr, sector_map_table_size) ) {
            tr_error("ERROR: init - Parse Sector Map Table Failed");
            status = SPIF_BD_ERROR_PARSING_FAILED;
            goto exit_point;
        }
    }

    // Configure  BUS Mode to 1_1_1 for all commands other than Read
    // Dummy And Mode Cycles Back default 0
    _dummy_and_mode_cycles = _write_dummy_and_mode_cycles;
    _is_initialized = true;

exit_point:
    _mutex->unlock();

    return status;
}


int SPIFBlockDevice::deinit()
{
    spif_bd_error status = SPIF_BD_ERROR_OK;

    _mutex->lock();

    if (!_is_initialized) {
        _init_ref_count = 0;
        goto exit_point;
    }

    _init_ref_count--;

    if (_init_ref_count) {
        goto exit_point;
    }

    // Disable Device for Writing
    status = _spi_send_general_command(SPIF_WRDI, SPI_NO_ADDRESS_COMMAND, NULL, 0, NULL, 0);
    if (status != SPIF_BD_ERROR_OK)  {
        tr_error("ERROR: Write Disable failed");
    }
    _is_initialized = false;

exit_point:
    _mutex->unlock();

    return status;
}

int SPIFBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size)
{
    if (!_is_initialized) {
        return BD_ERROR_DEVICE_ERROR;
    }

    int status = SPIF_BD_ERROR_OK;
    tr_info("INFO Read - Inst: 0x%xh", _read_instruction);
    _mutex->lock();

    // Set Dummy Cycles for Specific Read Command Mode
    _dummy_and_mode_cycles = _read_dummy_and_mode_cycles;

    status = _spi_send_read_command(_read_instruction, static_cast<uint8_t *>(buffer), addr, size);

    // Set Dummy Cycles for all other command modes
    _dummy_and_mode_cycles = _write_dummy_and_mode_cycles;

    _mutex->unlock();
    return status;
}

int SPIFBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t size)
{
    if (!_is_initialized) {
        return BD_ERROR_DEVICE_ERROR;
    }

    bool program_failed = false;
    int status = SPIF_BD_ERROR_OK;
    uint32_t offset = 0;
    uint32_t chunk = 0;

    tr_debug("DEBUG: program - Buff: 0x%lxh, addr: %llu, size: %llu", (uint32_t)buffer, addr, size);

    while (size > 0) {

        // Write on _page_size_bytes boundaries (Default 256 bytes a page)
        offset = addr % _page_size_bytes;
        chunk = (offset + size < _page_size_bytes) ? size : (_page_size_bytes - offset);

        _mutex->lock();

        //Send WREN
        if (_set_write_enable() != 0) {
            tr_error("ERROR: Write Enabe failed\n");
            program_failed = true;
            status = SPIF_BD_ERROR_WREN_FAILED;
            goto exit_point;
        }

        _spi_send_program_command(_prog_instruction, buffer, addr, chunk);

        buffer = static_cast<const uint8_t *>(buffer) + chunk;
        addr += chunk;
        size -= chunk;

        if ( false == _is_mem_ready()) {
            tr_error("ERROR: Device not ready after write, failed\n");
            program_failed = true;
            status = SPIF_BD_ERROR_READY_FAILED;
            goto exit_point;
        }
        _mutex->unlock();
    }

exit_point:
    if (program_failed) {
        _mutex->unlock();
    }

    return status;
}

int SPIFBlockDevice::erase(bd_addr_t addr, bd_size_t in_size)
{
    if (!_is_initialized) {
        return BD_ERROR_DEVICE_ERROR;
    }

    int type = 0;
    uint32_t offset = 0;
    uint32_t chunk = 4096;
    int cur_erase_inst = _erase_instruction;
    int size = (int)in_size;
    bool erase_failed = false;
    int status = SPIF_BD_ERROR_OK;
    // Find region of erased address
    int region = _utils_find_addr_region(addr);
    // Erase Types of selected region
    uint8_t bitfield = _region_erase_types_bitfield[region];

    tr_info("DEBUG: erase - addr: %llu, in_size: %llu", addr, in_size);

    if ((addr + in_size) > _device_size_bytes) {
        tr_error("ERROR: erase exceeds flash device size");
        return SPIF_BD_ERROR_INVALID_ERASE_PARAMS;
    }

    if ( ((addr % get_erase_size(addr)) != 0 ) ||  (((addr + in_size) % get_erase_size(addr + in_size - 1)) != 0 ) ) {
        tr_error("ERROR: invalid erase - unaligned address and size");
        return SPIF_BD_ERROR_INVALID_ERASE_PARAMS;
    }

    // For each iteration erase the largest section supported by current region
    while (size > 0) {

        // iterate to find next Largest erase type ( a. supported by region, b. smaller than size)
        // find the matching instruction and erase size chunk for that type.
        type = _utils_iterate_next_largest_erase_type(bitfield, size, (unsigned int)addr, _region_high_boundary[region]);
        cur_erase_inst = _erase_type_inst_arr[type];
        offset = addr % _erase_type_size_arr[type];
        chunk = ( (offset + size) < _erase_type_size_arr[type]) ? size : (_erase_type_size_arr[type] - offset);

        tr_debug("DEBUG: erase - addr: %llu, size:%d, Inst: 0x%xh, chunk: %lu , ",
                 addr, size, cur_erase_inst, chunk);
        tr_debug("DEBUG: erase - Region: %d, Type:%d",
                 region, type);

        _mutex->lock();

        if (_set_write_enable() != 0) {
            tr_error("ERROR: SPI Erase Device not ready - failed");
            erase_failed = true;
            status = SPIF_BD_ERROR_READY_FAILED;
            goto exit_point;
        }

        _spi_send_erase_command(cur_erase_inst, addr, size);

        addr += chunk;
        size -= chunk;

        if ( (size > 0) && (addr > _region_high_boundary[region]) ) {
            // erase crossed to next region
            region++;
            bitfield = _region_erase_types_bitfield[region];
        }

        if ( false == _is_mem_ready()) {
            tr_error("ERROR: SPI After Erase Device not ready - failed\n");
            erase_failed = true;
            status = SPIF_BD_ERROR_READY_FAILED;
            goto exit_point;
        }

        _mutex->unlock();
    }

exit_point:
    if (erase_failed) {
        _mutex->unlock();
    }

    return status;
}

bd_size_t SPIFBlockDevice::get_read_size() const
{
    // Assuming all devices support 1byte read granularity
    return SPIF_DEFAULT_READ_SIZE;
}

bd_size_t SPIFBlockDevice::get_program_size() const
{
    // Assuming all devices support 1byte program granularity
    return SPIF_DEFAULT_PROG_SIZE;
}

bd_size_t SPIFBlockDevice::get_erase_size() const
{
    // return minimal erase size supported by all regions (0 if none exists)
    return _min_common_erase_size;
}

// Find minimal erase size supported by the region to which the address belongs to
bd_size_t SPIFBlockDevice::get_erase_size(bd_addr_t addr)
{
    // Find region of current address
    int region = _utils_find_addr_region(addr);

    unsigned int min_region_erase_size = _min_common_erase_size;
    int8_t type_mask = ERASE_BITMASK_TYPE1;
    int i_ind = 0;

    if (region != -1) {
        type_mask = 0x01;

        for (i_ind = 0; i_ind < 4; i_ind++) {
            // loop through erase types bitfield supported by region
            if (_region_erase_types_bitfield[region] & type_mask) {

                min_region_erase_size = _erase_type_size_arr[i_ind];
                break;
            }
            type_mask = type_mask << 1;
        }

        if (i_ind == 4) {
            tr_error("ERROR: no erase type was found for region addr");
        }
    }

    return (bd_size_t)min_region_erase_size;
}

bd_size_t SPIFBlockDevice::size() const
{
    if (!_is_initialized) {
        return SPIF_BD_ERROR_DEVICE_ERROR;
    }

    return _device_size_bytes;
}

int SPIFBlockDevice::get_erase_value() const
{
    return 0xFF;
}

/***************************************************/
/*********** SPI Driver API Functions **************/
/***************************************************/
spif_bd_error SPIFBlockDevice::_spi_set_frequency(int freq)
{
    _spi.frequency(freq);
    return SPIF_BD_ERROR_OK;
}

spif_bd_error SPIFBlockDevice::_spi_send_read_command(int read_inst, uint8_t *buffer, bd_addr_t addr, bd_size_t size)
{
    uint32_t dummy_bytes = _dummy_and_mode_cycles / 8;
    int dummy_byte = 0;

    // csel must go low for the entire command (Inst, Address and Data)
    _cs = 0;

    // Write 1 byte Instruction
    _spi.write(read_inst);

    // Write Address (can be either 3 or 4 bytes long)
    for (int address_shift = ((_address_size - 1) * 8); address_shift >= 0; address_shift -= 8) {
        _spi.write((addr >> address_shift) & 0xFF);
    }

    // Write Dummy Cycles Bytes
    for (uint32_t i = 0; i < dummy_bytes; i++) {
        _spi.write(dummy_byte);
    }

    // Read Data
    for (bd_size_t i = 0; i < size; i++) {
        buffer[i] = _spi.write(0);
    }

    // csel back to high
    _cs = 1;
    return SPIF_BD_ERROR_OK;
}

spif_bd_error SPIFBlockDevice::_spi_send_program_command(int prog_inst, const void *buffer, bd_addr_t addr,
        bd_size_t size)
{
    // Send Program (write) command to device driver
    uint32_t dummy_bytes = _dummy_and_mode_cycles / 8;
    int dummy_byte = 0;
    uint8_t *data = (uint8_t *)buffer;

    // csel must go low for the entire command (Inst, Address and Data)
    _cs = 0;

    // Write 1 byte Instruction
    _spi.write(prog_inst);

    // Write Address (can be either 3 or 4 bytes long)
    for (int address_shift = ((_address_size - 1) * 8); address_shift >= 0; address_shift -= 8) {
        _spi.write((addr >> address_shift) & 0xFF);
    }

    // Write Dummy Cycles Bytes
    for (uint32_t i = 0; i < dummy_bytes; i++) {
        _spi.write(dummy_byte);
    }

    // Write Data
    for (bd_size_t i = 0; i < size; i++) {
        _spi.write(data[i]);
    }

    // csel back to high
    _cs = 1;

    return SPIF_BD_ERROR_OK;
}

spif_bd_error SPIFBlockDevice::_spi_send_erase_command(int erase_inst, bd_addr_t addr, bd_size_t size)
{
    tr_info("INFO: Erase Inst: 0x%xh, addr: %llu, size: %llu", erase_inst, addr, size);
    addr = (((int)addr) & 0x00FFF000);
    _spi_send_general_command(erase_inst, addr, NULL, 0, NULL, 0);
    return SPIF_BD_ERROR_OK;
}

spif_bd_error SPIFBlockDevice::_spi_send_general_command(int instruction, bd_addr_t addr, char *tx_buffer,
        size_t tx_length, char *rx_buffer, size_t rx_length)
{
    // Send a general command Instruction to driver
    uint32_t dummy_bytes = _dummy_and_mode_cycles / 8;
    uint8_t dummy_byte = 0x00;

    // csel must go low for the entire command (Inst, Address and Data)
    _cs = 0;

    // Write 1 byte Instruction
    _spi.write(instruction);

    // Reading SPI Bus registers does not require Flash Address
    if (addr != SPI_NO_ADDRESS_COMMAND) {
        // Write Address (can be either 3 or 4 bytes long)
        for (int address_shift = ((_address_size - 1) * 8); address_shift >= 0; address_shift -= 8) {
            _spi.write((addr >> address_shift) & 0xFF);
        }

        // Write Dummy Cycles Bytes
        for (uint32_t i = 0; i < dummy_bytes; i++) {
            _spi.write(dummy_byte);
        }
    }

    // Read/Write Data
    _spi.write(tx_buffer, (int)tx_length, rx_buffer, (int)rx_length);

    // csel back to high
    _cs = 1;

    return SPIF_BD_ERROR_OK;
}

/*********************************************************/
/********** SFDP Parsing and Detection Functions *********/
/*********************************************************/
int SPIFBlockDevice::_sfdp_parse_sector_map_table(uint32_t sector_map_table_addr, size_t sector_map_table_size)
{
    uint8_t sector_map_table[SFDP_DEFAULT_BASIC_PARAMS_TABLE_SIZE_BYTES]; /* Up To 16 DWORDS = 64 Bytes */
    uint32_t tmp_region_size = 0;
    int i_ind = 0;
    int prev_boundary = 0;
    // Default set to all type bits 1-4 are common
    int min_common_erase_type_bits = ERASE_BITMASK_ALL;


    spif_bd_error status = _spi_send_read_command(SPIF_SFDP, sector_map_table, sector_map_table_addr /*address*/,
                           sector_map_table_size);
    if (status != SPIF_BD_ERROR_OK) {
        tr_error("ERROR: init - Read SFDP First Table Failed");
        return -1;
    }

    // Currently we support only Single Map Descriptor
    if (! ( (sector_map_table[0] & 0x3) == 0x03 ) && (sector_map_table[1]  == 0x0) ) {
        tr_error("ERROR: Sector Map - Supporting Only Single! Map Descriptor (not map commands)");
        return -1;
    }

    _regions_count = sector_map_table[2] + 1;
    if (_regions_count > SPIF_MAX_REGIONS) {
        tr_error("ERROR: Supporting up to %d regions, current setup to %d regions - fail",
                 SPIF_MAX_REGIONS, _regions_count);
        return -1;
    }

    // Loop through Regions and set for each one: size, supported erase types, high boundary offset
    // Calculate minimum Common Erase Type for all Regions
    for (i_ind = 0; i_ind < _regions_count; i_ind++) {
        tmp_region_size = ((*((uint32_t *)&sector_map_table[(i_ind + 1) * 4])) >> 8) & 0x00FFFFFF; // bits 9-32
        _region_size_bytes[i_ind] = (tmp_region_size + 1) * 256; // Region size is 0 based multiple of 256 bytes;
        _region_erase_types_bitfield[i_ind] = sector_map_table[(i_ind + 1) * 4] & 0x0F; // bits 1-4
        min_common_erase_type_bits &= _region_erase_types_bitfield[i_ind];
        _region_high_boundary[i_ind] = (_region_size_bytes[i_ind] - 1) + prev_boundary;
        prev_boundary = _region_high_boundary[i_ind] + 1;
    }

    // Calc minimum Common Erase Size from min_common_erase_type_bits
    uint8_t type_mask = ERASE_BITMASK_TYPE1;
    for (i_ind = 0; i_ind < 4; i_ind++) {
        if (min_common_erase_type_bits & type_mask) {
            _min_common_erase_size = _erase_type_size_arr[i_ind];
            break;
        }
        type_mask = type_mask << 1;
    }

    if (i_ind == 4) {
        // No common erase type was found between regions
        _min_common_erase_size = 0;
    }

    return 0;
}

int SPIFBlockDevice::_sfdp_parse_basic_param_table(uint32_t basic_table_addr, size_t basic_table_size)
{
    uint8_t param_table[SFDP_DEFAULT_BASIC_PARAMS_TABLE_SIZE_BYTES]; /* Up To 16 DWORDS = 64 Bytes */
    //memset(param_table, 0, SFDP_DEFAULT_BASIC_PARAMS_TABLE_SIZE_BYTES);

    spif_bd_error status = _spi_send_read_command(SPIF_SFDP, param_table, basic_table_addr /*address*/,
                           basic_table_size);
    if (status != SPIF_BD_ERROR_OK) {
        tr_error("ERROR: init - Read SFDP First Table Failed");
        return -1;
    }

    // Check address size, currently only supports 3byte addresses
    if ((param_table[2] & 0x4) != 0 || (param_table[7] & 0x80) != 0) {
        tr_error("ERROR: init - verify 3byte addressing Failed");
        return -1;
    }

    // Get device density (stored in bits - 1)
    uint32_t density_bits = (
                                (param_table[7] << 24) |
                                (param_table[6] << 16) |
                                (param_table[5] << 8 ) |
                                param_table[4] );
    _device_size_bytes = (density_bits + 1) / 8;

    // Set Default read/program/erase Instructions
    _read_instruction = SPIF_READ;
    _prog_instruction = SPIF_PP;
    _erase_instruction = SPIF_SE;

    // Set Page Size (SPI write must be done on Page limits)
    _page_size_bytes = _sfdp_detect_page_size(param_table, basic_table_size);

    // Detect and Set Erase Types
    _sfdp_detect_erase_types_inst_and_size(param_table, basic_table_size, _erase4k_inst, _erase_type_inst_arr,
                                           _erase_type_size_arr);
    _erase_instruction = _erase4k_inst;

    // Detect and Set fastest Bus mode (default 1-1-1)
    _sfdp_detect_best_bus_read_mode(param_table, basic_table_size, _read_instruction);

    return 0;
}

int SPIFBlockDevice::_sfdp_parse_sfdp_headers(uint32_t& basic_table_addr, size_t& basic_table_size,
        uint32_t& sector_map_table_addr, size_t& sector_map_table_size)
{
    uint8_t sfdp_header[16];
    uint8_t param_header[SPIF_SFDP_HEADER_SIZE];
    size_t data_length = SPIF_SFDP_HEADER_SIZE;
    bd_addr_t addr = 0x0;

    // Set 1-1-1 bus mode for SFDP header parsing
    // Initial SFDP read tables are read with 8 dummy cycles
    _read_dummy_and_mode_cycles = 8;
    _dummy_and_mode_cycles = 8;

    spif_bd_error status = _spi_send_read_command(SPIF_SFDP, sfdp_header, addr /*address*/, data_length);
    if (status != SPIF_BD_ERROR_OK) {
        tr_error("ERROR: init - Read SFDP Failed");
        return -1;
    }

    // Verify SFDP signature for sanity
    // Also check that major/minor version is acceptable
    if (!(memcmp(&sfdp_header[0], "SFDP", 4) == 0 && sfdp_header[5] == 1)) {
        tr_error("ERROR: init - _verify SFDP signature and version Failed");
        return -1;
    } else {
        tr_info("INFO: init - verified SFDP Signature and version Successfully");
    }

    // Discover Number of Parameter Headers
    int number_of_param_headers = (int)(sfdp_header[6]) + 1;
    tr_debug("DEBUG: number of Param Headers: %d", number_of_param_headers);


    addr += SPIF_SFDP_HEADER_SIZE;
    data_length = SPIF_PARAM_HEADER_SIZE;

    // Loop over Param Headers and parse them (currently supported Basic Param Table and Sector Region Map Table)
    for (int i_ind = 0; i_ind < number_of_param_headers; i_ind++) {

        status = _spi_send_read_command(SPIF_SFDP, param_header, addr, data_length);
        if (status != SPIF_BD_ERROR_OK) {
            tr_error("ERROR: init - Read Param Table %d Failed", i_ind + 1);
            return -1;
        }

        // The SFDP spec indicates the standard table is always at offset 0
        // in the parameter headers, we check just to be safe
        if (param_header[2] != 1) {
            tr_error("ERROR: Param Table %d - Major Version should be 1!", i_ind + 1);
            return -1;
        }

        if ((param_header[0] == 0) && (param_header[7] == 0xFF)) {
            // Found Basic Params Table: LSB=0x00, MSB=0xFF
            tr_debug("DEBUG: Found Basic Param Table at Table: %d", i_ind + 1);
            basic_table_addr = ( (param_header[6] << 16) | (param_header[5] << 8) | (param_header[4]) );
            // Supporting up to 64 Bytes Table (16 DWORDS)
            basic_table_size = ((param_header[3] * 4) < SFDP_DEFAULT_BASIC_PARAMS_TABLE_SIZE_BYTES) ? (param_header[3] * 4) : 64;

        } else if ((param_header[0] == 81) && (param_header[7] == 0xFF)) {
            // Found Sector Map Table: LSB=0x81, MSB=0xFF
            tr_debug("DEBUG: Found Sector Map Table at Table: %d", i_ind + 1);
            sector_map_table_addr = ( (param_header[6] << 16) | (param_header[5] << 8) | (param_header[4]) );
            sector_map_table_size = param_header[3] * 4;

        }
        addr += SPIF_PARAM_HEADER_SIZE;

    }
    return 0;
}

unsigned int SPIFBlockDevice::_sfdp_detect_page_size(uint8_t *basic_param_table_ptr, int basic_param_table_size)
{
    unsigned int page_size = SPIF_DEFAULT_PAGE_SIZE;

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

int SPIFBlockDevice::_sfdp_detect_erase_types_inst_and_size(uint8_t *basic_param_table_ptr, int basic_param_table_size,
        int& erase4k_inst,
        int *erase_type_inst_arr, unsigned int *erase_type_size_arr)
{
    erase4k_inst = 0xff;
    bool found_4Kerase_type = false;
    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
    erase4k_inst = basic_param_table_ptr[SPIF_BASIC_PARAM_4K_ERASE_TYPE_BYTE];

    if (basic_param_table_size > SPIF_BASIC_PARAM_ERASE_TYPE_1_SIZE_BYTE) {
        // Loop Erase Types 1-4
        for (int i_ind = 0; i_ind < 4; i_ind++) {
            erase_type_inst_arr[i_ind] = 0xff; //0xFF default for unsupported type
            erase_type_size_arr[i_ind] = local_math_power(2,
                                         basic_param_table_ptr[SPIF_BASIC_PARAM_ERASE_TYPE_1_SIZE_BYTE + 2 * i_ind]); // Size given as 2^N
            tr_info("DEBUG: Erase Type(A) %d - Inst: 0x%xh, Size: %d", (i_ind + 1), erase_type_inst_arr[i_ind],
                    erase_type_size_arr[i_ind]);
            if (erase_type_size_arr[i_ind] > 1) {
                // if size==1 type is not supported
                erase_type_inst_arr[i_ind] = basic_param_table_ptr[SPIF_BASIC_PARAM_ERASE_TYPE_1_BYTE + 2 * i_ind];

                if ((erase_type_size_arr[i_ind] < _min_common_erase_size) || (_min_common_erase_size == 0) ) {
                    //Set default minimal common erase for singal region
                    _min_common_erase_size = erase_type_size_arr[i_ind];
                }

                // SFDP standard requires 4K Erase type to exist and its instruction to be identical to legacy field erase instruction
                if (erase_type_size_arr[i_ind] == 4096) {
                    found_4Kerase_type = true;
                    if (erase4k_inst != erase_type_inst_arr[i_ind]) {
                        //Verify 4KErase Type is identical to Legacy 4K erase type specified in Byte 1 of Param Table
                        erase4k_inst = erase_type_inst_arr[i_ind];
                        tr_warning("WARNING: _detectEraseTypesInstAndSize - Default 4K erase Inst is different than erase type Inst for 4K");

                    }
                }
                _region_erase_types_bitfield[0] |= bitfield; // If there's no region map, set region "0" types bitfield as defualt;
            }

            tr_info("INFO: Erase Type %d - Inst: 0x%xh, Size: %d", (i_ind + 1), erase_type_inst_arr[i_ind],
                    erase_type_size_arr[i_ind]);
            bitfield = bitfield << 1;
        }
    }

    if (false == found_4Kerase_type) {
        tr_warning("WARNING: Couldn't find Erase Type for 4KB size");
    }
    return 0;
}

int SPIFBlockDevice::_sfdp_detect_best_bus_read_mode(uint8_t *basic_param_table_ptr, int basic_param_table_size,
        int& read_inst)
{
    do {

        // TBD - SPIF Dual Read Modes Require SPI driver support
        /*
        uint8_t examined_byte;

        if (basic_param_table_size > SPIF_BASIC_PARAM_TABLE_QPI_READ_SUPPORT_BYTE) {
        	examined_byte = basic_param_table_ptr[SPIF_BASIC_PARAM_TABLE_QPI_READ_SUPPORT_BYTE];
        	if (examined_byte & 0x01) {
        		//  Fast Read 2-2-2 Supported
        		read_inst = basic_param_table_ptr[SPIF_BASIC_PARAM_TABLE_222_READ_INST_BYTE];
        		_read_dummy_and_mode_cycles = (basic_param_table_ptr[SPIF_BASIC_PARAM_TABLE_222_READ_INST_BYTE - 1] >> 5)
        								 + (basic_param_table_ptr[SPIF_BASIC_PARAM_TABLE_222_READ_INST_BYTE - 1] & 0x1F);
        		tr_info("\nDEBUG: Read Bus Mode set to 2-2-2, Instruction: 0x%xh", read_inst);
        		break;
        	}
        }
        examined_byte = basic_param_table_ptr[SPIF_BASIC_PARAM_TABLE_FAST_READ_SUPPORT_BYTE];
        if (examined_byte & 0x20) {
            //  Fast Read 1-2-2 Supported
            read_inst = basic_param_table_ptr[SPIF_BASIC_PARAM_TABLE_122_READ_INST_BYTE];
            _read_dummy_and_mode_cycles = (basic_param_table_ptr[SPIF_BASIC_PARAM_TABLE_122_READ_INST_BYTE - 1] >> 5)
                                     + (basic_param_table_ptr[SPIF_BASIC_PARAM_TABLE_122_READ_INST_BYTE - 1] & 0x1F);
            tr_debug("\nDEBUG: Read Bus Mode set to 1-2-2, Instruction: 0x%xh", read_inst);
            break;
        }
        if (examined_byte & 0x01) {
            // Fast Read 1-1-2 Supported
            read_inst = basic_param_table_ptr[SPIF_BASIC_PARAM_TABLE_112_READ_INST_BYTE];
            _read_dummy_and_mode_cycles = (basic_param_table_ptr[SPIF_BASIC_PARAM_TABLE_112_READ_INST_BYTE - 1] >> 5)
                                     + (basic_param_table_ptr[SPIF_BASIC_PARAM_TABLE_112_READ_INST_BYTE - 1] & 0x1F);
             tr_debug("\nDEBUG: Read Bus Mode set to 1-1-2, Instruction: 0x%xh", _read_instruction);
            break;
        }
         */
        _read_dummy_and_mode_cycles = 0;
        tr_debug("\nDEBUG: Read Bus Mode set to 1-1-1, Instruction: 0x%xh", read_inst);
    } while (false);

    return 0;
}

int SPIFBlockDevice::_reset_flash_mem()
{
    // Perform Soft Reset of the Device prior to initialization
    int status = 0;
    char status_value[2] = {0};
    tr_info("INFO: _reset_flash_mem:\n");
    //Read the Status Register from device
    if (SPIF_BD_ERROR_OK == _spi_send_general_command(SPIF_RDSR, SPI_NO_ADDRESS_COMMAND, NULL, 0, status_value, 1) ) {
        // store received values in status_value
        tr_debug("DEBUG: Reading Status Register Success: value = 0x%x\n", (int)status_value[0]);
    } else {
        tr_debug("ERROR: Reading Status Register failed\n");
        status = -1;
    }

    if (0 == status) {
        //Send Reset Enable
        if (SPIF_BD_ERROR_OK == _spi_send_general_command(SPIF_RSTEN, SPI_NO_ADDRESS_COMMAND, NULL, 0, NULL, 0) ) {
            // store received values in status_value
            tr_debug("DEBUG: Sending RSTEN Success\n");
        } else {
            tr_error("ERROR: Sending RSTEN failed\n");
            status = -1;
        }

        if (0 == status) {
            //Send Reset
            if (SPIF_BD_ERROR_OK == _spi_send_general_command(SPIF_RST, SPI_NO_ADDRESS_COMMAND, NULL, 0, NULL, 0)) {
                // store received values in status_value
                tr_debug("DEBUG: Sending RST Success\n");
            } else {
                tr_error("ERROR: Sending RST failed\n");
                status = -1;
            }
            _is_mem_ready();
        }
    }

    return status;
}

bool SPIFBlockDevice::_is_mem_ready()
{
    // Check Status Register Busy Bit to Verify the Device isn't Busy
    char status_value[2];
    int retries = 0;
    bool mem_ready = true;

    do {
        wait_ms(1);
        retries++;
        //Read the Status Register from device
        if (SPIF_BD_ERROR_OK != _spi_send_general_command(SPIF_RDSR, SPI_NO_ADDRESS_COMMAND, NULL, 0, status_value,
                1)) {   // store received values in status_value
            tr_error("ERROR: Reading Status Register failed\n");
        }
    } while ( (status_value[0] & SPIF_STATUS_BIT_WIP) != 0 && retries < IS_MEM_READY_MAX_RETRIES );

    if ((status_value[0] & SPIF_STATUS_BIT_WIP) != 0) {
        tr_error("ERROR: _is_mem_ready FALSE\n");
        mem_ready = false;
    }
    return mem_ready;
}

int SPIFBlockDevice::_set_write_enable()
{
    // Check Status Register Busy Bit to Verify the Device isn't Busy
    char status_value[2];
    int status = -1;

    do {
        if (SPIF_BD_ERROR_OK !=  _spi_send_general_command(SPIF_WREN, SPI_NO_ADDRESS_COMMAND, NULL, 0, NULL, 0)) {
            tr_error("ERROR:Sending WREN command FAILED\n");
            break;
        }

        if ( false == _is_mem_ready()) {
            tr_error("ERROR: Device not ready, write failed");
            break;
        }

        memset(status_value, 0, 2);
        if (SPIF_BD_ERROR_OK != _spi_send_general_command(SPIF_RDSR, SPI_NO_ADDRESS_COMMAND, NULL, 0, status_value,
                1)) {   // store received values in status_value
            tr_error("ERROR: Reading Status Register failed\n");
            break;
        }

        if ((status_value[0] & SPIF_STATUS_BIT_WEL) == 0) {
            tr_error("ERROR: _set_write_enable failed\n");
            break;
        }
        status = 0;
    } while (false);
    return status;
}

/*********************************************/
/************* Utility Functions *************/
/*********************************************/
int SPIFBlockDevice::_utils_find_addr_region(bd_size_t offset)
{
    //Find the region to which the given offset belong to
    if ((offset > _device_size_bytes) || (_regions_count == 0)) {
        return -1;
    }

    if (_regions_count == 1) {
        return 0;
    }

    for (int i_ind = _regions_count - 2; i_ind >= 0; i_ind--) {

        if (offset > _region_high_boundary[i_ind]) {
            return (i_ind + 1);
        }
    }
    return -1;

}

int SPIFBlockDevice::_utils_iterate_next_largest_erase_type(uint8_t& bitfield, int size, int offset, int boundry)
{
    // Iterate on all supported Erase Types of the Region to which the offset belong to.
    // Iterates from highest type to lowest
    uint8_t type_mask = ERASE_BITMASK_TYPE4;
    int i_ind  = 0;
    int largest_erase_type = 0;
    for (i_ind = 3; i_ind >= 0; i_ind--) {
        if (bitfield & type_mask) {
            largest_erase_type = i_ind;
            if ( (size > (int)(_erase_type_size_arr[largest_erase_type])) &&
                    ((boundry - offset) > (int)(_erase_type_size_arr[largest_erase_type])) ) {
                break;
            } else {
                bitfield &= ~type_mask;
            }
        }
        type_mask = type_mask >> 1;
    }

    if (i_ind == 4) {
        tr_error("ERROR: no erase type was found for current region addr");
    }
    return largest_erase_type;

}

/*********************************************/
/************** Local Functions **************/
/*********************************************/
static unsigned int local_math_power(int base, int exp)
{
    // Integer X^Y function, used to calculate size fields given in 2^N format
    int result = 1;
    while (exp) {
        result *= base;
        exp--;
    }
    return result;
}



EEPROM-based I2C block device example

Block device for EEPROM-based I2C devices.

/* Simple access class for I2C EEPROM chips like Microchip 24LC
 * Copyright (c) 2015 Robin Hourahane
 *
 * 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 "I2CEEBlockDevice.h"

#define I2CEE_TIMEOUT 10000
 

I2CEEBlockDevice::I2CEEBlockDevice(
        PinName sda, PinName scl, uint8_t addr,
        bd_size_t size, bd_size_t block, int freq)
    : _i2c_addr(addr), _size(size), _block(block)
{
    _i2c = new (_i2c_buffer) I2C(sda, scl);
    _i2c->frequency(freq);
}

I2CEEBlockDevice::I2CEEBlockDevice(
        I2C * i2c_obj, uint8_t addr,
        bd_size_t size, bd_size_t block)
    : _i2c_addr(addr), _size(size), _block(block)
{
    _i2c = i2c_obj;
}
I2CEEBlockDevice::~I2CEEBlockDevice()
{
    if (_i2c == (I2C*)_i2c_buffer) {
        _i2c->~I2C();
    }
}

int I2CEEBlockDevice::init()
{
    return _sync();
}

int I2CEEBlockDevice::deinit()
{
    return 0;
}

int I2CEEBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size)
{
    // Check the address and size fit onto the chip.
    MBED_ASSERT(is_valid_read(addr, size));

    _i2c->start();
    if (!_i2c->write(_i2c_addr | 0) ||
        !_i2c->write((char)(addr >> 8)) ||
        !_i2c->write((char)(addr & 0xff))) {
        return BD_ERROR_DEVICE_ERROR;
    }
    _i2c->stop();

    if (_i2c->read(_i2c_addr, static_cast<char*>(buffer), size) < 0) {
        return BD_ERROR_DEVICE_ERROR;
    }

    return 0;
}
 
int I2CEEBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t size)
{
    // Check the addr and size fit onto the chip.
    MBED_ASSERT(is_valid_program(addr, size));
        
    // While we have some more data to write.
    while (size > 0) {
        uint32_t off = addr % _block;
        uint32_t chunk = (off + size < _block) ? size : (_block - off);

        _i2c->start();
        if (!_i2c->write(_i2c_addr | 0) ||
            !_i2c->write((char)(addr >> 8)) ||
            !_i2c->write((char)(addr & 0xff))) {
            return BD_ERROR_DEVICE_ERROR;
        }

        for (unsigned i = 0; i < chunk; i++) {
            _i2c->write(static_cast<const char*>(buffer)[i]);
        }
        _i2c->stop();

        int err = _sync();
        if (err) {
            return err;
        }

        addr += chunk;
        size -= chunk;
        buffer = static_cast<const char*>(buffer) + chunk;
    }

    return 0;
}

int I2CEEBlockDevice::erase(bd_addr_t addr, bd_size_t size)
{
    // No erase needed
    return 0;
}

int I2CEEBlockDevice::_sync()
{
    // The chip doesn't ACK while writing to the actual EEPROM
    // so loop trying to do a zero byte write until it is ACKed
    // by the chip.
    for (int i = 0; i < I2CEE_TIMEOUT; i++) {
        if (_i2c->write(_i2c_addr | 0, 0, 0) < 1) {
            return 0;
        }

        wait_ms(1);
    }

    return BD_ERROR_DEVICE_ERROR;
}
 
bd_size_t I2CEEBlockDevice::get_read_size() const
{
    return 1;
}

bd_size_t I2CEEBlockDevice::get_program_size() const
{
    return 1;
}

bd_size_t I2CEEBlockDevice::get_erase_size() const
{
    return 1;
}

bd_size_t I2CEEBlockDevice::size() const
{
    return _size;
}

FileSystem

The file system API provides a common interface for implementing a file system on a block-based storage device. The file system API is a class-based interface, but implementing the file system API provides the standard POSIX file API familiar to C users.

File system class reference

Public Member Functions
 FileSystem (const char *name=NULL)
virtual int mount (BlockDevice *bd)=0
virtual int unmount ()=0
virtual int reformat (BlockDevice *bd=NULL)
virtual int remove (const char *path)
virtual int rename (const char *path, const char *newpath)
virtual int stat (const char *path, struct stat *st)
virtual int mkdir (const char *path, mode_t mode)
 Public Member Functions inherited from mbed::FileSystemLike
 FileSystemLike (const char *name=NULL)
FileHandleopen (const char *path, int flags)
DirHandleopendir (const char *path)
 Public Member Functions inherited from mbed::FileSystemHandle
virtual ~FileSystemHandle ()
 Public Member Functions inherited from mbed::FileBase
 FileBase (const char *name, PathType t)
const char * getName (void)
PathType getPathType (void)
Protected Member Functions
virtual int file_open (fs_file_t *file, const char *path, int flags)=0
virtual int file_close (fs_file_t file)=0
virtual ssize_t file_read (fs_file_t file, void *buffer, size_t size)=0
virtual ssize_t file_write (fs_file_t file, const void *buffer, size_t size)=0
virtual int file_sync (fs_file_t file)
virtual int file_isatty (fs_file_t file)
virtual off_t file_seek (fs_file_t file, off_t offset, int whence)=0
virtual off_t file_tell (fs_file_t file)
virtual void file_rewind (fs_file_t file)
virtual off_t file_size (fs_file_t file)
virtual int dir_open (fs_dir_t *dir, const char *path)
virtual int dir_close (fs_dir_t dir)
virtual ssize_t dir_read (fs_dir_t dir, struct dirent *ent)
virtual void dir_seek (fs_dir_t dir, off_t offset)
virtual off_t dir_tell (fs_dir_t dir)
virtual void dir_rewind (fs_dir_t dir)
virtual size_t dir_size (fs_dir_t dir)
virtual int open (FileHandle **file, const char *path, int flags)
virtual int open (DirHandle **dir, const char *path)
Friends
class File
class Dir
Additional Inherited Members
 Static Public Member Functions inherited from mbed::FileBase
static FileBaselookup (const char *name, unsigned int len)
static FileBaseget (int n)

File system example

#include "mbed.h"
#include "FATFileSystem.h"
#include "HeapBlockDevice.h"
#include <stdio.h>
#include <errno.h>

HeapBlockDevice bd(128 * 512, 512);
FATFileSystem fs("fs");

void return_error(int ret_val){
  if (ret_val)
    printf("Failure. %d\r\n", ret_val);
  else
    printf("done.\r\n");
}

void errno_error(void* ret_val){
  if (ret_val == NULL)
    printf(" Failure. %d \r\n", errno);
  else
    printf(" done.\r\n");
}

int main() {
  int error = 0;
  printf("Welcome to the filesystem example.\r\n"
         "Formatting a FAT, RAM-backed filesystem. ");
  error = FATFileSystem::format(&bd);
  return_error(error);

  printf("Mounting the filesystem on \"/fs\". ");
  error = fs.mount(&bd);
  return_error(error);

  printf("Opening a new file, numbers.txt.");
  FILE* fd = fopen("/fs/numbers.txt", "w");
  errno_error(fd);

  for (int i = 0; i < 20; i++){
    printf("Writing decimal numbers to a file (%d/20)\r", i);
    fprintf(fd, "%d\r\n", i);
  }
  printf("Writing decimal numbers to a file (20/20) done.\r\n");

  printf("Closing file.");
  fclose(fd);
  printf(" done.\r\n");

  printf("Re-opening file read-only.");
  fd = fopen("/fs/numbers.txt", "r");
  errno_error(fd);

  printf("Dumping file to screen.\r\n");
  char buff[16] = {0};
  while (!feof(fd)){
    int size = fread(&buff[0], 1, 15, fd);
    fwrite(&buff[0], 1, size, stdout);
  }
  printf("EOF.\r\n");

  printf("Closing file.");
  fclose(fd);
  printf(" done.\r\n");

  printf("Opening root directory.");
  DIR* dir = opendir("/fs/");
  errno_error(fd);

  struct dirent* de;
  printf("Printing all filenames:\r\n");
  while((de = readdir(dir)) != NULL){
    printf("  %s\r\n", &(de->d_name)[0]);
  }

  printf("Closing root directory. ");
  error = closedir(dir);
  return_error(error);
  printf("Filesystem Demo complete.\r\n");

  while (true) {}
}


Important Information for this Arm website

This site uses cookies to store information on your computer. By continuing to use our site, you consent to our cookies. If you are not happy with the use of these cookies, please review our Cookie Policy to learn how they can be disabled. By disabling cookies, some features of the site will not work.