/* * Copyright (c) 2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT jedec_mspi_nor #include #include #include #include #include #include #include "jesd216.h" #include "spi_nor.h" LOG_MODULE_REGISTER(flash_mspi_nor, CONFIG_FLASH_LOG_LEVEL); #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) #define WITH_RESET_GPIO 1 #endif struct flash_mspi_nor_data { struct k_sem acquired; struct mspi_xfer_packet packet; struct mspi_xfer xfer; }; struct flash_mspi_nor_config { const struct device *bus; uint32_t flash_size; struct mspi_dev_id mspi_id; struct mspi_dev_cfg mspi_cfg; enum mspi_dev_cfg_mask mspi_cfg_mask; #if defined(CONFIG_MSPI_XIP) struct mspi_xip_cfg xip_cfg; #endif #if defined(WITH_RESET_GPIO) struct gpio_dt_spec reset; uint32_t reset_pulse_us; uint32_t reset_recovery_us; #endif #if defined(CONFIG_FLASH_PAGE_LAYOUT) struct flash_pages_layout layout; #endif uint8_t jedec_id[SPI_NOR_MAX_ID_LEN]; }; static int acquire(const struct device *dev) { const struct flash_mspi_nor_config *dev_config = dev->config; struct flash_mspi_nor_data *dev_data = dev->data; int rc; k_sem_take(&dev_data->acquired, K_FOREVER); rc = pm_device_runtime_get(dev_config->bus); if (rc < 0) { LOG_ERR("pm_device_runtime_get() failed: %d", rc); } else { /* This acquires the MSPI controller and reconfigures it * if needed for the flash device. */ rc = mspi_dev_config(dev_config->bus, &dev_config->mspi_id, dev_config->mspi_cfg_mask, &dev_config->mspi_cfg); if (rc < 0) { LOG_ERR("mspi_dev_config() failed: %d", rc); } else { return 0; } (void)pm_device_runtime_put(dev_config->bus); } k_sem_give(&dev_data->acquired); return rc; } static void release(const struct device *dev) { const struct flash_mspi_nor_config *dev_config = dev->config; struct flash_mspi_nor_data *dev_data = dev->data; /* This releases the MSPI controller. */ (void)mspi_get_channel_status(dev_config->bus, 0); (void)pm_device_runtime_put(dev_config->bus); k_sem_give(&dev_data->acquired); } static inline uint32_t dev_flash_size(const struct device *dev) { const struct flash_mspi_nor_config *dev_config = dev->config; return dev_config->flash_size; } static inline uint16_t dev_page_size(const struct device *dev) { return SPI_NOR_PAGE_SIZE; } static int api_read(const struct device *dev, off_t addr, void *dest, size_t size) { const struct flash_mspi_nor_config *dev_config = dev->config; struct flash_mspi_nor_data *dev_data = dev->data; const uint32_t flash_size = dev_flash_size(dev); int rc; if (size == 0) { return 0; } if ((addr < 0) || ((addr + size) > flash_size)) { return -EINVAL; } rc = acquire(dev); if (rc < 0) { return rc; } /* TODO: get rid of all these hard-coded values for MX25Ux chips */ dev_data->xfer.cmd_length = 2; dev_data->xfer.addr_length = 4; dev_data->xfer.rx_dummy = 20; dev_data->packet.dir = MSPI_RX; dev_data->packet.cmd = SPI_NOR_OCMD_RD; dev_data->packet.address = addr; dev_data->packet.data_buf = dest; dev_data->packet.num_bytes = size; rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer); release(dev); if (rc < 0) { LOG_ERR("SPI_NOR_OCMD_RD xfer failed: %d", rc); return rc; } return 0; } static int wait_until_ready(const struct device *dev, k_timeout_t poll_period) { const struct flash_mspi_nor_config *dev_config = dev->config; struct flash_mspi_nor_data *dev_data = dev->data; uint8_t status_reg; int rc; while (true) { dev_data->xfer.cmd_length = 2; dev_data->xfer.addr_length = 4; dev_data->xfer.rx_dummy = 4; dev_data->packet.dir = MSPI_RX; dev_data->packet.cmd = SPI_NOR_OCMD_RDSR; dev_data->packet.address = 0; dev_data->packet.data_buf = &status_reg; dev_data->packet.num_bytes = sizeof(status_reg); rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer); if (rc < 0) { LOG_ERR("SPI_NOR_OCMD_RDSR xfer failed: %d", rc); return rc; } if (!(status_reg & SPI_NOR_WIP_BIT)) { break; } k_sleep(poll_period); } return 0; } static int api_write(const struct device *dev, off_t addr, const void *src, size_t size) { const struct flash_mspi_nor_config *dev_config = dev->config; struct flash_mspi_nor_data *dev_data = dev->data; const uint32_t flash_size = dev_flash_size(dev); const uint16_t page_size = dev_page_size(dev); int rc; if (size == 0) { return 0; } if ((addr < 0) || ((addr + size) > flash_size)) { return -EINVAL; } rc = acquire(dev); if (rc < 0) { return rc; } while (size > 0) { /* Split write into parts, each within one page only. */ uint16_t page_offset = (uint16_t)(addr % page_size); uint16_t page_left = page_size - page_offset; uint16_t to_write = (uint16_t)MIN(size, page_left); dev_data->xfer.cmd_length = 2; dev_data->xfer.tx_dummy = 0; dev_data->packet.dir = MSPI_TX; dev_data->xfer.addr_length = 0; dev_data->packet.cmd = SPI_NOR_OCMD_WREN; dev_data->packet.num_bytes = 0; rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer); if (rc < 0) { LOG_ERR("SPI_NOR_OCMD_WREN xfer failed: %d", rc); break; } dev_data->xfer.addr_length = 4; dev_data->packet.cmd = SPI_NOR_OCMD_PAGE_PRG; dev_data->packet.address = addr; dev_data->packet.data_buf = (uint8_t *)src; dev_data->packet.num_bytes = to_write; rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer); if (rc < 0) { LOG_ERR("SPI_NOR_OCMD_PAGE_PRG xfer failed: %d", rc); break; } addr += to_write; src = (const uint8_t *)src + to_write; size -= to_write; rc = wait_until_ready(dev, K_MSEC(1)); if (rc < 0) { break; } } release(dev); return rc; } static int api_erase(const struct device *dev, off_t addr, size_t size) { const struct flash_mspi_nor_config *dev_config = dev->config; struct flash_mspi_nor_data *dev_data = dev->data; const uint32_t flash_size = dev_flash_size(dev); int rc = 0; if ((addr < 0) || ((addr + size) > flash_size)) { return -EINVAL; } if (!SPI_NOR_IS_SECTOR_ALIGNED(addr)) { return -EINVAL; } if ((size % SPI_NOR_SECTOR_SIZE) != 0) { return -EINVAL; } rc = acquire(dev); if (rc < 0) { return rc; } while (size > 0) { dev_data->xfer.cmd_length = 2; dev_data->xfer.tx_dummy = 0; dev_data->packet.dir = MSPI_TX; dev_data->packet.num_bytes = 0; dev_data->xfer.addr_length = 0; dev_data->packet.cmd = SPI_NOR_OCMD_WREN; rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer); if (rc < 0) { LOG_ERR("SPI_NOR_OCMD_WREN xfer failed: %d", rc); break; } if (size == flash_size) { /* Chip erase. */ dev_data->xfer.addr_length = 0; dev_data->packet.cmd = SPI_NOR_OCMD_CE; size -= flash_size; } else { /* Sector erase. */ dev_data->xfer.addr_length = 4; dev_data->packet.cmd = SPI_NOR_OCMD_SE; dev_data->packet.address = addr; addr += SPI_NOR_SECTOR_SIZE; size -= SPI_NOR_SECTOR_SIZE; } rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer); if (rc < 0) { LOG_ERR("Erase command 0x%02x xfer failed: %d", dev_data->packet.cmd, rc); break; } rc = wait_until_ready(dev, K_MSEC(1)); if (rc < 0) { break; } } release(dev); return rc; } static const struct flash_parameters *api_get_parameters(const struct device *dev) { ARG_UNUSED(dev); static const struct flash_parameters parameters = { .write_block_size = 1, .erase_value = 0xff, }; return ¶meters; } static int read_jedec_id(const struct device *dev, uint8_t *id) { const struct flash_mspi_nor_config *dev_config = dev->config; struct flash_mspi_nor_data *dev_data = dev->data; int rc; dev_data->xfer.cmd_length = 2; dev_data->xfer.addr_length = 4; dev_data->xfer.rx_dummy = 4; dev_data->packet.dir = MSPI_RX; dev_data->packet.cmd = JESD216_OCMD_READ_ID; dev_data->packet.address = 0; dev_data->packet.data_buf = id; dev_data->packet.num_bytes = JESD216_READ_ID_LEN; rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer); if (rc < 0) { printk("mspi_transceive() failed: %d\n", rc); return rc; } return rc; } #if defined(CONFIG_FLASH_PAGE_LAYOUT) static void api_page_layout(const struct device *dev, const struct flash_pages_layout **layout, size_t *layout_size) { const struct flash_mspi_nor_config *dev_config = dev->config; *layout = &dev_config->layout; *layout_size = 1; } #endif /* CONFIG_FLASH_PAGE_LAYOUT */ #if defined(CONFIG_FLASH_JESD216_API) static int api_sfdp_read(const struct device *dev, off_t addr, void *dest, size_t size) { const struct flash_mspi_nor_config *dev_config = dev->config; struct flash_mspi_nor_data *dev_data = dev->data; int rc; if (size == 0) { return 0; } rc = acquire(dev); if (rc < 0) { return rc; } dev_data->xfer.cmd_length = 2; dev_data->xfer.addr_length = 4; dev_data->xfer.rx_dummy = 20; dev_data->packet.dir = MSPI_RX; dev_data->packet.cmd = JESD216_OCMD_READ_SFDP; dev_data->packet.address = addr; dev_data->packet.data_buf = dest; dev_data->packet.num_bytes = size; rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer); if (rc < 0) { printk("JESD216_OCMD_READ_SFDP xfer failed: %d\n", rc); return rc; } release(dev); return rc; } static int api_read_jedec_id(const struct device *dev, uint8_t *id) { int rc = 0; rc = acquire(dev); if (rc < 0) { return rc; } rc = read_jedec_id(dev, id); release(dev); return rc; } #endif /* CONFIG_FLASH_JESD216_API */ static int dev_pm_action_cb(const struct device *dev, enum pm_device_action action) { switch (action) { case PM_DEVICE_ACTION_SUSPEND: break; case PM_DEVICE_ACTION_RESUME: break; default: return -ENOTSUP; } return 0; } static int flash_chip_init(const struct device *dev) { const struct flash_mspi_nor_config *dev_config = dev->config; struct flash_mspi_nor_data *dev_data = dev->data; struct mspi_dev_cfg init_dev_cfg = dev_config->mspi_cfg; uint8_t id[JESD216_READ_ID_LEN] = {0}; int rc; init_dev_cfg.freq = MHZ(1); init_dev_cfg.io_mode = MSPI_IO_MODE_SINGLE; rc = mspi_dev_config(dev_config->bus, &dev_config->mspi_id, MSPI_DEVICE_CONFIG_ALL, &init_dev_cfg); if (rc < 0) { LOG_ERR("Failed to set initial device config: %d", rc); return rc; } dev_data->xfer.xfer_mode = MSPI_PIO; dev_data->xfer.packets = &dev_data->packet; dev_data->xfer.num_packet = 1; dev_data->xfer.timeout = 10; dev_data->xfer.cmd_length = 1; dev_data->xfer.addr_length = 0; dev_data->xfer.tx_dummy = 0; dev_data->xfer.rx_dummy = 0; dev_data->packet.dir = MSPI_RX; dev_data->packet.cmd = JESD216_CMD_READ_ID; dev_data->packet.data_buf = id; dev_data->packet.num_bytes = sizeof(id); rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer); if (rc < 0) { LOG_ERR("Failed to read JEDEC ID in single line mode: %d", rc); return rc; } /* * If the read ID does not match the one from DTS, assume the flash * is already in the Octa I/O mode, so switching it is not needed. */ if (memcmp(id, dev_config->jedec_id, sizeof(id)) == 0) { static const uint8_t enable_sopi[] = { 0x01 }; dev_data->packet.dir = MSPI_TX; dev_data->packet.cmd = SPI_NOR_CMD_WREN; dev_data->packet.num_bytes = 0; rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer); if (rc < 0) { LOG_ERR("SPI_NOR_CMD_WREN xfer failed: %d", rc); return rc; } dev_data->xfer.addr_length = 4; dev_data->packet.cmd = SPI_NOR_CMD_WR_CFGREG2; dev_data->packet.address = 0; dev_data->packet.data_buf = (uint8_t *)&enable_sopi; dev_data->packet.num_bytes = sizeof(enable_sopi); rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer); if (rc < 0) { printk("SPI_NOR_CMD_WR_CFGREG2 xfer failed: %d\n", rc); return rc; } } rc = mspi_dev_config(dev_config->bus, &dev_config->mspi_id, MSPI_DEVICE_CONFIG_ALL, &dev_config->mspi_cfg); if (rc < 0) { LOG_ERR("Failed to set device config: %d", rc); return rc; } rc = read_jedec_id(dev, id); if (rc < 0) { return rc; } if (memcmp(id, dev_config->jedec_id, sizeof(id)) != 0) { LOG_ERR("JEDEC ID mismatch, read: %02x %02x %02x, " "expected: %02x %02x %02x", id[0], id[1], id[2], dev_config->jedec_id[0], dev_config->jedec_id[1], dev_config->jedec_id[2]); return -ENODEV; } #if defined(CONFIG_MSPI_XIP) /* Enable XIP access for this chip if specified so in DT. */ if (dev_config->xip_cfg.enable) { rc = mspi_xip_config(dev_config->bus, &dev_config->mspi_id, &dev_config->xip_cfg); if (rc < 0) { return rc; } } #endif return 0; } static int drv_init(const struct device *dev) { const struct flash_mspi_nor_config *dev_config = dev->config; struct flash_mspi_nor_data *dev_data = dev->data; int rc; if (!device_is_ready(dev_config->bus)) { LOG_ERR("Device %s is not ready", dev_config->bus->name); return -ENODEV; } #if defined(WITH_RESET_GPIO) if (dev_config->reset.port) { if (!gpio_is_ready_dt(&dev_config->reset)) { LOG_ERR("Device %s is not ready", dev_config->reset.port->name); return -ENODEV; } rc = gpio_pin_configure_dt(&dev_config->reset, GPIO_OUTPUT_ACTIVE); if (rc < 0) { LOG_ERR("Failed to activate RESET: %d", rc); return -EIO; } if (dev_config->reset_pulse_us != 0) { k_busy_wait(dev_config->reset_pulse_us); } rc = gpio_pin_set_dt(&dev_config->reset, 0); if (rc < 0) { LOG_ERR("Failed to deactivate RESET: %d", rc); return -EIO; } if (dev_config->reset_recovery_us != 0) { k_busy_wait(dev_config->reset_recovery_us); } } #endif rc = pm_device_runtime_get(dev_config->bus); if (rc < 0) { LOG_ERR("pm_device_runtime_get() failed: %d", rc); return rc; } rc = flash_chip_init(dev); /* Release the MSPI controller - it was acquired by the call to * mspi_dev_config() in flash_chip_init(). */ (void)mspi_get_channel_status(dev_config->bus, 0); (void)pm_device_runtime_put(dev_config->bus); if (rc < 0) { return rc; } k_sem_init(&dev_data->acquired, 1, K_SEM_MAX_LIMIT); return pm_device_driver_init(dev, dev_pm_action_cb); } static DEVICE_API(flash, drv_api) = { .read = api_read, .write = api_write, .erase = api_erase, .get_parameters = api_get_parameters, #if defined(CONFIG_FLASH_PAGE_LAYOUT) .page_layout = api_page_layout, #endif #if defined(CONFIG_FLASH_JESD216_API) .sfdp_read = api_sfdp_read, .read_jedec_id = api_read_jedec_id, #endif }; #define FLASH_SIZE_INST(inst) (DT_INST_PROP(inst, size) / 8) #if defined(CONFIG_FLASH_PAGE_LAYOUT) BUILD_ASSERT((CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE % 4096) == 0, "MSPI_NOR_FLASH_LAYOUT_PAGE_SIZE must be multiple of 4096"); #define FLASH_PAGE_LAYOUT_DEFINE(inst) \ .layout = { \ .pages_size = CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE, \ .pages_count = FLASH_SIZE_INST(inst) \ / CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE, \ }, #define FLASH_PAGE_LAYOUT_CHECK(inst) \ BUILD_ASSERT((FLASH_SIZE_INST(inst) % CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE) == 0, \ "MSPI_NOR_FLASH_LAYOUT_PAGE_SIZE incompatible with flash size, instance " #inst); #else #define FLASH_PAGE_LAYOUT_DEFINE(inst) #define FLASH_PAGE_LAYOUT_CHECK(inst) #endif /* MSPI bus must be initialized before this device. */ #if (CONFIG_MSPI_INIT_PRIORITY < CONFIG_FLASH_INIT_PRIORITY) #define INIT_PRIORITY CONFIG_FLASH_INIT_PRIORITY #else #define INIT_PRIORITY UTIL_INC(CONFIG_MSPI_INIT_PRIORITY) #endif #define FLASH_MSPI_NOR_INST(inst) \ BUILD_ASSERT(DT_INST_ENUM_IDX(inst, mspi_io_mode) == \ MSPI_IO_MODE_OCTAL, \ "Only Octal I/O mode is supported for now"); \ PM_DEVICE_DT_INST_DEFINE(inst, dev_pm_action_cb); \ static struct flash_mspi_nor_data dev##inst##_data; \ static const struct flash_mspi_nor_config dev##inst##_config = { \ .bus = DEVICE_DT_GET(DT_INST_BUS(inst)), \ .flash_size = FLASH_SIZE_INST(inst), \ .mspi_id = MSPI_DEVICE_ID_DT_INST(inst), \ .mspi_cfg = MSPI_DEVICE_CONFIG_DT_INST(inst), \ .mspi_cfg_mask = DT_PROP(DT_INST_BUS(inst), \ software_multiperipheral) \ ? MSPI_DEVICE_CONFIG_ALL \ : MSPI_DEVICE_CONFIG_NONE, \ IF_ENABLED(CONFIG_MSPI_XIP, \ (.xip_cfg = MSPI_XIP_CONFIG_DT_INST(inst),)) \ IF_ENABLED(WITH_RESET_GPIO, \ (.reset = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {0}), \ .reset_pulse_us = DT_INST_PROP_OR(inst, t_reset_pulse, 0) \ / 1000, \ .reset_recovery_us = DT_INST_PROP_OR(inst, t_reset_recovery, 0) \ / 1000,)) \ FLASH_PAGE_LAYOUT_DEFINE(inst) \ .jedec_id = DT_INST_PROP(inst, jedec_id), \ }; \ FLASH_PAGE_LAYOUT_CHECK(inst) \ DEVICE_DT_INST_DEFINE(inst, \ drv_init, PM_DEVICE_DT_INST_GET(inst), \ &dev##inst##_data, &dev##inst##_config, \ POST_KERNEL, INIT_PRIORITY, \ &drv_api); DT_INST_FOREACH_STATUS_OKAY(FLASH_MSPI_NOR_INST)