blob: 46cfd17dc22a2cdfb02e559da31abdc6de85ad19 [file] [edit]
/*
* Copyright (c) 2022-2025 Macronix International Co., Ltd.
* Copyright (c) 2025 Embeint Pty Ltd
* Copyright (c) 2026 CodeWrights GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT jedec_spi_nand
#include <errno.h>
#include <string.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/crc.h>
#include <zephyr/sys/util.h>
#include "spi_nand.h"
struct spi_nand_config {
/* Devicetree SPI configuration */
struct spi_dt_spec spi;
#ifdef CONFIG_FLASH_PAGE_LAYOUT
/* Flash page layout can be determined from devicetree. */
struct flash_pages_layout layout;
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
/* Flash parameters */
const struct flash_parameters *parameters;
#ifdef CONFIG_SPI_NAND_BAD_BLOCK_CACHE
/* Bad block table */
uint8_t *bad_block_table;
#endif /* CONFIG_SPI_NAND_BAD_BLOCK_CACHE */
/* Size of device in bytes */
uint32_t flash_size;
/* Size of erase unit in bytes */
uint32_t block_size;
/* Maximum duration to erase a block */
uint16_t block_erase_us;
/* Maximum duration to program a page */
uint16_t page_program_us;
/* Maximum duration to read a page to cache */
uint16_t page_read_us;
/* Maximum duration for RESET command to execute */
uint16_t reset_us;
/* Mask to get column address */
uint32_t addr_offset_mask;
/* Shift to apply to get page address */
uint8_t addr_page_shift;
/* Shift to apply to get erase block address */
uint8_t addr_block_shift;
/* Number of bits used for plane selection */
uint8_t plane_addr_bits;
/* Expected JEDEC ID, from jedec-id property */
const uint8_t *jedec_id;
/* Length of the JEDEC ID */
uint8_t jedec_id_len;
/* Program commands support plane select */
bool has_program_plane_select;
/* Read commands support plane select */
bool has_read_plane_select;
};
struct spi_nand_data {
/* Access semaphore */
struct k_sem sem;
};
/* Indicates that an access command includes bytes for the address.
* If not provided the opcode is not followed by address bytes.
*/
#define NAND_ACCESS_ADDRESSED BIT(0)
/* Indicates that addressed access uses a 8-bit address */
#define NAND_ACCESS_8BIT_ADDR BIT(1)
/* Indicates that addressed access uses a 16-bit address */
#define NAND_ACCESS_16BIT_ADDR BIT(2)
/* Indicates that addressed access uses a 24-bit address */
#define NAND_ACCESS_24BIT_ADDR BIT(3)
/* Indicates that addressed access uses a 32-bit address */
#define NAND_ACCESS_32BIT_ADDR BIT(4)
/* Indicates that an access command is performing a write. If not
* provided access is a read.
*/
#define NAND_ACCESS_WRITE BIT(5)
/* Indicates that a dummy byte is to be sent following the address.
*/
#define NAND_ACCESS_DUMMY_BYTE BIT(6)
/* Offset of the bad block marker in the spare area */
#define BAD_BLOCK_MARKER_OFFSET 0x00
LOG_MODULE_REGISTER(spi_nand, CONFIG_FLASH_LOG_LEVEL);
/* Everything necessary to acquire owning access to the device. */
static void acquire_device(const struct device *dev)
{
const struct spi_nand_config *config = dev->config;
struct spi_nand_data *const data = dev->data;
k_sem_take(&data->sem, K_FOREVER);
(void)pm_device_runtime_get(config->spi.bus);
}
/* Everything necessary to release access to the device. */
static void release_device(const struct device *dev)
{
const struct spi_nand_config *config = dev->config;
struct spi_nand_data *const data = dev->data;
(void)pm_device_runtime_put(config->spi.bus);
k_sem_give(&data->sem);
}
static int spi_nand_transceive(const struct device *dev, const struct spi_buf_set *tx_set,
const struct spi_buf_set *rx_set)
{
const struct spi_nand_config *config = dev->config;
int ret;
if (rx_set == NULL) {
ret = spi_write_dt(&config->spi, tx_set);
} else {
ret = spi_transceive_dt(&config->spi, tx_set, rx_set);
}
if (ret == 0) {
/** Communications unreliable without minimal delay between transactions */
k_sleep(K_TICKS(1));
} else {
LOG_DBG("SPI transaction failed (%d)", ret);
}
return ret;
}
/*
* @brief Send an SPI command
*
* @param dev Device struct
* @param opcode The command to send
* @param access flags that determine how the command is constructed.
* See NAND_ACCESS_*.
* @param addr The address to send
* @param data The buffer to store or read the value
* @param length The size of the buffer
* @return 0 on success, negative errno code otherwise
*/
static int spi_nand_access(const struct device *const dev, uint8_t opcode, unsigned int access,
off_t addr, void *data, size_t length)
{
bool is_addressed = (access & NAND_ACCESS_ADDRESSED) != 0U;
bool is_write = (access & NAND_ACCESS_WRITE) != 0U;
uint8_t buf[6] = {0};
uint8_t address_len;
size_t tx_len = 1;
buf[0] = opcode;
if (is_addressed) {
union {
uint32_t u32;
uint8_t u8[4];
} addr32 = {
.u32 = sys_cpu_to_be32(addr),
};
if ((access & NAND_ACCESS_32BIT_ADDR) != 0U) {
address_len = 4;
} else if ((access & NAND_ACCESS_24BIT_ADDR) != 0U) {
address_len = 3;
} else if ((access & NAND_ACCESS_16BIT_ADDR) != 0U) {
address_len = 2;
} else if ((access & NAND_ACCESS_8BIT_ADDR) != 0U) {
address_len = 1;
} else {
address_len = 0;
}
memcpy(&buf[1], &addr32.u8[4 - address_len], address_len);
tx_len += address_len;
}
if (access & NAND_ACCESS_DUMMY_BYTE) {
tx_len += 1;
}
const struct spi_buf tx_buf[2] = {
{.buf = buf, .len = tx_len},
{.buf = (!is_write ? NULL : data), .len = length},
};
const struct spi_buf rx_buf[2] = {
{.buf = NULL, .len = tx_len},
{.buf = data, .len = length},
};
const struct spi_buf_set tx_set = {
.buffers = tx_buf,
/* Non zero length means that data follows opcode, so there are two buffers to tx */
.count = (length != 0) ? 2 : 1,
};
const struct spi_buf_set rx_set = {
.buffers = rx_buf,
.count = 2,
};
return spi_nand_transceive(dev, &tx_set, is_write ? NULL : &rx_set);
}
#define spi_nand_cmd_read(dev, opcode, dest, length) \
spi_nand_access(dev, opcode, 0, 0, dest, length)
#define spi_nand_cmd_read_dummy(dev, opcode, dest, length) \
spi_nand_access(dev, opcode, NAND_ACCESS_DUMMY_BYTE, 0, dest, length)
#define spi_nand_cmd_write(dev, opcode) spi_nand_access(dev, opcode, NAND_ACCESS_WRITE, 0, NULL, 0)
/* Single structure describing a GET_FEATURE/SET_FEATURE command */
struct spi_nand_feature_frame {
/* SPI_NAND_CMD_GET_FEATURE/SPI_NAND_CMD_SET_FEATURE */
uint8_t command;
/* Value from SPI_NAND_FEATURE_ADDR_* */
uint8_t address;
/* Value to write or is read */
uint8_t data;
};
/* Optimised version of spi_nand_access to minimise the overhead of polling status registers.
* Removes the need for the SPI controller to reconfigure the peripheral after sending the first 2
* bytes.
*/
static int spi_nand_feature_op(const struct device *dev, struct spi_nand_feature_frame *to_nand,
struct spi_nand_feature_frame *from_nand)
{
struct spi_buf spi_buf[2] = {
{
.buf = to_nand,
.len = sizeof(*to_nand),
},
{
.buf = from_nand,
.len = sizeof(*from_nand),
},
};
const struct spi_buf_set tx_set = {
.buffers = &spi_buf[0],
.count = 1,
};
const struct spi_buf_set rx_set = {
.buffers = &spi_buf[1],
.count = 1,
};
return spi_nand_transceive(dev, &tx_set, from_nand ? &rx_set : NULL);
}
/* Read feature data from a register */
static int spi_nand_get_feature(const struct device *dev, uint8_t reg, uint8_t *feature)
{
struct spi_nand_feature_frame out = {
.command = SPI_NAND_CMD_GET_FEATURE,
.address = reg,
.data = 0,
};
struct spi_nand_feature_frame in;
int ret;
ret = spi_nand_feature_op(dev, &out, &in);
*feature = in.data;
return ret;
}
/* Write feature data to a register */
static int spi_nand_set_feature(const struct device *dev, uint8_t reg, uint8_t feature)
{
struct spi_nand_feature_frame out = {
.command = SPI_NAND_CMD_SET_FEATURE,
.address = reg,
.data = feature,
};
return spi_nand_feature_op(dev, &out, NULL);
}
/* Wait until all operations are complete */
static int spi_nand_wait_until_ready(const struct device *dev, const char *op, uint32_t timeout_us,
uint32_t poll_us, uint8_t *status)
{
k_ticks_t t = k_uptime_ticks();
k_timepoint_t timeout;
bool expired;
int ret;
timeout = sys_timepoint_calc(K_USEC(timeout_us));
do {
/* Determine the expiry status before the read to ensure that this function only
* returns ETIMEDOUT if the flash still isn't ready *after* the provided timeout_us.
* Checking after the read (in the while condition) can result in the status being
* read once on function entry, then the thread not resuming from the k_sleep until
* after the timeout expires.
*/
expired = sys_timepoint_expired(timeout);
/* Query the flash status */
ret = spi_nand_get_feature(dev, SPI_NAND_FEATURE_ADDR_STATUS, status);
if (ret != 0) {
return ret;
}
if ((*status & SPI_NAND_FEATURE_STATUS_OIP) == 0U) {
t = k_uptime_ticks() - t;
LOG_DBG("Ready after %u us (Op %s, Status %02X)", k_ticks_to_us_near32(t),
op, *status);
return ret;
}
if (!expired) {
k_sleep(K_USEC(poll_us));
}
} while (!expired);
LOG_ERR("Ready timeout (Op %s, Status %02X)", op, *status);
return -ETIMEDOUT;
}
/* Read page to cache, assumes device already acquired */
static int spi_nand_page_read_to_cache(const struct device *dev, uint32_t page)
{
const struct spi_nand_config *config = dev->config;
uint8_t ecc_status;
uint8_t status;
int ret;
/* Trigger the read to cache */
ret = spi_nand_access(dev, SPI_NAND_CMD_PAGE_READ,
NAND_ACCESS_ADDRESSED | NAND_ACCESS_24BIT_ADDR, page, NULL, 0);
if (ret != 0) {
return ret;
}
/* Wait until the read to cache completes (poll with no delays) */
ret = spi_nand_wait_until_ready(dev, "read", config->page_read_us, 0, &status);
if (ret != 0) {
return ret;
}
/* Validate data integrity from ECC */
ecc_status = status & SPI_NAND_FEATURE_ECC_MASK;
switch (ecc_status) {
case SPI_NAND_FEATURE_ECC_ERROR_NOT_CORRECTED:
LOG_WRN("ECC uncorrectable error on page %06x", page);
/* Unique error code for corrupt data (retrying the read won't work) */
return -EBADMSG;
case SPI_NAND_FEATURE_ECC_ERROR_CORRECTED_REFRESH:
case SPI_NAND_FEATURE_ECC_ERROR_CORRECTED:
LOG_DBG("ECC errors corrected on page %06x", page);
break;
case SPI_NAND_FEATURE_ECC_NO_ERRORS:
break;
default:
__ASSERT(false, "Unreachable");
}
return 0;
}
/** Read data from cache, assumes device already acquired */
static int spi_nand_read_from_cache(const struct device *dev, uint8_t plane, uint16_t offset,
void *dest, size_t size)
{
const struct spi_nand_config *config = dev->config;
/* Some chips require plane address for read from cache command */
if (config->has_read_plane_select) {
offset |= (plane << (config->addr_page_shift + 1));
}
return spi_nand_access(dev, SPI_NAND_CMD_READ_CACHE,
NAND_ACCESS_ADDRESSED | NAND_ACCESS_16BIT_ADDR |
NAND_ACCESS_DUMMY_BYTE,
offset, dest, size);
}
/** Write data to cache, assumes device already acquired */
static int spi_nand_write_to_cache(const struct device *dev, uint8_t plane, uint16_t offset,
void *src, size_t size)
{
const struct spi_nand_config *config = dev->config;
/* Some chips require plane address for write to cache command */
if (config->has_program_plane_select) {
offset |= (plane << (config->addr_page_shift + 1));
}
return spi_nand_access(dev, SPI_NAND_CMD_PROGRAM_LOAD,
NAND_ACCESS_WRITE | NAND_ACCESS_ADDRESSED | NAND_ACCESS_16BIT_ADDR,
offset, src, size);
}
static bool valid_region(const struct device *dev, off_t addr, size_t size)
{
const struct spi_nand_config *config = dev->config;
if ((addr < 0) || (addr >= config->flash_size) || (size > config->flash_size) ||
((config->flash_size - addr) < size)) {
return false;
}
return true;
}
static int spi_nand_read(const struct device *dev, off_t addr, void *dest, size_t size)
{
const struct spi_nand_config *config = dev->config;
uint8_t *dest_u8 = dest;
uint32_t page_address;
uint8_t plane;
uint16_t page_offset;
uint16_t bytes_to_end;
uint16_t bytes_to_read;
int ret = 0;
if (size == 0) {
/* No work to do */
return 0;
}
/* Read area must be subregion of device */
if (!valid_region(dev, addr, size)) {
return -EINVAL;
}
acquire_device(dev);
while (size > 0) {
page_address = addr >> config->addr_page_shift;
page_offset = addr & config->addr_offset_mask;
plane = config->plane_addr_bits > 0 ? (addr >> config->addr_block_shift) &
((1 << config->plane_addr_bits) - 1)
: 0;
bytes_to_end = config->parameters->write_block_size - page_offset;
bytes_to_read = MIN(size, bytes_to_end);
/* Copy data from main storage to cache */
LOG_DBG("Read %d from %06x:%03x", bytes_to_read, page_address, page_offset);
ret = spi_nand_page_read_to_cache(dev, page_address);
if (ret != 0) {
LOG_DBG("Copy from NAND to device cache failed (%d)", ret);
break;
}
/* Read data out of cache */
ret = spi_nand_read_from_cache(dev, plane, page_offset, dest_u8, bytes_to_read);
if (ret != 0) {
LOG_DBG("Read from device cache failed (%d)", ret);
break;
}
/* Update for next iteration */
dest_u8 += bytes_to_read;
addr += bytes_to_read;
size -= bytes_to_read;
}
release_device(dev);
return ret;
}
static int spi_nand_write(const struct device *dev, off_t addr, const void *src, size_t size)
{
const struct spi_nand_config *config = dev->config;
uint32_t write_block = config->parameters->write_block_size;
uint8_t *src_u8 = (void *)src;
uint8_t plane = config->plane_addr_bits > 0 ? (addr >> config->addr_block_shift) &
((1 << config->plane_addr_bits) - 1)
: 0;
uint32_t page_address;
uint8_t status;
int ret = 0;
if (size == 0) {
/* No work to do */
return 0;
}
/* Write area must be subregion of device */
if (!valid_region(dev, addr, size)) {
return -EINVAL;
}
/* All writes must be page aligned in both start address and size */
if (addr % write_block) {
return -EINVAL;
}
if (size % write_block) {
return -EINVAL;
}
acquire_device(dev);
while (size > 0) {
/* Enable write operation */
ret = spi_nand_cmd_write(dev, SPI_NAND_CMD_WRITE_ENABLE);
if (ret != 0) {
break;
}
page_address = addr >> config->addr_page_shift;
LOG_DBG("Write %d to %06x:000", write_block, page_address);
/* Copy data to cache (at offset 0) */
ret = spi_nand_write_to_cache(dev, plane, 0, src_u8, write_block);
if (ret != 0) {
LOG_DBG("Copy to device cache failed (%d)", ret);
break;
}
/* Program the cache to the appropriate page */
ret = spi_nand_access(dev, SPI_NAND_CMD_PROGRAM_EXECUTE,
NAND_ACCESS_WRITE | NAND_ACCESS_ADDRESSED |
NAND_ACCESS_24BIT_ADDR,
page_address, NULL, 0);
if (ret != 0) {
LOG_DBG("Program from cache to NAND failed (%d)", ret);
break;
}
/* Wait for the write to complete (poll every 0.1ms) */
ret = spi_nand_wait_until_ready(dev, "write", config->page_program_us, 100,
&status);
if (ret != 0) {
break;
}
if (status & SPI_NAND_FEATURE_STATUS_PROGRAM_FAIL) {
LOG_ERR("Program operation failed");
ret = -EIO;
break;
}
/* Update for next iteration */
src_u8 += write_block;
size -= write_block;
addr += write_block;
}
release_device(dev);
return ret;
}
static int spi_nand_erase(const struct device *dev, off_t addr, size_t size)
{
const struct spi_nand_config *config = dev->config;
uint32_t page_address;
uint8_t status;
int ret = 0;
if (size == 0) {
/* No work to do */
return 0;
}
/* Erase area must be subregion of device */
if (!valid_region(dev, addr, size)) {
return -EINVAL;
}
/* All erases must be block aligned in both start address and size */
if (addr % config->block_size) {
return -EINVAL;
}
if (size % config->block_size) {
return -EINVAL;
}
acquire_device(dev);
while (size > 0) {
/* Enable write (erase) operation */
ret = spi_nand_cmd_write(dev, SPI_NAND_CMD_WRITE_ENABLE);
if (ret != 0) {
break;
}
/* Start the block erase */
page_address = addr >> config->addr_page_shift;
LOG_DBG("Erasing block starting at %06x", page_address);
ret = spi_nand_access(dev, SPI_NAND_CMD_BLOCK_ERASE,
NAND_ACCESS_ADDRESSED | NAND_ACCESS_24BIT_ADDR, page_address,
NULL, 0);
if (ret != 0) {
break;
}
/* Wait for the erase to complete (poll every 0.5ms) */
ret = spi_nand_wait_until_ready(dev, "erase", config->block_erase_us, 500, &status);
if (ret != 0) {
break;
}
if (status & SPI_NAND_FEATURE_STATUS_ERASE_FAIL) {
LOG_ERR("Erase operation failed");
ret = -EIO;
break;
}
/* Update for next iteration */
addr += config->block_size;
size -= config->block_size;
}
release_device(dev);
return ret;
}
static int spi_nand_reset(const struct device *dev)
{
const struct spi_nand_config *config = dev->config;
uint8_t status;
int ret;
ret = spi_nand_cmd_write(dev, SPI_NAND_CMD_RESET);
if (ret != 0) {
return ret;
}
ret = spi_nand_wait_until_ready(dev, "reset", config->reset_us, 100, &status);
return ret;
}
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
static void spi_nand_pages_layout(const struct device *dev,
const struct flash_pages_layout **layout, size_t *layout_size)
{
const struct spi_nand_config *config = dev->config;
*layout = &config->layout;
*layout_size = 1;
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#if defined(CONFIG_FLASH_EX_OP_ENABLED)
static int spi_nand_is_bad_block(const struct device *dev, off_t addr,
enum flash_block_status *status)
{
const struct spi_nand_config *config = dev->config;
const uint32_t bad_block_marker_offset =
config->parameters->write_block_size + BAD_BLOCK_MARKER_OFFSET;
uint8_t plane = config->plane_addr_bits > 0 ? (addr >> config->addr_block_shift) &
((1 << config->plane_addr_bits) - 1)
: 0;
uint32_t page_address;
uint8_t bad_block_marker;
int ret;
/* Address must be in subregion of device */
if (!valid_region(dev, addr, 1)) {
return -EINVAL;
}
/* Address must be aligned to erase block */
if (addr % config->block_size) {
return -EINVAL;
}
page_address = addr >> config->addr_page_shift;
/* Copy data from main storage to cache (ignore ECC errors) */
ret = spi_nand_page_read_to_cache(dev, page_address);
if ((ret != 0) && (ret != -EBADMSG)) {
LOG_DBG("Copy from NAND to device cache failed (%d)", ret);
return ret;
}
/* Read bad block marker out of cache */
ret = spi_nand_read_from_cache(dev, plane, bad_block_marker_offset, &bad_block_marker,
sizeof(bad_block_marker));
if (ret != 0) {
LOG_DBG("Read from device cache failed (%d)", ret);
return ret;
}
/* Verify bad block marker */
if (bad_block_marker != 0xff) {
LOG_DBG("Block at address %06x is bad (marker %02x)", page_address,
bad_block_marker);
*status = FLASH_BLOCK_BAD;
} else {
LOG_DBG("Block at address %06x is good", page_address);
*status = FLASH_BLOCK_GOOD;
}
return 0;
}
static int spi_nand_mark_bad_block(const struct device *dev, off_t addr)
{
const struct spi_nand_config *config = dev->config;
const uint32_t bad_block_marker_offset =
config->parameters->write_block_size + BAD_BLOCK_MARKER_OFFSET;
uint8_t plane = config->plane_addr_bits > 0 ? (addr >> config->addr_block_shift) &
((1 << config->plane_addr_bits) - 1)
: 0;
uint8_t bad_block_marker = 0x00;
uint32_t page_address;
uint8_t status;
int ret;
/* Address must be in subregion of device */
if (!valid_region(dev, addr, 1)) {
return -EINVAL;
}
/* Address must be aligned to erase block */
if (addr % config->block_size) {
return -EINVAL;
}
page_address = addr >> config->addr_page_shift;
LOG_DBG("Marking block starting at %06x as bad", page_address);
/* Enable write operation */
ret = spi_nand_cmd_write(dev, SPI_NAND_CMD_WRITE_ENABLE);
if (ret != 0) {
return ret;
}
/* Copy bad block marker to cache (all other bytes stay reset at 0xff) */
ret = spi_nand_write_to_cache(dev, plane, bad_block_marker_offset, &bad_block_marker,
sizeof(bad_block_marker));
if (ret != 0) {
LOG_DBG("Copy to device cache failed (%d)", ret);
return ret;
}
/* Program the cache to the appropriate page */
ret = spi_nand_access(dev, SPI_NAND_CMD_PROGRAM_EXECUTE,
NAND_ACCESS_WRITE | NAND_ACCESS_ADDRESSED | NAND_ACCESS_24BIT_ADDR,
page_address, NULL, 0);
if (ret != 0) {
LOG_DBG("Program from cache to NAND failed (%d)", ret);
return ret;
}
/* Wait for the write to complete (poll every 0.1ms) */
ret = spi_nand_wait_until_ready(dev, "write", config->page_program_us, 100, &status);
if (ret != 0) {
return ret;
}
if (status & SPI_NAND_FEATURE_STATUS_PROGRAM_FAIL) {
LOG_ERR("Program operation failed");
ret = -EIO;
return ret;
}
#ifdef CONFIG_SPI_NAND_BAD_BLOCK_CACHE
/* Update the bad block table */
uint32_t bbt_idx = addr / config->block_size;
config->bad_block_table[bbt_idx / BITS_PER_BYTE] |= BIT(bbt_idx % BITS_PER_BYTE);
#endif /* CONFIG_SPI_NAND_BAD_BLOCK_CACHE */
return 0;
}
#ifdef CONFIG_SPI_NAND_BAD_BLOCK_CACHE
static int bad_block_table_is_bad_block(const struct device *dev, off_t addr,
enum flash_block_status *status)
{
const struct spi_nand_config *config = dev->config;
uint32_t bbt_idx;
/* Address must be in subregion of device */
if (!valid_region(dev, addr, 1)) {
return -EINVAL;
}
/* Address must be aligned to erase block */
if (addr % config->block_size) {
return -EINVAL;
}
bbt_idx = addr / config->block_size;
if (config->bad_block_table[bbt_idx / BITS_PER_BYTE] & BIT(bbt_idx % BITS_PER_BYTE)) {
*status = FLASH_BLOCK_BAD;
} else {
*status = FLASH_BLOCK_GOOD;
}
return 0;
}
static int build_bad_block_table(const struct device *dev)
{
int ret;
enum flash_block_status status;
const struct spi_nand_config *config = dev->config;
const uint32_t bad_block_table_size =
DIV_ROUND_UP(config->flash_size / config->block_size, BITS_PER_BYTE);
/* Init all blocks as good */
memset(config->bad_block_table, 0, bad_block_table_size);
/* Scan all blocks */
for (uint32_t addr = 0; addr < config->flash_size; addr += config->block_size) {
ret = spi_nand_is_bad_block(dev, addr, &status);
if (ret != 0) {
return ret;
}
if (status == FLASH_BLOCK_BAD) {
uint32_t idx = addr / config->block_size;
config->bad_block_table[idx / BITS_PER_BYTE] |= BIT(idx % BITS_PER_BYTE);
}
}
LOG_HEXDUMP_DBG(config->bad_block_table, bad_block_table_size, "Bad block table");
return 0;
}
#endif /* CONFIG_SPI_NAND_BAD_BLOCK_CACHE */
static int spi_nand_ex_op(const struct device *dev, uint16_t code, const uintptr_t in, void *out)
{
int ret;
acquire_device(dev);
switch (code) {
case FLASH_EX_OP_RESET:
ret = spi_nand_reset(dev);
break;
case FLASH_EX_OP_IS_BAD_BLOCK:
#ifdef CONFIG_SPI_NAND_BAD_BLOCK_CACHE
ret = bad_block_table_is_bad_block(dev, *(const off_t *)in,
(enum flash_block_status *)out);
#else
ret = spi_nand_is_bad_block(dev, *(const off_t *)in,
(enum flash_block_status *)out);
#endif
break;
case FLASH_EX_OP_MARK_BAD_BLOCK:
ret = spi_nand_mark_bad_block(dev, *(const off_t *)in);
break;
default:
ret = -ENOTSUP;
break;
}
release_device(dev);
return ret;
}
#endif /* CONFIG_FLASH_EX_OP_ENABLED */
static const struct flash_parameters *flash_nand_get_parameters(const struct device *dev)
{
const struct spi_nand_config *config = dev->config;
return config->parameters;
}
static int flash_nand_get_size(const struct device *dev, uint64_t *size)
{
const struct spi_nand_config *config = dev->config;
*size = config->flash_size;
return 0;
}
static int onfi_parameters_load(const struct device *dev)
{
const struct spi_nand_config *config = dev->config;
struct spi_nand_onfi_parameter_page onfi;
uint16_t computed_crc;
uint32_t total_size;
uint32_t block_size;
uint32_t page_size;
uint8_t cfg;
int ret;
/* Configure device to allow reading parameter page */
ret = spi_nand_get_feature(dev, SPI_NAND_FEATURE_ADDR_CONFIG, &cfg);
if (ret != 0) {
return ret;
}
cfg |= SPI_NAND_FEATURE_CONFIG_OTP_EN;
ret = spi_nand_set_feature(dev, SPI_NAND_FEATURE_ADDR_CONFIG, cfg);
if (ret != 0) {
return ret;
}
/* Validate that OTP_EN bit was set, otherwise read can never succeed */
ret = spi_nand_get_feature(dev, SPI_NAND_FEATURE_ADDR_CONFIG, &cfg);
if (ret != 0) {
return ret;
}
if (!(cfg & SPI_NAND_FEATURE_CONFIG_OTP_EN)) {
LOG_ERR("Failed to enable OTP bit to read parameter page");
return -EIO;
}
/* Sanity check the on-chip ECC configuration */
if (!(cfg & SPI_NAND_FEATURE_CONFIG_ECC_EN)) {
LOG_WRN("On-chip ECC not enabled");
}
/* Load parameter info into cache (ignoring ECC errors) */
ret = spi_nand_page_read_to_cache(dev, 1);
if ((ret != 0) && (ret != -EBADMSG)) {
return ret;
}
/* The compiler does not know that config->parameters->write_block_size is always non-zero.
* As a result, it believes it is possible for the loop to be skipped, leaving both CRC
* variables uninitialized at the comparison point below the loop. Explicitly set the values
* to suppress the warnings.
*/
computed_crc = 0;
onfi.integrity_crc = 1;
/* Scan through the loaded info until we find a valid CRC.
* Use the assumed page size from devicetree.
*/
page_size = config->parameters->write_block_size;
for (int i = 0; i < page_size; i += sizeof(onfi)) {
ret = spi_nand_read_from_cache(dev, 0, i, &onfi, sizeof(onfi));
if (ret != 0) {
return ret;
}
computed_crc =
crc16(CRC16_POLY, 0x4F4E, (void *)&onfi, sizeof(onfi) - sizeof(uint16_t));
if (computed_crc == onfi.integrity_crc) {
LOG_DBG("Valid CRC: %04X", computed_crc);
break;
}
LOG_DBG("Parameters at offset %u corrupt (%04X != %04X)", i, computed_crc,
onfi.integrity_crc);
}
if (computed_crc != onfi.integrity_crc) {
LOG_ERR("No valid ONFI parameters blocks found");
return -ENOSPC;
}
/* Display parameters from ONFI block.
* "%{N}s" pads a string to N characters.
* "%.{N}s" prints at most N characters.
*/
LOG_DBG(" Manufacturer: %.12s", onfi.device_manufacturer);
LOG_DBG(" Model: %.20s", onfi.device_model);
LOG_DBG(" Page Size (data): %d", onfi.data_bytes_per_page);
LOG_DBG(" Page Size (spare): %d", onfi.spare_bytes_per_page);
LOG_DBG(" Pages per Block: %d", onfi.pages_per_block);
LOG_DBG(" Blocks per Unit: %d", onfi.blocks_per_lun);
LOG_DBG(" Units: %d", onfi.num_lun);
LOG_DBG("Plane Address Bits: %d", onfi.plane_address_bits);
/* Validate ONFI data against devicetree */
block_size = onfi.data_bytes_per_page * onfi.pages_per_block;
total_size = block_size * onfi.blocks_per_lun * onfi.num_lun;
if (onfi.data_bytes_per_page != config->parameters->write_block_size) {
LOG_WRN("Devicetree page size does not match ONFI page size (%d != %d)",
onfi.data_bytes_per_page, config->parameters->write_block_size);
}
if (block_size != config->block_size) {
LOG_WRN("Devicetree block size does not match ONFI block size (%d != %d)",
block_size, config->block_size);
}
if (onfi.plane_address_bits != config->plane_addr_bits) {
LOG_WRN("Devicetree plane address bits does not match ONFI plane address bits (%d "
"!= %d)",
onfi.plane_address_bits, config->plane_addr_bits);
}
if (total_size != config->flash_size) {
LOG_WRN("Devicetree total size does not match ONFI total size (%d != %d)",
total_size, config->flash_size);
}
/* Clear the parameter page read feature */
cfg &= ~SPI_NAND_FEATURE_CONFIG_OTP_EN;
return spi_nand_set_feature(dev, SPI_NAND_FEATURE_ADDR_CONFIG, cfg);
}
/**
* @brief Configure the flash
*
* @param dev The flash device structure
* @param info The flash info structure
* @return 0 on success, negative errno code otherwise
*/
static int spi_nand_configure(const struct device *dev)
{
const struct spi_nand_config *config = dev->config;
uint8_t jedec_id[SPI_NAND_MAX_ID_LEN];
int ret;
/* Validate bus and CS is ready */
if (!spi_is_ready_dt(&config->spi)) {
return -ENODEV;
}
acquire_device(dev);
/* Soft RESET chip and wait until ready again */
ret = spi_nand_reset(dev);
if (ret != 0) {
goto release;
}
/* Validate JEDEC ID */
ret = spi_nand_cmd_read_dummy(dev, SPI_NAND_CMD_READ_ID, jedec_id, config->jedec_id_len);
if (ret != 0) {
goto release;
}
if (memcmp(jedec_id, config->jedec_id, config->jedec_id_len) != 0) {
LOG_HEXDUMP_ERR(config->jedec_id, config->jedec_id_len, "Expected JEDEC ID");
LOG_HEXDUMP_ERR(jedec_id, config->jedec_id_len, "Queried JEDEC ID");
ret = -EINVAL;
goto release;
}
/* Load the ONFI parameter information */
ret = onfi_parameters_load(dev);
if (ret != 0) {
goto release;
}
/* Unlock all blocks */
ret = spi_nand_set_feature(dev, SPI_NAND_FEATURE_ADDR_BLOCK_PROT,
SPI_NAND_FEATURE_BLOCK_PROT_DISABLE_ALL);
if (ret != 0) {
goto release;
}
#ifdef CONFIG_SPI_NAND_BAD_BLOCK_CACHE
/* Build the in-memory bad block table */
ret = build_bad_block_table(dev);
#endif /* CONFIG_SPI_NAND_BAD_BLOCK_CACHE */
release:
release_device(dev);
return ret;
}
static int spi_nand_pm_control(const struct device *dev, enum pm_device_action action)
{
int rc = 0;
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
case PM_DEVICE_ACTION_RESUME:
/* Some Macronix parts support a "Deep Power Down" mode.
* Not implemented.
*/
break;
case PM_DEVICE_ACTION_TURN_ON:
/* Coming out of power off */
rc = spi_nand_configure(dev);
break;
case PM_DEVICE_ACTION_TURN_OFF:
break;
default:
rc = -ENOSYS;
}
return rc;
}
/**
* @brief Initialize and configure the flash
*
* @param name The flash name
* @return 0 on success, negative errno code otherwise
*/
static int spi_nand_init(const struct device *dev)
{
struct spi_nand_data *const data = dev->data;
k_sem_init(&data->sem, 1, K_SEM_MAX_LIMIT);
return pm_device_driver_init(dev, spi_nand_pm_control);
}
static DEVICE_API(flash, spi_nand_api) = {
.read = spi_nand_read,
.write = spi_nand_write,
.erase = spi_nand_erase,
.get_parameters = flash_nand_get_parameters,
.get_size = flash_nand_get_size,
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
.page_layout = spi_nand_pages_layout,
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#if defined(CONFIG_FLASH_EX_OP_ENABLED)
.ex_op = spi_nand_ex_op,
#endif /* CONFIG_FLASH_EX_OP_ENABLED */
};
/* clang-format off */
#define DEFINE_PAGE_LAYOUT(idx) \
IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT, \
(.layout = { \
.pages_count = DT_INST_PROP(idx, size_bytes) / \
DT_INST_PROP(idx, erase_block_size), \
.pages_size = DT_INST_PROP(idx, erase_block_size), \
},))
/* clang-format on */
#define SPI_NAND_INST(idx) \
BUILD_ASSERT(IS_POWER_OF_TWO(DT_INST_PROP(idx, write_block_size)), \
"write-block-size must be a power of 2"); \
BUILD_ASSERT(IS_POWER_OF_TWO(DT_INST_PROP(idx, erase_block_size)), \
"erase-block-size must be a power of 2"); \
BUILD_ASSERT(IS_POWER_OF_TWO(DT_INST_PROP(idx, plane_bytes)), \
"plane-bytes must be a power of 2"); \
BUILD_ASSERT(DT_INST_PROP(idx, erase_block_size) % DT_INST_PROP(idx, write_block_size) == \
0, \
"erase-block-size must be a multiple of write-block-size"); \
BUILD_ASSERT(DT_INST_PROP(idx, plane_bytes) % DT_INST_PROP(idx, erase_block_size) == 0, \
"plane-bytes must be a multiple of erase-block-size"); \
BUILD_ASSERT(DT_INST_PROP(idx, size_bytes) % DT_INST_PROP(idx, plane_bytes) == 0, \
"size-bytes must be a multiple of plane-bytes"); \
\
static const struct flash_parameters spi_nand_##idx##_parameters = { \
.write_block_size = DT_INST_PROP(idx, write_block_size), \
.erase_value = 0xff, \
}; \
\
static const uint8_t spi_nand_##idx##_jedec_id[] = DT_INST_PROP(idx, jedec_id); \
BUILD_ASSERT(ARRAY_SIZE(spi_nand_##idx##_jedec_id) <= SPI_NAND_MAX_ID_LEN); \
\
IF_ENABLED(CONFIG_SPI_NAND_BAD_BLOCK_CACHE, ( \
static uint8_t spi_nand_##idx##_bad_block_table[DIV_ROUND_UP( \
DT_INST_PROP(idx, size_bytes) / DT_INST_PROP(idx, erase_block_size), \
BITS_PER_BYTE)]; \
)) \
\
static const struct spi_nand_config spi_nand_##idx##_config = { \
.spi = SPI_DT_SPEC_INST_GET(idx, SPI_WORD_SET(8)), \
.parameters = &spi_nand_##idx##_parameters, \
.flash_size = DT_INST_PROP(idx, size_bytes), \
.block_size = DT_INST_PROP(idx, erase_block_size), \
.block_erase_us = DT_INST_PROP(idx, block_erase_duration_max), \
.page_program_us = DT_INST_PROP(idx, page_program_duration_max), \
.page_read_us = DT_INST_PROP(idx, page_read_duration_max), \
.reset_us = DT_INST_PROP(idx, reset_duration_max), \
.addr_offset_mask = DT_INST_PROP(idx, write_block_size) - 1, \
.addr_page_shift = LOG2(DT_INST_PROP(idx, write_block_size)), \
.addr_block_shift = LOG2(DT_INST_PROP(idx, erase_block_size)), \
.plane_addr_bits = \
LOG2(DT_INST_PROP(idx, size_bytes) / DT_INST_PROP(idx, plane_bytes)), \
.jedec_id = spi_nand_##idx##_jedec_id, \
.jedec_id_len = ARRAY_SIZE(spi_nand_##idx##_jedec_id), \
.has_program_plane_select = DT_INST_PROP(idx, has_program_plane_select), \
.has_read_plane_select = DT_INST_PROP(idx, has_read_plane_select), \
IF_ENABLED(CONFIG_SPI_NAND_BAD_BLOCK_CACHE, ( \
.bad_block_table = spi_nand_##idx##_bad_block_table, \
)) \
DEFINE_PAGE_LAYOUT(idx)}; \
\
static struct spi_nand_data spi_nand_##idx##_data; \
\
PM_DEVICE_DT_INST_DEFINE(idx, spi_nand_pm_control); \
DEVICE_DT_INST_DEFINE(idx, &spi_nand_init, PM_DEVICE_DT_INST_GET(idx), \
&spi_nand_##idx##_data, &spi_nand_##idx##_config, POST_KERNEL, \
CONFIG_SPI_NAND_INIT_PRIORITY, &spi_nand_api);
DT_INST_FOREACH_STATUS_OKAY(SPI_NAND_INST)