From e5badf5ae53b686ca2e04975dbca56a7eef8bf2a Mon Sep 17 00:00:00 2001 From: David Garske Date: Sat, 17 Oct 2026 10:45:55 -0700 Subject: [PATCH] PolarFire SoC: QSPI clock divider knob and word-mode FIFO reads --- docs/Targets.md | 9 +++++++ hal/mpfs250.c | 67 ++++++++++++++++++++++++++++++++++++++----------- hal/mpfs250.h | 30 +++++++++++++++++++--- options.mk | 5 ++++ 4 files changed, 92 insertions(+), 19 deletions(-) diff --git a/docs/Targets.md b/docs/Targets.md index e6071975..3b2bc6dc 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -965,6 +965,7 @@ These flags apply to `polarfire_mpfs250_m_qspi.config` and are added via `CFLAGS | `WATCHDOG` | undefined (disabled) | When defined, the E51 watchdog timer is **kept enabled** during wolfBoot operation with a generous timeout. When undefined, the WDT is **disabled** in `hal_init()` and re-enabled with the boot ROM default in `hal_prepare_boot()` before jumping to the application. Either way, the application receives a normal WDT. | | `WATCHDOG_TIMEOUT_MS` | `30000` (30 s) | Watchdog timeout in milliseconds when `WATCHDOG` is defined. ECDSA P-384 verification on E51 with portable C math is bounded at ~5 s; the default 30 s avoids any need to refresh the WDT during the long verify call. | | `MPFS_WDT_NODES_KEEP` (make variable, `=1`) | undefined | Linux boot only: keep the five MSS watchdog nodes enabled in the dtb and stop the parked E51 from refreshing them, so the OS owns the watchdogs. See "Watchdog policy" below. | +| `MPFS_QSPI_CLK_DIV` (make variable) | `QSPI_CLK_DIV_8` | QSPI SCK divider, a `QSPI_CLK_DIV_*` name from `hal/mpfs250.h`. See "PolarFire SoC QSPI". | #### Stack overflow detection @@ -1091,6 +1092,14 @@ Example single-shot build: `cp config/examples/polarfire_mpfs250_qspi.config .co Notes: - Both modes support full read, write, and erase operations. +- SCK is the controller clock divided by the CoreQSPI `CLKRATE` field, SCK = PCLK / (2 * CLKRATE). + `MPFS_QSPI_CLK_DIV` selects the divider by its `QSPI_CLK_DIV_*` name from `hal/mpfs250.h` + (`QSPI_CLK_DIV_2` through `QSPI_CLK_DIV_30`, CLKRATE 1..15). The default `QSPI_CLK_DIV_8` is + about 18.75 MHz from the 150 MHz APB clock, where read throughput stops improving: from there on + the polled FIFO transfer rather than SCK sets the rate, so a faster SCK buys nothing. The MSS QSPI + PCLK is the MSS APB clock, which is 40 MHz at the M-mode reset clock until wolfBoot's PLL + bring-up, so an M-mode build that starts QSPI before `mss_pll_init()` runs SCK that much slower; + the SC QSPI is clocked by the System Controller and is unaffected by the MSS PLL. - For QSPI-based boot flows, disable SD/eMMC in the config (`DISK_SDCARD=0`, `DISK_EMMC=0`) unless you explicitly want wolfBoot to load from disk and the application from QSPI. - The MSS QSPI path expects external flash on the MSS QSPI pins; the SC QSPI path is for diff --git a/hal/mpfs250.c b/hal/mpfs250.c index 2f63808a..3c996f7e 100644 --- a/hal/mpfs250.c +++ b/hal/mpfs250.c @@ -2127,13 +2127,13 @@ int qspi_init(void) QSPI_IEN = 0; /* Configure QSPI Control Register: - * - Clock divider for ~5MHz (conservative) + * - Clock divider (MPFS_QSPI_CLK_DIV, ~5 MHz by default) * - CPOL=1 (clock idle high) for SPI Mode 3 * - Sample on SCK edge * - Enable controller */ QSPI_CONTROL = - (QSPI_CLK_DIV_30 << QSPI_CTRL_CLKRATE_OFFSET) | + (MPFS_QSPI_CLK_DIV << QSPI_CTRL_CLKRATE_OFFSET) | QSPI_CTRL_CLKIDLE | QSPI_CTRL_SAMPLE_SCK | QSPI_CTRL_EN; @@ -2180,6 +2180,8 @@ static int qspi_transfer_block(uint8_t read_mode, const uint8_t *cmd, uint32_t i; uint32_t timeout; uint32_t frame_cmd; + uint32_t rx_word; + int word_read; /* Wait for controller to be ready before starting */ timeout = QSPI_TIMEOUT_TRIES; @@ -2216,10 +2218,18 @@ static int qspi_transfer_block(uint8_t read_mode, const uint8_t *cmd, * command phase avoids this. The flash determines command vs data * boundaries from the opcode, not the controller's phase. */ frame_cmd = read_mode ? cmd_len : total_bytes; + /* FBYTE makes RXAVAIL/TXAVAIL count single bytes; FWORD makes them count + * 32-bit words, so a read polls STATUS once per four bytes instead of + * once per byte and pulls them through QSPI_X4_RX_DATA in one access. + * The polled loop, not SCK, is what caps the byte-mode read rate (it + * measures the same from CLKRATE /8 down to /2). Word mode needs a data + * phase that is a whole number of words; anything else stays byte-mode. */ + word_read = (read_mode && data_len >= 4u && (data_len & 3u) == 0u) ? 1 : 0; frames = ((total_bytes & 0xFFFF) << QSPI_FRAMES_TOTALBYTES_OFFSET) | ((frame_cmd & 0x1FF) << QSPI_FRAMES_CMDBYTES_OFFSET) | ((dummy_cycles & 0xF) << QSPI_FRAMES_IDLE_OFFSET) | - (1u << QSPI_FRAMES_FBYTE_OFFSET); + (word_read ? (1u << QSPI_FRAMES_FWORD_OFFSET) + : (1u << QSPI_FRAMES_FBYTE_OFFSET)); QSPI_FRAMES = frames; @@ -2243,18 +2253,42 @@ static int qspi_transfer_block(uint8_t read_mode, const uint8_t *cmd, } if (read_mode) { - /* Read mode: poll RXAVAIL for each data byte. */ - for (i = 0; i < data_len; i++) { - timeout = QSPI_RX_TIMEOUT_TRIES; - while (!(QSPI_STATUS & QSPI_STATUS_RXAVAIL) && --timeout); - if (timeout == 0) { - #ifdef DEBUG_QSPI - wolfBoot_printf("QSPI: RX timeout at byte %d, status=0x%x\n", - i, QSPI_STATUS); - #endif - return -3; + if (word_read) { + /* One STATUS poll and one 32-bit read per four bytes. The + * destination is not necessarily word-aligned, so the word is + * stored a byte at a time; the saving is in the polling and the + * FIFO accesses, not the stores. */ + for (i = 0; i < data_len; i += 4u) { + timeout = QSPI_RX_TIMEOUT_TRIES; + while (!(QSPI_STATUS & QSPI_STATUS_RXAVAIL) && --timeout); + if (timeout == 0) { + #ifdef DEBUG_QSPI + wolfBoot_printf("QSPI: RX timeout at word %d, status=0x%x\n", + i >> 2, QSPI_STATUS); + #endif + return -3; + } + rx_word = QSPI_X4_RX_DATA; + data[i] = (uint8_t)(rx_word & 0xFFu); + data[i + 1] = (uint8_t)((rx_word >> 8) & 0xFFu); + data[i + 2] = (uint8_t)((rx_word >> 16) & 0xFFu); + data[i + 3] = (uint8_t)((rx_word >> 24) & 0xFFu); + } + } + else { + /* Byte mode: poll RXAVAIL for each data byte. */ + for (i = 0; i < data_len; i++) { + timeout = QSPI_RX_TIMEOUT_TRIES; + while (!(QSPI_STATUS & QSPI_STATUS_RXAVAIL) && --timeout); + if (timeout == 0) { + #ifdef DEBUG_QSPI + wolfBoot_printf("QSPI: RX timeout at byte %d, status=0x%x\n", + i, QSPI_STATUS); + #endif + return -3; + } + data[i] = QSPI_RX_DATA; } - data[i] = QSPI_RX_DATA; } /* Wait for receive complete */ timeout = QSPI_RX_TIMEOUT_TRIES; @@ -2353,7 +2387,10 @@ static int qspi_enter_4byte_mode(void) /* Read from QSPI flash (4-byte addressing) */ static int qspi_flash_read(uint32_t address, uint8_t *data, uint32_t len) { - const uint32_t max_chunk = 0xFFFF - 5; /* total_bytes is 16-bit, cmd is 5 */ + /* total_bytes is 16-bit and the command takes 5 of it; round down to a + * whole number of words so a bulk read stays on the word-mode FIFO path + * in qspi_transfer_block instead of falling back to byte mode. */ + const uint32_t max_chunk = (0xFFFFu - 5u) & ~3u; uint8_t cmd[5]; uint32_t remaining = len; uint32_t chunk_len; diff --git a/hal/mpfs250.h b/hal/mpfs250.h index 6ee22b37..79e3728e 100644 --- a/hal/mpfs250.h +++ b/hal/mpfs250.h @@ -789,8 +789,10 @@ int mpfs_pdma_memcpy(void *dst, const void *src, uint32_t bytes); /* QSPI Flash Controller * * Two CoreQSPI v2 controllers with identical register layouts: - * SC QSPI (MPFS_SC_SPI=1, default): 0x37020100 -- fabric-connected flash - * MSS QSPI (MPFS_SC_SPI=0): 0x21000000 -- MSS QSPI pins + * SC QSPI (-DMPFS_SC_SPI, every shipped QSPI config): 0x37020100, + * the System Controller's flash + * MSS QSPI (MPFS_SC_SPI undefined): 0x21000000, + * the MSS QSPI pins */ /* QSPI Controller Base Address */ @@ -851,14 +853,34 @@ int mpfs_pdma_memcpy(void *dst, const void *src, uint32_t bytes); #define QSPI_STATUS_TXAVAIL (1u << 3) #define QSPI_STATUS_READY (1u << 7) -/* QSPI Clock Configuration */ +/* QSPI Clock Configuration: SCK = PCLK / (2 * CLKRATE). The MSS QSPI PCLK + * is the APB clock, 150 MHz under HSS and 40 MHz at the M-mode reset clock + * (MSS_APB_AHB_CLK) until mss_pll_init() raises it; the SC QSPI has the + * System Controller's own clock. MPFS_QSPI_CLK_DIV picks the divider. */ #define QSPI_CLK_DIV_2 0x01u #define QSPI_CLK_DIV_4 0x02u #define QSPI_CLK_DIV_6 0x03u -#define QSPI_CLK_DIV_8 0x04u +#define QSPI_CLK_DIV_8 0x04u /* ~18.75 MHz from 150 MHz APB */ #define QSPI_CLK_DIV_10 0x05u #define QSPI_CLK_DIV_12 0x06u +#define QSPI_CLK_DIV_14 0x07u +#define QSPI_CLK_DIV_16 0x08u +#define QSPI_CLK_DIV_18 0x09u +#define QSPI_CLK_DIV_20 0x0Au +#define QSPI_CLK_DIV_22 0x0Bu +#define QSPI_CLK_DIV_24 0x0Cu +#define QSPI_CLK_DIV_26 0x0Du +#define QSPI_CLK_DIV_28 0x0Eu #define QSPI_CLK_DIV_30 0x0Fu /* ~5 MHz from 150 MHz APB */ +/* Divide by 8 is where read throughput stops improving: the polled FIFO + * transfer, not SCK, is the limit from there on. Anything slower only costs + * time, anything faster buys nothing. */ +#ifndef MPFS_QSPI_CLK_DIV +#define MPFS_QSPI_CLK_DIV QSPI_CLK_DIV_8 +#endif +#if (MPFS_QSPI_CLK_DIV < 0x01u) || (MPFS_QSPI_CLK_DIV > 0x0Fu) +#error MPFS_QSPI_CLK_DIV must be a CLKRATE of 1..15 (QSPI_CLK_DIV_2..QSPI_CLK_DIV_30) +#endif /* Micron MT25QL01G Flash Commands */ #define QSPI_CMD_READ_ID_OPCODE 0x9Fu /* JEDEC ID Read */ diff --git a/options.mk b/options.mk index 9d7a82a1..99bbe3b0 100644 --- a/options.mk +++ b/options.mk @@ -194,6 +194,11 @@ ifeq ($(DISK_FAILOVER_ZEROIZE),1) endif CFLAGS+=-D"DISK_FAILOVER_ZEROIZE" endif +# QSPI SCK divider: a QSPI_CLK_DIV_* name from hal/mpfs250.h, default QSPI_CLK_DIV_8. +ifneq ($(MPFS_QSPI_CLK_DIV),) + CFLAGS+=-DMPFS_QSPI_CLK_DIV=$(MPFS_QSPI_CLK_DIV) +endif + # Leave the MSS watchdog nodes in the dtb and stop the parked E51 refreshing them, so the OS owns # the watchdogs after the hand-off and must service them itself. ifeq ($(MPFS_WDT_NODES_KEEP),1)