From 63770ae8dcfbc08a27d4c8bd53241d92b5593839 Mon Sep 17 00:00:00 2001 From: Pascal Linder Date: Tue, 9 Dec 2025 16:28:16 +0100 Subject: [PATCH 1/3] drivers: flash: Add NAND flash API extensions Some API extensions are foreseen for bad block management by a flash translation layer (FTL). Signed-off-by: Pascal Linder --- include/zephyr/drivers/flash.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/include/zephyr/drivers/flash.h b/include/zephyr/drivers/flash.h index 91a8d199231b..a42130471766 100644 --- a/include/zephyr/drivers/flash.h +++ b/include/zephyr/drivers/flash.h @@ -697,6 +697,33 @@ enum flash_ex_op_types { * Reset flash device. */ FLASH_EX_OP_RESET = 0, + + /** + * Checks whether a block is marked as bad. As input it takes the address of the block + * (off_t *). As output it returns @ref flash_block_status (enum flash_block_status *). + */ + FLASH_IS_BAD_BLOCK, + + /** + * Marks a block as bad. As input it takes the address of the block (off_t *). There is no + * output. + */ + FLASH_MARK_BAD_BLOCK, +}; + +/** + * @brief Enumeration for flash block status. + */ +enum flash_block_status { + /** + * Block is functional. + */ + FLASH_BLOCK_GOOD = 0, + + /** + * Block is marked as bad. + */ + FLASH_BLOCK_BAD = 1, }; static inline int z_impl_flash_ex_op(const struct device *dev, uint16_t code, From 6d10fc0de61c14daaf26395b0f0b37863e5791dc Mon Sep 17 00:00:00 2001 From: Pascal Linder Date: Mon, 8 Dec 2025 12:33:04 +0100 Subject: [PATCH 2/3] drivers: flash: Add NAND flash driver for MT29F4G08 via STM32 FMC Introduces a NAND flash driver for Micron's MT29F4G08 memory device that is configurable via devicetree. A generic flash controller driver communicates to the flexible memory controller of STM32 MCUs. Any other NAND flash driver can be implemented to work via FMC in the future. The chip-specific driver provides the flash API for upper layers and enables the on-die ECC feature. As it is the first NAND flash driver in Zephyr, some API extensions are foreseen for bad block management by a flash translation layer (FTL). Signed-off-by: Pascal Linder Signed-off-by: Tim Pambor --- drivers/flash/CMakeLists.txt | 2 + drivers/flash/Kconfig | 2 + drivers/flash/Kconfig.mt29f4g08 | 27 + drivers/flash/Kconfig.stm32_fmc | 17 + drivers/flash/flash_stm32_fmc_mt29f4g08.c | 356 +++++++++ drivers/flash/flash_stm32_fmc_nand.c | 726 ++++++++++++++++++ drivers/flash/flash_stm32_fmc_nand.h | 50 ++ .../flash_controller/micron,mt29f4g08.yaml | 52 ++ .../memory-controllers/st,stm32-fmc-nand.yaml | 145 ++++ include/zephyr/drivers/flash/nand_flash.h | 42 + 10 files changed, 1419 insertions(+) create mode 100644 drivers/flash/Kconfig.mt29f4g08 create mode 100644 drivers/flash/Kconfig.stm32_fmc create mode 100644 drivers/flash/flash_stm32_fmc_mt29f4g08.c create mode 100644 drivers/flash/flash_stm32_fmc_nand.c create mode 100644 drivers/flash/flash_stm32_fmc_nand.h create mode 100644 dts/bindings/flash_controller/micron,mt29f4g08.yaml create mode 100644 dts/bindings/memory-controllers/st,stm32-fmc-nand.yaml create mode 100644 include/zephyr/drivers/flash/nand_flash.h diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index ba9f65f8b9b2..142fd29540ae 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -48,6 +48,8 @@ zephyr_library_sources_ifdef(CONFIG_FLASH_RENESAS_RZ_QSPI_SPIBSC flash_renesas_r zephyr_library_sources_ifdef(CONFIG_FLASH_RENESAS_RZ_QSPI_XSPI flash_renesas_rz_qspi.c) zephyr_library_sources_ifdef(CONFIG_FLASH_RPI_PICO flash_rpi_pico.c) zephyr_library_sources_ifdef(CONFIG_FLASH_SF32LB_MPI_QSPI_NOR flash_sf32lb_mpi_qspi_nor.c) +zephyr_library_sources_ifdef(CONFIG_FLASH_STM32_FMC_MT29F4G08 flash_stm32_fmc_mt29f4g08.c) +zephyr_library_sources_ifdef(CONFIG_FLASH_STM32_FMC_NAND flash_stm32_fmc_nand.c) zephyr_library_sources_ifdef(CONFIG_FLASH_STM32_OSPI flash_stm32_ospi.c) zephyr_library_sources_ifdef(CONFIG_FLASH_STM32_QSPI flash_stm32_qspi.c) zephyr_library_sources_ifdef(CONFIG_FLASH_STM32_XSPI flash_stm32_xspi.c) diff --git a/drivers/flash/Kconfig b/drivers/flash/Kconfig index 6a5980a82e61..32915f350754 100644 --- a/drivers/flash/Kconfig +++ b/drivers/flash/Kconfig @@ -187,6 +187,7 @@ source "drivers/flash/Kconfig.max32" source "drivers/flash/Kconfig.mchp" source "drivers/flash/Kconfig.mcux" source "drivers/flash/Kconfig.mspi" +source "drivers/flash/Kconfig.mt29f4g08" source "drivers/flash/Kconfig.nor" source "drivers/flash/Kconfig.nordic_qspi_nor" source "drivers/flash/Kconfig.npcx_fiu" @@ -214,6 +215,7 @@ source "drivers/flash/Kconfig.simulator" source "drivers/flash/Kconfig.siwx91x" source "drivers/flash/Kconfig.smartbond" source "drivers/flash/Kconfig.stm32" +source "drivers/flash/Kconfig.stm32_fmc" source "drivers/flash/Kconfig.stm32_ospi" source "drivers/flash/Kconfig.stm32_qspi" source "drivers/flash/Kconfig.stm32_xspi" diff --git a/drivers/flash/Kconfig.mt29f4g08 b/drivers/flash/Kconfig.mt29f4g08 new file mode 100644 index 000000000000..45ccb1ec78a5 --- /dev/null +++ b/drivers/flash/Kconfig.mt29f4g08 @@ -0,0 +1,27 @@ +# MT29F4G08 NAND flash driver configuration options + +# Copyright (c) 2025 Endress+Hauser GmbH+Co. KG +# SPDX-License-Identifier: Apache-2.0 + +menuconfig FLASH_STM32_FMC_MT29F4G08 + bool "MT29F4G08 NAND flash driver via STM32 FMC" + default y + depends on DT_HAS_MICRON_MT29F4G08_ENABLED + depends on FLASH_STM32_FMC_NAND + select FLASH_HAS_DRIVER_ENABLED + select FLASH_HAS_EX_OP + select FLASH_HAS_EXPLICIT_ERASE + select FLASH_HAS_PAGE_LAYOUT + help + Enable flash memory specific support for Micron MT29F4G08 via STM32 flexible memory + controller. + +if FLASH_STM32_FMC_MT29F4G08 + +config FLASH_MT29F4G08_ECC + bool "MT29F4G08 On-Die ECC" + default y + help + Enable on-die ECC feature of Micron MT29F4G08. + +endif # FLASH_STM32_FMC_MT29F4G08 diff --git a/drivers/flash/Kconfig.stm32_fmc b/drivers/flash/Kconfig.stm32_fmc new file mode 100644 index 000000000000..25df86688921 --- /dev/null +++ b/drivers/flash/Kconfig.stm32_fmc @@ -0,0 +1,17 @@ +# STM32 flexible memory controller configuration options + +# Copyright (c) 2025 Endress+Hauser GmbH+Co. KG +# SPDX-License-Identifier: Apache-2.0 + +DT_STM32_FMC_NAND_HAS_DMA := $(dt_compat_any_has_prop,$(DT_COMPAT_ST_STM32_FMC_NAND),dmas) + +config FLASH_STM32_FMC_NAND + bool "STM32 FMC NAND flash controller" + default y + depends on DT_HAS_ST_STM32_FMC_NAND_ENABLED + select MEMC + select USE_STM32_LL_FMC + select DMA if $(DT_STM32_FMC_NAND_HAS_DMA) + select USE_STM32_HAL_DMA if $(DT_STM32_FMC_NAND_HAS_DMA) + help + Enable parallel NAND flash support on the STM32 family of processors. diff --git a/drivers/flash/flash_stm32_fmc_mt29f4g08.c b/drivers/flash/flash_stm32_fmc_mt29f4g08.c new file mode 100644 index 000000000000..980e08192593 --- /dev/null +++ b/drivers/flash/flash_stm32_fmc_mt29f4g08.c @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2025 Endress+Hauser GmbH+Co. KG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT micron_mt29f4g08 + +#include +#include +#include + +#include "flash_stm32_fmc_nand.h" + +#include +LOG_MODULE_REGISTER(flash_stm32_fmc_mt29f4g08, CONFIG_FLASH_LOG_LEVEL); + +#define ECC_FEATURE_ADDR 0x90 +#define ECC_FEATURE_DATA {0x08, 0x00, 0x00, 0x00} + +struct flash_mt29f4g08_config { + const struct device *controller; + struct flash_parameters parameters; + uint8_t bank; + size_t page_size; + size_t spare_area_size; + size_t block_size; + size_t plane_size; + size_t flash_size; + uint8_t setup_time; + uint8_t wait_setup_time; + uint8_t hold_setup_time; + uint8_t hiz_setup_time; +#ifdef CONFIG_FLASH_PAGE_LAYOUT + struct flash_pages_layout layout; +#endif /* CONFIG_FLASH_PAGE_LAYOUT */ +}; + +static struct nand_flash_address +flash_mt29f4g08_calculate_address(const struct flash_mt29f4g08_config *config, off_t offset) +{ + off_t page_index = offset / config->page_size; + off_t block_index = offset / config->block_size; + off_t plane_index = offset / config->plane_size; + + return (struct nand_flash_address){ + .page = page_index % (config->block_size / config->page_size), + .block = block_index % (config->plane_size / config->block_size), + .plane = plane_index % (config->flash_size / config->plane_size), + }; +} + +static int flash_mt29f4g08_read(const struct device *dev, off_t offset, void *data, size_t len) +{ + const struct flash_mt29f4g08_config *config = dev->config; + const struct device *controller = config->controller; + int ret; + + if ((offset < 0) || (offset >= config->flash_size) || + (len > (config->flash_size - offset))) { + return -EINVAL; + } + + while (len > 0) { + off_t page_offset = offset % config->page_size; + size_t chunk = ((page_offset + len) < config->page_size) + ? len + : (config->page_size - page_offset); + struct nand_flash_address address = + flash_mt29f4g08_calculate_address(config, offset); + + ret = flash_stm32_fmc_nand_read_page_chunk(controller, &address, page_offset, chunk, + (uint8_t *)data); + if (ret != 0) { + LOG_ERR("Reading page %d at block %d/plane %d failed with error %d", + address.page, address.block, address.plane, ret); + return ret; + } + data = (uint8_t *)data + chunk; + offset += chunk; + len -= chunk; + } + + return 0; +} + +static int flash_mt29f4g08_write(const struct device *dev, off_t offset, const void *data, + size_t len) +{ + const struct flash_mt29f4g08_config *config = dev->config; + const struct device *controller = config->controller; + int ret; + + if ((offset < 0) || (offset >= config->flash_size) || + (len > (config->flash_size - offset))) { + return -EINVAL; + } + + if (((offset % config->page_size) != 0) || ((len % config->page_size) != 0)) { + return -EINVAL; + } + + while (len > 0) { + struct nand_flash_address address = + flash_mt29f4g08_calculate_address(config, offset); + + ret = flash_stm32_fmc_nand_write_page(controller, &address, (const uint8_t *)data); + if (ret != 0) { + LOG_ERR("Writing page %d at block %d/plane %d failed with error %d", + address.page, address.block, address.plane, ret); + return ret; + } + data = (const uint8_t *)data + config->page_size; + offset += config->page_size; + len -= config->page_size; + } + + return 0; +} + +static int flash_mt29f4g08_erase(const struct device *dev, off_t offset, size_t size) +{ + const struct flash_mt29f4g08_config *config = dev->config; + const struct device *controller = config->controller; + int ret; + + if ((offset < 0) || (offset >= config->flash_size) || + (size > (config->flash_size - offset))) { + return -EINVAL; + } + + if (((offset % config->block_size) != 0) || ((size % config->block_size) != 0)) { + return -EINVAL; + } + + while (size > 0) { + struct nand_flash_address address = + flash_mt29f4g08_calculate_address(config, offset); + + ret = flash_stm32_fmc_nand_erase_block(controller, &address); + if (ret != 0) { + LOG_ERR("Erasing block %d at plane %d failed with error %d", address.block, + address.plane, ret); + return ret; + } + offset += config->block_size; + size -= config->block_size; + } + + return 0; +} + +static const struct flash_parameters *flash_mt29f4g08_get_parameters(const struct device *dev) +{ + const struct flash_mt29f4g08_config *config = dev->config; + + return &config->parameters; +} + +static int flash_mt29f4g08_get_size(const struct device *dev, uint64_t *size) +{ + const struct flash_mt29f4g08_config *config = dev->config; + + *size = config->flash_size; + + return 0; +} + +#if CONFIG_FLASH_PAGE_LAYOUT +static void flash_mt29f4g08_page_layout(const struct device *dev, + const struct flash_pages_layout **layout, + size_t *layout_size) +{ + const struct flash_mt29f4g08_config *config = dev->config; + + *layout = &config->layout; + *layout_size = 1; +} +#endif /* CONFIG_FLASH_PAGE_LAYOUT */ + +#if CONFIG_FLASH_EX_OP_ENABLED +static int flash_mt29f4g08_is_bad_block(const struct device *dev, const off_t *offset, + enum flash_block_status *status) +{ + const struct flash_mt29f4g08_config *config = dev->config; + struct nand_flash_address address; + uint8_t spare_area[config->spare_area_size]; + int ret; + + if (status == NULL) { + return -EINVAL; + } + + if ((offset == NULL) || (*offset < 0) || (*offset >= config->flash_size) || + ((*offset % config->block_size) != 0)) { + *status = FLASH_BLOCK_BAD; + return -EINVAL; + } + + /* Check bad block marker in first page */ + memset(spare_area, 0x00, config->spare_area_size); + address = flash_mt29f4g08_calculate_address(config, *offset); + ret = flash_stm32_fmc_nand_read_spare_area(config->controller, &address, spare_area); + if (ret == 0) { + *status = (spare_area[0] != 0xFF) ? FLASH_BLOCK_BAD : FLASH_BLOCK_GOOD; + } else { + *status = FLASH_BLOCK_BAD; + } + + return ret; +} + +static int flash_mt29f4g08_mark_bad_block(const struct device *dev, const off_t *offset) +{ + const struct flash_mt29f4g08_config *config = dev->config; + struct nand_flash_address address; + uint8_t spare_area[config->spare_area_size]; + int ret; + + if ((offset == NULL) || (*offset < 0) || (*offset >= config->flash_size) || + ((*offset % config->block_size) != 0)) { + return -EINVAL; + } + + /* Mark bad block in first page */ + memset(spare_area, 0x00, config->spare_area_size); + address = flash_mt29f4g08_calculate_address(config, *offset); + ret = flash_stm32_fmc_nand_read_spare_area(config->controller, &address, spare_area); + if (ret == 0) { + spare_area[0] = 0x00; + ret = flash_stm32_fmc_nand_write_spare_area(config->controller, &address, + spare_area); + } + + return ret; +} + +int flash_mt29f4g08_ex_op(const struct device *dev, uint16_t code, const uintptr_t in, void *out) +{ + switch (code) { + case FLASH_IS_BAD_BLOCK: + return flash_mt29f4g08_is_bad_block(dev, (const off_t *)in, + (enum flash_block_status *)out); + + case FLASH_MARK_BAD_BLOCK: + return flash_mt29f4g08_mark_bad_block(dev, (const off_t *)in); + + default: + return -ENOTSUP; + } +} +#endif /* CONFIG_FLASH_EX_OP_ENABLED */ + +static int flash_stm32_fmc_mt29f4g08_init(const struct device *dev) +{ + const struct flash_mt29f4g08_config *config = dev->config; + const struct device *controller = config->controller; + int ret; + + if (!device_is_ready(controller)) { + LOG_ERR("Parent flash controller %s is not ready", controller->name); + return -ENODEV; + } + + struct flash_stm32_fmc_nand_init init = { + .bank = config->bank, + .page_size = config->page_size, + .spare_area_size = config->spare_area_size, + .block_size = config->block_size, + .plane_size = config->plane_size, + .flash_size = config->flash_size, + .setup_time = config->setup_time, + .wait_setup_time = config->wait_setup_time, + .hold_setup_time = config->hold_setup_time, + .hiz_setup_time = config->hiz_setup_time, + }; + + /* Initialise NAND bank */ + ret = flash_stm32_fmc_nand_init_bank(controller, &init); + if (ret != 0) { + LOG_ERR("NAND bank initialisation failed with error %d", ret); + return -EIO; + } + + /* Reset NAND flash */ + ret = flash_stm32_fmc_nand_reset(controller); + if (ret != 0) { + LOG_ERR("NAND flash reset failed with error %d", ret); + return -EIO; + } + +#ifdef CONFIG_FLASH_MT29F4G08_ECC + /* Enable on-die ECC feature */ + struct nand_flash_feature ecc_feature = { + .feature_addr = ECC_FEATURE_ADDR, + .feature_data = ECC_FEATURE_DATA, + }; + + ret = flash_stm32_fmc_nand_set_feature(controller, &ecc_feature); + if (ret != 0) { + LOG_ERR("Enabling on-die ECC failed with error %d", ret); + return -EIO; + } +#endif /* CONFIG_FLASH_MT29F4G08_ECC */ + + LOG_INF("MT29F4G08 flash initialised with FMC controller %s", controller->name); + + return 0; +} + +static DEVICE_API(flash, flash_stm32_fmc_mt29f4g08_api) = { + .read = flash_mt29f4g08_read, + .write = flash_mt29f4g08_write, + .erase = flash_mt29f4g08_erase, + .get_parameters = flash_mt29f4g08_get_parameters, + .get_size = flash_mt29f4g08_get_size, +#ifdef CONFIG_FLASH_PAGE_LAYOUT + .page_layout = flash_mt29f4g08_page_layout, +#endif +#ifdef CONFIG_FLASH_EX_OP_ENABLED + .ex_op = flash_mt29f4g08_ex_op, +#endif /* CONFIG_FLASH_EX_OP_ENABLED */ +}; + +/* A page in this context corresponds to the smallest erasable area which is a block */ +#define LAYOUT_PAGES_PROP(n) \ + IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT, \ + (.layout = { \ + .pages_count = DT_INST_PROP(n, flash_size) / DT_INST_PROP(n, block_size), \ + .pages_size = DT_INST_PROP(n, block_size), \ + },)) + +#define FLASH_STM32_FMC_MT29F4G08_INIT(n) \ + static const struct flash_mt29f4g08_config flash_stm32_fmc_mt29f4g08_config_##n = { \ + .controller = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + .parameters = { \ + .write_block_size = DT_INST_PROP(n, page_size), \ + .erase_value = 0xff, \ + }, \ + .bank = DT_INST_PROP(n, reg), \ + .page_size = DT_INST_PROP(n, page_size), \ + .spare_area_size = DT_INST_PROP(n, spare_area_size), \ + .block_size = DT_INST_PROP(n, block_size), \ + .plane_size = DT_INST_PROP(n, plane_size), \ + .flash_size = DT_INST_PROP(n, flash_size), \ + .setup_time = DT_INST_PROP(n, setup_time), \ + .wait_setup_time = DT_INST_PROP(n, wait_setup_time), \ + .hold_setup_time = DT_INST_PROP(n, hold_setup_time), \ + .hiz_setup_time = DT_INST_PROP(n, hiz_setup_time), \ + LAYOUT_PAGES_PROP(n)}; \ + \ + DEVICE_DT_INST_DEFINE(n, flash_stm32_fmc_mt29f4g08_init, NULL, NULL, \ + &flash_stm32_fmc_mt29f4g08_config_##n, POST_KERNEL, \ + CONFIG_FLASH_INIT_PRIORITY, &flash_stm32_fmc_mt29f4g08_api); + +DT_INST_FOREACH_STATUS_OKAY(FLASH_STM32_FMC_MT29F4G08_INIT) diff --git a/drivers/flash/flash_stm32_fmc_nand.c b/drivers/flash/flash_stm32_fmc_nand.c new file mode 100644 index 000000000000..e0bc8634cc29 --- /dev/null +++ b/drivers/flash/flash_stm32_fmc_nand.c @@ -0,0 +1,726 @@ +/* + * Copyright (c) 2025 Endress+Hauser GmbH+Co. KG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT st_stm32_fmc_nand + +#include +#include +#include + +#include "flash_stm32_fmc_nand.h" + +#define STM32_FMC_NAND_USE_DMA DT_ANY_INST_HAS_PROP_STATUS_OKAY(dmas) + +#if STM32_FMC_NAND_USE_DMA +#include +#include +#endif /* STM32_FMC_NAND_USE_DMA */ + +#include +LOG_MODULE_REGISTER(flash_stm32_fmc_nand, CONFIG_FLASH_LOG_LEVEL); + +#define DMA_TIMEOUT_MS 1000 +#define NAND_CMD_SET_FEATURES ((uint8_t)0xEF) +#define NAND_TIMEOUT_MS 2000 +#define PAGE_BUFFER_ALIGNMENT 4 + +#define STM32_FMC_NAND_ARRAY_ADDRESS(address, data) \ + ((address)->page + \ + (((address)->block + ((address)->plane * (data)->plane_size)) * (data)->block_size)) + +#define STM32_FMC_NAND_COLUMN_ADDRESS(data) ((data)->page_size) + +#if STM32_FMC_NAND_USE_DMA +struct stream { + bool enabled; + struct k_sem sync; + DMA_TypeDef *reg; + const struct device *dev; + uint32_t channel; + struct dma_config cfg; + struct dma_block_config block_cfg; +}; +#endif /* STM32_FMC_NAND_USE_DMA */ + +enum nand_state { + NAND_STATE_RESET = 0, /* Not yet initialised or disabled */ + NAND_STATE_READY, /* Initialised and ready */ + NAND_STATE_BUSY, /* Busy */ + NAND_STATE_ERROR, /* Failed */ +}; + +#if STM32_FMC_NAND_USE_DMA +struct flash_stm32_fmc_nand_config { + uint8_t *page_buffer; +}; +#endif /* STM32_FMC_NAND_USE_DMA */ + +struct flash_stm32_fmc_nand_data { + FMC_NAND_TypeDef *instance; + FMC_NAND_InitTypeDef init; + struct k_sem lock; + enum nand_state state; + size_t page_size; /* Page size in bytes or words depending on addressing */ + size_t spare_area_size; /* Spare area size in bytes or words depending on addressing */ + size_t block_size; /* Block size in number of pages */ + size_t plane_size; /* Plane size in number of blocks */ + size_t total_pages; /* Total number of pages */ +#if STM32_FMC_NAND_USE_DMA + struct stream dma; +#endif /* STM32_FMC_NAND_USE_DMA */ +}; + +/* Reads status until NAND is ready or reports an error */ +static int flash_stm32_fmc_nand_wait(void) +{ + uint8_t status; + k_timepoint_t deadline; + + deadline = sys_timepoint_calc(SYS_TIMEOUT_MS(NAND_TIMEOUT_MS)); + while (true) { + /* Send read status operation command */ + sys_write8(NAND_CMD_STATUS, NAND_DEVICE | CMD_AREA); + + /* Read status register data */ + status = sys_read8(NAND_DEVICE); + + if ((status & NAND_READY) == NAND_READY) { + break; + } else if ((status & NAND_ERROR) == NAND_ERROR) { + return -EIO; + } else if (sys_timepoint_expired(deadline)) { + return -ETIMEDOUT; + } + } + + return 0; +} + +int flash_stm32_fmc_nand_read_page_chunk(const struct device *dev, + const struct nand_flash_address *address, + off_t page_offset, size_t chunk, uint8_t *data) +{ + struct flash_stm32_fmc_nand_data *dev_data = dev->data; + uint32_t nand_address; + int ret; + + k_sem_take(&dev_data->lock, K_FOREVER); + + if (dev_data->state == NAND_STATE_BUSY) { + k_sem_give(&dev_data->lock); + return -EBUSY; + } else if (dev_data->state != NAND_STATE_READY) { + k_sem_give(&dev_data->lock); + return -EIO; + } + + dev_data->state = NAND_STATE_BUSY; + nand_address = STM32_FMC_NAND_ARRAY_ADDRESS(address, dev_data); /* Raw NAND address */ + + /* Send read page command sequence */ + sys_write8(NAND_CMD_AREA_A, NAND_DEVICE | CMD_AREA); + + if (dev_data->page_size <= 512) { + if (dev_data->total_pages <= 65535) { + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } else { + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_3RD_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } + } else { + if (dev_data->total_pages <= 65535) { + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } else { + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_3RD_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } + } + + sys_write8(NAND_CMD_AREA_TRUE1, NAND_DEVICE | CMD_AREA); + + /* Read status until NAND is ready or reports an error */ + ret = flash_stm32_fmc_nand_wait(); + if (ret == -EIO) { + LOG_ERR("Uncorrectable ECC error detected"); + dev_data->state = NAND_STATE_READY; + k_sem_give(&dev_data->lock); + return ret; + } else if (ret == -ETIMEDOUT) { + dev_data->state = NAND_STATE_ERROR; + k_sem_give(&dev_data->lock); + return ret; + } + + /* Go back to read mode */ + sys_write8(NAND_CMD_AREA_A, NAND_DEVICE | CMD_AREA); + +#if STM32_FMC_NAND_USE_DMA + if (dev_data->dma.enabled) { + const struct flash_stm32_fmc_nand_config *config = dev->config; + + /* Get page data into buffer */ + k_sem_reset(&dev_data->dma.sync); + if (dma_reload(dev_data->dma.dev, dev_data->dma.channel, NAND_DEVICE, + (uint32_t)config->page_buffer, dev_data->page_size) != 0) { + dev_data->state = NAND_STATE_ERROR; + k_sem_give(&dev_data->lock); + return -EIO; + } + if (k_sem_take(&dev_data->dma.sync, K_MSEC(DMA_TIMEOUT_MS)) != 0) { + dev_data->state = NAND_STATE_ERROR; + k_sem_give(&dev_data->lock); + return -EIO; + } + + /* Get chunk into output buffer */ + if (dma_reload(dev_data->dma.dev, dev_data->dma.channel, + (uint32_t)(config->page_buffer + page_offset), (uint32_t)data, + chunk) != 0) { + dev_data->state = NAND_STATE_ERROR; + k_sem_give(&dev_data->lock); + return -EIO; + } + } else { +#endif /* STM32_FMC_NAND_USE_DMA */ + + /* Read chunk of page data directly into output buffer */ + for (size_t index = 0; index < dev_data->page_size; index++) { + if (index >= page_offset && index < (page_offset + chunk)) { + *data = sys_read8(NAND_DEVICE); + data++; + } else { + (void)sys_read8(NAND_DEVICE); + } + } + +#if STM32_FMC_NAND_USE_DMA + } +#endif /* STM32_FMC_NAND_USE_DMA */ + + dev_data->state = NAND_STATE_READY; + k_sem_give(&dev_data->lock); + + return 0; +} + +int flash_stm32_fmc_nand_read_spare_area(const struct device *dev, + const struct nand_flash_address *address, uint8_t *data) +{ + struct flash_stm32_fmc_nand_data *dev_data = dev->data; + uint32_t nand_address, column_address; + uint8_t *buffer = data; + int ret; + + k_sem_take(&dev_data->lock, K_FOREVER); + + if (dev_data->state == NAND_STATE_BUSY) { + k_sem_give(&dev_data->lock); + return -EBUSY; + } else if (dev_data->state != NAND_STATE_READY) { + k_sem_give(&dev_data->lock); + return -EIO; + } + + dev_data->state = NAND_STATE_BUSY; + nand_address = STM32_FMC_NAND_ARRAY_ADDRESS(address, dev_data); /* Raw NAND address */ + column_address = STM32_FMC_NAND_COLUMN_ADDRESS(dev_data); + + /* Send read spare area command sequence */ + if (dev_data->page_size <= 512) { + sys_write8(NAND_CMD_AREA_C, NAND_DEVICE | CMD_AREA); + + if (dev_data->total_pages <= 65535) { + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } else { + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_3RD_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } + } else { + sys_write8(NAND_CMD_AREA_A, NAND_DEVICE | CMD_AREA); + + if (dev_data->total_pages <= 65535) { + sys_write8(COLUMN_1ST_CYCLE(column_address), NAND_DEVICE | ADDR_AREA); + sys_write8(COLUMN_2ND_CYCLE(column_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } else { + sys_write8(COLUMN_1ST_CYCLE(column_address), NAND_DEVICE | ADDR_AREA); + sys_write8(COLUMN_2ND_CYCLE(column_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_3RD_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } + } + + sys_write8(NAND_CMD_AREA_TRUE1, NAND_DEVICE | CMD_AREA); + + /* Read status until NAND is ready or reports an error */ + ret = flash_stm32_fmc_nand_wait(); + if (ret == -EIO) { + LOG_ERR("Uncorrectable ECC error detected"); + dev_data->state = NAND_STATE_READY; + k_sem_give(&dev_data->lock); + return ret; + } else if (ret == -ETIMEDOUT) { + dev_data->state = NAND_STATE_ERROR; + k_sem_give(&dev_data->lock); + return ret; + } + + /* Go back to read mode */ + sys_write8(NAND_CMD_AREA_A, NAND_DEVICE | CMD_AREA); + + /* Get spare area data into output buffer */ + for (size_t index = 0; index < dev_data->spare_area_size; index++) { + *buffer = sys_read8(NAND_DEVICE); + buffer++; + } + + dev_data->state = NAND_STATE_READY; + k_sem_give(&dev_data->lock); + + return 0; +} + +int flash_stm32_fmc_nand_write_page(const struct device *dev, + const struct nand_flash_address *address, const uint8_t *data) +{ + struct flash_stm32_fmc_nand_data *dev_data = dev->data; + uint32_t nand_address; + const uint8_t *buffer = data; + int ret; + + k_sem_take(&dev_data->lock, K_FOREVER); + + if (dev_data->state == NAND_STATE_BUSY) { + k_sem_give(&dev_data->lock); + return -EBUSY; + } else if (dev_data->state != NAND_STATE_READY) { + k_sem_give(&dev_data->lock); + return -EIO; + } + + dev_data->state = NAND_STATE_BUSY; + nand_address = STM32_FMC_NAND_ARRAY_ADDRESS(address, dev_data); /* Raw NAND address */ + + /* Send write page command sequence */ + sys_write8(NAND_CMD_AREA_A, NAND_DEVICE | CMD_AREA); + sys_write8(NAND_CMD_WRITE0, NAND_DEVICE | CMD_AREA); + + if (dev_data->page_size <= 512) { + if (dev_data->total_pages <= 65535) { + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } else { + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_3RD_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } + } else { + if (dev_data->total_pages <= 65535) { + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } else { + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_3RD_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } + } + + /* Write data to memory */ + for (size_t index = 0; index < dev_data->page_size; index++) { + sys_write8(*buffer, NAND_DEVICE); + buffer++; + } + + sys_write8(NAND_CMD_WRITE_TRUE1, NAND_DEVICE | CMD_AREA); + + /* Read status until NAND is ready or reports an error */ + ret = flash_stm32_fmc_nand_wait(); + if (ret != 0) { + dev_data->state = NAND_STATE_ERROR; + k_sem_give(&dev_data->lock); + return ret; + } + + dev_data->state = NAND_STATE_READY; + k_sem_give(&dev_data->lock); + + return 0; +} + +int flash_stm32_fmc_nand_write_spare_area(const struct device *dev, + const struct nand_flash_address *address, + const uint8_t *data) +{ + struct flash_stm32_fmc_nand_data *dev_data = dev->data; + uint32_t nand_address, column_address; + const uint8_t *buffer = data; + int ret; + + k_sem_take(&dev_data->lock, K_FOREVER); + + if (dev_data->state == NAND_STATE_BUSY) { + k_sem_give(&dev_data->lock); + return -EBUSY; + } else if (dev_data->state != NAND_STATE_READY) { + k_sem_give(&dev_data->lock); + return -EIO; + } + + dev_data->state = NAND_STATE_BUSY; + nand_address = STM32_FMC_NAND_ARRAY_ADDRESS(address, dev_data); /* Raw NAND address */ + column_address = STM32_FMC_NAND_COLUMN_ADDRESS(dev_data); + + /* Send write spare area command sequence */ + if (dev_data->page_size <= 512) { + sys_write8(NAND_CMD_AREA_C, NAND_DEVICE | CMD_AREA); + sys_write8(NAND_CMD_WRITE0, NAND_DEVICE | CMD_AREA); + + if (dev_data->total_pages <= 65535) { + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } else { + sys_write8(0x00, NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_3RD_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } + } else { + sys_write8(NAND_CMD_AREA_A, NAND_DEVICE | CMD_AREA); + sys_write8(NAND_CMD_WRITE0, NAND_DEVICE | CMD_AREA); + + if (dev_data->total_pages <= 65535) { + sys_write8(COLUMN_1ST_CYCLE(column_address), NAND_DEVICE | ADDR_AREA); + sys_write8(COLUMN_2ND_CYCLE(column_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } else { + sys_write8(COLUMN_1ST_CYCLE(column_address), NAND_DEVICE | ADDR_AREA); + sys_write8(COLUMN_2ND_CYCLE(column_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_3RD_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + } + } + + /* Write data to memory */ + for (size_t index = 0; index < dev_data->spare_area_size; index++) { + sys_write8(*buffer, NAND_DEVICE); + buffer++; + } + + sys_write8(NAND_CMD_WRITE_TRUE1, NAND_DEVICE | CMD_AREA); + + /* Read status until NAND is ready or reports an error */ + ret = flash_stm32_fmc_nand_wait(); + if (ret != 0) { + dev_data->state = NAND_STATE_ERROR; + k_sem_give(&dev_data->lock); + return ret; + } + + dev_data->state = NAND_STATE_READY; + k_sem_give(&dev_data->lock); + + return 0; +} + +int flash_stm32_fmc_nand_erase_block(const struct device *dev, + const struct nand_flash_address *address) +{ + struct flash_stm32_fmc_nand_data *dev_data = dev->data; + uint32_t nand_address; + int ret; + + k_sem_take(&dev_data->lock, K_FOREVER); + + if (dev_data->state == NAND_STATE_BUSY) { + k_sem_give(&dev_data->lock); + return -EBUSY; + } else if (dev_data->state != NAND_STATE_READY) { + k_sem_give(&dev_data->lock); + return -EIO; + } + + dev_data->state = NAND_STATE_BUSY; + nand_address = STM32_FMC_NAND_ARRAY_ADDRESS(address, dev_data); /* Raw NAND address */ + + /* Send erase block command sequence */ + sys_write8(NAND_CMD_ERASE0, NAND_DEVICE | CMD_AREA); + + sys_write8(ADDR_1ST_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_2ND_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + sys_write8(ADDR_3RD_CYCLE(nand_address), NAND_DEVICE | ADDR_AREA); + + sys_write8(NAND_CMD_ERASE1, NAND_DEVICE | CMD_AREA); + + /* Read status until NAND is ready or reports an error */ + ret = flash_stm32_fmc_nand_wait(); + if (ret == -EIO) { + LOG_ERR("Bad block detected"); + dev_data->state = NAND_STATE_READY; + k_sem_give(&dev_data->lock); + return ret; + } else if (ret == -ETIMEDOUT) { + dev_data->state = NAND_STATE_ERROR; + k_sem_give(&dev_data->lock); + return ret; + } + + dev_data->state = NAND_STATE_READY; + k_sem_give(&dev_data->lock); + + return 0; +} + +int flash_stm32_fmc_nand_init_bank(const struct device *dev, + const struct flash_stm32_fmc_nand_init *init) +{ + struct flash_stm32_fmc_nand_data *dev_data = dev->data; + + k_sem_take(&dev_data->lock, K_FOREVER); + + dev_data->instance = FMC_NAND_DEVICE; + + if (init->bank == 3) { + dev_data->init.NandBank = FMC_NAND_BANK3; + } else { + LOG_ERR("Unsupported FMC NAND bank %d", init->bank); + k_sem_give(&dev_data->lock); + return -EINVAL; + } + + dev_data->init.Waitfeature = FMC_NAND_WAIT_FEATURE_ENABLE; + dev_data->init.MemoryDataWidth = FMC_NAND_MEM_BUS_WIDTH_8; + dev_data->init.EccComputation = FMC_NAND_ECC_DISABLE; + dev_data->init.ECCPageSize = FMC_NAND_ECC_PAGE_SIZE_2048BYTE; + dev_data->init.TCLRSetupTime = 0; + dev_data->init.TARSetupTime = 0; + + dev_data->page_size = init->page_size; + dev_data->spare_area_size = init->spare_area_size; + dev_data->block_size = init->block_size / init->page_size; + dev_data->plane_size = init->plane_size / init->block_size; + dev_data->total_pages = init->flash_size / init->page_size; + + (void)FMC_NAND_Init(dev_data->instance, &dev_data->init); + + FMC_NAND_PCC_TimingTypeDef timing = { + .SetupTime = init->setup_time, + .WaitSetupTime = init->wait_setup_time, + .HoldSetupTime = init->hold_setup_time, + .HiZSetupTime = init->hiz_setup_time, + }; + + (void)FMC_NAND_CommonSpace_Timing_Init(dev_data->instance, &timing, + dev_data->init.NandBank); + (void)FMC_NAND_AttributeSpace_Timing_Init(dev_data->instance, &timing, + dev_data->init.NandBank); + + __FMC_NAND_ENABLE(dev_data->instance); + __FMC_ENABLE(); + + dev_data->state = NAND_STATE_READY; + k_sem_give(&dev_data->lock); + + return 0; +} + +int flash_stm32_fmc_nand_reset(const struct device *dev) +{ + struct flash_stm32_fmc_nand_data *dev_data = dev->data; + + k_sem_take(&dev_data->lock, K_FOREVER); + + if (dev_data->state == NAND_STATE_BUSY) { + k_sem_give(&dev_data->lock); + return -EBUSY; + } else if (dev_data->state != NAND_STATE_READY) { + k_sem_give(&dev_data->lock); + return -EIO; + } + + dev_data->state = NAND_STATE_BUSY; + + /* Send NAND reset command */ + sys_write8(NAND_CMD_RESET, NAND_DEVICE | CMD_AREA); + + dev_data->state = NAND_STATE_READY; + k_sem_give(&dev_data->lock); + + return 0; +} + +int flash_stm32_fmc_nand_set_feature(const struct device *dev, + const struct nand_flash_feature *feature) +{ + struct flash_stm32_fmc_nand_data *dev_data = dev->data; + int ret; + + k_sem_take(&dev_data->lock, K_FOREVER); + + if (dev_data->state == NAND_STATE_BUSY) { + k_sem_give(&dev_data->lock); + return -EBUSY; + } else if (dev_data->state != NAND_STATE_READY) { + k_sem_give(&dev_data->lock); + return -EIO; + } + + dev_data->state = NAND_STATE_BUSY; + + /* Send feature setting command sequence */ + sys_write8(NAND_CMD_SET_FEATURES, NAND_DEVICE | CMD_AREA); + sys_write8(feature->feature_addr, NAND_DEVICE | ADDR_AREA); + sys_write8(feature->feature_data[0], NAND_DEVICE); + sys_write8(feature->feature_data[1], NAND_DEVICE); + sys_write8(feature->feature_data[2], NAND_DEVICE); + sys_write8(feature->feature_data[3], NAND_DEVICE); + + /* Read status until NAND is ready or reports an error */ + ret = flash_stm32_fmc_nand_wait(); + if (ret != 0) { + dev_data->state = NAND_STATE_ERROR; + k_sem_give(&dev_data->lock); + return ret; + } + + dev_data->state = NAND_STATE_READY; + k_sem_give(&dev_data->lock); + + return 0; +} + +static int flash_stm32_fmc_nand_init(const struct device *dev) +{ + struct flash_stm32_fmc_nand_data *dev_data = dev->data; + uint32_t __maybe_unused fmc_freq; + + dev_data->state = NAND_STATE_RESET; + k_sem_init(&dev_data->lock, 1, 1); + + memc_stm32_fmc_clock_rate(&fmc_freq); + LOG_DBG("FMC clock rate: %d Hz", fmc_freq); + +#if STM32_FMC_NAND_USE_DMA + if (dev_data->dma.enabled) { + const struct flash_stm32_fmc_nand_config *config = dev->config; + int ret; + + if (!device_is_ready(dev_data->dma.dev)) { + LOG_ERR("DMA %s device is not ready", dev_data->dma.dev->name); + return -ENODEV; + } + + /* + * Dummy address configuration to avoid warnings in dma_config(). The correct + * addresses are set with dma_reload(). + */ + dev_data->dma.block_cfg.source_address = (uint32_t)config->page_buffer; + dev_data->dma.block_cfg.dest_address = (uint32_t)config->page_buffer; + + dev_data->dma.cfg.head_block = &dev_data->dma.block_cfg; + k_sem_init(&dev_data->dma.sync, 0, 1); + dev_data->dma.cfg.user_data = &dev_data->dma.sync; + + ret = dma_config(dev_data->dma.dev, dev_data->dma.channel, &dev_data->dma.cfg); + if (ret != 0) { + LOG_ERR("Failed to configure DMA channel %d with error %d", + dev_data->dma.channel, ret); + return -EIO; + } + + LOG_INF("FMC NAND with DMA transfer"); + } +#endif /* STM32_FMC_NAND_USE_DMA */ + + return 0; +} + +/* This function is executed in the interrupt context */ +#if STM32_FMC_NAND_USE_DMA +static void fmc_nand_dma_callback(const struct device *dev, void *user_data, uint32_t channel, + int status) +{ + ARG_UNUSED(dev); + + k_sem_give((struct k_sem *)user_data); + + if (status < 0) { + LOG_ERR("DMA callback error %d with channel %d", status, channel); + } +} + +#define DMA_CHANNEL_CONFIG(node, dir) DT_DMAS_CELL_BY_NAME(node, dir, channel_config) + +#define FMC_NAND_DMA_CHANNEL_INIT(node, dir) \ + .enabled = true, .reg = (DMA_TypeDef *)DT_REG_ADDR(DT_PHANDLE_BY_NAME(node, dmas, dir)), \ + .dev = DEVICE_DT_GET(DT_DMAS_CTLR(node)), \ + .channel = DT_DMAS_CELL_BY_NAME(node, dir, channel), \ + .cfg = { \ + .channel_direction = MEMORY_TO_MEMORY, \ + .channel_priority = STM32_DMA_CONFIG_PRIORITY(DMA_CHANNEL_CONFIG(node, dir)), \ + .source_data_size = \ + STM32_DMA_CONFIG_PERIPHERAL_DATA_SIZE(DMA_CHANNEL_CONFIG(node, dir)), \ + .dest_data_size = \ + STM32_DMA_CONFIG_MEMORY_DATA_SIZE(DMA_CHANNEL_CONFIG(node, dir)), \ + .source_burst_length = 64, \ + .dest_burst_length = 64, \ + .block_count = 1, \ + .dma_callback = fmc_nand_dma_callback, \ + } + +#define FMC_NAND_DMA_CHANNEL(node, dir) \ + .dma = {COND_CODE_1(DT_DMAS_HAS_NAME(node, dir), \ + (FMC_NAND_DMA_CHANNEL_INIT(node, dir)), \ + (NULL))}, + +#define FMC_NAND_PAGE_BUFFER(node) \ + static uint8_t __nocache __aligned(PAGE_BUFFER_ALIGNMENT) \ + flash_stm32_fmc_nand_page_buffer_##node[DT_INST_PROP(node, page_buffer_size)]; \ + \ + static const struct flash_stm32_fmc_nand_config flash_stm32_fmc_nand_config_##node = { \ + .page_buffer = flash_stm32_fmc_nand_page_buffer_##node, \ + }; +#else +#define FMC_NAND_DMA_CHANNEL(node, dir) +#define FMC_NAND_PAGE_BUFFER(node) +#endif /* STM32_FMC_NAND_USE_DMA */ + +#define FLASH_STM32_FMC_NAND_INIT(n) \ + FMC_NAND_PAGE_BUFFER(n) \ + static struct flash_stm32_fmc_nand_data flash_stm32_fmc_nand_data_##n = { \ + FMC_NAND_DMA_CHANNEL(DT_DRV_INST(n), tx_rx) \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, flash_stm32_fmc_nand_init, NULL, &flash_stm32_fmc_nand_data_##n, \ + COND_CODE_1(STM32_FMC_NAND_USE_DMA, \ + (&flash_stm32_fmc_nand_config_##n), (NULL)), \ + POST_KERNEL, CONFIG_FLASH_INIT_PRIORITY, NULL); + +DT_INST_FOREACH_STATUS_OKAY(FLASH_STM32_FMC_NAND_INIT) diff --git a/drivers/flash/flash_stm32_fmc_nand.h b/drivers/flash/flash_stm32_fmc_nand.h new file mode 100644 index 000000000000..0a5f3756801b --- /dev/null +++ b/drivers/flash/flash_stm32_fmc_nand.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 Endress+Hauser GmbH+Co. KG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __ZEPHYR_DRIVERS_FLASH_FLASH_STM32_FMC_NAND_H__ +#define __ZEPHYR_DRIVERS_FLASH_FLASH_STM32_FMC_NAND_H__ + +#include + +struct flash_stm32_fmc_nand_init { + uint8_t bank; + size_t page_size; + size_t spare_area_size; + size_t block_size; + size_t plane_size; + size_t flash_size; + uint8_t setup_time; + uint8_t wait_setup_time; + uint8_t hold_setup_time; + uint8_t hiz_setup_time; +}; + +int flash_stm32_fmc_nand_read_page_chunk(const struct device *dev, + const struct nand_flash_address *address, + off_t page_offset, size_t chunk, uint8_t *data); + +int flash_stm32_fmc_nand_read_spare_area(const struct device *dev, + const struct nand_flash_address *address, uint8_t *data); + +int flash_stm32_fmc_nand_write_page(const struct device *dev, + const struct nand_flash_address *address, const uint8_t *data); + +int flash_stm32_fmc_nand_write_spare_area(const struct device *dev, + const struct nand_flash_address *address, + const uint8_t *data); + +int flash_stm32_fmc_nand_erase_block(const struct device *dev, + const struct nand_flash_address *address); + +int flash_stm32_fmc_nand_init_bank(const struct device *dev, + const struct flash_stm32_fmc_nand_init *init); + +int flash_stm32_fmc_nand_reset(const struct device *dev); + +int flash_stm32_fmc_nand_set_feature(const struct device *dev, + const struct nand_flash_feature *feature); + +#endif /* __ZEPHYR_DRIVERS_FLASH_FLASH_STM32_FMC_NAND_H__ */ diff --git a/dts/bindings/flash_controller/micron,mt29f4g08.yaml b/dts/bindings/flash_controller/micron,mt29f4g08.yaml new file mode 100644 index 000000000000..23cd84d7fd0f --- /dev/null +++ b/dts/bindings/flash_controller/micron,mt29f4g08.yaml @@ -0,0 +1,52 @@ +# Copyright (c) 2025 Endress+Hauser GmbH+Co. KG +# SPDX-License-Identifier: Apache-2.0 + +description: | + NAND flash driver for Micron MT29F4G08 flash memory. + + The MT29F4G08 is a 4 Gb NAND flash organised in pages of 2048 bytes each. + + Refer to the Micron MT29F4G08 datasheet for more details. + +compatible: "micron,mt29f4g08" + +properties: + reg: + type: int + required: true + + page-size: + type: int + required: true + + spare-area-size: + type: int + required: true + + block-size: + type: int + required: true + + plane-size: + type: int + required: true + + flash-size: + type: int + required: true + + setup-time: + type: int + default: 0 + + wait-setup-time: + type: int + default: 0 + + hold-setup-time: + type: int + default: 0 + + hiz-setup-time: + type: int + default: 0 diff --git a/dts/bindings/memory-controllers/st,stm32-fmc-nand.yaml b/dts/bindings/memory-controllers/st,stm32-fmc-nand.yaml new file mode 100644 index 000000000000..71dd0f8dafd1 --- /dev/null +++ b/dts/bindings/memory-controllers/st,stm32-fmc-nand.yaml @@ -0,0 +1,145 @@ +# Copyright (c) 2025 Endress+Hauser GmbH+Co. KG +# SPDX-License-Identifier: Apache-2.0 + +description: | + STM32 Flexible Memory Controller (FMC) for NAND flashes. + + The FMC generates the appropriate signal timings to drive 8- and 16-bit NAND flash memories. + + A unique chip select signal (NE) is used per bank. All the other signals (addresses, data and + control) are shared. A wide range of devices is supported through programmable timings. + + Refer to the STM32 reference manual for more details. + + The FMC NAND controller must be defined below the FMC node and banks are defined as child nodes + of the controller node. + + An optional DMA channel specifier can be provided to enable memory-to-memory transfers using DMA. + The specifier should hold a phandle reference to the DMA controller, the channel number, the slot + number (unused for memory-to-memory transfers) and channel configuration. The followed DMA + channel name must be "tx_rx". Use constants defined in dt-bindings/dma/stm32_dma.h. + + You can enable the controller in the devicetree as follows: + + &fmc { + status = "okay"; + pinctrl-0 = <&fmc_nce_pc8 &fmc_ale_pd12 ...>; + pinctrl-names = "default"; + + nand-controller { + compatible = "st,stm32-fmc-nand"; + status = "okay"; + + #address-cells = <1>; + #size-cells = <0>; + + page-buffer-size = ; + + dmas = <&dma1 2 0 0x0000>; + dma-names = "tx_rx"; + + nand-flash@3 { + compatible = "micron,mt29f4g08"; + status = "okay"; + reg = <3>; + + page-size = ; + spare-area-size = <64>; + block-size = ; + plane-size = ; + flash-size = ; + }; + }; + }; + +compatible: "st,stm32-fmc-nand" + +include: base.yaml + +properties: + "#address-cells": + required: true + const: 1 + + "#size-cells": + required: true + const: 0 + + page-buffer-size: + type: int + required: true + description: | + Page buffer size in bytes. Must be equal to the largest NAND page size among the child nodes. + +child-binding: + description: NAND bank. + + properties: + reg: + type: int + required: true + description: | + NAND bank value. Used to chip select the bank to which the flash memory is connected. + Should be equal to a value of FMC_NAND_BANKx. + + page-size: + type: int + required: true + description: | + NAND page size in bytes. + + spare-area-size: + type: int + required: true + description: | + NAND spare area size in bytes. + + block-size: + type: int + required: true + description: | + NAND block size in bytes. Must be a multiple of page size. + + plane-size: + type: int + required: true + description: | + NAND plane size in bytes. Must be a multiple of block size. + + flash-size: + type: int + required: true + description: | + Total NAND flash size in bytes. Must be a multiple of plane size. + + setup-time: + type: int + default: 0 + description: | + Number of HCLK cycles to set up the address before asserting the command for NAND flash + read or write access. Applies to common, attribute, or I/O memory space, depending on the + timing configuration. Valid values: 0 to 254. + + wait-setup-time: + type: int + default: 0 + description: | + Minimum number of HCLK cycles to assert the command for NAND flash read or write access. + Applies to common, attribute, or I/O memory space, depending on the timing configuration. + Valid values: 0 to 254. + + hold-setup-time: + type: int + default: 0 + description: | + Number of HLCK cycles to hold address and possibly data after de-asserting the command for + NAND flash read or write access. Applies to common, attribute, or I/O memory space, + depending on the timing configuration. Valid values: 0 to 254. + + hiz-setup-time: + type: int + default: 0 + description: | + Number of HCLK cycles for keeping data bus in HiZ after starting a NAND flash write access. + Applies to common, attribute, or I/O memory space, depending on the timing configuration. + Valid values: 0 to 254. diff --git a/include/zephyr/drivers/flash/nand_flash.h b/include/zephyr/drivers/flash/nand_flash.h new file mode 100644 index 000000000000..ffcd963d35a3 --- /dev/null +++ b/include/zephyr/drivers/flash/nand_flash.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 Endress+Hauser GmbH+Co. KG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief NAND flash specific data structures. + */ + +#ifndef __ZEPHYR_INCLUDE_DRIVERS_FLASH_NAND_FLASH_H__ +#define __ZEPHYR_INCLUDE_DRIVERS_FLASH_NAND_FLASH_H__ + +#include + +/** + * @brief Structure representing a NAND flash address. + */ +struct nand_flash_address { + /* Page index of address. */ + uint16_t page; + + /* Block index of address. */ + uint16_t block; + + /* Plane index of address. */ + uint16_t plane; +}; + +/** + * @brief Structure representing a NAND flash feature. + */ +struct nand_flash_feature { + /* Feature address. */ + uint8_t feature_addr; + + /* Feature data. */ + uint8_t feature_data[4]; +}; + +#endif /* __ZEPHYR_INCLUDE_DRIVERS_FLASH_NAND_FLASH_H__ */ From a47feb5c074b71f04fbbc4981527c30895efecf3 Mon Sep 17 00:00:00 2001 From: Pascal Linder Date: Mon, 8 Dec 2025 15:05:16 +0100 Subject: [PATCH 3/3] tests: drivers: build_all: flash: Add build test for MT29F4G08 Renames overlays to be board-specific and adds a build test for the newly introduced NAND flash driver for MT29F4G08 via STM32 FMC. Signed-off-by: Pascal Linder --- .../flash/{app.overlay => native_sim.overlay} | 4 +-- .../flash/{spi.dtsi => native_sim_spi.dtsi} | 0 .../build_all/flash/stm32h573i_dk.overlay | 29 +++++++++++++++++++ tests/drivers/build_all/flash/testcase.yaml | 6 ++++ 4 files changed, 37 insertions(+), 2 deletions(-) rename tests/drivers/build_all/flash/{app.overlay => native_sim.overlay} (86%) rename tests/drivers/build_all/flash/{spi.dtsi => native_sim_spi.dtsi} (100%) create mode 100644 tests/drivers/build_all/flash/stm32h573i_dk.overlay diff --git a/tests/drivers/build_all/flash/app.overlay b/tests/drivers/build_all/flash/native_sim.overlay similarity index 86% rename from tests/drivers/build_all/flash/app.overlay rename to tests/drivers/build_all/flash/native_sim.overlay index 17bbec2f0aeb..436a2cb1c7b7 100644 --- a/tests/drivers/build_all/flash/app.overlay +++ b/tests/drivers/build_all/flash/native_sim.overlay @@ -24,14 +24,14 @@ status = "okay"; clock-frequency = <2000000>; - /* one entry for every devices at spi.dtsi */ + /* one entry for every devices at native_sim_spi.dtsi */ cs-gpios = <&test_gpio 0 0>, <&test_gpio 0 0>, <&test_gpio 0 0>, <&test_gpio 0 0>, <&test_gpio 0 0>; - #include "spi.dtsi" + #include "native_sim_spi.dtsi" }; }; }; diff --git a/tests/drivers/build_all/flash/spi.dtsi b/tests/drivers/build_all/flash/native_sim_spi.dtsi similarity index 100% rename from tests/drivers/build_all/flash/spi.dtsi rename to tests/drivers/build_all/flash/native_sim_spi.dtsi diff --git a/tests/drivers/build_all/flash/stm32h573i_dk.overlay b/tests/drivers/build_all/flash/stm32h573i_dk.overlay new file mode 100644 index 000000000000..484ba576b2e8 --- /dev/null +++ b/tests/drivers/build_all/flash/stm32h573i_dk.overlay @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Endress+Hauser GmbH+Co. KG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&fmc { + nand-controller { + compatible = "st,stm32-fmc-nand"; + status = "okay"; + + #address-cells = <1>; + #size-cells = <0>; + + page-buffer-size = <1>; + + mt29f4g08@3 { + compatible = "micron,mt29f4g08"; + status = "okay"; + reg = <3>; + + page-size = <1>; + spare-area-size = <1>; + block-size = <2>; + plane-size = <4>; + flash-size = <8>; + }; + }; +}; diff --git a/tests/drivers/build_all/flash/testcase.yaml b/tests/drivers/build_all/flash/testcase.yaml index 3aa9a679439c..6e708924d3dd 100644 --- a/tests/drivers/build_all/flash/testcase.yaml +++ b/tests/drivers/build_all/flash/testcase.yaml @@ -15,3 +15,9 @@ tests: - native_sim extra_configs: - CONFIG_EMUL=y + drivers.flash.stm32_fmc_mt29f4g08.build: + platform_allow: + - stm32h573i_dk + extra_configs: + - CONFIG_FLASH_EX_OP_ENABLED=y + - CONFIG_FLASH_MT29F4G08_ECC=y