drivers: flash: mspi_nor: support MODE_SINGLE and MODE_QUAD_1_4_4

Extend driver to support single lane and 1-4-4 IO modes.
Move flash chip quirks to a separate file.

Signed-off-by: Marcin Szymczyk <marcin.szymczyk@nordicsemi.no>
This commit is contained in:
Marcin Szymczyk 2025-02-07 15:01:18 +01:00 committed by Benjamin Cabé
commit b6642bb996
3 changed files with 798 additions and 187 deletions

View file

@ -6,47 +6,56 @@
#define DT_DRV_COMPAT jedec_mspi_nor
#include <zephyr/drivers/flash.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/mspi.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include "jesd216.h"
#include "spi_nor.h"
#include "flash_mspi_nor.h"
#include "flash_mspi_nor_quirks.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
void flash_mspi_command_set(const struct device *dev, const struct flash_mspi_nor_cmd *cmd)
{
struct flash_mspi_nor_data *dev_data = dev->data;
const struct flash_mspi_nor_config *dev_config = dev->config;
struct flash_mspi_nor_data {
struct k_sem acquired;
struct mspi_xfer_packet packet;
struct mspi_xfer xfer;
};
memset(&dev_data->xfer, 0, sizeof(dev_data->xfer));
memset(&dev_data->packet, 0, sizeof(dev_data->packet));
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];
};
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 = cmd->cmd_length;
dev_data->xfer.addr_length = cmd->addr_length;
dev_data->xfer.tx_dummy = (cmd->dir == MSPI_TX) ?
cmd->tx_dummy : dev_config->mspi_nor_cfg.tx_dummy;
dev_data->xfer.rx_dummy = (cmd->dir == MSPI_RX) ?
cmd->rx_dummy : dev_config->mspi_nor_cfg.rx_dummy;
dev_data->packet.dir = cmd->dir;
dev_data->packet.cmd = cmd->cmd;
}
static int dev_cfg_apply(const struct device *dev, const struct mspi_dev_cfg *cfg)
{
const struct flash_mspi_nor_config *dev_config = dev->config;
struct flash_mspi_nor_data *dev_data = dev->data;
if (dev_data->curr_cfg == cfg) {
return 0;
}
int rc = mspi_dev_config(dev_config->bus, &dev_config->mspi_id,
MSPI_DEVICE_CONFIG_ALL, cfg);
if (rc < 0) {
LOG_ERR("Failed to set device config: %p error: %d", cfg, rc);
}
return rc;
}
static int acquire(const struct device *dev)
{
@ -64,8 +73,8 @@ static int acquire(const struct device *dev)
* 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);
dev_config->mspi_nor_cfg_mask,
&dev_config->mspi_nor_cfg);
if (rc < 0) {
LOG_ERR("mspi_dev_config() failed: %d", rc);
} else {
@ -125,12 +134,18 @@ static int api_read(const struct device *dev, off_t addr, void *dest,
return rc;
}
if (dev_config->jedec_cmds->read.force_single) {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
} else {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_cfg);
}
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;
flash_mspi_command_set(dev, &dev_config->jedec_cmds->read);
dev_data->packet.address = addr;
dev_data->packet.data_buf = dest;
dev_data->packet.num_bytes = size;
@ -140,35 +155,58 @@ static int api_read(const struct device *dev, off_t addr, void *dest,
release(dev);
if (rc < 0) {
LOG_ERR("SPI_NOR_OCMD_RD xfer failed: %d", rc);
LOG_ERR("Read xfer failed: %d", rc);
return rc;
}
return 0;
}
static int wait_until_ready(const struct device *dev, k_timeout_t poll_period)
static int status_get(const struct device *dev, uint8_t *status)
{
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;
/* Enter command mode */
if (dev_config->jedec_cmds->status.force_single) {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
} else {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_cfg);
}
if (rc < 0) {
LOG_ERR("Switching to dev_cfg failed: %d", rc);
return rc;
}
flash_mspi_command_set(dev, &dev_config->jedec_cmds->status);
dev_data->packet.data_buf = status;
dev_data->packet.num_bytes = sizeof(uint8_t);
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
if (rc < 0) {
LOG_ERR("Status xfer failed: %d", rc);
return rc;
}
return rc;
}
static int wait_until_ready(const struct device *dev, k_timeout_t poll_period)
{
int rc;
uint8_t status_reg;
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);
rc = status_get(dev, &status_reg);
if (rc < 0) {
LOG_ERR("SPI_NOR_OCMD_RDSR xfer failed: %d", rc);
LOG_ERR("Wait until ready - status xfer failed: %d", rc);
return rc;
}
if (!(status_reg & SPI_NOR_WIP_BIT)) {
break;
}
@ -179,6 +217,26 @@ static int wait_until_ready(const struct device *dev, k_timeout_t poll_period)
return 0;
}
static int write_enable(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 (dev_config->jedec_cmds->write_en.force_single) {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
} else {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_cfg);
}
if (rc < 0) {
return rc;
}
flash_mspi_command_set(dev, &dev_config->jedec_cmds->write_en);
return mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
}
static int api_write(const struct device *dev, off_t addr, const void *src,
size_t size)
{
@ -207,29 +265,29 @@ static int api_write(const struct device *dev, off_t addr, const void *src,
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);
if (write_enable(dev) < 0) {
LOG_ERR("Write enable xfer failed: %d", rc);
break;
}
dev_data->xfer.addr_length = 4;
dev_data->packet.cmd = SPI_NOR_OCMD_PAGE_PRG;
if (dev_config->jedec_cmds->page_program.force_single) {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
} else {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_cfg);
}
if (rc < 0) {
return rc;
}
flash_mspi_command_set(dev, &dev_config->jedec_cmds->page_program);
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);
LOG_ERR("Page program xfer failed: %d", rc);
break;
}
@ -273,32 +331,40 @@ static int api_erase(const struct device *dev, off_t addr, size_t size)
}
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);
rc = write_enable(dev);
if (rc < 0) {
LOG_ERR("SPI_NOR_OCMD_WREN xfer failed: %d", rc);
LOG_ERR("Write enable failed.");
break;
}
if (size == flash_size) {
/* Chip erase. */
dev_data->xfer.addr_length = 0;
dev_data->packet.cmd = SPI_NOR_OCMD_CE;
if (dev_config->jedec_cmds->chip_erase.force_single) {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
} else {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_cfg);
}
if (rc < 0) {
return rc;
}
flash_mspi_command_set(dev, &dev_config->jedec_cmds->chip_erase);
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;
if (dev_config->jedec_cmds->sector_erase.force_single) {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
} else {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_cfg);
}
if (rc < 0) {
return rc;
}
flash_mspi_command_set(dev, &dev_config->jedec_cmds->sector_erase);
dev_data->packet.address = addr;
addr += SPI_NOR_SECTOR_SIZE;
size -= SPI_NOR_SECTOR_SIZE;
}
@ -340,19 +406,24 @@ static int read_jedec_id(const struct device *dev, uint8_t *id)
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;
if (dev_config->jedec_cmds->id.force_single) {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
} else {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_cfg);
}
if (rc < 0) {
return rc;
}
flash_mspi_command_set(dev, &dev_config->jedec_cmds->id);
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;
LOG_ERR("Read JEDEC ID failed: %d\n", rc);
}
return rc;
@ -387,18 +458,24 @@ static int api_sfdp_read(const struct device *dev, off_t addr, void *dest,
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;
if (dev_config->jedec_cmds->sfdp.force_single) {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
} else {
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_cfg);
}
if (rc < 0) {
return rc;
}
flash_mspi_command_set(dev, &dev_config->jedec_cmds->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);
printk("Read SFDP xfer failed: %d\n", rc);
return rc;
}
@ -409,9 +486,7 @@ static int api_sfdp_read(const struct device *dev, off_t addr, void *dest,
static int api_read_jedec_id(const struct device *dev, uint8_t *id)
{
int rc = 0;
rc = acquire(dev);
int rc = acquire(dev);
if (rc < 0) {
return rc;
}
@ -439,85 +514,191 @@ static int dev_pm_action_cb(const struct device *dev,
return 0;
}
static int quad_enable_set(const struct device *dev, bool enable)
{
const struct flash_mspi_nor_config *dev_config = dev->config;
struct flash_mspi_nor_data *dev_data = dev->data;
int rc;
flash_mspi_command_set(dev, &commands[MSPI_IO_MODE_SINGLE].write_en);
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
&dev_data->xfer);
if (rc < 0) {
LOG_ERR("Failed to set write enable: %d", rc);
return rc;
}
if (dev_config->dw15_qer == JESD216_DW15_QER_VAL_S1B6) {
const struct flash_mspi_nor_cmd cmd_status = {
.dir = MSPI_TX,
.cmd = SPI_NOR_CMD_WRSR,
.cmd_length = 1,
};
uint8_t mode_payload = enable ? BIT(6) : 0;
flash_mspi_command_set(dev, &cmd_status);
dev_data->packet.data_buf = &mode_payload;
dev_data->packet.num_bytes = sizeof(mode_payload);
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
if (rc < 0) {
LOG_ERR("Failed to enable/disable quad mode: %d", rc);
return rc;
}
} else {
/* TODO: handle all DW15 QER values */
return -ENOTSUP;
}
rc = wait_until_ready(dev, K_USEC(1));
if (rc < 0) {
LOG_ERR("Failed waiting until device ready after enabling quad: %d", rc);
return rc;
}
return 0;
}
static int default_io_mode(const struct device *dev)
{
const struct flash_mspi_nor_config *dev_config = dev->config;
enum mspi_io_mode io_mode = dev_config->mspi_nor_cfg.io_mode;
int rc = 0;
/* For Quad 1-1-4 and 1-4-4, entering or leaving mode is defined in JEDEC216 BFP DW15 QER */
if (io_mode == MSPI_IO_MODE_SINGLE) {
rc = quad_enable_set(dev, false);
} else if ((io_mode == MSPI_IO_MODE_QUAD_1_1_4) || (io_mode == MSPI_IO_MODE_QUAD_1_4_4)) {
rc = quad_enable_set(dev, true);
}
if (rc < 0) {
LOG_ERR("Failed to modify Quad Enable bit: %d", rc);
}
if ((dev_config->quirks != NULL) && (dev_config->quirks->post_switch_mode != NULL)) {
rc = dev_config->quirks->post_switch_mode(dev);
}
if (rc < 0) {
LOG_ERR("Failed to change IO mode: %d\n", rc);
return rc;
}
return dev_cfg_apply(dev, &dev_config->mspi_nor_cfg);
}
#if defined(WITH_RESET_GPIO)
static int gpio_reset(const struct device *dev)
{
const struct flash_mspi_nor_config *dev_config = dev->config;
int rc;
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);
}
}
return 0;
}
#endif
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;
enum mspi_io_mode io_mode = dev_config->mspi_nor_cfg.io_mode;
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 = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
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;
/* Some chips reuse RESET pin for data in Quad modes:
* force single line mode before resetting.
*/
if ((io_mode == MSPI_IO_MODE_SINGLE) || (io_mode == MSPI_IO_MODE_QUAD_1_1_4) ||
(io_mode == MSPI_IO_MODE_QUAD_1_4_4)) {
rc = quad_enable_set(dev, false);
dev_data->xfer.cmd_length = 1;
dev_data->xfer.addr_length = 0;
dev_data->xfer.tx_dummy = 0;
dev_data->xfer.rx_dummy = 0;
if (rc < 0) {
LOG_ERR("Failed to switch to single line mode: %d", rc);
return rc;
}
dev_data->packet.dir = MSPI_RX;
dev_data->packet.cmd = JESD216_CMD_READ_ID;
rc = wait_until_ready(dev, K_USEC(1));
if (rc < 0) {
LOG_ERR("Failed waiting for device after switch to single line: %d", rc);
return rc;
}
}
#if defined(WITH_RESET_GPIO)
rc = gpio_reset(dev);
if (rc < 0) {
LOG_ERR("Failed to reset with GPIO: %d", rc);
return rc;
}
#endif
flash_mspi_command_set(dev, &commands[MSPI_IO_MODE_SINGLE].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);
LOG_ERR("Failed to read JEDEC ID in initial 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.
rc = default_io_mode(dev);
if (rc < 0) {
LOG_ERR("Failed to switch to default io mode: %d", rc);
return rc;
}
/* Reading JEDEC ID for mode that forces single lane would be redundant,
* since it switches back to single lane mode. Use ID from previous read.
*/
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 (!dev_config->jedec_cmds->id.force_single) {
rc = read_jedec_id(dev, id);
if (rc < 0) {
LOG_ERR("SPI_NOR_CMD_WREN xfer failed: %d", rc);
LOG_ERR("Failed to read JEDEC ID in final line mode: %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) {
@ -555,37 +736,6 @@ static int drv_init(const struct device *dev)
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);
@ -624,7 +774,27 @@ static DEVICE_API(flash, drv_api) = {
#endif
};
#define FLASH_INITIAL_CONFIG(inst) \
{ \
.ce_num = DT_INST_PROP_OR(inst, mspi_hardware_ce_num, 0), \
.freq = MIN(DT_INST_PROP(inst, mspi_max_frequency), MHZ(50)), \
.io_mode = MSPI_IO_MODE_SINGLE, \
.data_rate = MSPI_DATA_RATE_SINGLE, \
.cpp = MSPI_CPP_MODE_0, \
.endian = MSPI_XFER_BIG_ENDIAN, \
.ce_polarity = MSPI_CE_ACTIVE_LOW, \
.dqs_enable = false, \
}
#define FLASH_SIZE_INST(inst) (DT_INST_PROP(inst, size) / 8)
#define FLASH_CMDS(inst) &commands[DT_INST_ENUM_IDX(inst, mspi_io_mode)]
#define FLASH_QUIRKS(inst) FLASH_MSPI_QUIRKS_GET(DT_DRV_INST(inst))
#define FLASH_DW15_QER_VAL(inst) _CONCAT(JESD216_DW15_QER_VAL_, \
DT_INST_STRING_TOKEN(inst, quad_enable_requirements))
#define FLASH_DW15_QER(inst) COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, quad_enable_requirements), \
(FLASH_DW15_QER_VAL(inst)), (JESD216_DW15_QER_VAL_NONE))
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
BUILD_ASSERT((CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE % 4096) == 0,
@ -651,17 +821,22 @@ BUILD_ASSERT((FLASH_SIZE_INST(inst) % CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE) ==
#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"); \
BUILD_ASSERT((DT_INST_ENUM_IDX(inst, mspi_io_mode) == \
MSPI_IO_MODE_SINGLE) || \
(DT_INST_ENUM_IDX(inst, mspi_io_mode) == \
MSPI_IO_MODE_QUAD_1_4_4) || \
(DT_INST_ENUM_IDX(inst, mspi_io_mode) == \
MSPI_IO_MODE_OCTAL), \
"Only 1x, 1-4-4 and 8x I/O modes are 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), \
.mspi_nor_cfg = MSPI_DEVICE_CONFIG_DT_INST(inst), \
.mspi_nor_init_cfg = FLASH_INITIAL_CONFIG(inst), \
.mspi_nor_cfg_mask = DT_PROP(DT_INST_BUS(inst), \
software_multiperipheral) \
? MSPI_DEVICE_CONFIG_ALL \
: MSPI_DEVICE_CONFIG_NONE, \
@ -675,6 +850,9 @@ BUILD_ASSERT((FLASH_SIZE_INST(inst) % CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE) ==
/ 1000,)) \
FLASH_PAGE_LAYOUT_DEFINE(inst) \
.jedec_id = DT_INST_PROP(inst, jedec_id), \
.jedec_cmds = FLASH_CMDS(inst), \
.quirks = FLASH_QUIRKS(inst), \
.dw15_qer = FLASH_DW15_QER(inst), \
}; \
FLASH_PAGE_LAYOUT_CHECK(inst) \
DEVICE_DT_INST_DEFINE(inst, \

View file

@ -0,0 +1,248 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __FLASH_MSPI_NOR_H__
#define __FLASH_MSPI_NOR_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <zephyr/drivers/flash.h>
#include <zephyr/drivers/mspi.h>
#include "jesd216.h"
#include "spi_nor.h"
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios)
#define WITH_RESET_GPIO 1
#endif
struct flash_mspi_nor_config {
const struct device *bus;
uint32_t flash_size;
struct mspi_dev_id mspi_id;
struct mspi_dev_cfg mspi_nor_cfg;
struct mspi_dev_cfg mspi_nor_init_cfg;
enum mspi_dev_cfg_mask mspi_nor_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];
struct flash_mspi_nor_cmds *jedec_cmds;
struct flash_mspi_nor_quirks *quirks;
uint8_t dw15_qer;
};
struct flash_mspi_nor_data {
struct k_sem acquired;
struct mspi_xfer_packet packet;
struct mspi_xfer xfer;
struct mspi_dev_cfg *curr_cfg;
};
struct flash_mspi_nor_cmd {
enum mspi_xfer_direction dir;
uint32_t cmd;
uint16_t tx_dummy;
uint16_t rx_dummy;
uint8_t cmd_length;
uint8_t addr_length;
bool force_single;
};
struct flash_mspi_nor_cmds {
struct flash_mspi_nor_cmd id;
struct flash_mspi_nor_cmd write_en;
struct flash_mspi_nor_cmd read;
struct flash_mspi_nor_cmd status;
struct flash_mspi_nor_cmd config;
struct flash_mspi_nor_cmd page_program;
struct flash_mspi_nor_cmd sector_erase;
struct flash_mspi_nor_cmd chip_erase;
struct flash_mspi_nor_cmd sfdp;
};
struct flash_mspi_nor_cmds commands[] = {
[MSPI_IO_MODE_SINGLE] = {
.id = {
.dir = MSPI_RX,
.cmd = JESD216_CMD_READ_ID,
.cmd_length = 1,
},
.write_en = {
.dir = MSPI_TX,
.cmd = SPI_NOR_CMD_WREN,
.cmd_length = 1,
},
.read = {
.dir = MSPI_RX,
.cmd = SPI_NOR_CMD_READ_FAST,
.cmd_length = 1,
.addr_length = 3,
.rx_dummy = 8,
},
.status = {
.dir = MSPI_RX,
.cmd = SPI_NOR_CMD_RDSR,
.cmd_length = 1,
},
.config = {
.dir = MSPI_RX,
.cmd = SPI_NOR_CMD_RDCR,
.cmd_length = 1,
},
.page_program = {
.dir = MSPI_TX,
.cmd = SPI_NOR_CMD_PP,
.cmd_length = 1,
.addr_length = 3,
},
.sector_erase = {
.dir = MSPI_TX,
.cmd = SPI_NOR_CMD_SE,
.cmd_length = 1,
.addr_length = 3,
},
.chip_erase = {
.dir = MSPI_TX,
.cmd = SPI_NOR_CMD_CE,
.cmd_length = 1,
},
.sfdp = {
.dir = MSPI_RX,
.cmd = JESD216_CMD_READ_SFDP,
.cmd_length = 1,
.addr_length = 3,
.rx_dummy = 8,
},
},
[MSPI_IO_MODE_QUAD_1_4_4] = {
.id = {
.dir = MSPI_RX,
.cmd = JESD216_CMD_READ_ID,
.cmd_length = 1,
.force_single = true,
},
.write_en = {
.dir = MSPI_TX,
.cmd = SPI_NOR_CMD_WREN,
.cmd_length = 1,
},
.read = {
.dir = MSPI_RX,
.cmd = SPI_NOR_CMD_4READ,
.cmd_length = 1,
.addr_length = 3,
.rx_dummy = 6,
},
.status = {
.dir = MSPI_RX,
.cmd = SPI_NOR_CMD_RDSR,
.cmd_length = 1,
.force_single = true,
},
.config = {
.dir = MSPI_RX,
.cmd = SPI_NOR_CMD_RDCR,
.cmd_length = 1,
.force_single = true,
},
.page_program = {
.dir = MSPI_TX,
.cmd = SPI_NOR_CMD_PP_1_4_4,
.cmd_length = 1,
.addr_length = 3,
},
.sector_erase = {
.dir = MSPI_TX,
.cmd = SPI_NOR_CMD_SE,
.cmd_length = 1,
.addr_length = 3,
.force_single = true,
},
.chip_erase = {
.dir = MSPI_TX,
.cmd = SPI_NOR_CMD_CE,
.cmd_length = 1,
},
.sfdp = {
.dir = MSPI_RX,
.cmd = JESD216_CMD_READ_SFDP,
.cmd_length = 1,
.addr_length = 3,
.rx_dummy = 8,
.force_single = true,
},
},
[MSPI_IO_MODE_OCTAL] = {
.id = {
.dir = MSPI_RX,
.cmd = JESD216_OCMD_READ_ID,
.cmd_length = 2,
.addr_length = 4,
.rx_dummy = 4
},
.write_en = {
.dir = MSPI_TX,
.cmd = SPI_NOR_OCMD_WREN,
.cmd_length = 2,
},
.read = {
.dir = MSPI_RX,
.cmd = SPI_NOR_OCMD_RD,
.cmd_length = 2,
.addr_length = 4,
.rx_dummy = 20,
},
.status = {
.dir = MSPI_RX,
.cmd = SPI_NOR_OCMD_RDSR,
.cmd_length = 2,
.addr_length = 4,
.rx_dummy = 4,
},
.page_program = {
.dir = MSPI_TX,
.cmd = SPI_NOR_OCMD_PAGE_PRG,
.cmd_length = 2,
.addr_length = 4,
},
.sector_erase = {
.dir = MSPI_TX,
.cmd = SPI_NOR_OCMD_SE,
.cmd_length = 2,
.addr_length = 4,
},
.chip_erase = {
.dir = MSPI_TX,
.cmd = SPI_NOR_OCMD_CE,
.cmd_length = 2,
},
.sfdp = {
.dir = MSPI_RX,
.cmd = JESD216_OCMD_READ_SFDP,
.cmd_length = 2,
.addr_length = 4,
.rx_dummy = 20,
},
},
};
void flash_mspi_command_set(const struct device *dev, const struct flash_mspi_nor_cmd *cmd);
#ifdef __cplusplus
}
#endif
#endif /*__FLASH_MSPI_NOR_H__*/

