zephyr/drivers/flash/flash_mspi_emul_device.c

460 lines
14 KiB
C

/*
* Copyright (c) 2024 Ambiq Micro Inc. <www.ambiq.com>
* SPDX-License-Identifier: Apache-2.0
* Emulate a memory device on MSPI emulator bus
*/
#define DT_DRV_COMPAT zephyr_mspi_emul_flash
#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/mspi.h>
#include <zephyr/drivers/mspi_emul.h>
#include <zephyr/drivers/flash.h>
#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);