From 9dad6f1774969c47d0aaa58345fc44d88c7b42d8 Mon Sep 17 00:00:00 2001 From: Ruijia Wang Date: Wed, 25 Mar 2026 01:13:25 +0800 Subject: [PATCH 1/7] dts: bindings: add FlexSPI NAND device binding Add device tree binding for ONFI-compatible NAND flash devices connected to NXP FlexSPI controllers. The binding extends the base FlexSPI device binding and includes soc-nv-flash.yaml to inherit the write-block-size and erase-block-size properties. Device geometry (page size, pages per block, total capacity) is probed at runtime from the ONFI parameter page. Signed-off-by: Ruijia Wang --- dts/bindings/mtd/nxp,imx-flexspi-nand.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 dts/bindings/mtd/nxp,imx-flexspi-nand.yaml diff --git a/dts/bindings/mtd/nxp,imx-flexspi-nand.yaml b/dts/bindings/mtd/nxp,imx-flexspi-nand.yaml new file mode 100644 index 000000000000..78e75e9507b7 --- /dev/null +++ b/dts/bindings/mtd/nxp,imx-flexspi-nand.yaml @@ -0,0 +1,9 @@ +# Copyright 2026 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + ONFI-compatible NAND device connected to an NXP FlexSPI controller. + +compatible: "nxp,imx-flexspi-nand" + +include: ["nxp,imx-flexspi-device.yaml", soc-nv-flash.yaml] From 0cc788344ae4a7a78a03989b3b5a524b302f82b9 Mon Sep 17 00:00:00 2001 From: Ruijia Wang Date: Wed, 25 Mar 2026 01:13:35 +0800 Subject: [PATCH 2/7] drivers: memc: flexspi: add apply_pinctrl and update_lut APIs Add memc_flexspi_apply_pinctrl() for configuring additional FlexSPI port pins when init is skipped due to XIP, and memc_flexspi_update_lut() for runtime LUT updates using a stack buffer to avoid XIP pointer rejection. Guard all irq_lock() calls with CONFIG_FLASH_MCUX_FLEXSPI_XIP so non-XIP systems do not incur unnecessary interrupt latency. Signed-off-by: Ruijia Wang --- drivers/memc/memc_mcux_flexspi.c | 73 +++++++++++++++++++++++++++++--- drivers/memc/memc_mcux_flexspi.h | 29 ++++++++++++- 2 files changed, 96 insertions(+), 6 deletions(-) diff --git a/drivers/memc/memc_mcux_flexspi.c b/drivers/memc/memc_mcux_flexspi.c index 31f4443f32a4..2db03502d7f4 100644 --- a/drivers/memc/memc_mcux_flexspi.c +++ b/drivers/memc/memc_mcux_flexspi.c @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 NXP + * Copyright 2020-2023,2026 NXP * * SPDX-License-Identifier: Apache-2.0 */ @@ -103,13 +103,19 @@ bool memc_flexspi_is_running_xip(const struct device *dev) return data->xip; } +int memc_flexspi_apply_pinctrl(const struct device *dev, uint8_t id) +{ + struct memc_flexspi_data *data = dev->data; + + return pinctrl_apply_state(data->pincfg, id); +} + int memc_flexspi_update_clock(const struct device *dev, flexspi_device_config_t *device_config, flexspi_port_t port, uint32_t freq_hz) { struct memc_flexspi_data *data = dev->data; FLEXSPI_Type *base = get_base(dev); - uint32_t key; uint32_t divider, actual_freq, flexspiRootClk_copy, ccm_clock; int ret = clock_control_get_rate(data->clock_dev, data->clock_subsys, &ccm_clock); @@ -137,7 +143,9 @@ int memc_flexspi_update_clock(const struct device *dev, * - reset the module * We CANNOT XIP at any point during this process */ - key = irq_lock(); +#if CONFIG_FLASH_MCUX_FLEXSPI_XIP + unsigned int key = irq_lock(); +#endif memc_flexspi_wait_bus_idle(dev); FLEXSPI_Enable(base, false); @@ -166,7 +174,9 @@ int memc_flexspi_update_clock(const struct device *dev, FLEXSPI_Enable(base, true); memc_flexspi_reset(dev); +#if CONFIG_FLASH_MCUX_FLEXSPI_XIP irq_unlock(key); +#endif return 0; } @@ -182,7 +192,6 @@ int memc_flexspi_set_device_config(const struct device *dev, struct memc_flexspi_data *data = dev->data; const uint32_t *lut_ptr = lut_array; uint8_t lut_used = 0U; - unsigned int key = 0; int ret; uint32_t divider; @@ -251,7 +260,9 @@ int memc_flexspi_set_device_config(const struct device *dev, tmp_config.flexspiRootClk /= (divider + 1); /* Lock IRQs before reconfiguring FlexSPI, to prevent XIP */ - key = irq_lock(); +#if CONFIG_FLASH_MCUX_FLEXSPI_XIP + unsigned int key = irq_lock(); +#endif FLEXSPI_SetFlashConfig(base, &tmp_config, port); #if (CONFIG_FLASH_MCUX_FLEXSPI_FORCE_USING_OVRDVAL == 1) @@ -261,7 +272,9 @@ int memc_flexspi_set_device_config(const struct device *dev, FLEXSPI_UpdateLUT(base, data->port_luts[port].lut_offset, lut_ptr, lut_count); +#if CONFIG_FLASH_MCUX_FLEXSPI_XIP irq_unlock(key); +#endif return 0; } @@ -292,6 +305,14 @@ int memc_flexspi_transfer(const struct device *dev, addr_offset += data->size[i]; } + /* + * Lock IRQs while an IP command is active. If an ISR fires and its handler + * code lives in NOR XIP, the CPU stalls on the AHB fetch while TransferBlocking + * can no longer drain the RX FIFO, causing a permanent deadlock. + */ +#if CONFIG_FLASH_MCUX_FLEXSPI_XIP + unsigned int key = irq_lock(); +#endif if ((seq_off != 0) || (addr_offset != 0)) { /* Adjust device address and sequence index for transfer */ memcpy(&tmp, transfer, sizeof(tmp)); @@ -302,6 +323,9 @@ int memc_flexspi_transfer(const struct device *dev, /* Transfer does not need adjustment */ status = FLEXSPI_TransferBlocking(base, transfer); } +#if CONFIG_FLASH_MCUX_FLEXSPI_XIP + irq_unlock(key); +#endif if (status != kStatus_Success) { LOG_ERR("Transfer error: %d", status); @@ -330,6 +354,44 @@ void *memc_flexspi_get_ahb_address(const struct device *dev, return ahb_base + offset; } +int memc_flexspi_update_lut(const struct device *dev, flexspi_port_t port, uint32_t seq_idx, + const uint32_t *lut_ptr, uint8_t lut_count) +{ + struct memc_flexspi_data *data = dev->data; + uint8_t offset = data->port_luts[port].lut_offset + seq_idx * MEMC_FLEXSPI_CMD_PER_SEQ; + FLEXSPI_Type *base = get_base(dev); + uint32_t tmp_lut[FLEXSPI_MAX_LUT]; + + if (lut_count > FLEXSPI_MAX_LUT) { + return -EINVAL; + } + + /* + * Copy LUT entries to a stack buffer before calling FLEXSPI_UpdateLUT(). + * FLEXSPI_CheckInputLutLocation() rejects any pointer that falls inside the + * FlexSPI AHB window (NOR XIP region). The caller's lut_ptr may point to .rodata + * in NOR flash. + */ + memcpy(tmp_lut, lut_ptr, lut_count * MEMC_FLEXSPI_CMD_SIZE); + + /* Lock IRQs to prevent ISR-triggered NOR XIP fetches. */ +#if CONFIG_FLASH_MCUX_FLEXSPI_XIP + unsigned int key = 0; + + if (memc_flexspi_is_running_xip(dev)) { + key = irq_lock(); + } +#endif + FLEXSPI_UpdateLUT(base, offset, tmp_lut, lut_count); +#if CONFIG_FLASH_MCUX_FLEXSPI_XIP + if (memc_flexspi_is_running_xip(dev)) { + irq_unlock(key); + } +#endif + + return 0; +} + static int memc_flexspi_init(const struct device *dev) { struct memc_flexspi_data *data = dev->data; @@ -351,6 +413,7 @@ static int memc_flexspi_init(const struct device *dev) return 0; } } + /* * SOCs such as the RT1064 and RT1024 have internal flash, and no pinmux * settings, continue if no pinctrl state found. diff --git a/drivers/memc/memc_mcux_flexspi.h b/drivers/memc/memc_mcux_flexspi.h index 6863649bc94a..e28578a91594 100644 --- a/drivers/memc/memc_mcux_flexspi.h +++ b/drivers/memc/memc_mcux_flexspi.h @@ -1,5 +1,5 @@ /* - * Copyright 2020,2023 NXP + * Copyright 2020,2023,2026 NXP * * SPDX-License-Identifier: Apache-2.0 */ @@ -35,6 +35,20 @@ void memc_flexspi_wait_bus_idle(const struct device *dev); */ bool memc_flexspi_is_running_xip(const struct device *dev); +/** + * @brief Apply pinctrl state for the FlexSPI controller + * + * This is intended for cases where the FlexSPI controller initialization is + * skipped (e.g. due to XIP), but board pinmux for additional FlexSPI ports is + * still required (e.g. PortB pins). + * + * @param dev FlexSPI device + * @param id Pin control state identifier (e.g. PINCTRL_STATE_DEFAULT) + * @return 0 on success, -ENOENT if the state does not exist, or a negative + * errno for other failures + */ +int memc_flexspi_apply_pinctrl(const struct device *dev, uint8_t id); + /** * @brief Update clock selection of the FlexSPI device * @@ -101,3 +115,16 @@ int memc_flexspi_transfer(const struct device *dev, */ void *memc_flexspi_get_ahb_address(const struct device *dev, flexspi_port_t port, off_t offset); + +/** + * @brief Allowing external modules to update LUT. + * + * @param dev: FlexSPI device pointer + * @param port: FlexSPI port + * @param seq_idx: Sequence index offset within the port's allocated LUT region + * @param lut_ptr: LUT array pointer + * @param lut_count: Number of LUT entries + * @return 0 on success, negative value on failure + */ +int memc_flexspi_update_lut(const struct device *dev, flexspi_port_t port, uint32_t seq_idx, + const uint32_t *lut_ptr, uint8_t lut_count); From 96bd62c1abec69c181e8ef90dcbdca5c63b1539b Mon Sep 17 00:00:00 2001 From: Ruijia Wang Date: Wed, 25 Mar 2026 01:13:45 +0800 Subject: [PATCH 3/7] drivers: flash: add NXP FlexSPI NAND driver with ONFI probe Add a SPI-NAND flash driver for NXP FlexSPI controller. The driver probes device geometry (page size, OOB size, pages per block, total capacity) from the ONFI parameter page at runtime and supports read, write, and block erase operations. Bad-block detection and marking are exposed through the flash_ex_op interface, including the FLASH_EX_OP_IS_BAD_BLOCK and FLASH_EX_OP_MARK_BAD_BLOCK operations. Signed-off-by: Ruijia Wang --- drivers/flash/CMakeLists.txt | 1 + drivers/flash/Kconfig.mcux | 28 + drivers/flash/flash_mcux_flexspi_nand.c | 979 ++++++++++++++++++++++++ 3 files changed, 1008 insertions(+) create mode 100644 drivers/flash/flash_mcux_flexspi_nand.c diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index a058d84b74b6..ffab9fe61ef5 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -35,6 +35,7 @@ zephyr_library_sources_ifdef(CONFIG_FLASH_INFINEON_PSOC4 flash_infineon_pdl.c) zephyr_library_sources_ifdef(CONFIG_FLASH_MCHP_NVMCTRL_G1 flash_mchp_nvmctrl_g1.c) zephyr_library_sources_ifdef(CONFIG_FLASH_MCUX_FLEXSPI_HYPERFLASH flash_mcux_flexspi_hyperflash.c) zephyr_library_sources_ifdef(CONFIG_FLASH_MCUX_FLEXSPI_MX25UM51345G flash_mcux_flexspi_mx25um51345g.c) +zephyr_library_sources_ifdef(CONFIG_FLASH_MCUX_FLEXSPI_NAND flash_mcux_flexspi_nand.c) zephyr_library_sources_ifdef(CONFIG_FLASH_MCUX_FLEXSPI_NOR flash_mcux_flexspi_nor.c) zephyr_library_sources_ifdef(CONFIG_FLASH_MCUX_XSPI flash_mcux_xspi.c) zephyr_library_sources_ifdef(CONFIG_FLASH_MSPI_ATXP032 flash_mspi_atxp032.c) diff --git a/drivers/flash/Kconfig.mcux b/drivers/flash/Kconfig.mcux index 64fc22b8cd25..658ccfb83ce8 100644 --- a/drivers/flash/Kconfig.mcux +++ b/drivers/flash/Kconfig.mcux @@ -74,6 +74,34 @@ config FLASH_MCUX_FLEXSPI_HYPERFLASH select MEMC select MEMC_MCUX_FLEXSPI +config FLASH_MCUX_FLEXSPI_NAND + bool "MCUX FlexSPI NAND (ONFI probe)" + default y + depends on DT_HAS_NXP_IMX_FLEXSPI_NAND_ENABLED + select FLASH_HAS_DRIVER_ENABLED + select FLASH_HAS_PAGE_LAYOUT + select FLASH_HAS_EXPLICIT_ERASE + select FLASH_HAS_EX_OP + select MEMC + select MEMC_MCUX_FLEXSPI + select CRC + help + NAND flash driver over NXP FlexSPI with ONFI parameter probe + and quad I/O for data read/write. + +config FLASH_MCUX_FLEXSPI_NAND_INIT_PRIORITY + int "MCUX FlexSPI NAND flash init priority" + default FLASH_INIT_PRIORITY + help + Initialization priority for FlexSPI NAND flash driver. + +config FLASH_MCUX_FLEXSPI_NAND_TIMEOUT_US + int "Timeout for FlexSPI NAND busy-wait polling" + default 0 + help + Maximum time in microseconds to wait for the NAND flash to + become ready after an operation. Set to 0 to poll indefinitely. + endmenu if FLASH_MCUX_FLEXSPI_MX25UM51345G diff --git a/drivers/flash/flash_mcux_flexspi_nand.c b/drivers/flash/flash_mcux_flexspi_nand.c new file mode 100644 index 000000000000..a19cc68b0c66 --- /dev/null +++ b/drivers/flash/flash_mcux_flexspi_nand.c @@ -0,0 +1,979 @@ +/* + * Copyright 2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_imx_flexspi_nand + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "memc_mcux_flexspi.h" + +LOG_MODULE_REGISTER(flash_flexspi_nand, CONFIG_FLASH_LOG_LEVEL); + +/* SPI-NAND common commands. */ +#define SPI_NAND_CMD_READ_REG 0x0FU +#define SPI_NAND_CMD_WRITE_REG 0x1FU +#define SPI_NAND_CMD_READ_ID 0x9FU +#define SPI_NAND_CMD_RESET 0xFFU +#define SPI_NAND_CMD_PAGE_READ 0x13U +#define SPI_NAND_CMD_READ_CACHE_X4 0x6BU +#define SPI_NAND_CMD_WRITE_ENABLE 0x06U +#define SPI_NAND_CMD_PROG_LOAD 0x32U +#define SPI_NAND_CMD_PROG_LOAD_X1 0x02U +#define SPI_NAND_CMD_PROG_EXEC 0x10U +#define SPI_NAND_CMD_BLOCK_ERASE 0xD8U + +/* + * W25N01GV OOB (spare) area starts at column address 2048. + * The overlay uses column-space = 11, so CADDR can only address columns 0–2047. + * For OOB access we build a custom LUT using raw SDR bytes to encode CA[15:8] = 0x08 + * (= 2048 >> 8) and CA[7:0] = 0x00 directly, bypassing the CADDR mechanism. + */ +#define SPI_NAND_OOB_COLUMN_HI 0x08U /* CA[15:8] for column 2048 (0x0800) */ +#define SPI_NAND_OOB_COLUMN_LO 0x00U /* CA[7:0] for column 2048 */ + +/* Register addresses. */ +#define SPI_NAND_REG_BLOCK_PROT 0xA0U +#define SPI_NAND_REG_CONFIG 0xB0U +#define SPI_NAND_REG_STATUS 0xC0U + +/* STATUS register bits. */ +#define SPI_NAND_STATUS_BUSY BIT(0) +#define SPI_NAND_STATUS_WEL BIT(1) +#define SPI_NAND_STATUS_E_FAIL BIT(2) +#define SPI_NAND_STATUS_P_FAIL BIT(3) +#define SPI_NAND_STATUS_ECC_MASK (BIT(4) | BIT(5)) +#define SPI_NAND_STATUS_ECC_SHIFT 4 + +/* CONFIG register bits. */ +#define SPI_NAND_CFG_ECC_EN BIT(4) +#define SPI_NAND_CFG_OTP_EN BIT(6) + +#define SPI_NAND_RESET_TIME_US 5 + + +/* ONFI parameter page, treat as a raw 256-byte block. + * CRC is calculated over the first 254 bytes, with seed 0x4F4E. + */ +struct nand_onfi_parameter_page_raw { + uint8_t data[256]; +}; + +enum { + DYNAMIC_SEQ = 0, + PAGE_READ, + READ_CACHE, + PROG_LOAD, + PROG_EXEC, + LUT_END, +}; + +struct flash_flexspi_nand_data { + const struct device *controller; + flexspi_device_config_t config; + flexspi_port_t port; + uint64_t size; + uint32_t page_size; + uint16_t oob_size; + uint32_t pages_per_block; + uint32_t blocks_per_unit; + uint8_t units; + uint32_t block_size; + struct k_mutex lock; +#if defined(CONFIG_FLASH_PAGE_LAYOUT) + struct flash_pages_layout layout; +#endif + struct flash_parameters flash_parameters; +}; + +static const uint32_t flexspi_lut[LUT_END][MEMC_FLEXSPI_CMD_PER_SEQ] = { + [DYNAMIC_SEQ] = {0}, + + [PAGE_READ] = { + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_CMD_PAGE_READ, + kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18), + }, + + [READ_CACHE] = { + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, + SPI_NAND_CMD_READ_CACHE_X4, kFLEXSPI_Command_CADDR_SDR, + kFLEXSPI_1PAD, 0x10), + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_1PAD, 0x08, + kFLEXSPI_Command_READ_SDR, kFLEXSPI_4PAD, 0x80), + }, + + [PROG_LOAD] = { + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_CMD_PROG_LOAD, + kFLEXSPI_Command_CADDR_SDR, kFLEXSPI_1PAD, 0x10), + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_4PAD, 0x40, + kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), + }, + + [PROG_EXEC] = { + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_CMD_PROG_EXEC, + kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18), + }, +}; + +static ALWAYS_INLINE bool area_is_subregion(const struct device *dev, off_t offset, size_t size) +{ + struct flash_flexspi_nand_data *nand = dev->data; + + return ((offset >= 0) && (offset < nand->size) && ((nand->size - offset) >= size)); +} + +static int flash_flexspi_nand_xfer(const struct device *dev, uint8_t seq_index, + flexspi_command_type_t type, uint32_t addr, void *data, + size_t data_len) +{ + struct flash_flexspi_nand_data *nand = dev->data; + flexspi_transfer_t transfer = { + .deviceAddress = addr, + .port = nand->port, + .cmdType = type, + .SeqNumber = 1, + .seqIndex = seq_index, + .data = data, + .dataSize = data_len, + }; + + return memc_flexspi_transfer(nand->controller, &transfer); +} + +static int flash_flexspi_update_lut(const struct device *dev, const uint32_t *lut_ptr) +{ + struct flash_flexspi_nand_data *nand = dev->data; + + return memc_flexspi_update_lut(nand->controller, nand->port, DYNAMIC_SEQ, lut_ptr, + MEMC_FLEXSPI_CMD_PER_SEQ); +} + +static int flash_flexspi_nand_read_reg(const struct device *dev, uint8_t reg, uint8_t *value) +{ + const uint32_t lut_read_reg[MEMC_FLEXSPI_CMD_PER_SEQ] = { + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_CMD_READ_REG, + kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, reg), + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x01, + kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), + }; + uint32_t data = 0; + int ret; + + ret = flash_flexspi_update_lut(dev, lut_read_reg); + if (ret != 0) { + return ret; + } + + ret = flash_flexspi_nand_xfer(dev, DYNAMIC_SEQ, kFLEXSPI_Read, 0, &data, 1); + if (ret == 0) { + *value = (uint8_t)data; + } + + return ret; +} + +static int flash_flexspi_nand_write_reg(const struct device *dev, uint8_t reg, uint8_t value) +{ + const uint32_t lut_write_reg[MEMC_FLEXSPI_CMD_PER_SEQ] = { + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_CMD_WRITE_REG, + kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, reg), + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x01, + kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), + }; + int ret; + + ret = flash_flexspi_update_lut(dev, lut_write_reg); + if (ret != 0) { + return ret; + } + + return flash_flexspi_nand_xfer(dev, DYNAMIC_SEQ, kFLEXSPI_Write, 0, &value, 1); +} + +static int flash_flexspi_nand_set_block_protect(const struct device *dev, uint8_t value) +{ + return flash_flexspi_nand_write_reg(dev, SPI_NAND_REG_BLOCK_PROT, value); +} + +static int flash_flexspi_nand_write_enable(const struct device *dev) +{ + const uint32_t lut_we[MEMC_FLEXSPI_CMD_PER_SEQ] = { + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_CMD_WRITE_ENABLE, + kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), + }; + int ret; + + ret = flash_flexspi_update_lut(dev, lut_we); + if (ret != 0) { + return ret; + } + + return flash_flexspi_nand_xfer(dev, DYNAMIC_SEQ, kFLEXSPI_Command, 0, NULL, 0); +} + +static int flash_flexspi_nand_wait_busy(const struct device *dev, uint32_t timeout_us) +{ + uint32_t cycles = k_us_to_cyc_ceil32(timeout_us); + uint32_t start = k_cycle_get_32(); + uint8_t status; + int ret; + + do { + ret = flash_flexspi_nand_read_reg(dev, SPI_NAND_REG_STATUS, &status); + if (ret != 0) { + return ret; + } + if ((status & SPI_NAND_STATUS_BUSY) == 0U) { + return 0; + } + k_busy_wait(5); + } while ((timeout_us == 0U) || ((k_cycle_get_32() - start) < cycles)); + + return -ETIMEDOUT; +} + +static int flash_flexspi_nand_reset(const struct device *dev) +{ + const uint32_t lut_reset[MEMC_FLEXSPI_CMD_PER_SEQ] = { + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_CMD_RESET, + kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), + }; + int ret; + + ret = flash_flexspi_update_lut(dev, lut_reset); + if (ret != 0) { + return ret; + } + + ret = flash_flexspi_nand_xfer(dev, DYNAMIC_SEQ, kFLEXSPI_Command, 0, NULL, 0); + if (ret != 0) { + return ret; + } + + /* W25N01GV tRST in IDLE. */ + k_usleep(SPI_NAND_RESET_TIME_US); + + return ret; +} + +static int flash_flexspi_nand_read_id(const struct device *dev, uint8_t id[3]) +{ + const uint32_t lut_read_id[MEMC_FLEXSPI_CMD_PER_SEQ] = { + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_CMD_READ_ID, + kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_1PAD, 0x08), + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x03, + kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), + }; + uint32_t value = 0; + int ret; + + ret = flash_flexspi_update_lut(dev, lut_read_id); + if (ret != 0) { + return ret; + } + + ret = flash_flexspi_nand_xfer(dev, DYNAMIC_SEQ, kFLEXSPI_Read, 0, &value, 3); + if (ret != 0) { + return ret; + } + + id[0] = (uint8_t)(value & 0xFF); + id[1] = (uint8_t)((value >> 8) & 0xFF); + id[2] = (uint8_t)((value >> 16) & 0xFF); + + return 0; +} + +static void flash_flexspi_nand_parse_onfi_fields(struct flash_flexspi_nand_data *nand, + const struct nand_onfi_parameter_page_raw *onfi, + uint16_t crc) +{ + char manufacturer[13]; + char model[21]; + uint64_t onfi_size; + + memcpy(manufacturer, &onfi->data[32], 12); + manufacturer[12] = '\0'; + memcpy(model, &onfi->data[44], 20); + model[20] = '\0'; + + LOG_DBG("NAND probe OK (ONFI CRC %04X)", crc); + LOG_DBG("Manufacturer: %s", manufacturer); + LOG_DBG("Model: %s", model); + + nand->page_size = sys_get_le32(&onfi->data[80]); + nand->oob_size = sys_get_le16(&onfi->data[84]); + nand->pages_per_block = sys_get_le32(&onfi->data[92]); + nand->blocks_per_unit = sys_get_le32(&onfi->data[96]); + nand->units = onfi->data[100]; + nand->block_size = nand->page_size * nand->pages_per_block; + + LOG_DBG("Page size (data): %u", nand->page_size); + LOG_DBG("Page size (spare/OOB): %u", nand->oob_size); + LOG_DBG("Pages per block: %u", nand->pages_per_block); + LOG_DBG("Blocks per unit: %u", nand->blocks_per_unit); + LOG_DBG("Units: %u", nand->units); + + /* Keep DT size as the authoritative limit, but log if it disagrees with ONFI. */ + if ((nand->page_size != 0U) && (nand->pages_per_block != 0U) && + (nand->blocks_per_unit != 0U) && (nand->units != 0U)) { + onfi_size = (uint64_t)nand->page_size * (uint64_t)nand->pages_per_block * + (uint64_t)nand->blocks_per_unit * (uint64_t)nand->units; + if ((onfi_size != 0U) && (nand->size != onfi_size)) { + LOG_WRN("DT size (%llu) != ONFI size (%llu)", nand->size, onfi_size); + } + } +} + +static int flash_flexspi_nand_onfi_read(const struct device *dev) +{ + struct nand_onfi_parameter_page_raw onfi; + uint16_t computed_crc; + uint8_t cfg; + uint8_t cfg_restore; + struct flash_flexspi_nand_data *nand = dev->data; + int ret, ret_temp; + + ret = flash_flexspi_nand_read_reg(dev, SPI_NAND_REG_CONFIG, &cfg); + if (ret != 0) { + return ret; + } + + cfg_restore = cfg; + + /* Enable OTP/parameter page access. */ + cfg |= SPI_NAND_CFG_OTP_EN; + ret = flash_flexspi_nand_write_reg(dev, SPI_NAND_REG_CONFIG, cfg); + if (ret != 0) { + return ret; + } + + ret = flash_flexspi_nand_wait_busy(dev, CONFIG_FLASH_MCUX_FLEXSPI_NAND_TIMEOUT_US); + if (ret != 0) { + return ret; + } + + /* Load ONFI parameter page. */ + ret = flash_flexspi_nand_xfer(dev, PAGE_READ, kFLEXSPI_Command, 2048, NULL, 0); + if (ret != 0) { + goto restore; + } + + ret = flash_flexspi_nand_wait_busy(dev, CONFIG_FLASH_MCUX_FLEXSPI_NAND_TIMEOUT_US); + if (ret != 0) { + goto restore; + } + + memset(&onfi, 0, sizeof(onfi)); + ret = flash_flexspi_nand_xfer(dev, READ_CACHE, kFLEXSPI_Read, 2048, &onfi, sizeof(onfi)); + if (ret != 0) { + goto restore; + } + + if (memcmp(&onfi.data[0], "ONFI", 4) != 0) { + ret = -EINVAL; + goto restore; + } + + computed_crc = crc16(CRC16_POLY, 0x4F4E, (void *)&onfi, sizeof(onfi) - sizeof(uint16_t)); + if (computed_crc != sys_get_le16(&onfi.data[254])) { + ret = -EIO; + goto restore; + } + + flash_flexspi_nand_parse_onfi_fields(nand, &onfi, computed_crc); + +restore: + /* Restore config register, clear OTP_EN. */ + ret_temp = flash_flexspi_nand_write_reg(dev, SPI_NAND_REG_CONFIG, + cfg_restore & ~SPI_NAND_CFG_OTP_EN); + if (ret == 0) { + ret = ret_temp; + } + ret_temp = flash_flexspi_nand_wait_busy(dev, CONFIG_FLASH_MCUX_FLEXSPI_NAND_TIMEOUT_US); + if (ret == 0) { + ret = ret_temp; + } + return ret; +} + +static int flash_flexspi_nand_enable_ecc(const struct device *dev) +{ + struct flash_flexspi_nand_data *nand = dev->data; + uint8_t cfg; + int ret; + + ret = flash_flexspi_nand_read_reg(dev, SPI_NAND_REG_CONFIG, &cfg); + if (ret != 0) { + return ret; + } + + if ((cfg & SPI_NAND_CFG_ECC_EN) != 0U) { + return 0; + } + + cfg |= SPI_NAND_CFG_ECC_EN; + + ret = flash_flexspi_nand_write_reg(dev, SPI_NAND_REG_CONFIG, cfg); + if (ret != 0) { + return ret; + } + + ret = flash_flexspi_nand_wait_busy(dev, CONFIG_FLASH_MCUX_FLEXSPI_NAND_TIMEOUT_US); + if (ret != 0) { + return ret; + } + + memc_flexspi_reset(nand->controller); + + return 0; +} + +static int flash_flexspi_nand_check_ecc(const struct device *dev) +{ + uint8_t status; + uint8_t ecc; + int ret; + + ret = flash_flexspi_nand_read_reg(dev, SPI_NAND_REG_STATUS, &status); + if (ret != 0) { + return ret; + } + + ecc = (status & SPI_NAND_STATUS_ECC_MASK) >> SPI_NAND_STATUS_ECC_SHIFT; + + if (ecc == 0x3U) { + LOG_ERR("Uncorrectable ECC error (STATUS=0x%02x)", status); + return -EIO; + } + if (ecc != 0U) { + LOG_WRN("ECC corrected bitflips (STATUS=0x%02x)", status); + } + + return 0; +} + +static int flash_flexspi_nand_read(const struct device *dev, off_t offset, void *data, size_t len) +{ + struct flash_flexspi_nand_data *nand = dev->data; + uint32_t off = (uint32_t)offset; + uint8_t *buffer = data; + size_t chunk; + int ret = 0; + + if (len == 0U) { + return 0; + } + + if ((data == NULL) || (!area_is_subregion(dev, offset, len))) { + return -EINVAL; + } + + k_mutex_lock(&nand->lock, K_FOREVER); + + while (len > 0U) { + chunk = MIN(len, (size_t)(nand->page_size - (off % nand->page_size))); + + ret = flash_flexspi_nand_xfer(dev, PAGE_READ, kFLEXSPI_Command, off, NULL, 0); + if (ret != 0) { + goto out; + } + + ret = flash_flexspi_nand_wait_busy(dev, CONFIG_FLASH_MCUX_FLEXSPI_NAND_TIMEOUT_US); + if (ret != 0) { + goto out; + } + + ret = flash_flexspi_nand_check_ecc(dev); + if (ret != 0) { + goto out; + } + + ret = flash_flexspi_nand_xfer(dev, READ_CACHE, kFLEXSPI_Read, off, buffer, chunk); + if (ret != 0) { + goto out; + } + + buffer += chunk; + off += chunk; + len -= chunk; + } + +out: + k_mutex_unlock(&nand->lock); + return ret; +} + +static int flash_flexspi_nand_write(const struct device *dev, off_t offset, const void *data, + size_t len) +{ + struct flash_flexspi_nand_data *nand = dev->data; + uint32_t off = (uint32_t)offset; + const uint8_t *buffer = data; + uint8_t status; + size_t chunk; + int ret = 0; + + if (len == 0U) { + return 0; + } + + if ((data == NULL) || (!area_is_subregion(dev, offset, len))) { + return -EINVAL; + } + + if (((off % nand->page_size) != 0U) || ((len % nand->page_size) != 0U)) { + return -EINVAL; + } + + k_mutex_lock(&nand->lock, K_FOREVER); + + while (len > 0U) { + chunk = nand->page_size; + + ret = flash_flexspi_nand_write_enable(dev); + if (ret != 0) { + goto out; + } + + ret = flash_flexspi_nand_xfer(dev, PROG_LOAD, kFLEXSPI_Write, off, (void *)buffer, + chunk); + if (ret != 0) { + goto out; + } + + ret = flash_flexspi_nand_xfer(dev, PROG_EXEC, kFLEXSPI_Command, off, NULL, 0); + if (ret != 0) { + goto out; + } + + ret = flash_flexspi_nand_wait_busy(dev, CONFIG_FLASH_MCUX_FLEXSPI_NAND_TIMEOUT_US); + if (ret != 0) { + goto out; + } + + ret = flash_flexspi_nand_read_reg(dev, SPI_NAND_REG_STATUS, &status); + if (ret != 0) { + goto out; + } + if ((status & SPI_NAND_STATUS_P_FAIL) != 0U) { + ret = -EIO; + goto out; + } + + off += chunk; + buffer += chunk; + len -= chunk; + } + +out: + k_mutex_unlock(&nand->lock); + return ret; +} + +static int flash_flexspi_nand_erase(const struct device *dev, off_t offset, size_t size) +{ + const uint32_t lut_block_erase[MEMC_FLEXSPI_CMD_PER_SEQ] = { + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_CMD_BLOCK_ERASE, + kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18), + }; + struct flash_flexspi_nand_data *nand = dev->data; + uint32_t block_size = nand->block_size; + uint32_t off = (uint32_t)offset; + uint8_t status; + int ret = 0; + + if (size == 0U) { + return 0; + } + + if (!area_is_subregion(dev, offset, size)) { + return -EINVAL; + } + + if (((off % block_size) != 0U) || ((size % block_size) != 0U)) { + return -EINVAL; + } + + k_mutex_lock(&nand->lock, K_FOREVER); + + while (size > 0U) { + ret = flash_flexspi_nand_write_enable(dev); + if (ret != 0) { + goto out; + } + + ret = flash_flexspi_update_lut(dev, lut_block_erase); + if (ret != 0) { + goto out; + } + + ret = flash_flexspi_nand_xfer(dev, DYNAMIC_SEQ, kFLEXSPI_Command, off, NULL, 0); + if (ret != 0) { + goto out; + } + + ret = flash_flexspi_nand_wait_busy(dev, CONFIG_FLASH_MCUX_FLEXSPI_NAND_TIMEOUT_US); + if (ret != 0) { + goto out; + } + + ret = flash_flexspi_nand_read_reg(dev, SPI_NAND_REG_STATUS, &status); + if (ret != 0) { + goto out; + } + if ((status & SPI_NAND_STATUS_E_FAIL) != 0U) { + ret = -EIO; + goto out; + } + + off += block_size; + size -= block_size; + } + +out: + k_mutex_unlock(&nand->lock); + return ret; +} + +#if defined(CONFIG_FLASH_PAGE_LAYOUT) +static void flash_flexspi_nand_page_layout(const struct device *dev, + const struct flash_pages_layout **layout, + size_t *layout_size) +{ + struct flash_flexspi_nand_data *nand = dev->data; + + *layout = &nand->layout; + *layout_size = 1; +} +#endif + +#if defined(CONFIG_FLASH_EX_OP_ENABLED) +/* + * Read OOB byte 0 of the page currently in cache. + * + * column-space = 11 → CADDR can only address columns 0–2047. + * OOB starts at column 2048 (0x0800), so we embed CA[15:8] and CA[7:0] as + * literal SDR bytes in a dynamic LUT instead of using CADDR. + * + * Sequence: READ_CACHE_X4 (0x6B) | CA[15:8]=0x08 | CA[7:0]=0x00 | Dummy(8) | Data + */ +static int flash_flexspi_nand_read_oob0(const struct device *dev, uint8_t *byte) +{ + const uint32_t lut_read_oob[MEMC_FLEXSPI_CMD_PER_SEQ] = { + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_CMD_READ_CACHE_X4, + kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_OOB_COLUMN_HI), + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_OOB_COLUMN_LO, + kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_1PAD, 8), + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_SDR, kFLEXSPI_4PAD, 0x04U, + kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), + 0, + }; + uint32_t buf = 0; + int ret; + + ret = flash_flexspi_update_lut(dev, lut_read_oob); + if (ret != 0) { + return ret; + } + + /* deviceAddress is unused (column is hardcoded in LUT via SDR bytes) */ + ret = flash_flexspi_nand_xfer(dev, DYNAMIC_SEQ, kFLEXSPI_Read, 0, &buf, 1); + if (ret != 0) { + return ret; + } + + *byte = (uint8_t)buf; + return 0; +} + +/* + * Write 0x00 to OOB byte 0 of the first page of a block (bad-block marker). + * + * Uses PROGRAM LOAD x1 (0x02) at column 2048 with a single marker byte, + * then PROGRAM EXECUTE. All bytes not loaded default to 0xFF in the internal + * page buffer, so the data area is written as all-0xFF (erased state). + * + * Sequence: PROG_LOAD_X1 (0x02) | CA[15:8]=0x08 | CA[7:0]=0x00 | Marker(1B) + * PROG_EXEC | row_addr + */ +static int flash_flexspi_nand_mark_oob0(const struct device *dev, off_t block_addr) +{ + const uint32_t lut_prog_oob[MEMC_FLEXSPI_CMD_PER_SEQ] = { + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_CMD_PROG_LOAD_X1, + kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_OOB_COLUMN_HI), + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, SPI_NAND_OOB_COLUMN_LO, + kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x04U), + FLEXSPI_LUT_SEQ(kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0, kFLEXSPI_Command_STOP, + kFLEXSPI_1PAD, 0), + 0, + }; + uint8_t marker = 0x00U; + uint8_t status; + int ret; + + ret = flash_flexspi_nand_write_enable(dev); + if (ret != 0) { + return ret; + } + + ret = flash_flexspi_update_lut(dev, lut_prog_oob); + if (ret != 0) { + return ret; + } + + ret = flash_flexspi_nand_xfer(dev, DYNAMIC_SEQ, kFLEXSPI_Write, 0, &marker, 1); + if (ret != 0) { + return ret; + } + + ret = flash_flexspi_nand_xfer(dev, PROG_EXEC, kFLEXSPI_Command, (uint32_t)block_addr, NULL, + 0); + if (ret != 0) { + return ret; + } + + ret = flash_flexspi_nand_wait_busy(dev, CONFIG_FLASH_MCUX_FLEXSPI_NAND_TIMEOUT_US); + if (ret != 0) { + return ret; + } + + ret = flash_flexspi_nand_read_reg(dev, SPI_NAND_REG_STATUS, &status); + if (ret != 0) { + return ret; + } + + /* P_FAIL is best-effort: log but don't fail — block is unusable anyway */ + if ((status & SPI_NAND_STATUS_P_FAIL) != 0U) { + LOG_WRN("bad-block mark at 0x%08lx: P_FAIL set", (long)block_addr); + } + + return 0; +} + +static int flash_flexspi_nand_ex_op(const struct device *dev, uint16_t code, const uintptr_t in, + void *out) +{ + struct flash_flexspi_nand_data *nand = dev->data; + int ret; + + k_mutex_lock(&nand->lock, K_FOREVER); + + switch (code) { + case FLASH_EX_OP_RESET: + ret = flash_flexspi_nand_reset(dev); + break; + + case FLASH_EX_OP_IS_BAD_BLOCK: { + /* + * W25N01GV bad-block convention: factory-bad blocks have + * OOB byte 0 of the first page set to a non-0xFF value. + * Read OOB[0] by doing PAGE_READ (load page into cache) then + * READ_CACHE at column 2048. + */ + const off_t block_addr = *(const off_t *)in; + uint8_t oob0; + + ret = flash_flexspi_nand_xfer(dev, PAGE_READ, kFLEXSPI_Command, + (uint32_t)block_addr, NULL, 0); + if (ret != 0) { + break; + } + + ret = flash_flexspi_nand_wait_busy(dev, CONFIG_FLASH_MCUX_FLEXSPI_NAND_TIMEOUT_US); + if (ret != 0) { + break; + } + + ret = flash_flexspi_nand_read_oob0(dev, &oob0); + if (ret != 0) { + break; + } + + *(int *)out = (oob0 != 0xFFU) ? FLASH_BLOCK_BAD : FLASH_BLOCK_GOOD; + break; + } + + case FLASH_EX_OP_MARK_BAD_BLOCK: { + /* + * Program 0x00 to OOB byte 0 of the first page of the block. + * This is a best-effort operation: if the block failed to erase, + * the program may also fail, but we log and continue so dhara + * can work around it through its journal mechanism. + */ + const off_t block_addr = *(const off_t *)in; + + ret = flash_flexspi_nand_mark_oob0(dev, block_addr); + break; + } + + default: + ret = -ENOTSUP; + break; + } + + k_mutex_unlock(&nand->lock); + return ret; +} +#endif /* CONFIG_FLASH_EX_OP_ENABLED */ + +static const struct flash_parameters *flash_flexspi_nand_get_parameters(const struct device *dev) +{ + struct flash_flexspi_nand_data *nand = dev->data; + + return &nand->flash_parameters; +} + +static int flash_flexspi_nand_get_size(const struct device *dev, uint64_t *size) +{ + struct flash_flexspi_nand_data *nand = dev->data; + + *size = nand->size; + return 0; +} + +static int flash_flexspi_nand_init(const struct device *dev) +{ + struct flash_flexspi_nand_data *nand = dev->data; + uint8_t id[3]; + int ret; + + k_mutex_init(&nand->lock); + + if (!device_is_ready(nand->controller)) { + LOG_ERR("FlexSPI controller not ready"); + return -ENODEV; + } + + ret = memc_flexspi_set_device_config( + nand->controller, &nand->config, (const uint32_t *)flexspi_lut, + sizeof(flexspi_lut) / MEMC_FLEXSPI_CMD_SIZE, nand->port); + if (ret != 0) { + LOG_ERR("Failed to configure FlexSPI NAND (%d)", ret); + return ret; + } + + /* Apply FlexSPI pinmux for the NAND port. */ + ret = memc_flexspi_apply_pinctrl(nand->controller, PINCTRL_STATE_DEFAULT); + if (ret != 0 && ret != -ENOENT) { + LOG_ERR("Failed to apply FlexSPI pinctrl (%d)", ret); + return ret; + } + + memc_flexspi_reset(nand->controller); + + ret = flash_flexspi_nand_reset(dev); + if (ret != 0) { + LOG_ERR("NAND reset failed (%d)", ret); + return ret; + } + + /* Clear all block protection bits for flash_shell convenience. */ + ret = flash_flexspi_nand_set_block_protect(dev, 0x00); + if (ret != 0) { + LOG_WRN("Failed to clear block protect (%d)", ret); + } + + ret = flash_flexspi_nand_read_id(dev, id); + if (ret != 0) { + LOG_ERR("NAND read-id failed (%d)", ret); + return ret; + } + LOG_DBG("JEDEC ID (raw): %02X %02X %02X", id[0], id[1], id[2]); + + ret = flash_flexspi_nand_onfi_read(dev); + if (ret != 0) { + LOG_ERR("ONFI parameter read/CRC failed (%d)", ret); + return ret; + } + + ret = flash_flexspi_nand_enable_ecc(dev); + if (ret != 0) { + LOG_ERR("Failed to enable on-die ECC (%d)", ret); + return ret; + } + + if (nand->page_size == 0U) { + LOG_ERR("ONFI did not provide a valid page size"); + return -EIO; + } + if ((nand->pages_per_block == 0U) || (nand->block_size == 0U)) { + LOG_ERR("ONFI did not provide a valid block geometry"); + return -EIO; + } + +#if defined(CONFIG_FLASH_PAGE_LAYOUT) + nand->layout.pages_size = nand->block_size; + nand->layout.pages_count = (size_t)(nand->size / nand->block_size); +#endif + + return 0; +} + +static DEVICE_API(flash, flash_flexspi_nand_api) = { + .erase = flash_flexspi_nand_erase, + .write = flash_flexspi_nand_write, + .read = flash_flexspi_nand_read, + .get_parameters = flash_flexspi_nand_get_parameters, + .get_size = flash_flexspi_nand_get_size, +#if defined(CONFIG_FLASH_PAGE_LAYOUT) + .page_layout = flash_flexspi_nand_page_layout, +#endif +#if defined(CONFIG_FLASH_EX_OP_ENABLED) + .ex_op = flash_flexspi_nand_ex_op, +#endif +}; + +#define CS_INTERVAL_UNIT(unit) UTIL_CAT(UTIL_CAT(kFLEXSPI_CsIntervalUnit, unit), SckCycle) +#define AHB_WRITE_WAIT_UNIT(unit) UTIL_CAT(UTIL_CAT(kFLEXSPI_AhbWriteWaitUnit, unit), AhbCycle) + +#define FLASH_FLEXSPI_DEVICE_CONFIG(n) \ + { \ + .flexspiRootClk = DT_INST_PROP(n, spi_max_frequency), \ + .flashSize = DT_INST_PROP(n, size) / 8 / KB(1), \ + .CSIntervalUnit = CS_INTERVAL_UNIT(DT_INST_PROP(n, cs_interval_unit)), \ + .CSInterval = DT_INST_PROP(n, cs_interval), \ + .CSHoldTime = DT_INST_PROP(n, cs_hold_time), \ + .CSSetupTime = DT_INST_PROP(n, cs_setup_time), \ + .dataValidTime = DT_INST_PROP(n, data_valid_time), \ + .columnspace = DT_INST_PROP(n, column_space), \ + .enableWordAddress = DT_INST_PROP(n, word_addressable), \ + .AHBWriteWaitUnit = AHB_WRITE_WAIT_UNIT(DT_INST_PROP(n, ahb_write_wait_unit)), \ + .AHBWriteWaitInterval = DT_INST_PROP(n, ahb_write_wait_interval), \ + } + +#define FLASH_FLEXSPI_NAND(n) \ + static struct flash_flexspi_nand_data flash_flexspi_nand_data_##n = { \ + .controller = DEVICE_DT_GET(DT_INST_BUS(n)), \ + .config = FLASH_FLEXSPI_DEVICE_CONFIG(n), \ + .port = DT_INST_REG_ADDR(n), \ + .size = DT_INST_PROP(n, size) / 8, \ + .page_size = 0, \ + .oob_size = 0, \ + .pages_per_block = 0, \ + .blocks_per_unit = 0, \ + .units = 0, \ + .flash_parameters = \ + { \ + .write_block_size = DT_INST_PROP(n, write_block_size), \ + .erase_value = 0xff, \ + }, \ + }; \ + DEVICE_DT_INST_DEFINE(n, flash_flexspi_nand_init, NULL, &flash_flexspi_nand_data_##n, \ + NULL, POST_KERNEL, \ + CONFIG_FLASH_MCUX_FLEXSPI_NAND_INIT_PRIORITY, \ + &flash_flexspi_nand_api); + +DT_INST_FOREACH_STATUS_OKAY(FLASH_FLEXSPI_NAND) From 920b70af335cd50478f320b0af1922f88dd6f0da Mon Sep 17 00:00:00 2001 From: Ruijia Wang Date: Wed, 25 Mar 2026 01:13:55 +0800 Subject: [PATCH 4/7] boards: frdm_rw612: add disabled W25N01GV NAND node Add Winbond W25N01GV (1 Gbit SPI NAND) device node to the FRDM-RW612 common DTS on FlexSPI PortB1 (CS2), disabled by default. The node is pin-compatible with the aps6404l PSRAM footprint at U12; enable at most one via devicetree overlay. write-block-size and erase-block-size are set to match the W25N01GV page and block geometry. Add pinctrl entry for FlexSPI PortB1 PSRAM/NAND pins and document the optional board rework procedure in the board documentation. Signed-off-by: Ruijia Wang --- boards/nxp/frdm_rw612/doc/index.rst | 19 ++++++++++ boards/nxp/frdm_rw612/frdm_rw612-pinctrl.dtsi | 7 ++++ boards/nxp/frdm_rw612/frdm_rw612_common.dtsi | 38 +++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/boards/nxp/frdm_rw612/doc/index.rst b/boards/nxp/frdm_rw612/doc/index.rst index 53dfff11d5f3..22481f9136d9 100644 --- a/boards/nxp/frdm_rw612/doc/index.rst +++ b/boards/nxp/frdm_rw612/doc/index.rst @@ -116,6 +116,25 @@ For example, below is part of an overlay to change the whole SRAM to be used for }; +NAND Flash (Board Rework) +************************* + +The FlexSPI PortB1 on FRDM-RW612 is pin-compatible with SPI NAND flash. +A Winbond W25N01GV (1 Gbit) can be fitted at U12 by board rework, providing +128 MB of NAND storage accessible via the FlexSPI controller. + +The ``w25n01gv`` device tree node is present in the board DTS but disabled +by default. Enable it with a devicetree overlay: + +.. code-block:: devicetree + + &w25n01gv { + status = "okay"; + }; + +The :zephyr:code-sample:`littlefs` sample provides ready-made board files +(``boards/frdm_rw612_nand.*``) that run LittleFS over Dhara FTL on this NAND. + Wireless Connectivity Support ***************************** diff --git a/boards/nxp/frdm_rw612/frdm_rw612-pinctrl.dtsi b/boards/nxp/frdm_rw612/frdm_rw612-pinctrl.dtsi index d689c0746348..3ec758cc6f88 100644 --- a/boards/nxp/frdm_rw612/frdm_rw612-pinctrl.dtsi +++ b/boards/nxp/frdm_rw612/frdm_rw612-pinctrl.dtsi @@ -115,4 +115,11 @@ slew-rate = "normal"; }; }; + + pinmux_flexspi_psram: pinmux_flexspi_psram { + group0 { + pinmux = ; + slew-rate = "normal"; + }; + }; }; diff --git a/boards/nxp/frdm_rw612/frdm_rw612_common.dtsi b/boards/nxp/frdm_rw612/frdm_rw612_common.dtsi index 30a4230983fc..551823310b42 100644 --- a/boards/nxp/frdm_rw612/frdm_rw612_common.dtsi +++ b/boards/nxp/frdm_rw612/frdm_rw612_common.dtsi @@ -216,6 +216,44 @@ mikrobus_serial: &flexcomm0 {}; ahb-write-wait-unit = <2>; ahb-write-wait-interval = <0>; }; + + /* + * Winbond W25N01GV NAND - FlexSPI PortB1 (CS2). + * Pin-compatible alternative to aps6404l@2. + * Mutually exclusive with aps6404l@2: enable at most one via overlay. + */ + w25n01gv: w25n01gv@2 { + compatible = "nxp,imx-flexspi-nand"; + reg = <2>; /* PortB1 */ + status = "disabled"; + + /* W25N01GVZEIG: 1 Gbit = 128 MiB; DT size is in bits */ + size = ; + + spi-max-frequency = ; + write-block-size = <2048>; + erase-block-size = ; + + cs-interval-unit = <1>; + cs-interval = <2>; + cs-hold-time = <3>; + cs-setup-time = <3>; + data-valid-time = <6>; + column-space = <11>; + ahb-write-wait-unit = <2>; + ahb-write-wait-interval = <0>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + nand_storage: partition@0 { + label = "nand-storage"; + reg = <0x0 DT_SIZE_M(128)>; + }; + }; + }; }; &hci { From 8d57051fcf42c1c542809c925de3876878b2d79e Mon Sep 17 00:00:00 2001 From: Ruijia Wang Date: Wed, 25 Mar 2026 01:14:02 +0800 Subject: [PATCH 5/7] samples: fs: littlefs: add FTL block device backend support Add configuration option and implementation for using an FTL (Flash Translation Layer) block device as storage backend for the littlefs sample, enabling the sample to run on NAND flash through the Dhara FTL disk driver. Signed-off-by: Ruijia Wang --- samples/subsys/fs/littlefs/Kconfig | 3 +++ samples/subsys/fs/littlefs/src/main.c | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/samples/subsys/fs/littlefs/Kconfig b/samples/subsys/fs/littlefs/Kconfig index 3ba067fb0fdf..66452ac20626 100644 --- a/samples/subsys/fs/littlefs/Kconfig +++ b/samples/subsys/fs/littlefs/Kconfig @@ -19,6 +19,9 @@ config APP_LITTLEFS_STORAGE_FLASH config APP_LITTLEFS_STORAGE_BLK_SDMMC bool "Use block device (e.g. SD MMC) backend" + +config APP_LITTLEFS_STORAGE_BLK_FTL + bool "Use FTL (Flash Translation Layer) block device backend" endchoice source "Kconfig.zephyr" diff --git a/samples/subsys/fs/littlefs/src/main.c b/samples/subsys/fs/littlefs/src/main.c index 7e565cada935..6b209f139529 100644 --- a/samples/subsys/fs/littlefs/src/main.c +++ b/samples/subsys/fs/littlefs/src/main.c @@ -309,7 +309,6 @@ static int littlefs_mount(struct fs_mount_t *mp) #endif /* CONFIG_APP_LITTLEFS_STORAGE_FLASH */ #ifdef CONFIG_APP_LITTLEFS_STORAGE_BLK_SDMMC - #if defined(CONFIG_DISK_DRIVER_SDMMC) #define DISK_NAME "SD" #elif defined(CONFIG_DISK_DRIVER_MMC) @@ -317,6 +316,14 @@ static int littlefs_mount(struct fs_mount_t *mp) #else #error "No disk device defined, is your board supported?" #endif +#endif /* CONFIG_APP_LITTLEFS_STORAGE_BLK_SDMMC */ + +#ifdef CONFIG_APP_LITTLEFS_STORAGE_BLK_FTL +#define DISK_NAME "NAND" +#endif /* CONFIG_APP_LITTLEFS_STORAGE_BLK_FTL */ + +#if defined(CONFIG_APP_LITTLEFS_STORAGE_BLK_SDMMC) || \ + defined(CONFIG_APP_LITTLEFS_STORAGE_BLK_FTL) struct fs_littlefs lfsfs; static struct fs_mount_t __mp = { @@ -336,7 +343,7 @@ static int littlefs_mount(struct fs_mount_t *mp) return fs_mount(mp); } -#endif /* CONFIG_APP_LITTLEFS_STORAGE_BLK_SDMMC */ +#endif /* CONFIG_APP_LITTLEFS_STORAGE_BLK_SDMMC || CONFIG_APP_LITTLEFS_STORAGE_BLK_FTL */ int main(void) { From 5dc104b804bd9c567023476be19aa970613aea1b Mon Sep 17 00:00:00 2001 From: Ruijia Wang Date: Wed, 25 Mar 2026 01:14:10 +0800 Subject: [PATCH 6/7] samples: fs: littlefs: add frdm_rw612 NAND build-only scenario Add a build-only twister scenario for the W25N01GV NAND flash on FRDM-RW612, exercising LittleFS over Dhara FTL over FlexSPI NAND. The scenario is marked build_only: true as CI does not have hardware with NAND soldered. Signed-off-by: Ruijia Wang --- .../fs/littlefs/boards/frdm_rw612_nand.conf | 20 ++++++++++++++++ .../littlefs/boards/frdm_rw612_nand.overlay | 23 +++++++++++++++++++ samples/subsys/fs/littlefs/sample.yaml | 6 +++++ 3 files changed, 49 insertions(+) create mode 100644 samples/subsys/fs/littlefs/boards/frdm_rw612_nand.conf create mode 100644 samples/subsys/fs/littlefs/boards/frdm_rw612_nand.overlay diff --git a/samples/subsys/fs/littlefs/boards/frdm_rw612_nand.conf b/samples/subsys/fs/littlefs/boards/frdm_rw612_nand.conf new file mode 100644 index 000000000000..2a38571653ff --- /dev/null +++ b/samples/subsys/fs/littlefs/boards/frdm_rw612_nand.conf @@ -0,0 +1,20 @@ +# Copyright 2026 NXP +# SPDX-License-Identifier: Apache-2.0 + +# Re-initialize FlexSPI even in XIP mode so Zephyr configures PortB1 (NAND). +# Without this, memc skips init when XIP is active and PortB1 is never set up. +CONFIG_MEMC_MCUX_FLEXSPI_INIT_XIP=y + +# Must be > MEMC_INIT_PRIORITY so FlexSPI controller is ready first. +CONFIG_FLASH_MCUX_FLEXSPI_NAND_INIT_PRIORITY=51 + +# FTL block device backend for littlefs +CONFIG_DISK_DRIVER_FTL=y +CONFIG_DISK_ACCESS=y +CONFIG_APP_LITTLEFS_STORAGE_BLK_FTL=y +CONFIG_FS_LITTLEFS_BLK_DEV=y +CONFIG_FS_LITTLEFS_CACHE_SIZE=2048 +CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE=64 + +# LittleFS + Dhara + FlexSPI HAL call chain exceeds default stack size. +CONFIG_MAIN_STACK_SIZE=16384 diff --git a/samples/subsys/fs/littlefs/boards/frdm_rw612_nand.overlay b/samples/subsys/fs/littlefs/boards/frdm_rw612_nand.overlay new file mode 100644 index 000000000000..b030df1c79fe --- /dev/null +++ b/samples/subsys/fs/littlefs/boards/frdm_rw612_nand.overlay @@ -0,0 +1,23 @@ +/* + * Copyright 2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&flexspi { + pinctrl-0 = <&pinmux_flexspi_psram>; + pinctrl-names = "default"; +}; + +&w25n01gv { + status = "okay"; +}; + +/ { + ftl_nand: ftl_nand { + compatible = "zephyr,ftl-dhara"; + partition = <&nand_storage>; + disk-name = "NAND"; + buffer-size = <2048>; + }; +}; diff --git a/samples/subsys/fs/littlefs/sample.yaml b/samples/subsys/fs/littlefs/sample.yaml index 80a26f962a68..a741c4067181 100644 --- a/samples/subsys/fs/littlefs/sample.yaml +++ b/samples/subsys/fs/littlefs/sample.yaml @@ -85,3 +85,9 @@ tests: - CONF_FILE=prj_blk.conf extra_configs: - CONFIG_SDMMC_STM32_HWFC=y + sample.filesystem.littlefs.frdm_rw612_nand: + build_only: true + platform_allow: frdm_rw612 + extra_args: + - EXTRA_CONF_FILE=boards/frdm_rw612_nand.conf + - DTC_OVERLAY_FILE=boards/frdm_rw612_nand.overlay From adf305e89f65fb3a650c0a2f2dc4ac013eea5d60 Mon Sep 17 00:00:00 2001 From: Ruijia Wang Date: Fri, 17 Apr 2026 10:29:13 +0800 Subject: [PATCH 7/7] samples: drivers: memc: fix DTS node reference for frdm_rw612 After commit b1afb494b0c ("flash: nxp: align FlexSPI NOR DTS with soc-nv-flash layout"), the w25q512jvfiq label points to the soc-nv-flash child node instead of the nxp,imx-flexspi-nor controller. Setting spi-max-frequency on &w25q512jvfiq causes a devicetree validation error because soc-nv-flash.yaml does not declare this property. Fix by referencing &ext_flash_ctrl (the flash controller node) instead. Signed-off-by: Ruijia Wang --- samples/drivers/memc/boards/frdm_rw612.overlay | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/drivers/memc/boards/frdm_rw612.overlay b/samples/drivers/memc/boards/frdm_rw612.overlay index 15ab0e8d99e4..5076364b1091 100644 --- a/samples/drivers/memc/boards/frdm_rw612.overlay +++ b/samples/drivers/memc/boards/frdm_rw612.overlay @@ -10,7 +10,7 @@ }; }; -&w25q512jvfiq { +&ext_flash_ctrl { /* * Lower max FlexSPI frequency to 109MHz, as the PSRAM does not support * higher frequencies at 3.3V