/* mbed Microcontroller Library * Copyright (c) 2016 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 "DataFlashBlockDevice.h" #include "mbed_critical.h" #include /* constants */ #define DATAFLASH_READ_SIZE 1 #define DATAFLASH_PROG_SIZE 1 #define DATAFLASH_TIMEOUT 10000 #define DATAFLASH_ID_MATCH 0x1F20 #define DATAFLASH_ID_DENSITY_MASK 0x001F #define DATAFLASH_PAGE_SIZE_256 0x0100 #define DATAFLASH_PAGE_SIZE_264 0x0108 #define DATAFLASH_PAGE_SIZE_512 0x0200 #define DATAFLASH_PAGE_SIZE_528 0x0210 #define DATAFLASH_BLOCK_SIZE_2K 0x0800 #define DATAFLASH_BLOCK_SIZE_2K1 0x0840 #define DATAFLASH_BLOCK_SIZE_4K 0x1000 #define DATAFLASH_BLOCK_SIZE_4K1 0x1080 #define DATAFLASH_PAGE_BIT_264 9 #define DATAFLASH_PAGE_BIT_528 10 /* enable debug */ #ifndef DATAFLASH_DEBUG #define DATAFLASH_DEBUG 0 #endif /* DATAFLASH_DEBUG */ #if DATAFLASH_DEBUG #define DEBUG_PRINTF(...) printf(__VA_ARGS__) #else #define DEBUG_PRINTF(...) #endif void _print_status(uint16_t status); /* non-exhaustive opcode list */ enum opcode { DATAFLASH_OP_NOP = 0x00, DATAFLASH_OP_STATUS = 0xD7, DATAFLASH_OP_ID = 0x9F, DATAFLASH_OP_READ_LOW_POWER = 0x01, DATAFLASH_OP_READ_LOW_FREQUENCY = 0x03, DATAFLASH_OP_PROGRAM_DIRECT = 0x02, // Program through Buffer 1 without Built-In Erase DATAFLASH_OP_PROGRAM_DIRECT_WITH_ERASE = 0x82, DATAFLASH_OP_ERASE_BLOCK = 0x50, }; /* non-exhaustive command list */ enum command { DATAFLASH_COMMAND_WRITE_DISABLE = 0x3D2A7FA9, DATAFLASH_COMMAND_WRITE_ENABLE = 0x3D2A7F9A, DATAFLASH_COMMAND_BINARY_PAGE_SIZE = 0x3D2A80A6, DATAFLASH_COMMAND_DATAFLASH_PAGE_SIZE = 0x3D2A80A7, }; /* bit masks for interpreting the status register */ enum status_bit { DATAFLASH_BIT_READY = (0x01 << 15), DATAFLASH_BIT_COMPARE = (0x01 << 14), DATAFLASH_BIT_DENSITY = (0x0F << 10), DATAFLASH_BIT_PROTECT = (0x01 << 9), DATAFLASH_BIT_PAGE_SIZE = (0x01 << 8), DATAFLASH_BIT_ERASE_PROGRAM_ERROR = (0x01 << 5), DATAFLASH_BIT_SECTOR_LOCKDOWN = (0x01 << 3), DATAFLASH_BIT_PROGRAM_SUSPEND_2 = (0x01 << 2), DATAFLASH_BIT_PROGRAM_SUSPEND_1 = (0x01 << 1), DATAFLASH_BIT_ERASE_SUSPEND = (0x01 << 0), }; /* bit masks for detecting density from status register */ enum status_density { DATAFLASH_STATUS_DENSITY_2_MBIT = (0x05 << 10), DATAFLASH_STATUS_DENSITY_4_MBIT = (0x07 << 10), DATAFLASH_STATUS_DENSITY_8_MBIT = (0x09 << 10), DATAFLASH_STATUS_DENSITY_16_MBIT = (0x0B << 10), DATAFLASH_STATUS_DENSITY_32_MBIT = (0x0D << 10), DATAFLASH_STATUS_DENSITY_64_MBIT = (0x0F << 10), }; /* code for calculating density */ enum id_density { DATAFLASH_ID_DENSITY_2_MBIT = 0x03, DATAFLASH_ID_DENSITY_4_MBIT = 0x04, DATAFLASH_ID_DENSITY_8_MBIT = 0x05, DATAFLASH_ID_DENSITY_16_MBIT = 0x06, DATAFLASH_ID_DENSITY_32_MBIT = 0x07, DATAFLASH_ID_DENSITY_64_MBIT = 0x08, }; /* typical duration in milliseconds for each operation */ enum timing { DATAFLASH_TIMING_ERASE_PROGRAM_PAGE = 17, DATAFLASH_TIMING_PROGRAM_PAGE = 3, DATAFLASH_TIMING_ERASE_PAGE = 12, DATAFLASH_TIMING_ERASE_BLOCK = 45, DATAFLASH_TIMING_ERASE_SECTOR = 700, DATAFLASH_TIMING_ERASE_CHIP = 45000 }; /* frequency domains */ enum frequency { DATAFLASH_LOW_POWER_FREQUENCY = 15000000, DATAFLASH_LOW_FREQUENCY = 50000000, DATAFLASH_HIGH_FREQUENCY = 85000000, DATAFLASH_HIGHEST_FREQUENCY = 104000000 }; /* number of dummy bytes required in each frequency domain */ enum dummy { DATAFLASH_LOW_POWER_BYTES = 0, DATAFLASH_LOW_FREQUENCY_BYTES = 0, DATAFLASH_HIGH_FREQUENCY_BYTES = 1, DATAFLASH_HIGHEST_FREQUENCY_BYTES = 2 }; DataFlashBlockDevice::DataFlashBlockDevice(PinName mosi, PinName miso, PinName sclk, PinName cs, int freq, PinName nwp) : _spi(mosi, miso, sclk), _cs(cs, 1), _nwp(nwp), _device_size(0), _page_size(0), _block_size(0), _is_initialized(0), _init_ref_count(0) { /* check that frequency is within range */ if (freq > DATAFLASH_LOW_FREQUENCY) { /* cap frequency at the highest supported one */ _spi.frequency(DATAFLASH_LOW_FREQUENCY); } else { /* freqency is valid, use as-is */ _spi.frequency(freq); } /* write protect chip if pin is connected */ if (nwp != NC) { _nwp = 0; } } int DataFlashBlockDevice::init() { DEBUG_PRINTF("init\r\n"); if (!_is_initialized) { _init_ref_count = 0; } uint32_t val = core_util_atomic_incr_u32(&_init_ref_count, 1); if (val != 1) { return BD_ERROR_OK; } int result = BD_ERROR_DEVICE_ERROR; /* read ID register to validate model and set dimensions */ uint16_t id = _get_register(DATAFLASH_OP_ID); DEBUG_PRINTF("id: %04X\r\n", id & DATAFLASH_ID_MATCH); /* get status register to verify the page size mode */ uint16_t status = _get_register(DATAFLASH_OP_STATUS); /* manufacture ID match */ if ((id & DATAFLASH_ID_MATCH) == DATAFLASH_ID_MATCH) { /* calculate density */ _device_size = 0x8000 << (id & DATAFLASH_ID_DENSITY_MASK); bool binary_page_size = true; /* check if device is configured for binary page sizes */ if ((status & DATAFLASH_BIT_PAGE_SIZE) == DATAFLASH_BIT_PAGE_SIZE) { DEBUG_PRINTF("Page size is binary\r\n"); #if MBED_CONF_DATAFLASH_DATAFLASH_SIZE /* send reconfiguration command */ _write_command(DATAFLASH_COMMAND_DATAFLASH_PAGE_SIZE, NULL, 0); /* wait for device to be ready and update return code */ result = _sync(); /* set binary flag */ binary_page_size = false; #else /* set binary flag */ binary_page_size = true; #endif } else { DEBUG_PRINTF("Page size is not binary\r\n"); #if MBED_CONF_DATAFLASH_BINARY_SIZE /* send reconfiguration command */ _write_command(DATAFLASH_COMMAND_BINARY_PAGE_SIZE, NULL, 0); /* wait for device to be ready and update return code */ result = _sync(); /* set binary flag */ binary_page_size = true; #else /* set binary flag */ binary_page_size = false; #endif } /* set page program size and block erase size */ switch (id & DATAFLASH_ID_DENSITY_MASK) { case DATAFLASH_ID_DENSITY_2_MBIT: case DATAFLASH_ID_DENSITY_4_MBIT: case DATAFLASH_ID_DENSITY_8_MBIT: case DATAFLASH_ID_DENSITY_64_MBIT: if (binary_page_size) { _page_size = DATAFLASH_PAGE_SIZE_256; _block_size = DATAFLASH_BLOCK_SIZE_2K; } else { _page_size = DATAFLASH_PAGE_SIZE_264; _block_size = DATAFLASH_BLOCK_SIZE_2K1; /* adjust device size */ _device_size = (_device_size / DATAFLASH_PAGE_SIZE_256) * DATAFLASH_PAGE_SIZE_264; } break; case DATAFLASH_ID_DENSITY_16_MBIT: case DATAFLASH_ID_DENSITY_32_MBIT: if (binary_page_size) { _page_size = DATAFLASH_PAGE_SIZE_512; _block_size = DATAFLASH_BLOCK_SIZE_4K; } else { _page_size = DATAFLASH_PAGE_SIZE_528; _block_size = DATAFLASH_BLOCK_SIZE_4K1; /* adjust device size */ _device_size = (_device_size / DATAFLASH_PAGE_SIZE_512) * DATAFLASH_PAGE_SIZE_528; } break; default: break; } DEBUG_PRINTF("density: %" PRIu16 "\r\n", id & DATAFLASH_ID_DENSITY_MASK); DEBUG_PRINTF("size: %" PRIu32 "\r\n", _device_size); DEBUG_PRINTF("page: %" PRIu16 "\r\n", _page_size); DEBUG_PRINTF("block: %" PRIu16 "\r\n", _block_size); /* device successfully detected, set OK error code */ result = BD_ERROR_OK; } /* write protect device when idle */ _write_enable(false); if (result == BD_ERROR_OK) { _is_initialized = true; } return result; } int DataFlashBlockDevice::deinit() { DEBUG_PRINTF("deinit\r\n"); if (!_is_initialized) { _init_ref_count = 0; return BD_ERROR_OK; } uint32_t val = core_util_atomic_decr_u32(&_init_ref_count, 1); if (val) { return BD_ERROR_OK; } _is_initialized = false; return BD_ERROR_OK; } int DataFlashBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size) { DEBUG_PRINTF("read: %p %" PRIX64 " %" PRIX64 "\r\n", buffer, addr, size); if (!_is_initialized) { return BD_ERROR_DEVICE_ERROR; } int result = BD_ERROR_DEVICE_ERROR; /* check parameters are valid and the read is within bounds */ if (is_valid_read(addr, size) && buffer) { uint8_t *external_buffer = static_cast(buffer); /* activate device */ _cs = 0; /* send read opcode */ _spi.write(DATAFLASH_OP_READ_LOW_FREQUENCY); /* translate address */ uint32_t address = _translate_address(addr); DEBUG_PRINTF("address: %" PRIX32 "\r\n", address); /* send read address */ _spi.write((address >> 16) & 0xFF); _spi.write((address >> 8) & 0xFF); _spi.write(address & 0xFF); /* clock out one byte at a time and store in external buffer */ for (uint32_t index = 0; index < size; index++) { external_buffer[index] = _spi.write(DATAFLASH_OP_NOP); } /* deactivate device */ _cs = 1; result = BD_ERROR_OK; } return result; } int DataFlashBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t size) { DEBUG_PRINTF("program: %p %" PRIX64 " %" PRIX64 "\r\n", buffer, addr, size); if (!_is_initialized) { return BD_ERROR_DEVICE_ERROR; } int result = BD_ERROR_DEVICE_ERROR; /* check parameters are valid and the write is within bounds */ if (is_valid_program(addr, size) && buffer) { const uint8_t *external_buffer = static_cast(buffer); /* Each write command can only cover one page at a time. Find page and current page offset for handling unaligned writes. */ uint32_t page_number = addr / _page_size; uint32_t page_offset = addr % _page_size; /* disable write protection */ _write_enable(true); /* continue until all bytes have been written */ uint32_t bytes_written = 0; while (bytes_written < size) { /* find remaining bytes to be written */ uint32_t bytes_remaining = size - bytes_written; /* cap the value at the page size and offset */ if (bytes_remaining > (_page_size - page_offset)) { bytes_remaining = _page_size - page_offset; } /* Write one page, bytes_written keeps track of the progress, page_number is the page address, and page_offset is non-zero for unaligned writes. */ result = _write_page(&external_buffer[bytes_written], page_number, page_offset, bytes_remaining); /* update loop variables upon success otherwise break loop */ if (result == BD_ERROR_OK) { bytes_written += bytes_remaining; page_number++; /* After the first successful write, all subsequent writes will be aligned. */ page_offset = 0; } else { break; } } /* enable write protection */ _write_enable(false); } return result; } int DataFlashBlockDevice::erase(bd_addr_t addr, bd_size_t size) { DEBUG_PRINTF("erase: %" PRIX64 " %" PRIX64 "\r\n", addr, size); if (!_is_initialized) { return BD_ERROR_DEVICE_ERROR; } int result = BD_ERROR_DEVICE_ERROR; /* check parameters are valid and the erase is within bounds */ if (is_valid_erase(addr, size)) { /* disable write protection */ _write_enable(true); /* erase one block at a time until the full size has been erased */ uint32_t erased = 0; while (erased < size) { /* set block erase opcode */ uint32_t command = DATAFLASH_OP_ERASE_BLOCK; /* translate address */ uint32_t address = _translate_address(addr); /* set block address */ command = (command << 8) | ((address >> 16) & 0xFF); command = (command << 8) | ((address >> 8) & 0xFF); command = (command << 8) | (address & 0xFF); /* send command to device */ _write_command(command, NULL, 0); /* wait until device is ready and update return value */ result = _sync(); /* if erase failed, break loop */ if (result != BD_ERROR_OK) { break; } /* update loop variables */ addr += _block_size; erased += _block_size; } /* enable write protection */ _write_enable(false); } return result; } bd_size_t DataFlashBlockDevice::get_read_size() const { DEBUG_PRINTF("read size: %d\r\n", DATAFLASH_READ_SIZE); return DATAFLASH_READ_SIZE; } bd_size_t DataFlashBlockDevice::get_program_size() const { DEBUG_PRINTF("program size: %d\r\n", DATAFLASH_PROG_SIZE); return DATAFLASH_PROG_SIZE; } bd_size_t DataFlashBlockDevice::get_erase_size() const { DEBUG_PRINTF("erase size: %" PRIX16 "\r\n", _block_size); return _block_size; } bd_size_t DataFlashBlockDevice::get_erase_size(bd_addr_t addr) const { DEBUG_PRINTF("erase size: %" PRIX16 "\r\n", _block_size); return _block_size; } bd_size_t DataFlashBlockDevice::size() const { DEBUG_PRINTF("device size: %" PRIX32 "\r\n", _device_size); return _device_size; } /** * @brief Function for reading a specific register. * @details Used for reading either the Status Register or Manufacture and ID Register. * * @param opcode Register to be read. * @return value. */ uint16_t DataFlashBlockDevice::_get_register(uint8_t opcode) { DEBUG_PRINTF("_get_register: %" PRIX8 "\r\n", opcode); /* activate device */ _cs = 0; /* write opcode */ _spi.write(opcode); /* read and store result */ int status = (_spi.write(DATAFLASH_OP_NOP)); status = (status << 8) | (_spi.write(DATAFLASH_OP_NOP)); /* deactivate device */ _cs = 1; return status; } /** * @brief Function for sending command and data to device. * @details The command can be an opcode with address and data or * a 4 byte command without data. * * The supported frequencies and the opcode used do not * require dummy bytes to be sent after command. * * @param command Opcode with address or 4 byte command. * @param buffer Data to be sent after command. * @param size Size of buffer. */ void DataFlashBlockDevice::_write_command(uint32_t command, const uint8_t *buffer, uint32_t size) { DEBUG_PRINTF("_write_command: %" PRIX32 " %p %" PRIX32 "\r\n", command, buffer, size); /* activate device */ _cs = 0; /* send command (opcode with data or 4 byte command) */ _spi.write((command >> 24) & 0xFF); _spi.write((command >> 16) & 0xFF); _spi.write((command >> 8) & 0xFF); _spi.write(command & 0xFF); /* send optional data */ if (buffer && size) { for (uint32_t index = 0; index < size; index++) { _spi.write(buffer[index]); } } /* deactivate device */ _cs = 1; } /** * @brief Enable and disable write protection. * * @param enable Boolean for enabling or disabling write protection. */ void DataFlashBlockDevice::_write_enable(bool enable) { DEBUG_PRINTF("_write_enable: %d\r\n", enable); /* enable writing, disable write protection */ if (enable) { /* if not-write-protected pin is connected, select it */ if (_nwp.is_connected()) { _nwp = 1; } /* send 4 byte command enabling writes */ _write_command(DATAFLASH_COMMAND_WRITE_ENABLE, NULL, 0); } else { /* if not-write-protected pin is connected, deselect it */ if (_nwp.is_connected()) { _nwp = 0; } /* send 4 byte command disabling writes */ _write_command(DATAFLASH_COMMAND_WRITE_DISABLE, NULL, 0); } } /** * @brief Sleep and poll status register until device is ready for next command. * * @return BlockDevice compatible error code. */ int DataFlashBlockDevice::_sync(void) { DEBUG_PRINTF("_sync\r\n"); /* default return value if operation times out */ int result = BD_ERROR_DEVICE_ERROR; /* Poll device until a hard coded timeout is reached. The polling interval is based on the typical page program time. */ for (uint32_t timeout = 0; timeout < DATAFLASH_TIMEOUT; timeout += DATAFLASH_TIMING_ERASE_PROGRAM_PAGE) { /* get status register */ uint16_t status = _get_register(DATAFLASH_OP_STATUS); /* erase/program bit set, exit with error code set */ if (status & DATAFLASH_BIT_ERASE_PROGRAM_ERROR) { DEBUG_PRINTF("DATAFLASH_BIT_ERASE_PROGRAM_ERROR\r\n"); break; /* device ready, set OK code set */ } else if (status & DATAFLASH_BIT_READY) { DEBUG_PRINTF("DATAFLASH_BIT_READY\r\n"); result = BD_ERROR_OK; break; /* wait the typical write period before trying again */ } else { DEBUG_PRINTF("wait_ms: %d\r\n", DATAFLASH_TIMING_ERASE_PROGRAM_PAGE); wait_ms(DATAFLASH_TIMING_ERASE_PROGRAM_PAGE); } } return result; } /** * @brief Write single page. * @details Address can be unaligned. * * @param buffer Data to write. * @param addr Address to write from. * @param size Bytes to write. Can at most be the full page. * @return BlockDevice error code. */ int DataFlashBlockDevice::_write_page(const uint8_t *buffer, uint32_t page, uint32_t offset, uint32_t size) { DEBUG_PRINTF("_write_page: %p %" PRIX32 " %" PRIX32 "\r\n", buffer, page, size); uint32_t command = DATAFLASH_OP_NOP; /* opcode for writing directly to device, in a single command, assuming the page has been erased before hand. */ command = DATAFLASH_OP_PROGRAM_DIRECT; uint32_t address = 0; /* convert page number and offset into device address based on address format */ if (_page_size == DATAFLASH_PAGE_SIZE_264) { address = (page << DATAFLASH_PAGE_BIT_264) | offset; } else if (_page_size == DATAFLASH_PAGE_SIZE_528) { address = (page << DATAFLASH_PAGE_BIT_528) | offset; } else { address = (page * _page_size) | offset; } /* set write address */ command = (command << 8) | ((address >> 16) & 0xFF); command = (command << 8) | ((address >> 8) & 0xFF); command = (command << 8) | (address & 0xFF); /* send write command with data */ _write_command(command, buffer, size); /* wait until device is ready before continuing */ int result = _sync(); return result; } /** * @brief Translate address. * @details If the device is configured for non-binary page sizes, * the address is translated from binary to non-binary form. * * @param addr Binary address. * @return Address in format expected by device. */ uint32_t DataFlashBlockDevice::_translate_address(bd_addr_t addr) { uint32_t address = addr; /* translate address if page size is non-binary */ if (_page_size == DATAFLASH_PAGE_SIZE_264) { address = ((addr / DATAFLASH_PAGE_SIZE_264) << DATAFLASH_PAGE_BIT_264) | (addr % DATAFLASH_PAGE_SIZE_264); } else if (_page_size == DATAFLASH_PAGE_SIZE_528) { address = ((addr / DATAFLASH_PAGE_SIZE_528) << DATAFLASH_PAGE_BIT_528) | (addr % DATAFLASH_PAGE_SIZE_528); } return address; } /** * @brief Internal function for printing out each bit set in status register. * * @param status Status register. */ void _print_status(uint16_t status) { #if DATAFLASH_DEBUG DEBUG_PRINTF("%04X\r\n", status); /* device is ready (after write/erase) */ if (status & DATAFLASH_BIT_READY) { DEBUG_PRINTF("DATAFLASH_BIT_READY\r\n"); } /* Buffer comparison failed */ if (status & DATAFLASH_BIT_COMPARE) { DEBUG_PRINTF("DATAFLASH_BIT_COMPARE\r\n"); } /* device size is 2 MB */ if (status & DATAFLASH_STATUS_DENSITY_2_MBIT) { DEBUG_PRINTF("DATAFLASH_STATUS_DENSITY_2_MBIT\r\n"); } /* device size is 4 MB */ if (status & DATAFLASH_STATUS_DENSITY_4_MBIT) { DEBUG_PRINTF("DATAFLASH_STATUS_DENSITY_4_MBIT\r\n"); } /* device size is 8 MB */ if (status & DATAFLASH_STATUS_DENSITY_8_MBIT) { DEBUG_PRINTF("DATAFLASH_STATUS_DENSITY_8_MBIT\r\n"); } /* device size is 16 MB */ if (status & DATAFLASH_STATUS_DENSITY_16_MBIT) { DEBUG_PRINTF("DATAFLASH_STATUS_DENSITY_16_MBIT\r\n"); } /* device size is 32 MB */ if (status & DATAFLASH_STATUS_DENSITY_32_MBIT) { DEBUG_PRINTF("DATAFLASH_STATUS_DENSITY_32_MBIT\r\n"); } /* device size is 64 MB */ if (status & DATAFLASH_STATUS_DENSITY_64_MBIT) { DEBUG_PRINTF("DATAFLASH_STATUS_DENSITY_64_MBIT\r\n"); } /* sector protectino enabled */ if (status & DATAFLASH_BIT_PROTECT) { DEBUG_PRINTF("DATAFLASH_BIT_PROTECT\r\n"); } /* page size is a power of 2 */ if (status & DATAFLASH_BIT_PAGE_SIZE) { DEBUG_PRINTF("DATAFLASH_BIT_PAGE_SIZE\r\n"); } /* erase/program error */ if (status & DATAFLASH_BIT_ERASE_PROGRAM_ERROR) { DEBUG_PRINTF("DATAFLASH_BIT_ERASE_PROGRAM_ERROR\r\n"); } /* sector lockdown still possible */ if (status & DATAFLASH_BIT_SECTOR_LOCKDOWN) { DEBUG_PRINTF("DATAFLASH_BIT_SECTOR_LOCKDOWN\r\n"); } /* program operation suspended while using buffer 2 */ if (status & DATAFLASH_BIT_PROGRAM_SUSPEND_2) { DEBUG_PRINTF("DATAFLASH_BIT_PROGRAM_SUSPEND_2\r\n"); } /* program operation suspended while using buffer 1 */ if (status & DATAFLASH_BIT_PROGRAM_SUSPEND_1) { DEBUG_PRINTF("DATAFLASH_BIT_PROGRAM_SUSPEND_1\r\n"); } /* erase has been suspended */ if (status & DATAFLASH_BIT_ERASE_SUSPEND) { DEBUG_PRINTF("DATAFLASH_BIT_ERASE_SUSPEND\r\n"); } #endif }