/* * Copyright (c) 2024 Ambiq Micro Inc. * SPDX-License-Identifier: Apache-2.0 * Emulate a memory device on MSPI emulator bus */ #define DT_DRV_COMPAT zephyr_mspi_emul_flash #include #include #include #include #include #include #include #include #include "spi_nor.h" LOG_MODULE_REGISTER(zephyr_mspi_emul_flash, CONFIG_FLASH_LOG_LEVEL); /* add else if for other SoC platforms */ #if defined(CONFIG_SOC_POSIX) typedef struct mspi_timing_cfg mspi_timing_cfg; typedef enum mspi_timing_param mspi_timing_param; #endif struct flash_mspi_emul_device_config { uint32_t size; struct flash_parameters flash_param; struct flash_pages_layout page_layout; struct mspi_dev_id dev_id; struct mspi_dev_cfg tar_dev_cfg; struct mspi_xip_cfg tar_xip_cfg; struct mspi_scramble_cfg tar_scramble_cfg; bool sw_multi_periph; }; struct flash_mspi_emul_device_data { const struct device *bus; 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 xfer; struct mspi_xfer_packet packet; struct k_sem lock; uint8_t *mem; }; /** * Acquire the device lock. * * @param flash MSPI emulation flash device. */ static void acquire(const struct device *flash) { const struct flash_mspi_emul_device_config *cfg = flash->config; struct flash_mspi_emul_device_data *data = flash->data; k_sem_take(&data->lock, K_FOREVER); if (cfg->sw_multi_periph) { while (mspi_dev_config(data->bus, &cfg->dev_id, MSPI_DEVICE_CONFIG_ALL, &data->dev_cfg)) { ; } } else { while (mspi_dev_config(data->bus, &cfg->dev_id, MSPI_DEVICE_CONFIG_NONE, NULL)) { ; } } } /** * Release the device lock. * * @param flash MSPI emulation flash device. */ static void release(const struct device *flash) { struct flash_mspi_emul_device_data *data = flash->data; while (mspi_get_channel_status(data->bus, 0)) { } k_sem_give(&data->lock); } /** * API implementation of emul_mspi_dev_api_transceive transceive. * * @param target Pointer to MSPI device emulator. * @param dev_id Pointer to the device ID structure from a device. * @param xfer Pointer to the MSPI transfer started by dev_id. * * @retval 0 if successful. * @retval -ESTALE device ID don't match, need to call mspi_dev_config first. * @retval -Error transfer failed. */ static int emul_mspi_device_transceive(const struct emul *target, const struct mspi_xfer_packet *packets, uint32_t num_packet, bool async, uint32_t timeout) { ARG_UNUSED(timeout); const struct flash_mspi_emul_device_config *cfg = target->dev->config; struct flash_mspi_emul_device_data *data = target->dev->data; struct emul_mspi_driver_api *api = (struct emul_mspi_driver_api *)data->bus->api; __ASSERT_NO_MSG(api); __ASSERT_NO_MSG(api->trigger_event); for (uint32_t count = 0; count < num_packet; ++count) { const struct mspi_xfer_packet *packet = &packets[count]; if (packet->address > cfg->size || packet->address + packet->num_bytes > cfg->size) { return -ENOMEM; } if (packet->dir == MSPI_RX) { memcpy(packet->data_buf, data->mem + packet->address, packet->num_bytes); } else if (packet->dir == MSPI_TX) { memcpy(data->mem + packet->address, packet->data_buf, packet->num_bytes); } if (async) { if (packet->cb_mask == MSPI_BUS_XFER_COMPLETE_CB) { api->trigger_event(data->bus, MSPI_BUS_XFER_COMPLETE); } } } return 0; } static int flash_mspi_emul_erase(const struct device *flash, off_t offset, size_t size) { const struct flash_mspi_emul_device_config *cfg = flash->config; struct flash_mspi_emul_device_data *data = flash->data; const size_t num_sectors = size / SPI_NOR_SECTOR_SIZE; const size_t num_blocks = size / SPI_NOR_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; } if ((offset == 0) && (size == cfg->size)) { memset(data->mem, cfg->flash_param.erase_value, size); } else if ((0 == (offset % SPI_NOR_BLOCK_SIZE)) && (0 == (size % SPI_NOR_BLOCK_SIZE))) { for (i = 0; i < num_blocks; i++) { memset(data->mem + offset, cfg->flash_param.erase_value, SPI_NOR_BLOCK_SIZE); offset += SPI_NOR_BLOCK_SIZE; } } else { for (i = 0; i < num_sectors; i++) { memset(data->mem + offset, cfg->flash_param.erase_value, SPI_NOR_SECTOR_SIZE); offset += SPI_NOR_SECTOR_SIZE; } } release(flash); return 0; } /** * API implementation of flash write. * * @param flash Pointer to MSPI flash device. * @param offset Flash device address. * @param wdata Pointer to the write data buffer. * @param len Number of bytes to write. * * @retval 0 if successful. * @retval -Error flash read fail. */ static int flash_mspi_emul_write(const struct device *flash, off_t offset, const void *wdata, size_t len) { const struct flash_mspi_emul_device_config *cfg = flash->config; struct flash_mspi_emul_device_data *data = flash->data; int ret; uint8_t *src = (uint8_t *)wdata; int i; acquire(flash); data->xfer.async = false; data->xfer.xfer_mode = MSPI_DMA; data->xfer.tx_dummy = data->dev_cfg.tx_dummy; data->xfer.cmd_length = data->dev_cfg.cmd_length; data->xfer.addr_length = data->dev_cfg.addr_length; data->xfer.hold_ce = false; data->xfer.priority = 1; data->xfer.packets = &data->packet; data->xfer.num_packet = 1; data->xfer.timeout = CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE; 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); data->packet.dir = MSPI_TX; data->packet.cmd = data->dev_cfg.write_cmd; data->packet.address = offset; data->packet.data_buf = src; data->packet.num_bytes = i; LOG_DBG("Write %d bytes to 0x%08zx", i, (ssize_t)offset); ret = mspi_transceive(data->bus, &cfg->dev_id, (const struct mspi_xfer *)&data->xfer); if (ret) { LOG_ERR("%u, MSPI write transaction failed with code: %d", __LINE__, ret); return -EIO; } /* emulate flash write busy wait */ k_busy_wait(100); src += i; offset += i; len -= i; } release(flash); return ret; } /** * API implementation of flash read. * * @param flash Pointer to MSPI flash device. * @param offset Flash device address. * @param rdata Pointer to the read data buffer. * @param len Number of bytes to read. * * @retval 0 if successful. * @retval -Error flash read fail. */ static int flash_mspi_emul_read(const struct device *flash, off_t offset, void *rdata, size_t len) { const struct flash_mspi_emul_device_config *cfg = flash->config; struct flash_mspi_emul_device_data *data = flash->data; int ret; acquire(flash); 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->xfer.async = false; data->xfer.xfer_mode = MSPI_DMA; data->xfer.rx_dummy = data->dev_cfg.rx_dummy; data->xfer.cmd_length = data->dev_cfg.cmd_length; data->xfer.addr_length = data->dev_cfg.addr_length; data->xfer.hold_ce = false; data->xfer.priority = 1; data->xfer.packets = &data->packet; data->xfer.num_packet = 1; data->xfer.timeout = CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE; LOG_DBG("Read %d bytes from 0x%08zx", len, (ssize_t)offset); ret = mspi_transceive(data->bus, &cfg->dev_id, (const struct mspi_xfer *)&data->xfer); if (ret) { LOG_ERR("%u, MSPI read transaction failed with code: %d", __LINE__, ret); return -EIO; } release(flash); return ret; } /** * API implementation of flash get_parameters. * * @param flash Pointer to MSPI flash device. * * @retval @ref flash_parameters. */ static const struct flash_parameters *flash_mspi_emul_get_parameters(const struct device *flash) { const struct flash_mspi_emul_device_config *cfg = flash->config; return &cfg->flash_param; } #if defined(CONFIG_FLASH_PAGE_LAYOUT) /** * API implementation of flash pages_layout. * * @param flash Pointer to MSPI flash device. * @param layout @ref flash_pages_layout. * @param layout_size */ static void flash_mspi_emul_pages_layout(const struct device *flash, const struct flash_pages_layout **layout, size_t *layout_size) { const struct flash_mspi_emul_device_config *cfg = flash->config; *layout = &cfg->page_layout; *layout_size = 1; } #endif /* CONFIG_FLASH_PAGE_LAYOUT */ static const struct flash_driver_api flash_mspi_emul_device_api = { .erase = flash_mspi_emul_erase, .write = flash_mspi_emul_write, .read = flash_mspi_emul_read, .get_parameters = flash_mspi_emul_get_parameters, #if defined(CONFIG_FLASH_PAGE_LAYOUT) .page_layout = flash_mspi_emul_pages_layout, #endif }; static const struct emul_mspi_device_api emul_mspi_dev_api = { .transceive = emul_mspi_device_transceive, }; /** * Set up a new MSPI device emulator * * @param emul The MSPI device emulator instance itself * @param bus The MSPI bus emulator instance * @return 0 If successful */ static int emul_mspi_device_init(const struct emul *emul_flash, const struct device *bus) { const struct flash_mspi_emul_device_config *cfg = emul_flash->dev->config; struct flash_mspi_emul_device_data *data = emul_flash->dev->data; data->bus = bus; if (mspi_dev_config(data->bus, &cfg->dev_id, MSPI_DEVICE_CONFIG_ALL, &cfg->tar_dev_cfg)) { LOG_ERR("%u, Failed to config mspi controller", __LINE__); return -EIO; } data->dev_cfg = cfg->tar_dev_cfg; #if CONFIG_MSPI_XIP if (cfg->tar_xip_cfg.enable) { if (mspi_xip_config(data->bus, &cfg->dev_id, &cfg->tar_xip_cfg)) { LOG_ERR("%u, Failed to enable XIP.", __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(data->bus, &cfg->dev_id, &cfg->tar_scramble_cfg)) { LOG_ERR("%u, Failed to enable scrambling.", __LINE__); return -EIO; } data->scramble_cfg = cfg->tar_scramble_cfg; } #endif #if CONFIG_MSPI_TIMING if (mspi_timing_config(data->bus, &cfg->dev_id, MSPI_TIMING_PARAM_DUMMY, &data->timing_cfg)) { LOG_ERR("%u, Failed to configure timing.", __LINE__); return -EIO; } #endif release(emul_flash->dev); return 0; } static int flash_mspi_emul_device_init_stub(const struct device *dev) { ARG_UNUSED(dev); return 0; } #define FLASH_MSPI_EMUL_DEVICE(n) \ static uint8_t flash_mspi_emul_device_mem##n[DT_INST_PROP(n, size) / 8]; \ static const struct flash_mspi_emul_device_config flash_mspi_emul_device_config_##n = { \ .size = DT_INST_PROP(n, size) / 8, \ .flash_param = \ { \ .write_block_size = 1, \ .erase_value = 0xff, \ }, \ .page_layout = \ { \ .pages_count = DT_INST_PROP(n, size) / 8 / SPI_NOR_PAGE_SIZE,\ .pages_size = SPI_NOR_PAGE_SIZE, \ }, \ .dev_id = MSPI_DEVICE_ID_DT_INST(n), \ .tar_dev_cfg = MSPI_DEVICE_CONFIG_DT_INST(n), \ .tar_xip_cfg = MSPI_XIP_CONFIG_DT_INST(n), \ .tar_scramble_cfg = MSPI_SCRAMBLE_CONFIG_DT_INST(n), \ .sw_multi_periph = DT_PROP(DT_INST_BUS(n), software_multiperipheral) \ }; \ static struct flash_mspi_emul_device_data flash_mspi_emul_device_data_##n = { \ .lock = Z_SEM_INITIALIZER(flash_mspi_emul_device_data_##n.lock, 0, 1), \ .mem = (uint8_t *)flash_mspi_emul_device_mem##n, \ }; \ DEVICE_DT_INST_DEFINE(n, \ flash_mspi_emul_device_init_stub, \ NULL, \ &flash_mspi_emul_device_data_##n, \ &flash_mspi_emul_device_config_##n, \ POST_KERNEL, \ CONFIG_FLASH_INIT_PRIORITY, \ &flash_mspi_emul_device_api); #define EMUL_TEST(n) \ EMUL_DT_INST_DEFINE(n, \ emul_mspi_device_init, \ NULL, \ NULL, \ &emul_mspi_dev_api, \ NULL); DT_INST_FOREACH_STATUS_OKAY(EMUL_TEST); DT_INST_FOREACH_STATUS_OKAY(FLASH_MSPI_EMUL_DEVICE);