diff --git a/config/examples/versal_vmk180.config b/config/examples/versal_vmk180.config index d41ac04d..09f7bdb1 100644 --- a/config/examples/versal_vmk180.config +++ b/config/examples/versal_vmk180.config @@ -92,3 +92,7 @@ CFLAGS_EXTRA+=-DWOLFBOOT_SHA_BLOCK_SIZE=4096 # UART Configuration - UART0 for APU console CFLAGS_EXTRA+=-DDEBUG_UART_NUM=0 + +# QSPI flash debug and test options (uncomment to enable) +#CFLAGS_EXTRA+=-DDEBUG_QSPI +#CFLAGS_EXTRA+=-DTEST_EXT_FLASH diff --git a/hal/versal.c b/hal/versal.c index 923440fc..9e4507af 100644 --- a/hal/versal.c +++ b/hal/versal.c @@ -259,6 +259,709 @@ void hal_delay_us(uint32_t us) } +/* ============================================================================ + * QSPI Flash Driver + * ============================================================================ + * Bare-metal QSPI driver for Versal VMK180 + * Dual parallel MT25QU01GBBB (128MB each, 256MB total) + */ + +#ifdef EXT_FLASH + +/* Debug logging for QSPI driver */ +#ifdef DEBUG_QSPI + #define QSPI_DEBUG_PRINTF(...) wolfBoot_printf(__VA_ARGS__) +#else + #define QSPI_DEBUG_PRINTF(...) do {} while(0) +#endif + +/* QSPI device structure */ +typedef struct { + uint32_t mode; /* GQSPI_GEN_FIFO_MODE_SPI/DSPI/QSPI */ + uint32_t bus; /* GQSPI_GEN_FIFO_BUS_LOW/UP/BOTH */ + uint32_t cs; /* GQSPI_GEN_FIFO_CS_LOWER/UPPER/BOTH */ + uint32_t stripe; /* 0 or GQSPI_GEN_FIFO_STRIPE for dual parallel */ +} QspiDev_t; + +static QspiDev_t qspiDev; +static int qspi_initialized = 0; + +/* Forward declarations */ +static int qspi_transfer(QspiDev_t *dev, const uint8_t *txData, uint32_t txLen, + uint8_t *rxData, uint32_t rxLen, uint32_t dummyClocks); +static int qspi_wait_ready(QspiDev_t *dev); + +/* Wait for GenFIFO empty (all entries processed) with timeout */ +static int qspi_wait_genfifo_empty(void) +{ + uint32_t timeout = GQSPI_TIMEOUT_TRIES; + uint32_t isr; + + isr = GQSPI_ISR; + while (!(isr & GQSPI_IXR_GEN_FIFO_EMPTY) && --timeout) { + isr = GQSPI_ISR; + } + if (timeout == 0) { + QSPI_DEBUG_PRINTF("QSPI: GenFIFO empty timeout\n"); + return -1; + } + return 0; +} + +/* Wait for TX FIFO empty with timeout */ +static int qspi_wait_tx_empty(void) +{ + uint32_t timeout = GQSPI_TIMEOUT_TRIES; + while (!(GQSPI_ISR & GQSPI_IXR_TX_FIFO_EMPTY) && --timeout) + ; + if (timeout == 0) { + QSPI_DEBUG_PRINTF("QSPI: TX empty timeout\n"); + return -1; + } + return 0; +} + +/* Write to GenFIFO (without triggering - batch mode) */ +static int qspi_gen_fifo_push(uint32_t entry) +{ + uint32_t timeout = GQSPI_TIMEOUT_TRIES; + uint32_t isr; + + /* Wait for GenFIFO not full */ + isr = GQSPI_ISR; + while (!(isr & GQSPI_IXR_GEN_FIFO_NOT_FULL) && --timeout) { + isr = GQSPI_ISR; + } + if (timeout == 0) { + QSPI_DEBUG_PRINTF("QSPI: GenFIFO full timeout\n"); + return -1; + } + + /* Write the entry to GenFIFO */ + GQSPI_GEN_FIFO = entry; + + return 0; +} + +/* Trigger GenFIFO processing and wait for completion */ +static int qspi_gen_fifo_start_and_wait(void) +{ + uint32_t cfg; + uint32_t timeout = GQSPI_TIMEOUT_TRIES; + uint32_t isr; + + dsb(); /* Ensure all writes complete */ + + /* Trigger GenFIFO processing by setting START_GEN_FIFO */ + cfg = GQSPI_CFG; + cfg |= GQSPI_CFG_START_GEN_FIFO; + GQSPI_CFG = cfg; + dsb(); + + /* Wait for GenFIFO to empty (all entries processed) */ + isr = GQSPI_ISR; + while (!(isr & GQSPI_IXR_GEN_FIFO_EMPTY) && --timeout) { + isr = GQSPI_ISR; + } + if (timeout == 0) { + QSPI_DEBUG_PRINTF("QSPI: GenFIFO start timeout\n"); + return -1; + } + return 0; +} + +/* Legacy wrapper for compatibility */ +static int qspi_gen_fifo_write(uint32_t entry) +{ + int ret = qspi_gen_fifo_push(entry); + if (ret == 0) { + ret = qspi_gen_fifo_start_and_wait(); + } + return ret; +} + +/* Chip select control */ +static int qspi_cs(QspiDev_t *dev, int assert) +{ + uint32_t entry; + + entry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) | GQSPI_GEN_FIFO_MODE_SPI; + if (assert) { + entry |= (dev->cs & GQSPI_GEN_FIFO_CS_MASK); + } + /* Idle clocks for CS setup/hold */ + entry |= GQSPI_GEN_FIFO_IMM(2); + + return qspi_gen_fifo_write(entry); +} + +/* TX via FIFO (polling mode) */ +static int qspi_fifo_tx(const uint8_t *data, uint32_t len) +{ + uint32_t tmp32; + uint32_t timeout; + + while (len > 0) { + /* Wait for TX FIFO not full */ + timeout = GQSPI_TIMEOUT_TRIES; + while ((GQSPI_ISR & GQSPI_IXR_TX_FIFO_FULL) && --timeout) + ; + if (timeout == 0) { + QSPI_DEBUG_PRINTF("QSPI: TX FIFO full timeout\n"); + return -1; + } + + if (len >= 4) { + tmp32 = *((uint32_t*)data); + GQSPI_TXD = tmp32; + data += 4; + len -= 4; + } else { + tmp32 = 0; + memcpy(&tmp32, data, len); + GQSPI_TXD = tmp32; + len = 0; + } + } + return 0; +} + +/* RX via FIFO (polling mode) */ +static int qspi_fifo_rx(uint8_t *data, uint32_t len) +{ + uint32_t tmp32; + uint32_t timeout; + + while (len > 0) { + /* Wait for RX FIFO not empty */ + timeout = GQSPI_TIMEOUT_TRIES; + while (!(GQSPI_ISR & GQSPI_IXR_RX_FIFO_NOT_EMPTY) && --timeout) + ; + if (timeout == 0) { + QSPI_DEBUG_PRINTF("QSPI: RX FIFO empty timeout\n"); + return -1; + } + + tmp32 = GQSPI_RXD; + if (len >= 4) { + *((uint32_t*)data) = tmp32; + data += 4; + len -= 4; + } else { + memcpy(data, &tmp32, len); + len = 0; + } + } + return 0; +} + +/* Core QSPI transfer function using GenFIFO */ +static int qspi_transfer(QspiDev_t *dev, const uint8_t *txData, uint32_t txLen, + uint8_t *rxData, uint32_t rxLen, uint32_t dummyClocks) +{ + int ret = 0; + uint32_t entry; + uint32_t i; + + /* Enable GQSPI controller */ + GQSPI_EN = 1; + dsb(); + + /* Base entry: bus + CS + SPI mode */ + entry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) | + (dev->cs & GQSPI_GEN_FIFO_CS_MASK) | + GQSPI_GEN_FIFO_MODE_SPI; + + /* === CS Assert + TX Phase (batch all entries, then trigger) === */ + + /* CS assertion entry - just set CS with some idle clocks */ + ret = qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(1)); + + /* TX Phase - send command bytes via immediate data */ + for (i = 0; i < txLen && ret == 0; i++) { + uint32_t txEntry = entry | GQSPI_GEN_FIFO_TX | + GQSPI_GEN_FIFO_IMM(txData[i]); + ret = qspi_gen_fifo_push(txEntry); + } + + /* Trigger and wait for TX to complete */ + if (ret == 0) { + ret = qspi_gen_fifo_start_and_wait(); + } + + /* Dummy clocks phase (for fast read commands) */ + if (ret == 0 && dummyClocks > 0) { + ret = qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(dummyClocks)); + if (ret == 0) { + ret = qspi_gen_fifo_start_and_wait(); + } + } + + /* === RX Phase === */ + if (dev->stripe) { + /* Striped mode: IMM(1) reads 1 byte from each flash = 2 bytes total */ + for (i = 0; i < rxLen && ret == 0; i += 2) { + uint32_t rxEntry = entry | GQSPI_GEN_FIFO_RX | + GQSPI_GEN_FIFO_DATA_XFER | + GQSPI_GEN_FIFO_STRIPE | + GQSPI_GEN_FIFO_IMM(1); + + ret = qspi_gen_fifo_push(rxEntry); + if (ret == 0) { + ret = qspi_gen_fifo_start_and_wait(); + } + if (ret == 0) { + /* Read 2 bytes (one from each flash, interleaved) */ + ret = qspi_fifo_rx(&rxData[i], 2); + } + } + } else { + /* Single flash: read 1 byte at a time */ + for (i = 0; i < rxLen && ret == 0; i++) { + uint32_t rxEntry = entry | GQSPI_GEN_FIFO_RX | + GQSPI_GEN_FIFO_DATA_XFER | + GQSPI_GEN_FIFO_IMM(1); + + ret = qspi_gen_fifo_push(rxEntry); + if (ret == 0) { + ret = qspi_gen_fifo_start_and_wait(); + } + if (ret == 0) { + ret = qspi_fifo_rx(&rxData[i], 1); + } + } + } + + /* === CS Deassert === */ + /* Remove CS bits from entry for deassert */ + entry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) | GQSPI_GEN_FIFO_MODE_SPI; + qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(1)); + qspi_gen_fifo_start_and_wait(); + + /* Disable controller */ + GQSPI_EN = 0; + dsb(); + + return ret; +} + +/* Write page data to flash (for page programming) */ +static int qspi_write_page(QspiDev_t *dev, const uint8_t *cmd, uint32_t cmdLen, + const uint8_t *data, uint32_t dataLen) +{ + int ret = 0; + uint32_t entry; + uint32_t i; + + /* Enable GQSPI controller */ + GQSPI_EN = 1; + dsb(); + + /* Base entry: bus + CS + SPI mode (page program uses SPI mode) */ + entry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) | + (dev->cs & GQSPI_GEN_FIFO_CS_MASK) | + GQSPI_GEN_FIFO_MODE_SPI; + + /* CS assertion */ + ret = qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(1)); + + /* TX Phase - send command bytes (includes address) via immediate mode */ + for (i = 0; i < cmdLen && ret == 0; i++) { + uint32_t txEntry = entry | GQSPI_GEN_FIFO_TX | + GQSPI_GEN_FIFO_IMM(cmd[i]); + ret = qspi_gen_fifo_push(txEntry); + } + + /* Trigger and wait for command to complete */ + if (ret == 0) { + ret = qspi_gen_fifo_start_and_wait(); + } + + /* TX Phase - send data via TX FIFO (not immediate mode) */ + if (ret == 0 && dataLen > 0) { + uint32_t txEntry = entry | GQSPI_GEN_FIFO_TX | GQSPI_GEN_FIFO_DATA_XFER; + + /* Note: stripe mode is handled externally by de-interleaving data + * and writing to each flash separately */ + while (dataLen > 0 && ret == 0) { + uint32_t chunkLen = (dataLen > 255) ? 255 : dataLen; + uint32_t chunkEntry = txEntry | GQSPI_GEN_FIFO_IMM(chunkLen); + + ret = qspi_gen_fifo_push(chunkEntry); + if (ret != 0) break; + + /* Start GenFIFO processing so it drains TX FIFO as we fill it */ + GQSPI_CFG |= GQSPI_CFG_START_GEN_FIFO; + dsb(); + + /* Push data to TX FIFO */ + ret = qspi_fifo_tx(data, chunkLen); + if (ret != 0) break; + + /* Wait for GenFIFO to complete */ + ret = qspi_wait_genfifo_empty(); + + data += chunkLen; + dataLen -= chunkLen; + } + } + + /* CS Deassert */ + entry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) | GQSPI_GEN_FIFO_MODE_SPI; + qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(1)); + qspi_gen_fifo_start_and_wait(); + + /* Disable controller */ + GQSPI_EN = 0; + dsb(); + + return ret; +} + +/* Read flash ID */ +static int qspi_read_id(QspiDev_t *dev, uint8_t *id, uint32_t len) +{ + uint8_t cmd[1]; + int ret; + + cmd[0] = FLASH_CMD_READ_ID; + ret = qspi_transfer(dev, cmd, 1, id, len, 0); + + return ret; +} + +/* Read flash status register */ +static int qspi_read_status(QspiDev_t *dev, uint8_t *status) +{ + uint8_t cmd[1]; + uint8_t data[4]; /* Space for 2 bytes from each chip */ + int ret; + QspiDev_t tmpDev; + + /* For dual parallel, read status from each chip separately */ + if (dev->stripe) { + /* Read from lower chip */ + tmpDev = *dev; + tmpDev.bus = GQSPI_GEN_FIFO_BUS_LOW; + tmpDev.cs = GQSPI_GEN_FIFO_CS_LOWER; + tmpDev.stripe = 0; + cmd[0] = FLASH_CMD_READ_STATUS; + ret = qspi_transfer(&tmpDev, cmd, 1, &data[0], 1, 0); + if (ret != 0) return ret; + + /* Read from upper chip */ + tmpDev.bus = GQSPI_GEN_FIFO_BUS_UP; + tmpDev.cs = GQSPI_GEN_FIFO_CS_UPPER; + ret = qspi_transfer(&tmpDev, cmd, 1, &data[1], 1, 0); + if (ret != 0) return ret; + + /* AND the status from both chips */ + *status = data[0] & data[1]; + return 0; + } + + cmd[0] = FLASH_CMD_READ_STATUS; + ret = qspi_transfer(dev, cmd, 1, data, 1, 0); + if (ret == 0) { + *status = data[0]; + } + return ret; +} + +/* Read flash flag status register */ +static int qspi_read_flag_status(QspiDev_t *dev, uint8_t *status) +{ + uint8_t cmd[1]; + uint8_t data[4]; + int ret; + QspiDev_t tmpDev; + + /* For dual parallel, read status from each chip separately */ + if (dev->stripe) { + /* Read from lower chip */ + tmpDev = *dev; + tmpDev.bus = GQSPI_GEN_FIFO_BUS_LOW; + tmpDev.cs = GQSPI_GEN_FIFO_CS_LOWER; + tmpDev.stripe = 0; + cmd[0] = FLASH_CMD_READ_FLAG_STATUS; + ret = qspi_transfer(&tmpDev, cmd, 1, &data[0], 1, 0); + if (ret != 0) return ret; + + /* Read from upper chip */ + tmpDev.bus = GQSPI_GEN_FIFO_BUS_UP; + tmpDev.cs = GQSPI_GEN_FIFO_CS_UPPER; + ret = qspi_transfer(&tmpDev, cmd, 1, &data[1], 1, 0); + if (ret != 0) return ret; + + /* AND the status from both chips */ + *status = data[0] & data[1]; + return 0; + } + + cmd[0] = FLASH_CMD_READ_FLAG_STATUS; + ret = qspi_transfer(dev, cmd, 1, data, 1, 0); + if (ret == 0) { + *status = data[0]; + } + return ret; +} + +/* Wait for flash ready (not busy) */ +static int qspi_wait_ready(QspiDev_t *dev) +{ + uint8_t status = 0; + uint32_t timeout = GQSPI_FLASH_READY_TRIES; + int ret; + + while (timeout-- > 0) { + ret = qspi_read_flag_status(dev, &status); + if (ret == 0 && (status & FLASH_FSR_READY)) { + return 0; + } + } + QSPI_DEBUG_PRINTF("QSPI: Flash ready timeout\n"); + return -1; +} + +/* Write Enable */ +static int qspi_write_enable(QspiDev_t *dev) +{ + uint8_t cmd[1]; + uint8_t status = 0; + int ret; + uint32_t timeout = GQSPI_FLASH_READY_TRIES; + QspiDev_t tmpDev; + + cmd[0] = FLASH_CMD_WRITE_ENABLE; + + /* For dual parallel, send write enable to both chips separately */ + if (dev->stripe) { + /* Send to lower chip */ + tmpDev = *dev; + tmpDev.bus = GQSPI_GEN_FIFO_BUS_LOW; + tmpDev.cs = GQSPI_GEN_FIFO_CS_LOWER; + tmpDev.stripe = 0; + ret = qspi_transfer(&tmpDev, cmd, 1, NULL, 0, 0); + if (ret != 0) return ret; + + /* Send to upper chip */ + tmpDev.bus = GQSPI_GEN_FIFO_BUS_UP; + tmpDev.cs = GQSPI_GEN_FIFO_CS_UPPER; + ret = qspi_transfer(&tmpDev, cmd, 1, NULL, 0, 0); + if (ret != 0) return ret; + } else { + ret = qspi_transfer(dev, cmd, 1, NULL, 0, 0); + if (ret != 0) return ret; + } + + /* Wait for WEL bit to be set */ + while (timeout-- > 0) { + ret = qspi_read_status(dev, &status); + if (ret == 0 && (status & FLASH_SR_WEL)) { + return 0; + } + } + QSPI_DEBUG_PRINTF("QSPI: Write enable timeout\n"); + return -1; +} + +/* Write Disable */ +static int qspi_write_disable(QspiDev_t *dev) +{ + uint8_t cmd[1]; + + cmd[0] = FLASH_CMD_WRITE_DISABLE; + return qspi_transfer(dev, cmd, 1, NULL, 0, 0); +} + +/* Enter 4-byte address mode */ +static int qspi_enter_4byte_addr(QspiDev_t *dev) +{ + uint8_t cmd[1]; + int ret; + + qspi_wait_ready(dev); + ret = qspi_write_enable(dev); + if (ret != 0) return ret; + + cmd[0] = FLASH_CMD_ENTER_4B_MODE; + ret = qspi_transfer(dev, cmd, 1, NULL, 0, 0); + QSPI_DEBUG_PRINTF("QSPI: Enter 4-byte mode: ret=%d\n", ret); + + if (ret == 0) { + ret = qspi_wait_ready(dev); + } + qspi_write_disable(dev); + return ret; +} + +#ifdef TEST_EXT_FLASH +#ifndef TEST_EXT_ADDRESS +#define TEST_EXT_ADDRESS 0x2800000 /* 40MB */ +#endif + +static int test_ext_flash(QspiDev_t* dev) +{ + int ret; + uint32_t i; + uint8_t pageData[FLASH_PAGE_SIZE * 4]; + + (void)dev; + wolfBoot_printf("Testing ext flash at 0x%x...\n", TEST_EXT_ADDRESS); + +#ifndef TEST_FLASH_READONLY + /* Erase sector */ + ret = ext_flash_erase(TEST_EXT_ADDRESS, FLASH_SECTOR_SIZE); + wolfBoot_printf("Erase Sector: Ret %d\n", ret); + + /* Write Pages */ + for (i = 0; i < sizeof(pageData); i++) { + pageData[i] = (i & 0xff); + } + ret = ext_flash_write(TEST_EXT_ADDRESS, pageData, sizeof(pageData)); + wolfBoot_printf("Write Page: Ret %d\n", ret); +#endif /* !TEST_FLASH_READONLY */ + + /* Read page */ + memset(pageData, 0, sizeof(pageData)); + ret = ext_flash_read(TEST_EXT_ADDRESS, pageData, sizeof(pageData)); + wolfBoot_printf("Read Page: Ret %d\n", ret); + if (ret != 0) { + wolfBoot_printf("Flash read failed!\n"); + return ret; + } + + /* Print first 32 bytes of data */ + wolfBoot_printf("Data: "); + for (i = 0; i < 32 && i < sizeof(pageData); i++) { + wolfBoot_printf("%02x ", pageData[i]); + } + wolfBoot_printf("...\n"); + +#ifndef TEST_FLASH_READONLY + wolfBoot_printf("Checking pattern...\n"); + /* Check data */ + for (i = 0; i < sizeof(pageData); i++) { + if (pageData[i] != (i & 0xff)) { + wolfBoot_printf("Check Data @ %d failed: got 0x%02x, expected 0x%02x\n", + i, pageData[i], (i & 0xff)); + return -1; + } + } + wolfBoot_printf("Flash Test Passed!\n"); +#else + wolfBoot_printf("Flash Read Test Complete (readonly mode)\n"); +#endif + + return ret; +} +#endif /* TEST_EXT_FLASH */ + +/* Initialize QSPI controller */ +static void qspi_init(void) +{ + uint32_t cfg; + uint8_t id[4]; + int ret; + + QSPI_DEBUG_PRINTF("QSPI: Initializing (base=0x%lx)...\n", + (unsigned long)VERSAL_QSPI_BASE); + + /* Read initial state left by PLM */ + cfg = GQSPI_CFG; + QSPI_DEBUG_PRINTF("QSPI: PLM state - CFG=0x%08x ISR=0x%08x\n", + cfg, GQSPI_ISR); + + /* Disable controller during reconfiguration */ + GQSPI_EN = 0; + dsb(); + + /* Select GQSPI mode (not linear LQSPI) */ + GQSPI_SEL = GQSPI_SEL_GQSPI; + dsb(); + + /* Don't reset FIFOs - just drain any stale data by reading RXD */ + while (GQSPI_ISR & GQSPI_IXR_RX_FIFO_NOT_EMPTY) { + (void)GQSPI_RXD; /* Discard any stale RX data */ + } + + /* Clear all interrupt status bits */ + GQSPI_ISR = GQSPI_IXR_ALL_MASK; + dsb(); + + /* Preserve PLM's CFG but switch to IO mode for our transfers + * PLM: 0xA0080010 = DMA mode | manual start | WP_HOLD | CLK_POL + * Key: Keep manual start mode (bit 29) and clock settings */ + cfg = (cfg & ~GQSPI_CFG_MODE_EN_MASK); /* Clear mode bits */ + cfg |= GQSPI_CFG_MODE_EN_IO; /* Set IO mode */ + GQSPI_CFG = cfg; + dsb(); + + /* Set thresholds */ + GQSPI_TX_THRESH = 1; + GQSPI_RX_THRESH = 1; + GQSPI_GF_THRESH = 16; + + QSPI_DEBUG_PRINTF("QSPI: After config - CFG=0x%08x\n", GQSPI_CFG); + + /* Configure device for single flash (lower) first */ + qspiDev.mode = GQSPI_GEN_FIFO_MODE_SPI; + qspiDev.bus = GQSPI_GEN_FIFO_BUS_LOW; + qspiDev.cs = GQSPI_GEN_FIFO_CS_LOWER; + qspiDev.stripe = 0; + + memset(id, 0, sizeof(id)); + ret = qspi_read_id(&qspiDev, id, 3); + wolfBoot_printf("QSPI: Lower ID: %02x %02x %02x\n", id[0], id[1], id[2]); + + /* Enter 4-byte address mode for lower flash */ + ret = qspi_enter_4byte_addr(&qspiDev); + if (ret != 0) { + QSPI_DEBUG_PRINTF("QSPI: 4-byte mode failed (lower)\n"); + } + + /* Read ID from upper flash */ + qspiDev.bus = GQSPI_GEN_FIFO_BUS_UP; + qspiDev.cs = GQSPI_GEN_FIFO_CS_UPPER; + + memset(id, 0, sizeof(id)); + ret = qspi_read_id(&qspiDev, id, 3); + wolfBoot_printf("QSPI: Upper ID: %02x %02x %02x\n", id[0], id[1], id[2]); + + /* Enter 4-byte address mode for upper flash */ + ret = qspi_enter_4byte_addr(&qspiDev); + if (ret != 0) { + QSPI_DEBUG_PRINTF("QSPI: 4-byte mode failed (upper)\n"); + } + + /* Configure for dual parallel operation */ + qspiDev.mode = GQSPI_GEN_FIFO_MODE_SPI; + qspiDev.bus = GQSPI_GEN_FIFO_BUS_BOTH; + qspiDev.cs = GQSPI_GEN_FIFO_CS_BOTH; + qspiDev.stripe = GQSPI_GEN_FIFO_STRIPE; + + /* QSPI bare-metal driver */ + wolfBoot_printf("QSPI Init: Ref=%dMHz, Div=%d, Bus=%d, IO=%s\n", + GQSPI_CLK_REF/1000000, + (2 << GQSPI_CLK_DIV), + (GQSPI_CLK_REF / (2 << GQSPI_CLK_DIV)), + #ifdef GQSPI_MODE_IO + "Poll" + #else + "DMA" + #endif + ); + + qspi_initialized = 1; + +#ifdef TEST_EXT_FLASH + test_ext_flash(&qspiDev); +#endif +} + +#endif /* EXT_FLASH */ + + /* ============================================================================ * HAL Public Interface * ============================================================================ @@ -279,7 +982,9 @@ void hal_init(void) wolfBoot_printf("Current EL: %d\n", current_el()); wolfBoot_printf("Timer Freq: %lu Hz\n", (unsigned long)timer_get_freq()); - /* TODO: Initialize flash controller (OSPI/SD) */ +#ifdef EXT_FLASH + qspi_init(); +#endif } void hal_prepare_boot(void) @@ -373,7 +1078,7 @@ int RAMFUNCTION hal_flash_erase(uintptr_t address, int len) /* ============================================================================ - * External Flash Support (STUBS) + * External Flash Interface * ============================================================================ */ @@ -381,41 +1086,249 @@ int RAMFUNCTION hal_flash_erase(uintptr_t address, int len) void ext_flash_lock(void) { - /* Stub */ + /* No-op - flash protection handled elsewhere */ } void ext_flash_unlock(void) { - /* Stub */ + /* No-op - flash protection handled elsewhere */ +} + +/* Write to a single flash chip - used for dual parallel writes */ +static int ext_flash_write_chip(uintptr_t addr, const uint8_t *data, int len, + uint32_t bus, uint32_t cs) +{ + int ret = 0; + uint8_t cmd[5]; + uint32_t xferSz, page, pages; + QspiDev_t tmpDev; + + tmpDev.mode = GQSPI_GEN_FIFO_MODE_SPI; + tmpDev.bus = bus; + tmpDev.cs = cs; + tmpDev.stripe = 0; + + /* Write by page */ + pages = ((len + (FLASH_PAGE_SIZE - 1)) / FLASH_PAGE_SIZE); + for (page = 0; page < pages && ret == 0; page++) { + ret = qspi_write_enable(&tmpDev); + if (ret != 0) break; + + xferSz = len; + if (xferSz > FLASH_PAGE_SIZE) + xferSz = FLASH_PAGE_SIZE; + + /* Page Program with 4-byte address */ + cmd[0] = FLASH_CMD_PAGE_PROG_4B; + cmd[1] = ((addr + page * FLASH_PAGE_SIZE) >> 24) & 0xFF; + cmd[2] = ((addr + page * FLASH_PAGE_SIZE) >> 16) & 0xFF; + cmd[3] = ((addr + page * FLASH_PAGE_SIZE) >> 8) & 0xFF; + cmd[4] = (addr + page * FLASH_PAGE_SIZE) & 0xFF; + + /* Send command + data */ + ret = qspi_write_page(&tmpDev, cmd, 5, + data + (page * FLASH_PAGE_SIZE), xferSz); + if (ret != 0) break; + + ret = qspi_wait_ready(&tmpDev); + qspi_write_disable(&tmpDev); + len -= xferSz; + } + + return ret; } int ext_flash_write(uintptr_t address, const uint8_t *data, int len) { - (void)address; - (void)data; - (void)len; + int ret = 0; + uint8_t cmd[5]; + uint32_t xferSz, page, pages; + uintptr_t addr; + uint32_t i; - wolfBoot_printf("ext_flash_write: STUB\n"); - return -1; + if (!qspi_initialized) { + return -1; + } + + if (qspiDev.stripe) { + /* For dual parallel: split data and write to each flash separately */ + uint8_t *lower_data = NULL; + uint8_t *upper_data = NULL; + uint32_t half_len = (len + 1) / 2; + uintptr_t flash_addr = address / 2; + + /* Allocate temp buffers for split data */ + /* Note: For production, would use static buffer or stack */ + static uint8_t lower_buf[FLASH_PAGE_SIZE * 4]; + static uint8_t upper_buf[FLASH_PAGE_SIZE * 4]; + + if (len > (int)sizeof(lower_buf) * 2) { + QSPI_DEBUG_PRINTF("ext_flash_write: len too large\n"); + return -1; + } + + lower_data = lower_buf; + upper_data = upper_buf; + + /* De-interleave data: even bytes to lower, odd bytes to upper */ + for (i = 0; i < (uint32_t)len; i++) { + if (i & 1) { + upper_data[i / 2] = data[i]; + } else { + lower_data[i / 2] = data[i]; + } + } + + /* Write to lower flash */ + ret = ext_flash_write_chip(flash_addr, lower_data, half_len, + GQSPI_GEN_FIFO_BUS_LOW, GQSPI_GEN_FIFO_CS_LOWER); + if (ret != 0) return ret; + + /* Write to upper flash */ + ret = ext_flash_write_chip(flash_addr, upper_data, half_len, + GQSPI_GEN_FIFO_BUS_UP, GQSPI_GEN_FIFO_CS_UPPER); + return ret; + } + + /* Single flash write */ + pages = ((len + (FLASH_PAGE_SIZE - 1)) / FLASH_PAGE_SIZE); + for (page = 0; page < pages && ret == 0; page++) { + ret = qspi_write_enable(&qspiDev); + if (ret != 0) break; + + xferSz = len; + if (xferSz > FLASH_PAGE_SIZE) + xferSz = FLASH_PAGE_SIZE; + + addr = address + (page * FLASH_PAGE_SIZE); + + /* Page Program with 4-byte address */ + cmd[0] = FLASH_CMD_PAGE_PROG_4B; + cmd[1] = (addr >> 24) & 0xFF; + cmd[2] = (addr >> 16) & 0xFF; + cmd[3] = (addr >> 8) & 0xFF; + cmd[4] = addr & 0xFF; + + /* Send command + data */ + ret = qspi_write_page(&qspiDev, cmd, 5, + data + (page * FLASH_PAGE_SIZE), xferSz); + if (ret != 0) break; + + ret = qspi_wait_ready(&qspiDev); + qspi_write_disable(&qspiDev); + len -= xferSz; + } + + return ret; } int ext_flash_read(uintptr_t address, uint8_t *data, int len) { - (void)address; - (void)data; - (void)len; + uint8_t cmd[5]; + int ret; + uintptr_t addr = address; + uint32_t i; - wolfBoot_printf("ext_flash_read: STUB\n"); - return -1; + if (!qspi_initialized) { + return -1; + } + + if (qspiDev.stripe) { + /* For dual parallel: read from each chip separately and interleave */ + static uint8_t lower_buf[FLASH_PAGE_SIZE * 4]; + static uint8_t upper_buf[FLASH_PAGE_SIZE * 4]; + uint32_t half_len = (len + 1) / 2; + QspiDev_t tmpDev; + + if (len > (int)sizeof(lower_buf) * 2) { + return -1; + } + + addr = address / 2; /* Flash address for each chip */ + + cmd[0] = FLASH_CMD_READ_4B; + cmd[1] = (addr >> 24) & 0xFF; + cmd[2] = (addr >> 16) & 0xFF; + cmd[3] = (addr >> 8) & 0xFF; + cmd[4] = addr & 0xFF; + + /* Read from lower chip */ + tmpDev.mode = GQSPI_GEN_FIFO_MODE_SPI; + tmpDev.bus = GQSPI_GEN_FIFO_BUS_LOW; + tmpDev.cs = GQSPI_GEN_FIFO_CS_LOWER; + tmpDev.stripe = 0; + ret = qspi_transfer(&tmpDev, cmd, 5, lower_buf, half_len, 0); + if (ret != 0) return ret; + + /* Read from upper chip */ + tmpDev.bus = GQSPI_GEN_FIFO_BUS_UP; + tmpDev.cs = GQSPI_GEN_FIFO_CS_UPPER; + ret = qspi_transfer(&tmpDev, cmd, 5, upper_buf, half_len, 0); + if (ret != 0) return ret; + + /* Interleave: even positions from lower, odd positions from upper */ + for (i = 0; i < (uint32_t)len; i++) { + if (i & 1) { + data[i] = upper_buf[i / 2]; + } else { + data[i] = lower_buf[i / 2]; + } + } + return 0; + } + + /* Single flash read */ + cmd[0] = FLASH_CMD_READ_4B; + cmd[1] = (addr >> 24) & 0xFF; + cmd[2] = (addr >> 16) & 0xFF; + cmd[3] = (addr >> 8) & 0xFF; + cmd[4] = addr & 0xFF; + + ret = qspi_transfer(&qspiDev, cmd, 5, data, len, 0); + + return ret; } int ext_flash_erase(uintptr_t address, int len) { - (void)address; - (void)len; + int ret = 0; + uint8_t cmd[5]; + uintptr_t addr; - wolfBoot_printf("ext_flash_erase: STUB\n"); - return -1; + if (!qspi_initialized) { + return -1; + } + + while (len > 0 && ret == 0) { + addr = address; + if (qspiDev.stripe) { + /* For dual parallel the address divide by 2 */ + addr /= 2; + } + + ret = qspi_write_enable(&qspiDev); + if (ret != 0) break; + + /* Sector Erase with 4-byte address */ + cmd[0] = FLASH_CMD_SECTOR_ERASE_4B; + cmd[1] = (addr >> 24) & 0xFF; + cmd[2] = (addr >> 16) & 0xFF; + cmd[3] = (addr >> 8) & 0xFF; + cmd[4] = addr & 0xFF; + + ret = qspi_transfer(&qspiDev, cmd, 5, NULL, 0, 0); + QSPI_DEBUG_PRINTF("ext_flash_erase: addr=0x%lx\n", (unsigned long)address); + + if (ret == 0) { + ret = qspi_wait_ready(&qspiDev); + } + qspi_write_disable(&qspiDev); + + address += FLASH_SECTOR_SIZE; + len -= FLASH_SECTOR_SIZE; + } + + return ret; } #endif /* EXT_FLASH */ diff --git a/hal/versal.h b/hal/versal.h index 60e28029..96f3f756 100644 --- a/hal/versal.h +++ b/hal/versal.h @@ -291,12 +291,167 @@ /* ============================================================================ - * OSPI (Octal SPI) Flash Controller + * QSPI (Quad SPI) Flash Controller - GQSPI * ============================================================================ - * Versal uses OSPI instead of QSPI (though QSPI mode is supported) - * Stub definitions - to be implemented + * Versal QSPI is similar to ZynqMP but at different base address. + * VMK180 uses dual parallel MT25QU01GBBB (128MB each, 256MB total) */ -#define VERSAL_OSPI_BASE 0xF1010000UL +#define VERSAL_QSPI_BASE 0xF1030000UL + +/* QSPI Enable Register (at base, not +0x100) */ +#define QSPI_EN_REG (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x14))) + +/* GQSPI Registers (at offset 0x100 from QSPI base) */ +#define GQSPI_CFG (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x100))) +#define GQSPI_ISR (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x104))) +#define GQSPI_IER (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x108))) +#define GQSPI_IDR (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x10C))) +#define GQSPI_IMR (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x110))) +#define GQSPI_EN (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x114))) +#define GQSPI_TXD (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x11C))) +#define GQSPI_RXD (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x120))) +#define GQSPI_TX_THRESH (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x128))) +#define GQSPI_RX_THRESH (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x12C))) +#define GQSPI_GPIO (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x130))) +#define GQSPI_LPBK_DLY_ADJ (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x138))) +#define GQSPI_GEN_FIFO (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x140))) +#define GQSPI_SEL (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x144))) +#define GQSPI_FIFO_CTRL (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x14C))) +#define GQSPI_GF_THRESH (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x150))) +#define GQSPI_POLL_CFG (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x154))) +#define GQSPI_P_TIMEOUT (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x158))) +#define GQSPI_XFER_STS (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x15C))) +#define GQSPI_DATA_DLY_ADJ (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x1F8))) +#define GQSPI_MOD_ID (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x1FC))) + +/* DMA Registers (at offset 0x800 from QSPI base) */ +#define GQSPIDMA_DST (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x800))) +#define GQSPIDMA_SIZE (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x804))) +#define GQSPIDMA_STS (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x808))) +#define GQSPIDMA_CTRL (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x80C))) +#define GQSPIDMA_ISR (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x814))) +#define GQSPIDMA_IER (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x818))) +#define GQSPIDMA_IDR (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x81C))) +#define GQSPIDMA_IMR (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x820))) +#define GQSPIDMA_CTRL2 (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x824))) +#define GQSPIDMA_DST_MSB (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x828))) + +/* Tap Delay Bypass Register - Versal specific location */ +#define IOU_TAPDLY_BYPASS (*((volatile uint32_t*)(VERSAL_QSPI_BASE + 0x03C))) +#define IOU_TAPDLY_BYPASS_LQSPI_RX (1UL << 2) + +/* GQSPI_CFG: Configuration register bits */ +#define GQSPI_CFG_CLK_POL (1UL << 1) +#define GQSPI_CFG_CLK_PH (1UL << 2) +#define GQSPI_CFG_BAUD_RATE_DIV_MASK (7UL << 3) +#define GQSPI_CFG_BAUD_RATE_DIV(d) (((d) << 3) & GQSPI_CFG_BAUD_RATE_DIV_MASK) +#define GQSPI_CFG_WP_HOLD (1UL << 19) +#define GQSPI_CFG_EN_POLL_TIMEOUT (1UL << 20) +#define GQSPI_CFG_ENDIAN (1UL << 26) +#define GQSPI_CFG_START_GEN_FIFO (1UL << 28) +#define GQSPI_CFG_GEN_FIFO_START_MODE (1UL << 29) +#define GQSPI_CFG_MODE_EN_MASK (3UL << 30) +#define GQSPI_CFG_MODE_EN_IO (0UL << 30) +#define GQSPI_CFG_MODE_EN_DMA (2UL << 30) + +/* GQSPI_ISR/IER/IDR: Interrupt bits */ +#define GQSPI_IXR_POLL_TIME_EXPIRE (1UL << 1) +#define GQSPI_IXR_TX_FIFO_NOT_FULL (1UL << 2) +#define GQSPI_IXR_TX_FIFO_FULL (1UL << 3) +#define GQSPI_IXR_RX_FIFO_NOT_EMPTY (1UL << 4) +#define GQSPI_IXR_RX_FIFO_FULL (1UL << 5) +#define GQSPI_IXR_GEN_FIFO_EMPTY (1UL << 7) +#define GQSPI_IXR_TX_FIFO_EMPTY (1UL << 8) +#define GQSPI_IXR_GEN_FIFO_NOT_FULL (1UL << 9) +#define GQSPI_IXR_GEN_FIFO_FULL (1UL << 10) +#define GQSPI_IXR_RX_FIFO_EMPTY (1UL << 11) +#define GQSPI_IXR_ALL_MASK 0x0FBEU +#define GQSPI_ISR_WR_TO_CLR_MASK 0x02U + +/* GenFIFO Entry bits */ +#define GQSPI_GEN_FIFO_IMM_MASK 0xFFU +#define GQSPI_GEN_FIFO_IMM(x) ((x) & GQSPI_GEN_FIFO_IMM_MASK) +#define GQSPI_GEN_FIFO_DATA_XFER (1UL << 8) +#define GQSPI_GEN_FIFO_EXP (1UL << 9) +#define GQSPI_GEN_FIFO_MODE_SPI (1UL << 10) +#define GQSPI_GEN_FIFO_MODE_DSPI (2UL << 10) +#define GQSPI_GEN_FIFO_MODE_QSPI (3UL << 10) +#define GQSPI_GEN_FIFO_MODE_MASK (3UL << 10) +#define GQSPI_GEN_FIFO_CS_LOWER (1UL << 12) +#define GQSPI_GEN_FIFO_CS_UPPER (1UL << 13) +#define GQSPI_GEN_FIFO_CS_MASK (3UL << 12) +#define GQSPI_GEN_FIFO_CS_BOTH (3UL << 12) +#define GQSPI_GEN_FIFO_BUS_LOW (1UL << 14) +#define GQSPI_GEN_FIFO_BUS_UP (1UL << 15) +#define GQSPI_GEN_FIFO_BUS_BOTH (3UL << 14) +#define GQSPI_GEN_FIFO_BUS_MASK (3UL << 14) +#define GQSPI_GEN_FIFO_TX (1UL << 16) +#define GQSPI_GEN_FIFO_RX (1UL << 17) +#define GQSPI_GEN_FIFO_STRIPE (1UL << 18) +#define GQSPI_GEN_FIFO_POLL (1UL << 19) + +/* DMA Control bits */ +#define GQSPIDMA_CTRL_DEF 0x403FFA00UL +#define GQSPIDMA_CTRL2_DEF 0x081BFFF8UL +#define GQSPIDMA_CTRL_ENDIANNESS (1UL << 23) + +/* DMA Status bits */ +#define GQSPIDMA_STS_BUSY (1UL << 0) +#define GQSPIDMA_STS_WTC (7UL << 13) + +/* DMA Interrupt bits */ +#define GQSPIDMA_ISR_DONE (1UL << 1) +#define GQSPIDMA_ISR_ALL_MASK 0xFEU + +/* FIFO Control bits */ +#define GQSPI_FIFO_CTRL_RST_GEN (1UL << 0) +#define GQSPI_FIFO_CTRL_RST_TX (1UL << 1) +#define GQSPI_FIFO_CTRL_RST_RX (1UL << 2) + +/* QSPI Select */ +#define GQSPI_SEL_GQSPI (1UL << 0) + +/* Flash Commands */ +#define FLASH_CMD_READ_ID 0x9F +#define FLASH_CMD_READ_STATUS 0x05 +#define FLASH_CMD_READ_FLAG_STATUS 0x70 +#define FLASH_CMD_WRITE_ENABLE 0x06 +#define FLASH_CMD_WRITE_DISABLE 0x04 +#define FLASH_CMD_READ 0x03 +#define FLASH_CMD_FAST_READ 0x0B +#define FLASH_CMD_QUAD_READ 0x6B +#define FLASH_CMD_READ_4B 0x13 +#define FLASH_CMD_FAST_READ_4B 0x0C +#define FLASH_CMD_QUAD_READ_4B 0x6C +#define FLASH_CMD_PAGE_PROG 0x02 +#define FLASH_CMD_PAGE_PROG_4B 0x12 +#define FLASH_CMD_SECTOR_ERASE 0xD8 +#define FLASH_CMD_SECTOR_ERASE_4B 0xDC +#define FLASH_CMD_ENTER_4B_MODE 0xB7 +#define FLASH_CMD_EXIT_4B_MODE 0xE9 + +/* Flash Status Register bits */ +#define FLASH_SR_WIP (1UL << 0) /* Write In Progress */ +#define FLASH_SR_WEL (1UL << 1) /* Write Enable Latch */ +#define FLASH_FSR_READY (1UL << 7) /* Flag Status Ready */ + +/* Flash Configuration for MT25QU01GBBB */ +#define FLASH_JEDEC_MICRON 0x20 +#define FLASH_JEDEC_MT25QU01G 0x20BB21 +#define FLASH_PAGE_SIZE 256 +#define FLASH_SECTOR_SIZE 0x10000 /* 64KB */ +#define FLASH_DEVICE_SIZE 0x8000000 /* 128MB per chip */ + +/* QSPI Timing */ +#define GQSPI_CLK_DIV 2 /* Divide by 8 (300MHz / 8 = 37.5MHz) */ +#define GQSPI_DUMMY_CLOCKS 8 +#define GQSPI_TIMEOUT_TRIES 100000 +#define GQSPIDMA_TIMEOUT_TRIES 100000000 +#define GQSPI_FLASH_READY_TRIES 1000000 /* Erase can take seconds */ + +/* QSPI DMA alignment */ +#define GQSPI_DMA_ALIGN 32 +#define XALIGNED(x) __attribute__((aligned(x))) /* ============================================================================ diff --git a/src/boot_aarch64_start.S b/src/boot_aarch64_start.S index 5c52b405..eb0c2325 100644 --- a/src/boot_aarch64_start.S +++ b/src/boot_aarch64_start.S @@ -681,10 +681,19 @@ MMUTableL2: .set SECT, SECT+0x200000 .endr +#ifdef TARGET_versal +/* Versal: LPD/PMC peripherals at 0xF0000000 - 0xF7FFFFFF (includes QSPI @ 0xF1030000) */ +.rept 0x040 /* 0xF000_0000 - 0xF7FF_FFFF */ +.8byte SECT + Device /* 128MB LPD peripherals (QSPI, I2C, etc) */ +.set SECT, SECT+0x200000 +.endr +#else +/* ZynqMP: This region is reserved */ .rept 0x040 /* 0xF000_0000 - 0xF7FF_FFFF */ .8byte SECT + reserved /* 128MB Reserved */ .set SECT, SECT+0x200000 .endr +#endif .rept 0x8 /* 0xF800_0000 - 0xF8FF_FFFF */ .8byte SECT + Device /* 16MB coresight */