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 { 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) 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); 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] 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 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/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 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) {