ls1028a: wait for the NOR program/erase cycle to finish

XSPI_IPCMDDONE only says the controller finished driving the sequence
on the bus. The device keeps WIP set for the ~ms the cycle takes and
ignores Write Enable while it is set, so the per-page WREN this PR
added was dropped for pages 2..N and their Page Programs with it.

The erase loops polled the same bit with inverted polarity: they
exited as soon as WIP read *set*, i.e. once the erase had started, and
would spin forever if they missed the busy window. FLASH_READY_MSK is
renamed FLASH_SR_WIP_MSK so the sense is not guessable, and both paths
now share xspi_wait_ready().

The test device models WIP, so a missing wait shows up as a rejected
Write Enable.
pull/862/head
Daniele Lacamera 2026-08-18 09:10:13 +02:00
parent 589550c612
commit 737bb55f76
3 changed files with 50 additions and 17 deletions

View File

@ -520,6 +520,20 @@ void xspi_read_sr(uint8_t* rxbuf, uint32_t addr, uint32_t len)
XSPI_INTR = XSPI_IPCMDDONE;
}
/* Block until the NOR device finishes its program/erase cycle.
* XSPI_IPCMDDONE only reports that the controller finished driving the
* sequence on the bus; the device keeps WIP set for the ~ms the cycle
* takes, and ignores Write Enable and further program/erase commands
* until it clears. */
void xspi_wait_ready(uint32_t addr)
{
uint8_t status[4] = {0, 0, 0, 0};
do {
xspi_read_sr(status, addr, 1);
} while (status[0] & FLASH_SR_WIP_MSK);
}
void xspi_sw_reset(void)
{
XSPI_SWRESET();
@ -599,6 +613,10 @@ void xspi_flash_write(uintptr_t address, const uint8_t *data, uint32_t len)
XSPI_IPTXFCR = XSPI_IPRCFCR_FLUSH;
XSPI_INTR = XSPI_IPCMDDONE;
/* The program cycle is still running: the next iteration's
* Write Enable would be ignored and its Page Program dropped. */
xspi_wait_ready(address);
len -= size;
address += size;
}
@ -639,14 +657,10 @@ int hal_flash_erase(uintptr_t address, int len)
num_sectors += (len % FLASH_ERASE_SIZE) ? 1 : 0;
for (i = 0; i < num_sectors; i++) {
uint8_t status[4] = {0, 0, 0, 0};
xspi_write_en(address + i * FLASH_ERASE_SIZE);
xspi_flash_sec_erase(address + i * FLASH_ERASE_SIZE);
while (!(status[0] & FLASH_READY_MSK)) {
xspi_read_sr(status, 0, 1);
}
xspi_wait_ready(address + i * FLASH_ERASE_SIZE);
}
xspi_sw_reset();
@ -685,14 +699,10 @@ int ext_flash_erase(uintptr_t address, int len)
num_sectors += (len % FLASH_ERASE_SIZE) ? 1 : 0;
for (i = 0; i < num_sectors; i++) {
uint8_t status[4] = {0, 0, 0, 0};
xspi_write_en(address + i * FLASH_ERASE_SIZE);
xspi_flash_sec_erase(address + i * FLASH_ERASE_SIZE);
while (!(status[0] & FLASH_READY_MSK)) {
xspi_read_sr(status, 0, 1);
}
xspi_wait_ready(address + i * FLASH_ERASE_SIZE);
}
xspi_sw_reset();

View File

@ -463,7 +463,10 @@
#define FLASH_SECTOR_CNT (FLASH_BANK_SIZE / FLASH_ERASE_SIZE)
#define FLASH_ERASE_TOUT 60000 /* Flash Erase Timeout (ms) */
#define FLASH_WRITE_TOUT 500 /* Flash Write Timeout (ms) */
#define FLASH_READY_MSK (0x1 << 0)
/* RDSR (0x05) bit 0 is WIP: 1 while a program or erase is running,
* 0 when the device is ready. The device ignores Write Enable and any
* further program/erase while WIP is set. */
#define FLASH_SR_WIP_MSK (0x1 << 0)
#define MASK_32BIT 0xffffffff
/* LUT register helper */

View File

@ -82,6 +82,11 @@ static int g_xspi_log_n;
/* Emulated NOR flash plus the write-enable latch real NOR keeps. */
static uint8_t g_nor[2 * 128 * 1024];
static int g_wel;
/* RDSR polls remaining before the emulated device reports WIP clear.
* A program or erase arms it; Write Enable and program/erase issued
* while it is armed are ignored, exactly as the device does. */
#define NOR_BUSY_POLLS 2
static int g_busy;
/* The FlexSPI drains the TX FIFO while the command runs, so the flash
* receives the bytes in the order the driver hands them to the FIFO.
@ -108,8 +113,21 @@ static void xspi_emu_start(void)
uint32_t len = ipcr1 & 0xFFFF;
uint32_t addr = XSPI_IPCR0;
uint32_t i;
int e = g_xspi_log_n < XSPI_LOG_MAX ? g_xspi_log_n : XSPI_LOG_MAX - 1;
int e;
/* Status polls are not operations: answer them without taking a
* log slot, so the assertions below stay about WEN/PP/SE order. */
if (seq == LUT_INDEX_RDSR) {
/* WIP stays set for a few polls after a program or erase */
XSPI_RFD(0) = g_busy ? FLASH_SR_WIP_MSK : 0;
if (g_busy)
g_busy--;
XSPI_INTR |= XSPI_IPCMDDONE;
g_tfd_stream_len = 0;
return;
}
e = g_xspi_log_n < XSPI_LOG_MAX ? g_xspi_log_n : XSPI_LOG_MAX - 1;
if (g_xspi_log_n < XSPI_LOG_MAX)
g_xspi_log_n++;
g_xspi_log[e].addr = addr;
@ -118,7 +136,10 @@ static void xspi_emu_start(void)
if (seq == LUT_INDEX_WRITE_EN) {
g_xspi_log[e].cmd = XSPI_CMD_WEN;
g_wel = 1;
if (g_busy)
g_xspi_log[e].rejected = 1; /* WREN ignored while WIP set */
else
g_wel = 1;
}
else if (seq == LUT_INDEX_PP) {
g_xspi_log[e].cmd = XSPI_CMD_PP;
@ -135,6 +156,7 @@ static void xspi_emu_start(void)
g_tfd_stream[i];
}
g_wel = 0; /* the program consumes the latch */
g_busy = NOR_BUSY_POLLS;
}
else {
g_xspi_log[e].rejected = 1; /* NOR ignores PP without WEN */
@ -145,15 +167,12 @@ static void xspi_emu_start(void)
if (g_wel) {
memset(&g_nor[addr], 0xFF, len);
g_wel = 0;
g_busy = NOR_BUSY_POLLS;
}
else {
g_xspi_log[e].rejected = 1;
}
}
else if (seq == LUT_INDEX_RDSR) {
g_xspi_log[e].cmd = XSPI_CMD_RDSR;
XSPI_RFD(0) = FLASH_READY_MSK; /* status: READY */
}
XSPI_INTR |= XSPI_IPCMDDONE;
g_tfd_stream_len = 0; /* the FIFO was drained by the command */
@ -189,6 +208,7 @@ static void setup(void)
memset(g_nor, 0xFF, sizeof(g_nor));
g_xspi_log_n = 0;
g_wel = 0;
g_busy = 0;
g_tfd_stream_len = 0;
XSPI_INTR = 0x1 << 6; /* TX FIFO write enable */
}