| /* |
| * Copyright (c) 2025, Ambiq Micro Inc. <www.ambiq.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT mspi_is25xx0xx |
| /* |
| * This driver supports the non-standard 1s-1/8s-8s operation |
| * as well as basic 1s-1s-1s operation. |
| */ |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/mspi.h> |
| #include <zephyr/cache.h> |
| #include <zephyr/drivers/flash.h> |
| #include "spi_nor.h" |
| |
| #if CONFIG_SOC_FAMILY_AMBIQ |
| |
| #include "mspi_ambiq.h" |
| typedef struct mspi_ambiq_timing_cfg mspi_timing_cfg; |
| typedef enum mspi_ambiq_timing_param mspi_timing_param; |
| |
| #else |
| |
| typedef struct mspi_timing_cfg mspi_timing_cfg; |
| typedef enum mspi_timing_param mspi_timing_param; |
| |
| #endif |
| |
| LOG_MODULE_REGISTER(flash_mspi_is25xX0xx, CONFIG_FLASH_LOG_LEVEL); |
| |
| #define NOR_WRITE_SIZE 1 |
| #define NOR_ERASE_VALUE 0xff |
| |
| #define IS25XX0XX_VENDOR_ID 0x9D |
| |
| #define IS25XX0XX_DC_DEFAULT 0x1F |
| |
| #define IS25XX0XX_32KSECTOR_SIZE 0x8000 |
| #define IS25XX0XX_BLOCK_SIZE 0x20000 |
| |
| #define IS25XX0XX_WRITE_VOL_REG_CMD 0x81 |
| |
| enum is25xX0xx_io_mode { |
| IS25XX0XX_IO_MODE_EXTENDED_SPI = 0xFF, |
| IS25XX0XX_IO_MODE_EXTENDED_SPI_NONDQS = 0xDF, |
| }; |
| |
| struct flash_mspi_is25xX0xx_config { |
| uint8_t port; |
| uint32_t mem_size; |
| struct flash_parameters flash_param; |
| struct flash_pages_layout page_layout; |
| |
| const struct device *bus; |
| struct mspi_dev_id dev_id; |
| struct mspi_dev_cfg serial_cfg; |
| struct mspi_dev_cfg tar_dev_cfg; |
| |
| MSPI_XIP_CFG_STRUCT_DECLARE(tar_xip_cfg) |
| MSPI_XIP_BASE_ADDR_DECLARE(xip_base_addr) |
| MSPI_SCRAMBLE_CFG_STRUCT_DECLARE(tar_scramble_cfg) |
| MSPI_TIMING_CFG_STRUCT_DECLARE(tar_timing_cfg) |
| MSPI_TIMING_PARAM_DECLARE(timing_cfg_mask) |
| |
| bool sw_multi_periph; |
| |
| struct gpio_dt_spec reset_gpio; |
| uint32_t reset_pulse_us; |
| uint32_t reset_recovery_us; |
| }; |
| |
| struct flash_mspi_is25xX0xx_data { |
| struct mspi_dev_cfg dev_cfg; |
| struct mspi_xip_cfg xip_cfg; |
| struct mspi_scramble_cfg scramble_cfg; |
| mspi_timing_cfg timing_cfg; |
| struct mspi_xfer trans; |
| struct mspi_xfer_packet packet; |
| |
| struct k_sem lock; |
| uint8_t id[20]; |
| }; |
| |
| static int is25xX0xx_get_dummy_clk(uint8_t rxdummy, uint8_t *dummy_clk) |
| { |
| if (rxdummy == 0 || rxdummy > 30) { |
| return 1; |
| } |
| *dummy_clk = rxdummy; |
| return 0; |
| } |
| |
| static int flash_mspi_is25xX0xx_enter_command_mode(const struct device *flash) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| int ret; |
| |
| if (cfg->serial_cfg.io_mode == data->dev_cfg.io_mode) { |
| return 0; |
| } |
| |
| ret = mspi_dev_config(cfg->bus, &cfg->dev_id, |
| MSPI_DEVICE_CONFIG_ALL, &cfg->serial_cfg); |
| if (ret) { |
| LOG_ERR("Failed to enter command mode/%u", __LINE__); |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| static int flash_mspi_is25xX0xx_exit_command_mode(const struct device *flash) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| int ret; |
| |
| if (cfg->serial_cfg.io_mode == data->dev_cfg.io_mode) { |
| return 0; |
| } |
| |
| ret = mspi_dev_config(cfg->bus, &cfg->dev_id, |
| MSPI_DEVICE_CONFIG_ALL, &data->dev_cfg); |
| if (ret) { |
| LOG_ERR("Failed to exit command mode/%u", __LINE__); |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| static int flash_mspi_is25xX0xx_command_write(const struct device *flash, uint8_t cmd, |
| uint32_t addr, uint16_t addr_len, uint32_t tx_dummy, |
| uint8_t *wdata, uint32_t length) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| int ret; |
| |
| data->packet.dir = MSPI_TX; |
| data->packet.cmd = cmd; |
| data->packet.address = addr; |
| data->packet.data_buf = wdata; |
| data->packet.num_bytes = length; |
| |
| data->trans.async = false; |
| data->trans.xfer_mode = MSPI_PIO; |
| data->trans.tx_dummy = tx_dummy; |
| data->trans.rx_dummy = data->dev_cfg.rx_dummy; |
| data->trans.cmd_length = 1; |
| data->trans.addr_length = addr_len; |
| data->trans.hold_ce = false; |
| data->trans.packets = &data->packet; |
| data->trans.num_packet = 1; |
| data->trans.timeout = 10; |
| |
| ret = mspi_transceive(cfg->bus, &cfg->dev_id, (const struct mspi_xfer *)&data->trans); |
| if (ret) { |
| LOG_ERR("MSPI write transaction failed with code: %d/%u", ret, __LINE__); |
| return -EIO; |
| } |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_command_read(const struct device *flash, uint8_t cmd, |
| uint32_t addr, uint16_t addr_len, uint32_t rx_dummy, |
| uint8_t *rdata, uint32_t length) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| int ret; |
| |
| data->packet.dir = MSPI_RX; |
| data->packet.cmd = cmd; |
| data->packet.address = addr; |
| data->packet.data_buf = rdata; |
| data->packet.num_bytes = length; |
| |
| data->trans.async = false; |
| data->trans.xfer_mode = MSPI_PIO; |
| data->trans.rx_dummy = rx_dummy; |
| data->trans.tx_dummy = data->dev_cfg.tx_dummy; |
| data->trans.cmd_length = 1; |
| data->trans.addr_length = addr_len; |
| data->trans.hold_ce = false; |
| data->trans.packets = &data->packet; |
| data->trans.num_packet = 1; |
| data->trans.timeout = 10; |
| |
| ret = mspi_transceive(cfg->bus, &cfg->dev_id, (const struct mspi_xfer *)&data->trans); |
| if (ret) { |
| LOG_ERR("MSPI read transaction failed with code: %d/%u", ret, __LINE__); |
| return -EIO; |
| } |
| return ret; |
| } |
| |
| static void acquire(const struct device *flash) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| |
| k_sem_take(&data->lock, K_FOREVER); |
| |
| if (cfg->sw_multi_periph) { |
| while (mspi_dev_config(cfg->bus, &cfg->dev_id, |
| MSPI_DEVICE_CONFIG_ALL, &data->dev_cfg)) { |
| ; |
| } |
| } else { |
| while (mspi_dev_config(cfg->bus, &cfg->dev_id, |
| MSPI_DEVICE_CONFIG_NONE, NULL)) { |
| ; |
| } |
| } |
| } |
| |
| static void release(const struct device *flash) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| |
| while (mspi_get_channel_status(cfg->bus, cfg->port)) { |
| ; |
| } |
| |
| k_sem_give(&data->lock); |
| } |
| |
| static int flash_mspi_is25xX0xx_write_enable(const struct device *flash) |
| { |
| int ret; |
| |
| LOG_DBG("Enabling write"); |
| ret = flash_mspi_is25xX0xx_command_write(flash, SPI_NOR_CMD_WREN, 0, 0, 0, NULL, 0); |
| |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_write_disable(const struct device *flash) |
| { |
| int ret; |
| |
| LOG_DBG("Disabling write"); |
| ret = flash_mspi_is25xX0xx_command_write(flash, SPI_NOR_CMD_WRDI, 0, 0, 0, NULL, 0); |
| |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_is_ready(const struct device *flash) |
| { |
| uint32_t flag_stat = 0; |
| uint32_t rx_dummy = 0; |
| uint32_t timeout = 400; /* max tSSE */ |
| int ret; |
| |
| do { |
| LOG_DBG("Reading flag status register"); |
| ret = flash_mspi_is25xX0xx_command_read(flash, 0x70, 0, 0, rx_dummy, |
| (uint8_t *)&flag_stat, 1); |
| if (ret) { |
| LOG_ERR("Could not read flag status"); |
| return ret; |
| } |
| |
| LOG_DBG("flag status: 0x%x", flag_stat); |
| if (flag_stat & BIT(7)) { |
| LOG_DBG("Device is ready"); |
| break; |
| } |
| |
| k_sleep(K_MSEC(1)); |
| timeout--; |
| } while (timeout); |
| |
| if (timeout == 0) { |
| LOG_ERR("Operation timed out"); |
| return -ETIMEDOUT; |
| } |
| |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_reset(const struct device *flash) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| int ret; |
| |
| LOG_DBG("RESETTING"); |
| |
| if (cfg->reset_gpio.port) { |
| if (!gpio_is_ready_dt(&cfg->reset_gpio)) { |
| LOG_ERR("Device %s is not ready", |
| cfg->reset_gpio.port->name); |
| return -ENODEV; |
| } |
| |
| ret = gpio_pin_configure_dt(&cfg->reset_gpio, |
| GPIO_OUTPUT_ACTIVE); |
| if (ret < 0) { |
| LOG_ERR("Failed to activate RESET: %d", ret); |
| return -EIO; |
| } |
| |
| if (cfg->reset_pulse_us != 0) { |
| k_busy_wait(cfg->reset_pulse_us); |
| } |
| |
| ret = gpio_pin_set_dt(&cfg->reset_gpio, 0); |
| if (ret < 0) { |
| LOG_ERR("Failed to deactivate RESET: %d", ret); |
| return -EIO; |
| } |
| |
| if (cfg->reset_recovery_us != 0) { |
| k_busy_wait(cfg->reset_recovery_us); |
| } |
| } else { |
| ret = flash_mspi_is25xX0xx_command_write(flash, SPI_NOR_CMD_RESET_EN, |
| 0, 0, 0, NULL, 0); |
| if (ret) { |
| return ret; |
| } |
| ret = flash_mspi_is25xX0xx_command_write(flash, SPI_NOR_CMD_RESET_MEM, |
| 0, 0, 0, NULL, 0); |
| if (ret) { |
| return ret; |
| } |
| } |
| |
| ret = flash_mspi_is25xX0xx_is_ready(flash); |
| |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_get_vendor_id(const struct device *flash, uint8_t *vendor_id) |
| { |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| int ret; |
| |
| if (vendor_id == NULL) { |
| return -EINVAL; |
| } |
| |
| LOG_DBG("Reading id"); |
| /* serial mode */ |
| ret = flash_mspi_is25xX0xx_command_read(flash, SPI_NOR_CMD_RDID, 0, 0, 0, |
| (uint8_t *)data->id, 20); |
| *vendor_id = data->id[0]; |
| |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_erase_sector(const struct device *flash, off_t addr) |
| { |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| int ret; |
| |
| LOG_DBG("Erasing sector at 0x%08zx", (ssize_t)addr); |
| |
| ret = flash_mspi_is25xX0xx_command_write(flash, SPI_NOR_CMD_SE, addr, |
| data->dev_cfg.addr_length, 0, NULL, 0); |
| |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_erase_32k_sector(const struct device *flash, off_t addr) |
| { |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| int ret; |
| |
| LOG_DBG("Erasing sector at 0x%08zx", (ssize_t)addr); |
| |
| ret = flash_mspi_is25xX0xx_command_write(flash, SPI_NOR_CMD_BE_32K, addr, |
| data->dev_cfg.addr_length, 0, NULL, 0); |
| |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_erase_block(const struct device *flash, off_t addr) |
| { |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| int ret; |
| |
| LOG_DBG("Erasing block at 0x%08zx", (ssize_t)addr); |
| |
| ret = flash_mspi_is25xX0xx_command_write(flash, SPI_NOR_CMD_BE, addr, |
| data->dev_cfg.addr_length, 0, NULL, 0); |
| |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_erase_chip(const struct device *flash) |
| { |
| int ret; |
| |
| LOG_DBG("Erasing chip"); |
| |
| ret = flash_mspi_is25xX0xx_command_write(flash, SPI_NOR_CMD_CE, 0, 0, 0, NULL, 0); |
| |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_page_program(const struct device *flash, off_t offset, |
| void *wdata, size_t len) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| int ret; |
| |
| data->packet.dir = MSPI_TX; |
| data->packet.cmd = data->dev_cfg.write_cmd; |
| data->packet.address = offset; |
| data->packet.data_buf = wdata; |
| data->packet.num_bytes = len; |
| |
| data->trans.async = false; |
| data->trans.xfer_mode = MSPI_DMA; |
| data->trans.tx_dummy = data->dev_cfg.tx_dummy; |
| data->trans.rx_dummy = data->dev_cfg.rx_dummy; |
| data->trans.cmd_length = data->dev_cfg.cmd_length; |
| data->trans.addr_length = data->dev_cfg.addr_length; |
| data->trans.hold_ce = false; |
| data->trans.priority = MSPI_XFER_PRIORITY_MEDIUM; |
| data->trans.packets = &data->packet; |
| data->trans.num_packet = 1; |
| data->trans.timeout = CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE; |
| |
| LOG_DBG("Page programming %d bytes to 0x%08zx", len, (ssize_t)offset); |
| |
| ret = mspi_transceive(cfg->bus, &cfg->dev_id, (const struct mspi_xfer *)&data->trans); |
| if (ret) { |
| LOG_ERR("MSPI write transaction failed with code: %d/%u", ret, __LINE__); |
| return -EIO; |
| } |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_busy_wait(const struct device *flash, unsigned int timeout) |
| { |
| uint32_t status = 0; |
| uint32_t flag_stat = 0; |
| uint32_t rx_dummy = 0; |
| int ret; |
| |
| do { |
| LOG_DBG("Reading status register"); |
| ret = flash_mspi_is25xX0xx_command_read(flash, SPI_NOR_CMD_RDSR, 0, 0, rx_dummy, |
| (uint8_t *)&status, 1); |
| if (ret) { |
| LOG_ERR("Could not read status"); |
| return ret; |
| } |
| ret = flash_mspi_is25xX0xx_command_read(flash, 0x70, 0, 0, rx_dummy, |
| (uint8_t *)&flag_stat, 1); |
| if (ret) { |
| LOG_ERR("Could not read flag status"); |
| return ret; |
| } |
| LOG_DBG("status: 0x%x, flag status: 0x%x", status, flag_stat); |
| |
| if (flag_stat & BIT(1)) { |
| LOG_ERR("Access denied"); |
| return -EACCES; |
| } else if (flag_stat & BIT(4)) { |
| LOG_ERR("Program operation failed"); |
| return -EIO; |
| } else if (flag_stat & BIT(5)) { |
| LOG_ERR("Erase operation failed"); |
| return -EIO; |
| } |
| |
| k_sleep(K_MSEC(1)); |
| timeout--; |
| } while ((status & SPI_NOR_WIP_BIT) && timeout); |
| |
| if (timeout == 0) { |
| LOG_ERR("Operation timed out"); |
| return -ETIMEDOUT; |
| } |
| |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_read(const struct device *flash, off_t offset, void *rdata, |
| size_t len) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| int ret; |
| |
| acquire(flash); |
| |
| #if CONFIG_FLASH_MSPI_XIP_READ |
| if (cfg->tar_xip_cfg.enable) { |
| uint32_t xip_addr = cfg->xip_base_addr + cfg->tar_xip_cfg.address_offset + offset; |
| |
| memcpy(rdata, (void *)xip_addr, len); |
| } else { |
| #endif /* CONFIG_FLASH_MSPI_XIP_READ */ |
| |
| #if CONFIG_FLASH_MSPI_HANDLE_CACHE |
| if (!buf_in_nocache((uintptr_t)rdata, len)) { |
| if (len > CONFIG_FLASH_MSPI_RANGE_HANDLE_CACHE_SIZE) { |
| sys_cache_data_flush_all(); |
| } else { |
| sys_cache_data_flush_range(rdata, len); |
| } |
| } |
| #endif /* CONFIG_FLASH_MSPI_HANDLE_CACHE */ |
| |
| data->packet.dir = MSPI_RX; |
| data->packet.cmd = data->dev_cfg.read_cmd; |
| data->packet.address = offset; |
| data->packet.data_buf = rdata; |
| data->packet.num_bytes = len; |
| |
| data->trans.async = false; |
| data->trans.xfer_mode = MSPI_DMA; |
| data->trans.tx_dummy = data->dev_cfg.tx_dummy; |
| data->trans.rx_dummy = data->dev_cfg.rx_dummy; |
| data->trans.cmd_length = data->dev_cfg.cmd_length; |
| data->trans.addr_length = data->dev_cfg.addr_length; |
| data->trans.hold_ce = false; |
| data->trans.priority = MSPI_XFER_PRIORITY_MEDIUM; |
| data->trans.packets = &data->packet; |
| data->trans.num_packet = 1; |
| data->trans.timeout = CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE; |
| |
| LOG_DBG("Read %d bytes from 0x%08zx", len, (ssize_t)offset); |
| |
| ret = mspi_transceive(cfg->bus, &cfg->dev_id, |
| (const struct mspi_xfer *)&data->trans); |
| if (ret) { |
| LOG_ERR("MSPI read transaction failed with code: %d/%u", ret, __LINE__); |
| return -EIO; |
| } |
| |
| #if CONFIG_FLASH_MSPI_HANDLE_CACHE |
| if (!buf_in_nocache((uintptr_t)rdata, len)) { |
| if (len > CONFIG_FLASH_MSPI_RANGE_HANDLE_CACHE_SIZE) { |
| sys_cache_data_flush_and_invd_all(); |
| } else { |
| sys_cache_data_invd_range(rdata, len); |
| } |
| } |
| #endif /* CONFIG_FLASH_MSPI_HANDLE_CACHE */ |
| #if CONFIG_FLASH_MSPI_XIP_READ |
| } |
| #endif /* CONFIG_FLASH_MSPI_XIP_READ */ |
| |
| release(flash); |
| |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_write(const struct device *flash, off_t offset, const void *wdata, |
| size_t len) |
| { |
| int ret; |
| uint8_t *src = (uint8_t *)wdata; |
| int i; |
| #if CONFIG_FLASH_MSPI_HANDLE_CACHE && CONFIG_FLASH_MSPI_XIP_READ |
| off_t addr = offset; |
| size_t size = len; |
| #endif |
| |
| acquire(flash); |
| |
| #if CONFIG_FLASH_MSPI_HANDLE_CACHE |
| if (!buf_in_nocache((uintptr_t)src, len)) { |
| if (len > CONFIG_FLASH_MSPI_RANGE_HANDLE_CACHE_SIZE) { |
| sys_cache_data_flush_all(); |
| } else { |
| sys_cache_data_flush_range(src, len); |
| } |
| } |
| #endif |
| |
| while (len) { |
| /* If the offset isn't a multiple of the NOR page size, we first need |
| * to write the remaining part that fits, otherwise the write could |
| * be wrapped around within the same page |
| */ |
| i = MIN(SPI_NOR_PAGE_SIZE - (offset % SPI_NOR_PAGE_SIZE), len); |
| |
| ret = flash_mspi_is25xX0xx_enter_command_mode(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_write_enable(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_exit_command_mode(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_page_program(flash, offset, src, i); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_enter_command_mode(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_busy_wait(flash, 3); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_exit_command_mode(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| src += i; |
| offset += i; |
| len -= i; |
| } |
| |
| ret = flash_mspi_is25xX0xx_write_disable(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| #if CONFIG_FLASH_MSPI_HANDLE_CACHE && CONFIG_FLASH_MSPI_XIP_READ |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| |
| if (cfg->tar_xip_cfg.enable) { |
| uint32_t xip_addr = cfg->xip_base_addr + cfg->tar_xip_cfg.address_offset + addr; |
| |
| if (!buf_in_nocache((uintptr_t)xip_addr, size)) { |
| if (size > CONFIG_FLASH_MSPI_RANGE_HANDLE_CACHE_SIZE) { |
| sys_cache_data_flush_and_invd_all(); |
| } else { |
| sys_cache_data_invd_range((void *)xip_addr, size); |
| } |
| } |
| } |
| #endif |
| |
| release(flash); |
| |
| return ret; |
| } |
| |
| static int flash_mspi_is25xX0xx_erase(const struct device *flash, off_t offset, size_t size) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| int ret = 0; |
| const size_t num_sectors = size / SPI_NOR_SECTOR_SIZE; |
| const size_t num_32k_sectors = size / IS25XX0XX_32KSECTOR_SIZE; |
| const size_t num_blocks = size / IS25XX0XX_BLOCK_SIZE; |
| int i; |
| |
| acquire(flash); |
| |
| if (offset % SPI_NOR_SECTOR_SIZE) { |
| LOG_ERR("Invalid offset"); |
| return -EINVAL; |
| } |
| |
| if (size % SPI_NOR_SECTOR_SIZE) { |
| LOG_ERR("Invalid size"); |
| return -EINVAL; |
| } |
| |
| ret = flash_mspi_is25xX0xx_enter_command_mode(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| if ((offset == 0) && (size == cfg->mem_size)) { |
| ret = flash_mspi_is25xX0xx_write_enable(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_erase_chip(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_busy_wait(flash, 45000); |
| if (ret) { |
| return ret; |
| } |
| } else if ((0 == (offset % IS25XX0XX_BLOCK_SIZE)) && |
| (0 == (size % IS25XX0XX_BLOCK_SIZE))) { |
| for (i = 0; i < num_blocks; i++) { |
| ret = flash_mspi_is25xX0xx_write_enable(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_erase_block(flash, offset); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_busy_wait(flash, 1000); |
| if (ret) { |
| return ret; |
| } |
| |
| offset += IS25XX0XX_BLOCK_SIZE; |
| } |
| } else if ((0 == (offset % IS25XX0XX_32KSECTOR_SIZE)) && |
| (0 == (size % IS25XX0XX_32KSECTOR_SIZE))) { |
| for (i = 0; i < num_32k_sectors; i++) { |
| ret = flash_mspi_is25xX0xx_write_enable(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_erase_32k_sector(flash, offset); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_busy_wait(flash, 1000); |
| if (ret) { |
| return ret; |
| } |
| |
| offset += IS25XX0XX_32KSECTOR_SIZE; |
| } |
| } else { |
| for (i = 0; i < num_sectors; i++) { |
| ret = flash_mspi_is25xX0xx_write_enable(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_erase_sector(flash, offset); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = flash_mspi_is25xX0xx_busy_wait(flash, 400); |
| if (ret) { |
| return ret; |
| } |
| |
| offset += SPI_NOR_SECTOR_SIZE; |
| } |
| } |
| |
| ret = flash_mspi_is25xX0xx_exit_command_mode(flash); |
| if (ret) { |
| return ret; |
| } |
| |
| release(flash); |
| |
| return ret; |
| } |
| |
| static const struct flash_parameters * |
| flash_mspi_is25xX0xx_get_parameters(const struct device *flash) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| |
| return &cfg->flash_param; |
| } |
| |
| #if defined(CONFIG_FLASH_PAGE_LAYOUT) |
| static void flash_mspi_is25xX0xx_pages_layout(const struct device *flash, |
| const struct flash_pages_layout **layout, |
| size_t *layout_size) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| |
| *layout = &cfg->page_layout; |
| *layout_size = 1; |
| } |
| #endif /* CONFIG_FLASH_PAGE_LAYOUT */ |
| |
| static int flash_mspi_is25xX0xx_init(const struct device *flash) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| uint8_t vendor_id; |
| uint8_t reg_dummy; |
| uint8_t reg_io_mode; |
| |
| if (!device_is_ready(cfg->bus)) { |
| LOG_ERR("Controller device is not ready."); |
| return -ENODEV; |
| } |
| |
| switch (cfg->tar_dev_cfg.io_mode) { |
| case MSPI_IO_MODE_SINGLE: |
| case MSPI_IO_MODE_OCTAL_1_1_8: |
| case MSPI_IO_MODE_OCTAL_1_8_8: |
| reg_io_mode = IS25XX0XX_IO_MODE_EXTENDED_SPI; |
| break; |
| default: |
| LOG_ERR("bus mode %d not supported/%u", cfg->tar_dev_cfg.io_mode, __LINE__); |
| return -EIO; |
| } |
| |
| if (mspi_dev_config(cfg->bus, &cfg->dev_id, MSPI_DEVICE_CONFIG_ALL, &cfg->serial_cfg)) { |
| LOG_ERR("Failed to config mspi controller/%u", __LINE__); |
| return -EIO; |
| } |
| data->dev_cfg = cfg->serial_cfg; |
| |
| if (flash_mspi_is25xX0xx_reset(flash)) { |
| LOG_ERR("Could not reset Flash/%u", __LINE__); |
| return -EIO; |
| } |
| |
| if (flash_mspi_is25xX0xx_get_vendor_id(flash, &vendor_id)) { |
| LOG_ERR("Could not read vendor id/%u", __LINE__); |
| return -EIO; |
| } |
| LOG_DBG("Vendor id: 0x%0x", vendor_id); |
| if (vendor_id != IS25XX0XX_VENDOR_ID) { |
| LOG_WRN("Vendor ID does not match expected value of 0x%0x/%u", |
| IS25XX0XX_VENDOR_ID, __LINE__); |
| } |
| |
| if (is25xX0xx_get_dummy_clk(cfg->tar_dev_cfg.rx_dummy, ®_dummy)) { |
| return -ENOTSUP; |
| } |
| |
| if (flash_mspi_is25xX0xx_write_enable(flash)) { |
| return -EIO; |
| } |
| |
| if (flash_mspi_is25xX0xx_command_write(flash, IS25XX0XX_WRITE_VOL_REG_CMD, |
| 0x1, 1, 0, ®_dummy, 1)) { |
| return -EIO; |
| } |
| |
| if (!cfg->tar_dev_cfg.dqs_enable) { |
| reg_io_mode = IS25XX0XX_IO_MODE_EXTENDED_SPI_NONDQS; |
| if (flash_mspi_is25xX0xx_write_enable(flash)) { |
| return -EIO; |
| } |
| if (flash_mspi_is25xX0xx_command_write(flash, IS25XX0XX_WRITE_VOL_REG_CMD, |
| 0x0, 1, 0, ®_io_mode, 1)) { |
| return -EIO; |
| } |
| } |
| |
| if (cfg->tar_dev_cfg.addr_length == 4) { |
| LOG_DBG("Enter 4 byte address mode"); |
| if (flash_mspi_is25xX0xx_write_enable(flash)) { |
| return -EIO; |
| } |
| if (flash_mspi_is25xX0xx_command_write(flash, SPI_NOR_CMD_4BA, |
| 0, 0, 0, NULL, 0)) { |
| return -EIO; |
| } |
| } |
| |
| if (mspi_dev_config(cfg->bus, &cfg->dev_id, |
| MSPI_DEVICE_CONFIG_ALL, &cfg->tar_dev_cfg)) { |
| LOG_ERR("Failed to config mspi controller/%u", __LINE__); |
| return -EIO; |
| } |
| data->dev_cfg = cfg->tar_dev_cfg; |
| |
| #if CONFIG_MSPI_TIMING |
| if (mspi_timing_config(cfg->bus, &cfg->dev_id, cfg->timing_cfg_mask, |
| (void *)&cfg->tar_timing_cfg)) { |
| LOG_ERR("Failed to config mspi timing/%u", __LINE__); |
| return -EIO; |
| } |
| data->timing_cfg = cfg->tar_timing_cfg; |
| #endif |
| |
| #if CONFIG_MSPI_XIP |
| if (cfg->tar_xip_cfg.enable) { |
| if (mspi_xip_config(cfg->bus, &cfg->dev_id, &cfg->tar_xip_cfg)) { |
| LOG_ERR("Failed to enable XIP/%u", __LINE__); |
| return -EIO; |
| } |
| data->xip_cfg = cfg->tar_xip_cfg; |
| } |
| #endif |
| |
| #if CONFIG_MSPI_SCRAMBLE |
| if (cfg->tar_scramble_cfg.enable) { |
| if (mspi_scramble_config(cfg->bus, &cfg->dev_id, &cfg->tar_scramble_cfg)) { |
| LOG_ERR("Failed to enable scrambling/%u", __LINE__); |
| return -EIO; |
| } |
| data->scramble_cfg = cfg->tar_scramble_cfg; |
| } |
| #endif |
| |
| release(flash); |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_FLASH_JESD216_API) |
| static int flash_mspi_is25xX0xx_read_sfdp(const struct device *flash, off_t addr, void *rdata, |
| size_t size) |
| { |
| const struct flash_mspi_is25xX0xx_config *cfg = flash->config; |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| int ret; |
| |
| acquire(flash); |
| |
| data->packet.dir = MSPI_RX; |
| data->packet.cmd = 0x5A; |
| data->packet.address = addr; |
| data->packet.data_buf = rdata; |
| data->packet.num_bytes = size; |
| |
| data->trans.async = false; |
| data->trans.xfer_mode = MSPI_DMA; |
| data->trans.rx_dummy = 8; |
| data->trans.cmd_length = 1; |
| data->trans.addr_length = 3; |
| data->trans.hold_ce = false; |
| data->trans.priority = MSPI_XFER_PRIORITY_MEDIUM; |
| data->trans.packets = &data->packet; |
| data->trans.num_packet = 1; |
| data->trans.timeout = CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE; |
| |
| LOG_DBG("Read %d bytes from 0x%08zx", size, (ssize_t)addr); |
| |
| ret = mspi_transceive(cfg->bus, &cfg->dev_id, (const struct mspi_xfer *)&data->trans); |
| |
| if (ret) { |
| LOG_ERR("MSPI read transaction failed with code: %d/%u", ret, __LINE__); |
| return -EIO; |
| } |
| |
| release(flash); |
| return 0; |
| } |
| static int flash_mspi_is25xX0xx_read_jedec_id(const struct device *flash, uint8_t *id) |
| { |
| struct flash_mspi_is25xX0xx_data *data = flash->data; |
| |
| id = &data->id; |
| return 0; |
| } |
| #endif /* CONFIG_FLASH_JESD216_API */ |
| |
| static DEVICE_API(flash, flash_mspi_is25xX0xx_api) = { |
| .erase = flash_mspi_is25xX0xx_erase, |
| .write = flash_mspi_is25xX0xx_write, |
| .read = flash_mspi_is25xX0xx_read, |
| .get_parameters = flash_mspi_is25xX0xx_get_parameters, |
| #if defined(CONFIG_FLASH_PAGE_LAYOUT) |
| .page_layout = flash_mspi_is25xX0xx_pages_layout, |
| #endif |
| #if defined(CONFIG_FLASH_JESD216_API) |
| .sfdp_read = flash_mspi_is25xX0xx_read_sfdp, |
| .read_jedec_id = flash_mspi_is25xX0xx_read_jedec_id, |
| #endif |
| }; |
| |
| #define MSPI_DEVICE_CONFIG_SERIAL(n) \ |
| { \ |
| .ce_num = DT_INST_PROP(n, mspi_hardware_ce_num), \ |
| .freq = 12000000, \ |
| .io_mode = MSPI_IO_MODE_SINGLE, \ |
| .data_rate = MSPI_DATA_RATE_SINGLE, \ |
| .cpp = MSPI_CPP_MODE_0, \ |
| .endian = MSPI_XFER_LITTLE_ENDIAN, \ |
| .ce_polarity = MSPI_CE_ACTIVE_LOW, \ |
| .dqs_enable = false, \ |
| .rx_dummy = 8, \ |
| .tx_dummy = 0, \ |
| .read_cmd = SPI_NOR_CMD_READ_FAST, \ |
| .write_cmd = SPI_NOR_CMD_PP, \ |
| .cmd_length = 1, \ |
| .addr_length = 3, \ |
| .mem_boundary = 0, \ |
| .time_to_break = 0, \ |
| } |
| |
| #define MSPI_TIMING_CONFIG(n) \ |
| COND_CODE_1(CONFIG_SOC_FAMILY_AMBIQ, \ |
| (MSPI_AMBIQ_TIMING_CONFIG(n)), ({})) |
| |
| #define MSPI_TIMING_CONFIG_MASK(n) \ |
| COND_CODE_1(CONFIG_SOC_FAMILY_AMBIQ, \ |
| (MSPI_AMBIQ_TIMING_CONFIG_MASK(n)), (MSPI_TIMING_PARAM_DUMMY)) |
| |
| #define MSPI_PORT(n) \ |
| COND_CODE_1(CONFIG_SOC_FAMILY_AMBIQ, \ |
| (MSPI_AMBIQ_PORT(n)), (0)) |
| |
| #define FLASH_MSPI_IS25XX0XX(n) \ |
| static const struct flash_mspi_is25xX0xx_config flash_mspi_is25xX0xx_config_##n = { \ |
| .mem_size = DT_INST_PROP(n, size) / 8, \ |
| .port = MSPI_PORT(n), \ |
| .flash_param = \ |
| { \ |
| .write_block_size = NOR_WRITE_SIZE, \ |
| .erase_value = NOR_ERASE_VALUE, \ |
| }, \ |
| .page_layout = \ |
| { \ |
| .pages_count = DT_INST_PROP(n, size) / 8 / SPI_NOR_PAGE_SIZE, \ |
| .pages_size = SPI_NOR_PAGE_SIZE, \ |
| }, \ |
| .bus = DEVICE_DT_GET(DT_INST_BUS(n)), \ |
| .dev_id = MSPI_DEVICE_ID_DT_INST(n), \ |
| .serial_cfg = MSPI_DEVICE_CONFIG_SERIAL(n), \ |
| .tar_dev_cfg = MSPI_DEVICE_CONFIG_DT_INST(n), \ |
| MSPI_OPTIONAL_CFG_STRUCT_INIT(CONFIG_MSPI_XIP, \ |
| tar_xip_cfg, MSPI_XIP_CONFIG_DT_INST(n)) \ |
| MSPI_XIP_BASE_ADDR_INIT(xip_base_addr, DT_INST_BUS(n)) \ |
| MSPI_OPTIONAL_CFG_STRUCT_INIT(CONFIG_MSPI_SCRAMBLE, \ |
| tar_scramble_cfg, MSPI_SCRAMBLE_CONFIG_DT_INST(n)) \ |
| MSPI_OPTIONAL_CFG_STRUCT_INIT(CONFIG_MSPI_TIMING, \ |
| tar_timing_cfg, MSPI_TIMING_CONFIG(n)) \ |
| MSPI_OPTIONAL_CFG_STRUCT_INIT(CONFIG_MSPI_TIMING, \ |
| timing_cfg_mask, MSPI_TIMING_CONFIG_MASK(n)) \ |
| .sw_multi_periph = DT_PROP(DT_INST_BUS(n), software_multiperipheral), \ |
| .reset_gpio = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpios, {0}), \ |
| .reset_pulse_us = DT_INST_PROP_OR(n, t_reset_pulse, 0), \ |
| .reset_recovery_us = DT_INST_PROP_OR(n, t_reset_recovery, 0), \ |
| }; \ |
| static struct flash_mspi_is25xX0xx_data flash_mspi_is25xX0xx_data_##n = { \ |
| .lock = Z_SEM_INITIALIZER(flash_mspi_is25xX0xx_data_##n.lock, 0, 1), \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(n, \ |
| flash_mspi_is25xX0xx_init, \ |
| NULL, \ |
| &flash_mspi_is25xX0xx_data_##n, \ |
| &flash_mspi_is25xX0xx_config_##n, \ |
| POST_KERNEL, \ |
| CONFIG_FLASH_INIT_PRIORITY, \ |
| &flash_mspi_is25xX0xx_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(FLASH_MSPI_IS25XX0XX) |