Skip to content

drivers: flash: spi_nor: optimizations and better Micron flash support #88467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 109 additions & 16 deletions drivers/flash/spi_nor.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ LOG_MODULE_REGISTER(spi_nor, CONFIG_FLASH_LOG_LEVEL);
#define ANY_INST_HAS_WP_GPIOS DT_ANY_INST_HAS_PROP_STATUS_OKAY(wp_gpios)
#define ANY_INST_HAS_HOLD_GPIOS DT_ANY_INST_HAS_PROP_STATUS_OKAY(hold_gpios)
#define ANY_INST_USE_4B_ADDR_OPCODES DT_ANY_INST_HAS_BOOL_STATUS_OKAY(use_4b_addr_opcodes)
#define ANY_INST_HAS_FLSR \
DT_ANY_INST_HAS_BOOL_STATUS_OKAY(use_flag_status_register)
#define ANY_INST_USE_FAST_READ DT_ANY_INST_HAS_BOOL_STATUS_OKAY(use_fast_read)

#ifdef CONFIG_SPI_NOR_ACTIVE_DWELL_MS
#define ACTIVE_DWELL_MS CONFIG_SPI_NOR_ACTIVE_DWELL_MS
Expand Down Expand Up @@ -150,6 +153,8 @@ struct spi_nor_config {
bool requires_ulbpr_exist:1;
bool wp_gpios_exist:1;
bool hold_gpios_exist:1;
bool has_flsr: 1;
bool use_fast_read: 1;
};

/**
Expand Down Expand Up @@ -356,6 +361,10 @@ static inline void delay_until_exit_dpd_ok(const struct device *const dev)
*/
#define NOR_ACCESS_32BIT_ADDR BIT(2)

/* Indicates that a dummy byte is to be sent following the address.
*/
#define NOR_ACCESS_DUMMY_BYTE BIT(3)

