PolarFire SoC: QSPI clock divider knob and word-mode FIFO reads

pull/916/head
David Garske 2026-10-17 10:45:55 -07:00
parent 14a9081f3c
commit e5badf5ae5
4 changed files with 92 additions and 19 deletions

View File

@ -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

View File

@ -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;

View File

@ -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 */

View File

@ -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)