View file

@ -0,0 +1,185 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __FLASH_MSPI_NOR_QUIRKS_H__
#define __FLASH_MSPI_NOR_QUIRKS_H__
/* Flash chip specific quirks */
struct flash_mspi_nor_quirks {
/* Called after switching to default IO mode. */
int (*post_switch_mode)(const struct device *dev);
};
/* Extend this macro when adding new flash chip with quirks */
#define FLASH_MSPI_QUIRKS_GET(node) \
COND_CODE_1(DT_NODE_HAS_COMPAT_STATUS(node, mxicy_mx25r, okay), \
(&flash_quirks_mxicy_mx25r), \
(COND_CODE_1(DT_NODE_HAS_COMPAT_STATUS(node, mxicy_mx25u, okay), \
(&flash_quirks_mxicy_mx25u), \
(NULL))))
#if DT_HAS_COMPAT_STATUS_OKAY(mxicy_mx25r)
#define MXICY_MX25R_LH_MASK BIT(1)
#define MXICY_MX25R_QE_MASK BIT(6)
#define MXICY_MX25R_REGS_LEN 3
static uint8_t mxicy_mx25r_hp_payload[MXICY_MX25R_REGS_LEN] = {
MXICY_MX25R_QE_MASK, 0x0, MXICY_MX25R_LH_MASK
};
/* For quad io mode above 8 MHz and single io mode above 33 MHz,
* high performance mode needs to be enabled.
*/
static inline bool needs_hp(enum mspi_io_mode io_mode, uint32_t freq)
{
if ((io_mode == MSPI_IO_MODE_QUAD_1_1_4) || (io_mode == MSPI_IO_MODE_QUAD_1_4_4)) {
if (freq > MHZ(8)) {
return true;
}
} else if (io_mode == MSPI_IO_MODE_SINGLE) {
if (freq > MHZ(33)) {
return true;
}
}
return false;
}
static inline int mxicy_mx25r_post_switch_mode(const struct device *dev)
{
const struct flash_mspi_nor_config *dev_config = dev->config;
struct flash_mspi_nor_data *dev_data = dev->data;
enum mspi_io_mode io_mode = dev_config->mspi_nor_cfg.io_mode;
uint32_t freq = dev_config->mspi_nor_cfg.freq;
int rc;
uint8_t status;
uint8_t config[MXICY_MX25R_REGS_LEN - 1];
if (!needs_hp(io_mode, freq)) {
return 0;
}
/* Wait for previous write to finish */
do {
flash_mspi_command_set(dev, &dev_config->jedec_cmds->status);
dev_data->packet.data_buf = &status;
dev_data->packet.num_bytes = sizeof(status);
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
if (rc < 0) {
return rc;
}
} while (status & SPI_NOR_WIP_BIT);
/* Write enable */
flash_mspi_command_set(dev, &commands[MSPI_IO_MODE_SINGLE].write_en);
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
&dev_data->xfer);
if (rc < 0) {
return rc;
}
/* Write status and config registers */
const struct flash_mspi_nor_cmd cmd_status = {
.dir = MSPI_TX,
.cmd = SPI_NOR_CMD_WRSR,
.cmd_length = 1,
};
flash_mspi_command_set(dev, &cmd_status);
dev_data->packet.data_buf = mxicy_mx25r_hp_payload;
dev_data->packet.num_bytes = sizeof(mxicy_mx25r_hp_payload);
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
if (rc < 0) {
return rc;
}
/* Wait for write to end and verify status register */
do {
flash_mspi_command_set(dev, &dev_config->jedec_cmds->status);
dev_data->packet.data_buf = &status;
dev_data->packet.num_bytes = sizeof(status);
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
if (rc < 0) {
return rc;
}
} while (status & SPI_NOR_WIP_BIT);
if (status != mxicy_mx25r_hp_payload[0]) {
return -EIO;
}
/* Verify configuration registers */
flash_mspi_command_set(dev, &dev_config->jedec_cmds->config);
dev_data->packet.data_buf = config;
dev_data->packet.num_bytes = sizeof(config);
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
if (rc < 0) {
return rc;
}
for (uint8_t i = 0; i < MXICY_MX25R_REGS_LEN - 1; i++) {
if (config[i] != mxicy_mx25r_hp_payload[i + 1]) {
return -EIO;
}
}
return 0;
}
struct flash_mspi_nor_quirks flash_quirks_mxicy_mx25r = {
.post_switch_mode = mxicy_mx25r_post_switch_mode,
};
#endif /* DT_HAS_COMPAT_STATUS_OKAY(mxicy_mx25r) */
#if DT_HAS_COMPAT_STATUS_OKAY(mxicy_mx25u)
#define MXICY_MX25R_OE_MASK BIT(0)
static uint8_t mxicy_mx25u_oe_payload = MXICY_MX25R_OE_MASK;
static inline int mxicy_mx25u_post_switch_mode(const struct device *dev)
{
const struct flash_mspi_nor_config *dev_config = dev->config;
struct flash_mspi_nor_data *dev_data = dev->data;
enum mspi_io_mode io_mode = dev_config->mspi_nor_cfg.io_mode;
int rc;
if (io_mode != MSPI_IO_MODE_OCTAL) {
return 0;
}
/* Write enable */
flash_mspi_command_set(dev, &commands[MSPI_IO_MODE_SINGLE].write_en);
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
&dev_data->xfer);
if (rc < 0) {
return rc;
}
/* Write config register 2 */
const struct flash_mspi_nor_cmd cmd_status = {
.dir = MSPI_TX,
.cmd = SPI_NOR_CMD_WR_CFGREG2,
.cmd_length = 1,
.addr_length = 4,
};
flash_mspi_command_set(dev, &cmd_status);
dev_data->packet.data_buf = &mxicy_mx25u_oe_payload;
dev_data->packet.num_bytes = sizeof(mxicy_mx25u_oe_payload);
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, &dev_data->xfer);
return rc;
}
struct flash_mspi_nor_quirks flash_quirks_mxicy_mx25u = {
.post_switch_mode = mxicy_mx25u_post_switch_mode,
};
#endif /* DT_HAS_COMPAT_STATUS_OKAY(mxicy_mx25u) */
#endif /*__FLASH_MSPI_NOR_QUIRKS_H__*/