/* Indicates that an access command is performing a write. If not
* provided access is a read.
*/
Expand All @@ -381,8 +390,9 @@ static int spi_nor_access(const struct device *const dev,
struct spi_nor_data *const driver_data = dev->data;
bool is_addressed = (access & NOR_ACCESS_ADDRESSED) != 0U;
bool is_write = (access & NOR_ACCESS_WRITE) != 0U;
uint8_t buf[5] = { 0 };
struct spi_buf spi_buf[2] = {
bool has_dummy = (access & NOR_ACCESS_DUMMY_BYTE) != 0U;
uint8_t buf[6] = {opcode};
struct spi_buf spi_buf_tx[2] = {
{
.buf = buf,
.len = 1,
Expand All @@ -392,8 +402,17 @@ static int spi_nor_access(const struct device *const dev,
.len = length
}
};
struct spi_buf spi_buf_rx[2] = {
{
.buf = NULL,
.len = 1,
},
{
.buf = data,
.len = length
}
};

buf[0] = opcode;
if (is_addressed) {
bool access_24bit = (access & NOR_ACCESS_24BIT_ADDR) != 0;
bool access_32bit = (access & NOR_ACCESS_32BIT_ADDR) != 0;
Expand All @@ -409,20 +428,26 @@ static int spi_nor_access(const struct device *const dev,

if (use_32bit) {
memcpy(&buf[1], &addr32.u8[0], 4);
spi_buf[0].len += 4;
spi_buf_tx[0].len += 4;
spi_buf_rx[0].len += 4;
} else {
memcpy(&buf[1], &addr32.u8[1], 3);
spi_buf[0].len += 3;
spi_buf_tx[0].len += 3;
spi_buf_rx[0].len += 3;
}
};
Copy link
Preview

Copilot AI May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider adding a comment clarifying that the dummy byte is intentionally left with its default (zero) value to improve code clarity.

Suggested change
};
};
/* If a dummy byte is required, increase the buffer lengths.
* The dummy byte is intentionally left with its default (zero) value
* as it serves as a placeholder for timing or protocol requirements.
*/

Copilot uses AI. Check for mistakes.

if (has_dummy) {
spi_buf_tx[0].len++;
spi_buf_rx[0].len++;
}

const struct spi_buf_set tx_set = {
.buffers = spi_buf,
.count = (length != 0) ? 2 : 1,
.buffers = spi_buf_tx,
.count = (is_write && length != 0) ? 2 : 1,
};

const struct spi_buf_set rx_set = {
.buffers = spi_buf,
.buffers = spi_buf_rx,
.count = 2,
};

Expand All @@ -443,6 +468,17 @@ static int spi_nor_access(const struct device *const dev,
#define spi_nor_cmd_addr_read_4b(dev, opcode, addr, dest, length) \
spi_nor_access(dev, opcode, NOR_ACCESS_32BIT_ADDR | NOR_ACCESS_ADDRESSED, addr, dest, \
length)
#define spi_nor_cmd_addr_fast_read(dev, opcode, addr, dest, length) \
spi_nor_access(dev, opcode, NOR_ACCESS_ADDRESSED | NOR_ACCESS_DUMMY_BYTE, addr, dest, \
length)
#define spi_nor_cmd_addr_fast_read_3b(dev, opcode, addr, dest, length) \
spi_nor_access(dev, opcode, \
NOR_ACCESS_24BIT_ADDR | NOR_ACCESS_ADDRESSED | NOR_ACCESS_DUMMY_BYTE, addr, \
dest, length)
#define spi_nor_cmd_addr_fast_read_4b(dev, opcode, addr, dest, length) \
spi_nor_access(dev, opcode, \
NOR_ACCESS_32BIT_ADDR | NOR_ACCESS_ADDRESSED | NOR_ACCESS_DUMMY_BYTE, addr, \
dest, length)
#define spi_nor_cmd_write(dev, opcode) \
spi_nor_access(dev, opcode, NOR_ACCESS_WRITE, 0, NULL, 0)
#define spi_nor_cmd_addr_write(dev, opcode, addr, src, length) \
Expand Down Expand Up @@ -474,16 +510,53 @@ static int spi_nor_access(const struct device *const dev,
*/
static int spi_nor_wait_until_ready(const struct device *dev, k_timeout_t poll_delay)
{
const struct spi_nor_config *cfg = dev->config;
int ret;
uint8_t reg;

ARG_UNUSED(poll_delay);

while (true) {
ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDSR, &reg, sizeof(reg));
/* Exit on error or no longer WIP */
if (ret || !(reg & SPI_NOR_WIP_BIT)) {
break;
/* If flag status register is present, check it rather than the standard
* status register since it allows better error detection. Also, some devices
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This correction should be done in the second commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, not sure how it ended up in the third one. Moved this change to the second commit.

* that have it require it to be read after a program operation.
*/
if (IS_ENABLED(ANY_INST_HAS_FLSR) && cfg->has_flsr) {
ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDFLSR, &reg, sizeof(reg));
if (ret) {
break;
}
if (reg & SPI_NOR_FLSR_READY) {
if (reg & SPI_NOR_FLSR_ERASE_FAIL) {
LOG_ERR("Erase failure");
ret = -EIO;
}
if (reg & SPI_NOR_FLSR_PROGRAM_FAIL) {
LOG_ERR("Program failure");
ret = -EIO;
}
if (reg & SPI_NOR_FLSR_PROT_ERROR) {
LOG_ERR("Protection violation");
ret = -EIO;
}

if (ret) {
/* Clear flag status register for next operation */
int ret2 = spi_nor_cmd_write(dev, SPI_NOR_CMD_CLRFLSR);

if (ret2) {
LOG_ERR("Failed to clear flag status register: %d",
ret2);
}
}
break;
}
} else {
ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDSR, &reg, sizeof(reg));
/* Exit on error or no longer WIP */
if (ret || !(reg & SPI_NOR_WIP_BIT)) {
break;
}
}
#ifdef CONFIG_SPI_NOR_SLEEP_WHILE_WAITING_UNTIL_READY
/* Don't monopolise the CPU while waiting for ready */
Expand Down Expand Up @@ -788,6 +861,7 @@ static int mxicy_configure(const struct device *dev, const uint8_t *jedec_id)
static int spi_nor_read(const struct device *dev, off_t addr, void *dest,
size_t size)
{
const struct spi_nor_config *cfg = dev->config;
const size_t flash_size = dev_flash_size(dev);
int ret;

Expand All @@ -803,14 +877,31 @@ static int spi_nor_read(const struct device *dev, off_t addr, void *dest,

acquire_device(dev);

if (IS_ENABLED(ANY_INST_USE_4B_ADDR_OPCODES) && DEV_CFG(dev)->use_4b_addr_opcodes) {
if (IS_ENABLED(ANY_INST_USE_4B_ADDR_OPCODES) && cfg->use_4b_addr_opcodes) {
if (addr > SPI_NOR_3B_ADDR_MAX) {
ret = spi_nor_cmd_addr_read_4b(dev, SPI_NOR_CMD_READ_4B, addr, dest, size);
if (IS_ENABLED(ANY_INST_USE_FAST_READ) && cfg->use_fast_read) {
ret = spi_nor_cmd_addr_fast_read_4b(dev, SPI_NOR_CMD_READ_FAST_4B,
addr, dest, size);
} else {
ret = spi_nor_cmd_addr_read_4b(dev, SPI_NOR_CMD_READ_4B, addr, dest,
size);
}
} else {
ret = spi_nor_cmd_addr_read_3b(dev, SPI_NOR_CMD_READ, addr, dest, size);
if (IS_ENABLED(ANY_INST_USE_FAST_READ) && cfg->use_fast_read) {
ret = spi_nor_cmd_addr_fast_read_3b(dev, SPI_NOR_CMD_READ_FAST,
addr, dest, size);
} else {
ret = spi_nor_cmd_addr_read_3b(dev, SPI_NOR_CMD_READ, addr, dest,
size);
}
}
} else {
ret = spi_nor_cmd_addr_read(dev, SPI_NOR_CMD_READ, addr, dest, size);
if (IS_ENABLED(ANY_INST_USE_FAST_READ) && cfg->use_fast_read) {
ret = spi_nor_cmd_addr_fast_read(dev, SPI_NOR_CMD_READ_FAST, addr, dest,
size);
} else {
ret = spi_nor_cmd_addr_read(dev, SPI_NOR_CMD_READ, addr, dest, size);
}
}

release_device(dev);
Expand Down Expand Up @@ -1807,6 +1898,8 @@ static DEVICE_API(flash, spi_nor_api) = {
.wp_gpios_exist = DT_INST_NODE_HAS_PROP(idx, wp_gpios), \
.hold_gpios_exist = DT_INST_NODE_HAS_PROP(idx, hold_gpios), \
.use_4b_addr_opcodes = DT_INST_PROP(idx, use_4b_addr_opcodes), \
.has_flsr = DT_INST_PROP(idx, use_flag_status_register), \
.use_fast_read = DT_INST_PROP(idx, use_fast_read), \
IF_ENABLED(INST_HAS_LOCK(idx), (.has_lock = DT_INST_PROP(idx, has_lock),)) \
IF_ENABLED(ANY_INST_HAS_DPD, (INIT_T_ENTER_DPD(idx),)) \
IF_ENABLED(UTIL_AND(ANY_INST_HAS_DPD, ANY_INST_HAS_T_EXIT_DPD), \
Expand Down
11 changes: 11 additions & 0 deletions drivers/flash/spi_nor.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@
#define SPI_NOR_WIP_BIT BIT(0) /* Write in progress */
#define SPI_NOR_WEL_BIT BIT(1) /* Write enable latch */

/* Flag status register bits */
#define SPI_NOR_FLSR_READY BIT(7) /* Ready (program/erase not in progress) */
#define SPI_NOR_FLSR_ERASE_SUSPEND BIT(6) /* Erase suspend active */
#define SPI_NOR_FLSR_ERASE_FAIL BIT(5) /* Last erase failed */
#define SPI_NOR_FLSR_PROGRAM_FAIL BIT(4) /* Last program failed */
#define SPI_NOR_FLSR_PROGRAM_SUSPEND BIT(2) /* Program suspend active */
#define SPI_NOR_FLSR_PROT_ERROR BIT(1) /* Protection violation */
#define SPI_NOR_FLSR_4BA BIT(0) /* 4-byte address mode active */

/* Flash opcodes */
#define SPI_NOR_CMD_WRSR 0x01 /* Write status register */
#define SPI_NOR_CMD_RDSR 0x05 /* Read status register */
Expand Down Expand Up @@ -60,6 +69,8 @@
#define SPI_NOR_CMD_PP_4B 0x12 /* Page Program 4 Byte Address */
#define SPI_NOR_CMD_PP_1_1_4_4B 0x34 /* Quad Page program (1-1-4) 4 Byte Address */
#define SPI_NOR_CMD_PP_1_4_4_4B 0x3e /* Quad Page program (1-4-4) 4 Byte Address */
#define SPI_NOR_CMD_RDFLSR 0x70 /* Read Flag Status Register */
#define SPI_NOR_CMD_CLRFLSR 0x50 /* Clear Flag Status Register */

/* Flash octal opcodes */
#define SPI_NOR_OCMD_SE 0x21DE /* Octal Sector erase */
Expand Down
20 changes: 20 additions & 0 deletions dts/bindings/mtd/jedec,spi-nor-common.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,23 @@ properties:
Some devices support 4-byte address opcodes for read/write/erase
operations. Use this property to indicate that the device supports
4-byte address opcodes.

use-flag-status-register:
type: boolean
description: |
Indicates the device supports a flag status register.

Some devices (Micron and possibly others) support a flag status register
which indicates more details on the status of program or erase operations.
In some cases, program operations will not function properly if the flag
status register is not read after the operation.

use-fast-read:
type: boolean
description: |
Indicates the device supports fast read.

Most SPI NOR devices support a fast read command that allows the device to
output data at a higher clock rate than the standard read command. This
property indicates that the device supports the fast read command with
8 dummy cycles after the address phase of the command.