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:
parent
0f0964f873
commit
b6642bb996
3 changed files with 798 additions and 187 deletions
|
@ -6,47 +6,56 @@
|
||||||
|
|
||||||
#define DT_DRV_COMPAT jedec_mspi_nor
|
#define DT_DRV_COMPAT jedec_mspi_nor
|
||||||
|
|
||||||
#include <zephyr/drivers/flash.h>
|
|
||||||
#include <zephyr/drivers/gpio.h>
|
#include <zephyr/drivers/gpio.h>
|
||||||
#include <zephyr/drivers/mspi.h>
|
|
||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
#include <zephyr/pm/device.h>
|
#include <zephyr/pm/device.h>
|
||||||
#include <zephyr/pm/device_runtime.h>
|
#include <zephyr/pm/device_runtime.h>
|
||||||
|
|
||||||
#include "jesd216.h"
|
#include "flash_mspi_nor.h"
|
||||||
#include "spi_nor.h"
|
#include "flash_mspi_nor_quirks.h"
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(flash_mspi_nor, CONFIG_FLASH_LOG_LEVEL);
|
LOG_MODULE_REGISTER(flash_mspi_nor, CONFIG_FLASH_LOG_LEVEL);
|
||||||
|
|
||||||
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios)
|
void flash_mspi_command_set(const struct device *dev, const struct flash_mspi_nor_cmd *cmd)
|
||||||
#define WITH_RESET_GPIO 1
|
{
|
||||||
#endif
|
struct flash_mspi_nor_data *dev_data = dev->data;
|
||||||
|
const struct flash_mspi_nor_config *dev_config = dev->config;
|
||||||
|
|
||||||
struct flash_mspi_nor_data {
|
memset(&dev_data->xfer, 0, sizeof(dev_data->xfer));
|
||||||
struct k_sem acquired;
|
memset(&dev_data->packet, 0, sizeof(dev_data->packet));
|
||||||
struct mspi_xfer_packet packet;
|
|
||||||
struct mspi_xfer xfer;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct flash_mspi_nor_config {
|
dev_data->xfer.xfer_mode = MSPI_PIO;
|
||||||
const struct device *bus;
|
dev_data->xfer.packets = &dev_data->packet;
|
||||||
uint32_t flash_size;
|
dev_data->xfer.num_packet = 1;
|
||||||
struct mspi_dev_id mspi_id;
|
dev_data->xfer.timeout = 10;
|
||||||
struct mspi_dev_cfg mspi_cfg;
|
|
||||||
enum mspi_dev_cfg_mask mspi_cfg_mask;
|
dev_data->xfer.cmd_length = cmd->cmd_length;
|
||||||
#if defined(CONFIG_MSPI_XIP)
|
dev_data->xfer.addr_length = cmd->addr_length;
|
||||||
struct mspi_xip_cfg xip_cfg;
|
dev_data->xfer.tx_dummy = (cmd->dir == MSPI_TX) ?
|
||||||
#endif
|
cmd->tx_dummy : dev_config->mspi_nor_cfg.tx_dummy;
|
||||||
#if defined(WITH_RESET_GPIO)
|
dev_data->xfer.rx_dummy = (cmd->dir == MSPI_RX) ?
|
||||||
struct gpio_dt_spec reset;
|
cmd->rx_dummy : dev_config->mspi_nor_cfg.rx_dummy;
|
||||||
uint32_t reset_pulse_us;
|
|
||||||
uint32_t reset_recovery_us;
|
dev_data->packet.dir = cmd->dir;
|
||||||
#endif
|
dev_data->packet.cmd = cmd->cmd;
|
||||||
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
}
|
||||||
struct flash_pages_layout layout;
|
|
||||||
#endif
|
static int dev_cfg_apply(const struct device *dev, const struct mspi_dev_cfg *cfg)
|
||||||
uint8_t jedec_id[SPI_NOR_MAX_ID_LEN];
|
{
|
||||||
};
|
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)
|
static int acquire(const struct device *dev)
|
||||||
{
|
{
|
||||||
|
@ -64,8 +73,8 @@ static int acquire(const struct device *dev)
|
||||||
* if needed for the flash device.
|
* if needed for the flash device.
|
||||||
*/
|
*/
|
||||||
rc = mspi_dev_config(dev_config->bus, &dev_config->mspi_id,
|
rc = mspi_dev_config(dev_config->bus, &dev_config->mspi_id,
|
||||||
dev_config->mspi_cfg_mask,
|
dev_config->mspi_nor_cfg_mask,
|
||||||
&dev_config->mspi_cfg);
|
&dev_config->mspi_nor_cfg);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
LOG_ERR("mspi_dev_config() failed: %d", rc);
|
LOG_ERR("mspi_dev_config() failed: %d", rc);
|
||||||
} else {
|
} else {
|
||||||
|
@ -125,12 +134,18 @@ static int api_read(const struct device *dev, off_t addr, void *dest,
|
||||||
return rc;
|
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 */
|
/* TODO: get rid of all these hard-coded values for MX25Ux chips */
|
||||||
dev_data->xfer.cmd_length = 2;
|
flash_mspi_command_set(dev, &dev_config->jedec_cmds->read);
|
||||||
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.address = addr;
|
||||||
dev_data->packet.data_buf = dest;
|
dev_data->packet.data_buf = dest;
|
||||||
dev_data->packet.num_bytes = size;
|
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);
|
release(dev);
|
||||||
|
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
LOG_ERR("SPI_NOR_OCMD_RD xfer failed: %d", rc);
|
LOG_ERR("Read xfer failed: %d", rc);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
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;
|
const struct flash_mspi_nor_config *dev_config = dev->config;
|
||||||
struct flash_mspi_nor_data *dev_data = dev->data;
|
struct flash_mspi_nor_data *dev_data = dev->data;
|
||||||
uint8_t status_reg;
|
|
||||||
int rc;
|
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) {
|
while (true) {
|
||||||
dev_data->xfer.cmd_length = 2;
|
rc = status_get(dev, &status_reg);
|
||||||
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) {
|
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;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(status_reg & SPI_NOR_WIP_BIT)) {
|
if (!(status_reg & SPI_NOR_WIP_BIT)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -179,6 +217,26 @@ static int wait_until_ready(const struct device *dev, k_timeout_t poll_period)
|
||||||
return 0;
|
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,
|
static int api_write(const struct device *dev, off_t addr, const void *src,
|
||||||
size_t size)
|
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 page_left = page_size - page_offset;
|
||||||
uint16_t to_write = (uint16_t)MIN(size, page_left);
|
uint16_t to_write = (uint16_t)MIN(size, page_left);
|
||||||
|
|
||||||
dev_data->xfer.cmd_length = 2;
|
if (write_enable(dev) < 0) {
|
||||||
dev_data->xfer.tx_dummy = 0;
|
LOG_ERR("Write enable xfer failed: %d", rc);
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_data->xfer.addr_length = 4;
|
if (dev_config->jedec_cmds->page_program.force_single) {
|
||||||
dev_data->packet.cmd = SPI_NOR_OCMD_PAGE_PRG;
|
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.address = addr;
|
||||||
dev_data->packet.data_buf = (uint8_t *)src;
|
dev_data->packet.data_buf = (uint8_t *)src;
|
||||||
dev_data->packet.num_bytes = to_write;
|
dev_data->packet.num_bytes = to_write;
|
||||||
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
|
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
|
||||||
&dev_data->xfer);
|
&dev_data->xfer);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
LOG_ERR("SPI_NOR_OCMD_PAGE_PRG xfer failed: %d", rc);
|
LOG_ERR("Page program xfer failed: %d", rc);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,32 +331,40 @@ static int api_erase(const struct device *dev, off_t addr, size_t size)
|
||||||
}
|
}
|
||||||
|
|
||||||
while (size > 0) {
|
while (size > 0) {
|
||||||
dev_data->xfer.cmd_length = 2;
|
rc = write_enable(dev);
|
||||||
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) {
|
if (rc < 0) {
|
||||||
LOG_ERR("SPI_NOR_OCMD_WREN xfer failed: %d", rc);
|
LOG_ERR("Write enable failed.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size == flash_size) {
|
if (size == flash_size) {
|
||||||
/* Chip erase. */
|
/* Chip erase. */
|
||||||
dev_data->xfer.addr_length = 0;
|
if (dev_config->jedec_cmds->chip_erase.force_single) {
|
||||||
dev_data->packet.cmd = SPI_NOR_OCMD_CE;
|
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;
|
size -= flash_size;
|
||||||
} else {
|
} else {
|
||||||
/* Sector erase. */
|
/* Sector erase. */
|
||||||
dev_data->xfer.addr_length = 4;
|
if (dev_config->jedec_cmds->sector_erase.force_single) {
|
||||||
dev_data->packet.cmd = SPI_NOR_OCMD_SE;
|
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
|
||||||
dev_data->packet.address = addr;
|
} 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;
|
addr += SPI_NOR_SECTOR_SIZE;
|
||||||
size -= 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;
|
struct flash_mspi_nor_data *dev_data = dev->data;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
dev_data->xfer.cmd_length = 2;
|
if (dev_config->jedec_cmds->id.force_single) {
|
||||||
dev_data->xfer.addr_length = 4;
|
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
|
||||||
dev_data->xfer.rx_dummy = 4;
|
} else {
|
||||||
dev_data->packet.dir = MSPI_RX;
|
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_cfg);
|
||||||
dev_data->packet.cmd = JESD216_OCMD_READ_ID;
|
}
|
||||||
dev_data->packet.address = 0;
|
|
||||||
|
if (rc < 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
flash_mspi_command_set(dev, &dev_config->jedec_cmds->id);
|
||||||
dev_data->packet.data_buf = id;
|
dev_data->packet.data_buf = id;
|
||||||
dev_data->packet.num_bytes = JESD216_READ_ID_LEN;
|
dev_data->packet.num_bytes = JESD216_READ_ID_LEN;
|
||||||
|
|
||||||
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
|
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
|
||||||
&dev_data->xfer);
|
&dev_data->xfer);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
printk("mspi_transceive() failed: %d\n", rc);
|
LOG_ERR("Read JEDEC ID failed: %d\n", rc);
|
||||||
return rc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
|
@ -387,18 +458,24 @@ static int api_sfdp_read(const struct device *dev, off_t addr, void *dest,
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_data->xfer.cmd_length = 2;
|
if (dev_config->jedec_cmds->sfdp.force_single) {
|
||||||
dev_data->xfer.addr_length = 4;
|
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
|
||||||
dev_data->xfer.rx_dummy = 20;
|
} else {
|
||||||
dev_data->packet.dir = MSPI_RX;
|
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_cfg);
|
||||||
dev_data->packet.cmd = JESD216_OCMD_READ_SFDP;
|
}
|
||||||
|
|
||||||
|
if (rc < 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
flash_mspi_command_set(dev, &dev_config->jedec_cmds->sfdp);
|
||||||
dev_data->packet.address = addr;
|
dev_data->packet.address = addr;
|
||||||
dev_data->packet.data_buf = dest;
|
dev_data->packet.data_buf = dest;
|
||||||
dev_data->packet.num_bytes = size;
|
dev_data->packet.num_bytes = size;
|
||||||
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
|
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
|
||||||
&dev_data->xfer);
|
&dev_data->xfer);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
printk("JESD216_OCMD_READ_SFDP xfer failed: %d\n", rc);
|
printk("Read SFDP xfer failed: %d\n", rc);
|
||||||
return 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)
|
static int api_read_jedec_id(const struct device *dev, uint8_t *id)
|
||||||
{
|
{
|
||||||
int rc = 0;
|
int rc = acquire(dev);
|
||||||
|
|
||||||
rc = acquire(dev);
|
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
@ -439,85 +514,191 @@ static int dev_pm_action_cb(const struct device *dev,
|
||||||
return 0;
|
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)
|
static int flash_chip_init(const struct device *dev)
|
||||||
{
|
{
|
||||||
const struct flash_mspi_nor_config *dev_config = dev->config;
|
const struct flash_mspi_nor_config *dev_config = dev->config;
|
||||||
struct flash_mspi_nor_data *dev_data = dev->data;
|
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};
|
uint8_t id[JESD216_READ_ID_LEN] = {0};
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
init_dev_cfg.freq = MHZ(1);
|
rc = dev_cfg_apply(dev, &dev_config->mspi_nor_init_cfg);
|
||||||
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) {
|
if (rc < 0) {
|
||||||
LOG_ERR("Failed to set initial device config: %d", rc);
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_data->xfer.xfer_mode = MSPI_PIO;
|
/* Some chips reuse RESET pin for data in Quad modes:
|
||||||
dev_data->xfer.packets = &dev_data->packet;
|
* force single line mode before resetting.
|
||||||
dev_data->xfer.num_packet = 1;
|
*/
|
||||||
dev_data->xfer.timeout = 10;
|
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;
|
if (rc < 0) {
|
||||||
dev_data->xfer.addr_length = 0;
|
LOG_ERR("Failed to switch to single line mode: %d", rc);
|
||||||
dev_data->xfer.tx_dummy = 0;
|
return rc;
|
||||||
dev_data->xfer.rx_dummy = 0;
|
}
|
||||||
|
|
||||||
dev_data->packet.dir = MSPI_RX;
|
rc = wait_until_ready(dev, K_USEC(1));
|
||||||
dev_data->packet.cmd = JESD216_CMD_READ_ID;
|
|
||||||
|
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.data_buf = id;
|
||||||
dev_data->packet.num_bytes = sizeof(id);
|
dev_data->packet.num_bytes = sizeof(id);
|
||||||
|
|
||||||
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
|
rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id,
|
||||||
&dev_data->xfer);
|
&dev_data->xfer);
|
||||||
if (rc < 0) {
|
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;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
rc = default_io_mode(dev);
|
||||||
* 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 (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) {
|
if (!dev_config->jedec_cmds->id.force_single) {
|
||||||
static const uint8_t enable_sopi[] = { 0x01 };
|
rc = read_jedec_id(dev, id);
|
||||||
|
|
||||||
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) {
|
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;
|
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) {
|
if (memcmp(id, dev_config->jedec_id, sizeof(id)) != 0) {
|
||||||
|
@ -555,37 +736,6 @@ static int drv_init(const struct device *dev)
|
||||||
return -ENODEV;
|
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);
|
rc = pm_device_runtime_get(dev_config->bus);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
LOG_ERR("pm_device_runtime_get() failed: %d", rc);
|
LOG_ERR("pm_device_runtime_get() failed: %d", rc);
|
||||||
|
@ -624,7 +774,27 @@ static DEVICE_API(flash, drv_api) = {
|
||||||
#endif
|
#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_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)
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
||||||
BUILD_ASSERT((CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE % 4096) == 0,
|
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
|
#endif
|
||||||
|
|
||||||
#define FLASH_MSPI_NOR_INST(inst) \
|
#define FLASH_MSPI_NOR_INST(inst) \
|
||||||
BUILD_ASSERT(DT_INST_ENUM_IDX(inst, mspi_io_mode) == \
|
BUILD_ASSERT((DT_INST_ENUM_IDX(inst, mspi_io_mode) == \
|
||||||
MSPI_IO_MODE_OCTAL, \
|
MSPI_IO_MODE_SINGLE) || \
|
||||||
"Only Octal I/O mode is supported for now"); \
|
(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); \
|
PM_DEVICE_DT_INST_DEFINE(inst, dev_pm_action_cb); \
|
||||||
static struct flash_mspi_nor_data dev##inst##_data; \
|
static struct flash_mspi_nor_data dev##inst##_data; \
|
||||||
static const struct flash_mspi_nor_config dev##inst##_config = { \
|
static const struct flash_mspi_nor_config dev##inst##_config = { \
|
||||||
.bus = DEVICE_DT_GET(DT_INST_BUS(inst)), \
|
.bus = DEVICE_DT_GET(DT_INST_BUS(inst)), \
|
||||||
.flash_size = FLASH_SIZE_INST(inst), \
|
.flash_size = FLASH_SIZE_INST(inst), \
|
||||||
.mspi_id = MSPI_DEVICE_ID_DT_INST(inst), \
|
.mspi_id = MSPI_DEVICE_ID_DT_INST(inst), \
|
||||||
.mspi_cfg = MSPI_DEVICE_CONFIG_DT_INST(inst), \
|
.mspi_nor_cfg = MSPI_DEVICE_CONFIG_DT_INST(inst), \
|
||||||
.mspi_cfg_mask = DT_PROP(DT_INST_BUS(inst), \
|
.mspi_nor_init_cfg = FLASH_INITIAL_CONFIG(inst), \
|
||||||
|
.mspi_nor_cfg_mask = DT_PROP(DT_INST_BUS(inst), \
|
||||||
software_multiperipheral) \
|
software_multiperipheral) \
|
||||||
? MSPI_DEVICE_CONFIG_ALL \
|
? MSPI_DEVICE_CONFIG_ALL \
|
||||||
: MSPI_DEVICE_CONFIG_NONE, \
|
: MSPI_DEVICE_CONFIG_NONE, \
|
||||||
|
@ -675,6 +850,9 @@ BUILD_ASSERT((FLASH_SIZE_INST(inst) % CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE) ==
|
||||||
/ 1000,)) \
|
/ 1000,)) \
|
||||||
FLASH_PAGE_LAYOUT_DEFINE(inst) \
|
FLASH_PAGE_LAYOUT_DEFINE(inst) \
|
||||||
.jedec_id = DT_INST_PROP(inst, jedec_id), \
|
.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) \
|
FLASH_PAGE_LAYOUT_CHECK(inst) \
|
||||||
DEVICE_DT_INST_DEFINE(inst, \
|
DEVICE_DT_INST_DEFINE(inst, \
|
||||||
|
|
248
drivers/flash/flash_mspi_nor.h
Normal file
248
drivers/flash/flash_mspi_nor.h
Normal 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__*/
|
185
drivers/flash/flash_mspi_nor_quirks.h
Normal file
185
drivers/flash/flash_mspi_nor_quirks.h
Normal 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__*/
|
Loading…
Add table
Add a link
Reference in a new issue