/* versal.c * * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfBoot. * * wolfBoot is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * wolfBoot is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA * * AMD Versal ACAP HAL implementation for wolfBoot * Target: VMK180 Evaluation Board (VM1802 Versal Prime) * * Features: * - UART driver (ARM PL011 UART / UARTPSV) * - ARM Generic Timer * - QSPI flash driver (GQSPI - dual parallel MT25QU01GBBB) * * QSPI Driver Notes: * This driver is a port of the ZynqMP GQSPI driver (hal/zynq.c) with the * following Versal-specific adaptations: * * 1. Different base address (0xF1030000 vs 0xFF0F0000) * 2. Tap delay bypass register is in QSPI block (not IOU_SLCR) * 3. Preserves PLM's QSPI configuration instead of full reset * 4. UART init skips MIO/clock setup when EL2 (PLM already did it) * * The register layout, GenFIFO format, and DMA interface are identical * to ZynqMP since both use the same Xilinx GQSPI IP block. * * See hal/versal.h for detailed comparison with ZynqMP. */ #ifdef TARGET_versal #include #include #include "hal.h" #include "hal/versal.h" #include "image.h" #include "printf.h" #include "fdt.h" #ifndef ARCH_AARCH64 # error "wolfBoot versal HAL: wrong architecture. Please compile with ARCH=AARCH64." #endif /* ============================================================================ * Linux Boot Arguments * ============================================================================ * DTB fixup for kernel command line. Override LINUX_BOOTARGS or * LINUX_BOOTARGS_ROOT in your config to customize. */ /* Linux kernel command line arguments */ #ifndef LINUX_BOOTARGS #ifndef LINUX_BOOTARGS_ROOT /* Default Versal SD layout: rootfs on partition 2. Configurations that use * the 4-partition MBR layout with OFP_A/OFP_B slots * (boot / OFP_A / OFP_B / rootfs, e.g. config/examples/zynqmp_sdcard.config) * should override LINUX_BOOTARGS_ROOT to "/dev/mmcblk0p4". */ #define LINUX_BOOTARGS_ROOT "/dev/mmcblk0p2" #endif #define LINUX_BOOTARGS \ "earlycon root=" LINUX_BOOTARGS_ROOT " rootwait" #endif /* ============================================================================ * UART Driver * ============================================================================ * ARM PL011 UART controller * Note: In JTAG boot mode, the PLM doesn't run so UART may be inaccessible. * Timeouts are added to prevent infinite loops. */ #ifdef DEBUG_UART /* Timeout to prevent infinite loops if UART is inaccessible (e.g., JTAG boot) */ #define UART_TIMEOUT 10000 /** * Calculate baud rate divisors for ARM PL011 UART * Formula: baud = UART_CLK / (16 * divisor) * divisor = IBRD + (FBRD / 64) * IBRD = integer part of (UART_CLK / (16 * baud)) * FBRD = integer part of ((fractional * 64) + 0.5) */ static void uart_calc_baud(uint32_t ref_clk, uint32_t baud, uint32_t *ibrd, uint32_t *fbrd) { uint32_t divisor_x64; /* Calculate divisor * 64 to get fractional part */ /* divisor = ref_clk / (16 * baud) */ /* divisor_x64 = (ref_clk * 64) / (16 * baud) = (ref_clk * 4) / baud */ divisor_x64 = (ref_clk * 4) / baud; /* Integer part: divisor_x64 / 64 */ *ibrd = divisor_x64 >> 6; /* Fractional part: divisor_x64 % 64 (already in correct format) */ *fbrd = divisor_x64 & 0x3F; } void uart_init(void) { #if defined(EL2_HYPERVISOR) && EL2_HYPERVISOR == 1 /* When booting via PLM -> BL31 -> wolfBoot (EL2), UART is already * fully configured by PLM. Do NOT reinitialize - just use it as-is. * Any reconfiguration at EL2 may fail or corrupt the UART state. */ #else /* Full UART initialization for JTAG boot mode or EL3 boot */ uint32_t ibrd, fbrd; uint32_t lcr; volatile uint32_t timeout; volatile uint32_t *uart_clk_ctrl; volatile uint32_t *uart_rst_ctrl; int rx_pin, tx_pin; /* Select which UART to use */ #if defined(DEBUG_UART_NUM) && DEBUG_UART_NUM == 1 uart_clk_ctrl = (volatile uint32_t*)&CRL_UART1_REF_CTRL; uart_rst_ctrl = (volatile uint32_t*)&CRL_RST_UART1; rx_pin = MIO_UART1_RX_PIN; tx_pin = MIO_UART1_TX_PIN; #else uart_clk_ctrl = (volatile uint32_t*)&CRL_UART0_REF_CTRL; uart_rst_ctrl = (volatile uint32_t*)&CRL_RST_UART0; rx_pin = MIO_UART0_RX_PIN; tx_pin = MIO_UART0_TX_PIN; #endif /* Configure MIO pins for UART (required in JTAG boot mode) */ PMC_IOU_SLCR_MIO_PIN(tx_pin) = MIO_UART_TX_CFG; PMC_IOU_SLCR_MIO_PIN(rx_pin) = MIO_UART_RX_CFG; /* Ensure clock is enabled with proper divisor */ *uart_clk_ctrl = 0x02000600; /* Clear UART reset */ *uart_rst_ctrl = 0; /* Delay to let reset clear and clock stabilize */ for (timeout = 1000; timeout > 0; timeout--) __asm__ volatile("nop"); /* ===== Step 1: Disable UART before configuration (per TRM) ===== */ UART_CR = 0; /* Wait for UART to finish any current TX (with timeout) */ timeout = UART_TIMEOUT; while ((UART_FR & UART_FR_BUSY) && --timeout) ; /* ===== Step 2: Flush FIFOs by disabling FEN in LCR ===== */ UART_LCR = 0; /* ===== Step 3: Clear all pending interrupts ===== */ UART_IMSC = 0; /* Disable all interrupts */ UART_ICR = UART_INT_ALL; /* Clear any pending */ /* ===== Step 4: Calculate and set baud rate divisors ===== */ uart_calc_baud(UART_CLK_REF, DEBUG_UART_BAUD, &ibrd, &fbrd); UART_IBRD = ibrd; UART_FBRD = fbrd; /* ===== Step 5: Write LCR to latch baud rate (REQUIRED per TRM!) ===== * The TRM states: "do write of LCR after writing to baud rate registers" * Configure: 8 data bits, 1 stop bit, no parity, FIFOs enabled */ lcr = UART_LCR_WLEN_8 | UART_LCR_FEN; UART_LCR = lcr; /* ===== Step 6: Set FIFO trigger levels ===== */ UART_IFLS = UART_IFLS_RXIFLSEL_1_2 | UART_IFLS_TXIFLSEL_1_2; /* ===== Step 7: Enable UART with TX and RX ===== */ UART_CR = UART_CR_UARTEN | UART_CR_TXE | UART_CR_RXE; /* Small delay to let UART stabilize */ for (timeout = 100; timeout > 0; timeout--) __asm__ volatile("nop"); #endif /* EL2_HYPERVISOR */ } static void uart_tx(uint8_t c) { volatile uint32_t timeout = UART_TIMEOUT; /* Wait for TX FIFO to have space (not full) with timeout */ while ((UART_FR & UART_FR_TXFF) && --timeout) ; /* Write character to data register */ UART_DR = c; } void uart_write(const char *buf, uint32_t len) { uint32_t i; volatile uint32_t timeout; for (i = 0; i < len; i++) { if (buf[i] == '\n') { uart_tx('\r'); } uart_tx((uint8_t)buf[i]); } /* Wait for transmit FIFO to empty (with timeout) */ timeout = UART_TIMEOUT; while (!(UART_FR & UART_FR_TXFE) && --timeout) ; /* Wait for UART to finish transmitting (with timeout) */ timeout = UART_TIMEOUT; while ((UART_FR & UART_FR_BUSY) && --timeout) ; } #else #define uart_init() do {} while(0) #endif /* DEBUG_UART */ /* ============================================================================ * Timer Functions (ARM Generic Timer) * ============================================================================ */ /* Get current timer count (physical counter) */ static inline uint64_t timer_get_count(void) { uint64_t cntpct; __asm__ volatile("mrs %0, cntpct_el0" : "=r" (cntpct)); return cntpct; } /* Get timer frequency with fallback to TIMER_CLK_FREQ if not configured */ static inline uint64_t timer_get_freq(void) { uint64_t cntfrq; __asm__ volatile("mrs %0, cntfrq_el0" : "=r" (cntfrq)); return cntfrq ? cntfrq : TIMER_CLK_FREQ; } /* Get current time in milliseconds */ uint64_t hal_timer_ms(void) { return (timer_get_count() * 1000ULL) / timer_get_freq(); } /* Delay for specified number of microseconds */ void hal_delay_us(uint32_t us) { uint64_t freq = timer_get_freq(); uint64_t target = timer_get_count() + ((uint64_t)us * freq) / 1000000ULL; while (timer_get_count() < target) ; } /* Get current time in microseconds (for benchmarking) */ uint64_t hal_get_timer_us(void) { return (timer_get_count() * 1000000ULL) / timer_get_freq(); } /* ============================================================================ * QSPI Flash Driver (GQSPI) * ============================================================================ * Bare-metal QSPI driver for Versal VMK180. * Hardware: Dual parallel MT25QU01GBBB (128MB each, 256MB total). * * This driver is adapted from the ZynqMP GQSPI driver (hal/zynq.c). * Both platforms use the same Xilinx GQSPI IP block with identical: * - Register offsets (GQSPI at +0x100, DMA at +0x800 from base) * - GenFIFO entry format (command, address, data, stripe bits) * - Interrupt status bits and DMA interface * * Versal-specific differences from ZynqMP: * - Base address: 0xF1030000 (vs 0xFF0F0000 on ZynqMP) * - Tap delay register: In QSPI block (vs IOU_SLCR on ZynqMP) * - Initialization: Preserves PLM config (vs full reset on ZynqMP) * * Supported modes (same as ZynqMP): * - DMA mode (default) or IO polling mode (GQSPI_MODE_IO) * - Quad SPI (4-bit), Dual SPI (2-bit), or Standard SPI (1-bit) * - 4-byte addressing for flash >16MB (GQPI_USE_4BYTE_ADDR) * - Dual parallel with hardware striping (GQPI_USE_DUAL_PARALLEL) * - EXP (exponent) length mode for large transfers * * Clock: 300MHz ref / (2 << DIV) = 75MHz default (DIV=1) * MT25QU01GBBB supports up to 133MHz for Quad Output Read. */ #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; /* Macros to configure QspiDev_t for single-chip access in dual-parallel mode */ #define QSPI_DEV_LOWER(tmpDev, srcDev) do { \ (tmpDev) = *(srcDev); \ (tmpDev).bus = GQSPI_GEN_FIFO_BUS_LOW; \ (tmpDev).cs = GQSPI_GEN_FIFO_CS_LOWER; \ (tmpDev).stripe = 0; \ } while(0) #define QSPI_DEV_UPPER(tmpDev) do { \ (tmpDev).bus = GQSPI_GEN_FIFO_BUS_UP; \ (tmpDev).cs = GQSPI_GEN_FIFO_CS_UPPER; \ } while(0) 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, const uint8_t *writeData, uint32_t writeLen); 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; } /* Calculate EXP mode for large transfers (returns actual transfer size) * For transfers > 255 bytes, use exponent mode where IMM = power of 2 * Pattern from zynq.c qspi_calc_exp() */ static uint32_t qspi_calc_exp(uint32_t xferSz, uint32_t *reg_genfifo) { uint32_t expval; *reg_genfifo &= ~(GQSPI_GEN_FIFO_IMM_MASK | GQSPI_GEN_FIFO_EXP); if (xferSz > GQSPI_GEN_FIFO_IMM_MASK) { /* Use exponent mode (max is 2^28 for DMA) */ for (expval = 28; expval >= 8; expval--) { /* Find highest power of 2 that fits */ if (xferSz >= (1UL << expval)) { *reg_genfifo |= GQSPI_GEN_FIFO_EXP; *reg_genfifo |= GQSPI_GEN_FIFO_IMM(expval); xferSz = (1UL << expval); break; } } } else { /* Use immediate length mode */ *reg_genfifo |= GQSPI_GEN_FIFO_IMM(xferSz); } return xferSz; } /* Chip select control */ static int qspi_cs(QspiDev_t *dev, int assert) { uint32_t entry; int ret; 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); ret = qspi_gen_fifo_push(entry); if (ret == 0) { ret = qspi_gen_fifo_start_and_wait(); } return ret; } /* DMA temporary buffer for unaligned transfers (DMA is default, IO is optional) */ #ifndef GQSPI_MODE_IO static uint8_t XALIGNED(GQSPI_DMA_ALIGN) dma_tmpbuf[GQSPI_DMA_TMPSZ]; /* Flush data cache for DMA coherency */ static void flush_dcache_range(uintptr_t start, uintptr_t end) { /* ARM64: Clean and invalidate by virtual address to PoC */ uintptr_t addr; for (addr = (start & ~(GQSPI_DMA_ALIGN - 1)); addr < end; addr += GQSPI_DMA_ALIGN) { __asm__ volatile("dc civac, %0" : : "r"(addr) : "memory"); } __asm__ volatile("dsb sy" : : : "memory"); } /* Wait for DMA completion * Returns: 0 on success, -1 on timeout */ static int qspi_dma_wait(void) { uint32_t timeout = GQSPIDMA_TIMEOUT_TRIES; while (!(GQSPIDMA_ISR & GQSPIDMA_ISR_DONE) && --timeout) ; if (timeout == 0) { QSPI_DEBUG_PRINTF("QSPI: DMA timeout\n"); /* Clear any pending interrupts */ GQSPIDMA_ISR = GQSPIDMA_ISR_ALL_MASK; return -1; } /* Clear DMA done interrupt */ GQSPIDMA_ISR = GQSPIDMA_ISR_DONE; return 0; } #endif /* !GQSPI_MODE_IO */ /* 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; } /* RX using FIFO polling (IO mode) - helper to avoid code duplication */ static int qspi_rx_io_mode(uint8_t *rxData, uint32_t rxLen, uint32_t *rxEntry) { int ret = 0; uint32_t remaining = rxLen; uint32_t offset = 0; uint32_t xferSz; while (ret == 0 && remaining > 0) { xferSz = qspi_calc_exp(remaining, rxEntry); ret = qspi_gen_fifo_push(*rxEntry); if (ret == 0) { ret = qspi_gen_fifo_start_and_wait(); } if (ret == 0) { ret = qspi_fifo_rx(&rxData[offset], xferSz); } offset += xferSz; remaining -= xferSz; } return ret; } /* 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, const uint8_t *writeData, uint32_t writeLen) { int ret = 0; uint32_t entry; uint32_t i; uint32_t chunkLen; uint32_t txEntry, chunkEntry; const uint8_t *writePtr; uint32_t remaining, xferSz; uint32_t rxEntry; /* Enable GQSPI controller */ /* Set DMA mode for fast/quad reads (indicated by dummyClocks > 0) unless IO mode forced */ if (dummyClocks > 0 && rxLen > 0) { #ifndef GQSPI_MODE_IO GQSPI_CFG = (GQSPI_CFG & ~GQSPI_CFG_MODE_EN_MASK) | GQSPI_CFG_MODE_EN_DMA; #endif } 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) * Use QSPI mode if dummy clocks are present (indicates Quad Read) */ if (ret == 0 && dummyClocks > 0) { uint32_t dummyEntry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) | (dev->cs & GQSPI_GEN_FIFO_CS_MASK) | GQSPI_QSPI_MODE | GQSPI_GEN_FIFO_DATA_XFER | GQSPI_GEN_FIFO_IMM(dummyClocks); ret = qspi_gen_fifo_push(dummyEntry); if (ret == 0) { ret = qspi_gen_fifo_start_and_wait(); } } /* === TX Write Data Phase === */ if (ret == 0 && writeLen > 0 && writeData != NULL) { txEntry = entry | GQSPI_GEN_FIFO_TX | GQSPI_GEN_FIFO_DATA_XFER | (dev->stripe & GQSPI_GEN_FIFO_STRIPE); writePtr = writeData; chunkLen = writeLen; while (chunkLen > 0 && ret == 0) { uint32_t chunk = (chunkLen > 255) ? 255 : chunkLen; chunkEntry = txEntry | GQSPI_GEN_FIFO_IMM(chunk); 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(writePtr, chunk); if (ret != 0) break; /* Wait for GenFIFO to complete */ ret = qspi_wait_genfifo_empty(); writePtr += chunk; chunkLen -= chunk; } } /* === RX Phase === */ if (ret == 0 && rxLen > 0) { /* Use QSPI mode for RX if dummy clocks were used (Quad Read) */ if (dummyClocks > 0) { rxEntry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) | (dev->cs & GQSPI_GEN_FIFO_CS_MASK) | GQSPI_QSPI_MODE | GQSPI_GEN_FIFO_RX | GQSPI_GEN_FIFO_DATA_XFER | (dev->stripe & GQSPI_GEN_FIFO_STRIPE); #ifndef GQSPI_MODE_IO /* DMA mode: Use DMA for RX phase */ if ((GQSPI_CFG & GQSPI_CFG_MODE_EN_MASK) == GQSPI_CFG_MODE_EN_DMA) { uint8_t *dmaPtr; uint32_t dmaLen; uint32_t rxDone = 0; int useTemp = 0; /* Check alignment - DMA requires cache-line aligned buffer. * If unaligned or not a multiple of 4 bytes, use temp buffer. * CRITICAL: GenFIFO transfer size must match DMA size! */ useTemp = (((uintptr_t)rxData & (GQSPI_DMA_ALIGN - 1)) || (rxLen & 3)) ? 1 : 0; /* Run the RX in passes: through the temp buffer (at most * sizeof(dma_tmpbuf) per pass) when the destination is * unaligned, directly into rxData otherwise. Only the * bytes actually DMA'd in a pass may be copied out. */ while (ret == 0 && rxDone < rxLen) { uint32_t copyLen = rxLen - rxDone; if (useTemp) { dmaPtr = dma_tmpbuf; if (copyLen > sizeof(dma_tmpbuf)) copyLen = sizeof(dma_tmpbuf); /* Bounds check before alignment to prevent integer overflow */ dmaLen = (copyLen + GQSPI_DMA_ALIGN - 1) & ~(GQSPI_DMA_ALIGN - 1); if (dmaLen > sizeof(dma_tmpbuf)) dmaLen = sizeof(dma_tmpbuf); if (copyLen > dmaLen) copyLen = dmaLen; } else { dmaPtr = rxData + rxDone; dmaLen = copyLen; } /* GenFIFO must request the same number of bytes as DMA expects */ remaining = dmaLen; /* Setup DMA destination */ GQSPIDMA_DST = ((uintptr_t)dmaPtr & 0xFFFFFFFFUL); GQSPIDMA_DST_MSB = ((uintptr_t)dmaPtr >> 32); GQSPIDMA_SIZE = dmaLen; /* Enable DMA done interrupt */ GQSPIDMA_IER = GQSPIDMA_ISR_DONE; /* Flush dcache for DMA coherency */ flush_dcache_range((uintptr_t)dmaPtr, (uintptr_t)dmaPtr + dmaLen); /* Push all GenFIFO entries first (use EXP mode for large transfers) */ while (ret == 0 && remaining > 0) { xferSz = qspi_calc_exp(remaining, &rxEntry); ret = qspi_gen_fifo_push(rxEntry); remaining -= xferSz; } /* Trigger GenFIFO */ if (ret == 0) { GQSPI_CFG |= GQSPI_CFG_START_GEN_FIFO; dsb(); } /* Wait for DMA completion */ if (ret == 0) { ret = qspi_dma_wait(); } /* Invalidate cache after DMA */ flush_dcache_range((uintptr_t)dmaPtr, (uintptr_t)dmaPtr + dmaLen); /* Copy from temp buffer if needed (only the bytes this * pass actually transferred) */ if (ret == 0 && useTemp) { memcpy(rxData + rxDone, dmaPtr, copyLen); } rxDone += copyLen; } } else { /* IO mode: Use FIFO polling (fallback when DMA mode not enabled) */ ret = qspi_rx_io_mode(rxData, rxLen, &rxEntry); } #else /* GQSPI_MODE_IO */ /* IO mode: Use FIFO polling */ ret = qspi_rx_io_mode(rxData, rxLen, &rxEntry); #endif /* !GQSPI_MODE_IO */ } else { /* SPI mode for simple reads */ rxEntry = entry | GQSPI_GEN_FIFO_RX | GQSPI_GEN_FIFO_DATA_XFER | (dev->stripe & GQSPI_GEN_FIFO_STRIPE) | GQSPI_GEN_FIFO_IMM(1); uint32_t readSz = dev->stripe ? 2 : 1; for (i = 0; i < rxLen && ret == 0; i += readSz) { 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], readSz); } } } } /* === 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(); /* Switch back to IO mode if DMA was used and disable controller */ #ifndef GQSPI_MODE_IO if ((GQSPI_CFG & GQSPI_CFG_MODE_EN_MASK) == GQSPI_CFG_MODE_EN_DMA) { GQSPI_CFG = (GQSPI_CFG & ~GQSPI_CFG_MODE_EN_MASK) | GQSPI_CFG_MODE_EN_IO; } #endif 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, NULL, 0); return ret; } /* Generic flash register read helper (handles dual parallel) */ static int qspi_read_register(QspiDev_t *dev, uint8_t cmd, uint8_t *status) { uint8_t cmdByte[1]; uint8_t data[2]; int ret; QspiDev_t tmpDev; cmdByte[0] = cmd; /* For dual parallel, read from each chip separately and AND the results */ if (dev->stripe) { QSPI_DEV_LOWER(tmpDev, dev); ret = qspi_transfer(&tmpDev, cmdByte, 1, &data[0], 1, 0, NULL, 0); if (ret != 0) return ret; QSPI_DEV_UPPER(tmpDev); ret = qspi_transfer(&tmpDev, cmdByte, 1, &data[1], 1, 0, NULL, 0); if (ret != 0) return ret; *status = data[0] & data[1]; return 0; } /* Single chip mode */ ret = qspi_transfer(dev, cmdByte, 1, data, 1, 0, NULL, 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_register(dev, FLASH_CMD_READ_FLAG_STATUS, &status); if (ret == 0 && (status & FLASH_FSR_READY)) { return 0; } /* Add small delay every 100 polls to reduce bus traffic during erase/write ops */ if ((timeout % 100) == 0) { hal_delay_us(10); } } 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) { QSPI_DEV_LOWER(tmpDev, dev); ret = qspi_transfer(&tmpDev, cmd, sizeof(cmd), NULL, 0, 0, NULL, 0); if (ret != 0) return ret; QSPI_DEV_UPPER(tmpDev); ret = qspi_transfer(&tmpDev, cmd, sizeof(cmd), NULL, 0, 0, NULL, 0); if (ret != 0) return ret; } else { ret = qspi_transfer(dev, cmd, sizeof(cmd), NULL, 0, 0, NULL, 0); if (ret != 0) return ret; } /* Wait for WEL bit to be set */ while (timeout-- > 0) { ret = qspi_read_register(dev, FLASH_CMD_READ_STATUS, &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, sizeof(cmd), NULL, 0, 0, NULL, 0); } #if GQPI_USE_4BYTE_ADDR == 1 /* 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, sizeof(cmd), NULL, 0, 0, NULL, 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; } /* Exit 4-byte address mode */ static int qspi_exit_4byte_addr(QspiDev_t *dev) { uint8_t cmd[1]; int ret; ret = qspi_write_enable(dev); if (ret != 0) return ret; cmd[0] = FLASH_CMD_EXIT_4B_MODE; ret = qspi_transfer(dev, cmd, sizeof(cmd), NULL, 0, 0, NULL, 0); QSPI_DEBUG_PRINTF("QSPI: Exit 4-byte mode: ret=%d\n", ret); if (ret == 0) { ret = qspi_wait_ready(dev); } qspi_write_disable(dev); return ret; } #endif #ifdef TEST_EXT_FLASH #ifndef TEST_EXT_ADDRESS #define TEST_EXT_ADDRESS 0x2800000 /* 40MB */ #endif #ifndef TEST_EXT_SIZE #define TEST_EXT_SIZE (FLASH_PAGE_SIZE * 4) #endif static int test_ext_flash(void) { int ret; uint32_t i; uint8_t pageData[TEST_EXT_SIZE]; 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, WOLFBOOT_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; /* 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 set IO mode for initial commands (ID read, etc.) * PLM: 0xA0080010 = DMA mode | manual start | WP_HOLD | CLK_POL * Key: Keep manual start mode (bit 29) and clock settings * Note: ext_flash_read() will switch to DMA mode for reads if not in IO mode */ cfg = (cfg & ~GQSPI_CFG_MODE_EN_MASK); /* Clear mode bits */ cfg |= GQSPI_CFG_MODE_EN_IO; /* Set IO mode for init */ GQSPI_CFG = cfg; dsb(); /* Set thresholds */ GQSPI_TX_THRESH = 1; GQSPI_RX_THRESH = 1; GQSPI_GF_THRESH = 16; #ifndef GQSPI_MODE_IO /* Initialize DMA controller - this was missing compared to zynq.c! * Without this, DMA transfers can hang or timeout because the DMA * controller is in an undefined state after PLM handoff. */ GQSPIDMA_CTRL = GQSPIDMA_CTRL_DEF; GQSPIDMA_CTRL2 = GQSPIDMA_CTRL2_DEF; GQSPIDMA_ISR = GQSPIDMA_ISR_ALL_MASK; /* Clear all pending interrupts */ GQSPIDMA_IER = GQSPIDMA_ISR_ALL_MASK; /* Enable all interrupts */ dsb(); #endif /* 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)); (void)qspi_read_id(&qspiDev, id, 3); wolfBoot_printf("QSPI: Lower ID: %02x %02x %02x\n", id[0], id[1], id[2]); #if GQPI_USE_4BYTE_ADDR == 1 /* 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"); } #endif #if GQPI_USE_DUAL_PARALLEL == 1 /* Read ID from upper flash */ qspiDev.bus = GQSPI_GEN_FIFO_BUS_UP; qspiDev.cs = GQSPI_GEN_FIFO_CS_UPPER; memset(id, 0, sizeof(id)); (void)qspi_read_id(&qspiDev, id, 3); wolfBoot_printf("QSPI: Upper ID: %02x %02x %02x\n", id[0], id[1], id[2]); #if GQPI_USE_4BYTE_ADDR == 1 /* 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"); } #endif /* 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; #endif /* QSPI bare-metal driver info */ { #if GQSPI_QSPI_MODE == GQSPI_GEN_FIFO_MODE_QSPI const char *mode_str = "Quad"; #elif GQSPI_QSPI_MODE == GQSPI_GEN_FIFO_MODE_DSPI const char *mode_str = "Dual"; #else const char *mode_str = "SPI"; #endif #ifdef GQSPI_MODE_IO const char *xfer_str = "Poll"; #else const char *xfer_str = "DMA"; #endif wolfBoot_printf("QSPI: %dMHz, %s, %s\n", (GQSPI_CLK_REF / (2 << GQSPI_CLK_DIV)) / 1000000, mode_str, xfer_str); } qspi_initialized = 1; #ifdef TEST_EXT_FLASH test_ext_flash(); #endif } #endif /* EXT_FLASH */ /* ============================================================================ * HAL Public Interface * ============================================================================ */ void hal_init(void) { uart_init(); #if defined(DEBUG_UART) && defined(__WOLFBOOT) wolfBoot_printf( "\n========================================\n" "wolfBoot Secure Boot - AMD Versal\n" #ifndef WOLFBOOT_REPRODUCIBLE_BUILD "Build: " __DATE__ " " __TIME__ "\n" #endif "========================================\n"); wolfBoot_printf("Current EL: %d\n", current_el()); /* BL31 enters wolfBoot with all of DAIF masked (SPSR 0x3c9), so an * asynchronous external abort - e.g. a DDR uncorrectable ECC error * returned to the A72 - stays pending and invisible while the boot * dies downstream. Unmask SError now that the console is up so it is * taken at EL2 and reported by SErrorInterrupt() instead. */ #if defined(EL2_HYPERVISOR) && EL2_HYPERVISOR == 1 if (current_el() == 2) { __asm__ volatile("msr daifclr, #4"); __asm__ volatile("isb"); } #endif #endif #ifdef EXT_FLASH qspi_init(); #endif } void hal_prepare_boot(void) { #if defined(EXT_FLASH) && GQPI_USE_4BYTE_ADDR == 1 /* Exit 4-byte address mode before handing off to application */ qspi_exit_4byte_addr(&qspiDev); #endif /* Flush any pending UART output (with timeout) */ #ifdef DEBUG_UART { volatile uint32_t timeout = UART_TIMEOUT; while (!(UART_FR & UART_FR_TXFE) && --timeout) ; timeout = UART_TIMEOUT; while ((UART_FR & UART_FR_BUSY) && --timeout) ; } #endif /* Clean and invalidate caches for the loaded application. * The application was written to RAM via D-cache, but the CPU will * fetch instructions via I-cache from main memory. We must: * 1. Clean D-cache (flush dirty data to memory) * 2. Invalidate I-cache (ensure fresh instruction fetch) */ /* Clean entire D-cache to Point of Coherency */ __asm__ volatile("dsb sy"); /* Clean D-cache for application region */ { uintptr_t addr; uintptr_t end = WOLFBOOT_LOAD_ADDRESS + APP_CACHE_FLUSH_SIZE; for (addr = WOLFBOOT_LOAD_ADDRESS; addr < end; addr += CACHE_LINE_SIZE) { /* DC CVAC - Clean data cache line by VA to PoC */ __asm__ volatile("dc cvac, %0" : : "r"(addr)); } } /* Data synchronization barrier - ensure clean completes */ __asm__ volatile("dsb sy"); /* Invalidate instruction cache to ensure fresh code is fetched */ __asm__ volatile("ic iallu"); /* Ensure cache invalidation completes before jumping */ __asm__ volatile("dsb sy"); __asm__ volatile("isb"); } #ifdef MMU /** * Get the Device Tree address for the boot partition * Returns the DTS load address in RAM */ void* hal_get_dts_address(void) { #ifdef WOLFBOOT_LOAD_DTS_ADDRESS return (void*)WOLFBOOT_LOAD_DTS_ADDRESS; #else return NULL; #endif } /** * Get the update Device Tree address */ void* hal_get_dts_update_address(void) { #ifdef WOLFBOOT_DTS_UPDATE_ADDRESS return (void*)WOLFBOOT_DTS_UPDATE_ADDRESS; #else return NULL; #endif } #ifdef __WOLFBOOT /** * Fixup Device Tree before booting Linux * * This function modifies the DTB to set bootargs for the kernel. * Called from do_boot() before jumping to the kernel. * * @param dts_addr: Pointer to the device tree blob in memory * @param capacity: Bytes readable/writable at dts_addr; bounds every * in-place fixup made here * @return: 0 on success, negative error code on failure */ int hal_dts_fixup(void* dts_addr, uint32_t capacity) { fdt_ctx ctx; int off, ret; /* Validate the blob against the window it actually occupies. */ ret = fdt_open(&ctx, dts_addr, capacity); if (ret != 0) { wolfBoot_printf("FDT: Invalid header! %d\n", ret); return ret; } wolfBoot_printf("FDT: Size %d\n", (int)fdt_size(&ctx)); /* Reserve free space to allow adding/modifying properties (bootargs * and, when WOLFBOOT_FIT_RAMDISK is in play, linux,initrd-{start,end}). * Sizing comes from WOLFBOOT_FDT_FIXUP_HEADROOM in include/fdt.h. */ ret = fdt_grow(&ctx, WOLFBOOT_FDT_FIXUP_HEADROOM); if (ret != 0) { wolfBoot_printf("FDT: No headroom for fixups (%d)\n", ret); return ret; } /* Find /chosen node; create it only if genuinely missing. Any other * negative return (malformed FDT, etc.) is surfaced directly rather * than masked by a follow-on fdt_add_subnode() failure. */ off = fdt_subnode_offset(&ctx, 0, "chosen"); if (off == -FDT_ERR_NOTFOUND) { off = fdt_add_subnode(&ctx, 0, "chosen"); } if (off < 0) { wolfBoot_printf("FDT: Failed to find/create chosen node (%d)\n", off); return off; } /* Set bootargs property - overrides the PetaLinux default root= with * the wolfBoot partition layout. */ ret = fdt_fixup_str(&ctx, off, "chosen", "bootargs", LINUX_BOOTARGS); if (ret < 0) { wolfBoot_printf("FDT: Failed to set bootargs (%d)\n", ret); return ret; } return 0; } #endif /* __WOLFBOOT */ #endif /* MMU */ #if defined(WOLFBOOT_DUALBOOT) && !defined(WOLFBOOT_NO_PARTITIONS) /** * Get the primary (boot) partition address in flash * Returns the flash address where the boot partition starts */ void* hal_get_primary_address(void) { return (void*)WOLFBOOT_PARTITION_BOOT_ADDRESS; } /** * Get the update partition address in flash * Returns the flash address where the update partition starts */ void* hal_get_update_address(void) { return (void*)WOLFBOOT_PARTITION_UPDATE_ADDRESS; } #endif /* WOLFBOOT_DUALBOOT && !WOLFBOOT_NO_PARTITIONS */ /* ============================================================================ * Flash Functions (STUBS) * ============================================================================ * There is no "internal flash" on the Versal, so these are stubs. */ void RAMFUNCTION hal_flash_unlock(void) { /* Stub - no-op for now */ } void RAMFUNCTION hal_flash_lock(void) { /* Stub - no-op for now */ } int RAMFUNCTION hal_flash_write(uintptr_t address, const uint8_t *data, int len) { (void)address; (void)data; (void)len; return -1; } int RAMFUNCTION hal_flash_erase(uintptr_t address, int len) { (void)address; (void)len; return -1; } #ifdef WOLFBOOT_FPGA_BITSTREAM /* Versal programs the PL with a PDI (Programmable Device Image), not a raw * bitstream. Runtime PL configuration is done by the PLM via the XilLoader * "Load PDI" command (XLOADER_CMD_ID_LOAD_PDI), reached by building an IPI * request to the PLM channel with the DDR PDI address/size and polling the * PLM response. That path is not yet implemented; until it lands, leave * FPGA_BITSTREAM disabled for Versal (the FIT loader treats a failed load * as fatal unless FPGA_NONFATAL is set). */ int hal_fpga_load(uint32_t flags, uintptr_t addr, size_t size) { (void)flags; (void)addr; (void)size; wolfBoot_printf("Versal FPGA/PDI load not implemented " "(needs PLM XilLoader Load-PDI IPI)\n"); return -1; } #endif /* WOLFBOOT_FPGA_BITSTREAM */ /* ============================================================================ * External Flash Interface * ============================================================================ */ #ifdef EXT_FLASH void ext_flash_lock(void) { /* No-op - flash protection handled elsewhere */ } void ext_flash_unlock(void) { /* No-op - flash protection handled elsewhere */ } int ext_flash_write(uintptr_t address, const uint8_t *data, int len) { int ret = 0; uint8_t cmd[5]; uint32_t xferSz, page_room; uintptr_t addr; const uint8_t *pageData; if (!qspi_initialized) { return -1; } /* Validate flash address bounds */ if (address >= FLASH_TOTAL_SIZE || (address + len) > FLASH_TOTAL_SIZE) { QSPI_DEBUG_PRINTF("ext_flash_write: address 0x%lx+%d exceeds flash size\n", (unsigned long)address, len); return -1; } QSPI_DEBUG_PRINTF("ext_flash_write: addr=0x%lx, len=%d\n", (unsigned long)address, len); /* Write by page, capping each transfer at the end of the physical * page holding its start address: NOR wraps the write pointer at * the page boundary, so a program crossing it clobbers the start * of the page. */ while (len > 0) { page_room = FLASH_PAGE_SIZE - ((uint32_t)address % FLASH_PAGE_SIZE); xferSz = ((uint32_t)len > page_room) ? page_room : (uint32_t)len; ret = qspi_write_enable(&qspiDev); if (ret != 0) break; addr = address; if (qspiDev.stripe) { /* For dual parallel the address is divided by 2 */ addr /= 2; } /* 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; pageData = data; ret = qspi_transfer(&qspiDev, cmd, sizeof(cmd), NULL, 0, 0, pageData, xferSz); QSPI_DEBUG_PRINTF("Flash Page Write: addr=0x%lx, len=%u, ret=%d\n", (unsigned long)address, xferSz, ret); if (ret != 0) break; ret = qspi_wait_ready(&qspiDev); qspi_write_disable(&qspiDev); if (ret != 0) break; data = pageData + xferSz; address += xferSz; len -= xferSz; } return ret; } int ext_flash_read(uintptr_t address, uint8_t *data, int len) { uint8_t cmd[5]; int ret = 0; uintptr_t addr = address; if (!qspi_initialized) { return -1; } /* Validate flash address bounds */ if (address >= FLASH_TOTAL_SIZE || (address + len) > FLASH_TOTAL_SIZE) { QSPI_DEBUG_PRINTF("ext_flash_read: address 0x%lx+%d exceeds flash size\n", (unsigned long)address, len); return -1; } QSPI_DEBUG_PRINTF("ext_flash_read: addr=0x%lx len=%d\n", (unsigned long)address, len); if (qspiDev.stripe) { /* For dual parallel the address is divided by 2 */ addr /= 2; } /* Use Quad Read command (0x6C) with 4-byte address */ cmd[0] = FLASH_CMD_QUAD_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, sizeof(cmd), data, len, GQSPI_DUMMY_READ, NULL, 0); /* On error, fill buffer with 0xFF to simulate unwritten flash */ if (ret != 0) { memset(data, 0xFF, len); } QSPI_DEBUG_PRINTF("ext_flash_read: ret=%d\n", ret); return (ret == 0) ? len : ret; } int ext_flash_erase(uintptr_t address, int len) { int ret = 0; uint8_t cmd[5]; uintptr_t addr; if (!qspi_initialized) { return -1; } /* Validate flash address bounds */ if (address >= FLASH_TOTAL_SIZE || (address + len) > FLASH_TOTAL_SIZE) { QSPI_DEBUG_PRINTF("ext_flash_erase: address 0x%lx+%d exceeds flash size\n", (unsigned long)address, len); return -1; } QSPI_DEBUG_PRINTF("ext_flash_erase: addr=0x%lx, len=%d\n", (unsigned long)address, len); 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, sizeof(cmd), NULL, 0, 0, NULL, 0); QSPI_DEBUG_PRINTF(" Flash Erase: Ret %d, Address 0x%x\n", ret, 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 */ /* ============================================================================ * SD Card Support (SDHCI) * ============================================================================ * The Versal uses an Arasan SDHCI controller with standard register layout, * unlike PolarFire which uses a Cadence SD4HC controller. The generic SDHCI * driver (src/sdhci.c) expects Cadence register offsets (HRS at 0x000, * SRS at 0x200), so we translate in the HAL register access functions. * * SD1 at 0xF1050000 is the external SD card slot on VMK180. * PLM already initializes the SD controller, so platform init is minimal. * Initial implementation uses polling mode (no GIC setup required). */ #if defined(DISK_SDCARD) || defined(DISK_EMMC) #include "sdhci.h" /* SD controller base address selection: * SD0 (VERSAL_SD0_BASE = 0xF1040000) - internal, typically eMMC * SD1 (VERSAL_SD1_BASE = 0xF1050000) - external SD card slot on VMK180 * Note: VMK180 board does not have eMMC hardware, only SD1 is used. */ #define VERSAL_SDHCI_BASE VERSAL_SD1_BASE /* ============================================================================ * Register Translation: Cadence SD4HC -> Standard SDHCI (Arasan) * ============================================================================ * The generic SDHCI driver (src/sdhci.c) uses Cadence SD4HC register offsets: * - HRS registers at 0x000-0x01F (Cadence-specific: reset, PHY, eMMC mode) * - SRS registers at 0x200-0x2FF (standard SDHCI mapped at offset +0x200) * * Versal uses the Arasan SDHCI controller with standard register layout: * - Standard SDHCI registers at 0x000-0x0FF (no 0x200 offset) * * Translation: * - SRS offsets (>= 0x200): subtract 0x200 to get standard offset * - HRS00 (0x000): map SWR bit to standard Software Reset All (SRA) * - HRS01, HRS04, HRS06: Cadence-specific, not applicable on Versal */ #define CADENCE_SRS_OFFSET 0x200 /* Standard SDHCI Software Reset is in the Clock/Timeout/Reset register */ #define STD_SDHCI_RESET_REG 0x2C /* Clock Control / Timeout / SW Reset */ #define STD_SDHCI_SRA (1U << 24) /* Software Reset for All */ /* Handle reads from Cadence HRS registers (0x000-0x1FF) */ static uint32_t versal_sdhci_hrs_read(uint32_t hrs_offset) { volatile uint8_t *base = (volatile uint8_t *)VERSAL_SDHCI_BASE; switch (hrs_offset) { case 0x000: /* HRS00 - Software Reset */ { /* Map standard SRA (bit 24 of 0x2C) to Cadence SWR (bit 0) */ uint32_t val = *((volatile uint32_t *)(base + STD_SDHCI_RESET_REG)); return (val & STD_SDHCI_SRA) ? 1U : 0U; } case 0x010: /* HRS04 - PHY access (Cadence-specific) */ /* Return ACK set to prevent wait loops from hanging */ return (1U << 26); /* SDHCI_HRS04_UIS_ACK */ default: /* HRS01 (debounce), HRS02, HRS06 (eMMC mode) - not applicable */ return 0; } } /* Handle writes to Cadence HRS registers (0x000-0x1FF) */ static void versal_sdhci_hrs_write(uint32_t hrs_offset, uint32_t val) { volatile uint8_t *base = (volatile uint8_t *)VERSAL_SDHCI_BASE; switch (hrs_offset) { case 0x000: /* HRS00 - Software Reset */ if (val & 1U) { /* SWR bit -> standard SRA */ uint32_t reg = *((volatile uint32_t *)(base + STD_SDHCI_RESET_REG)); reg |= STD_SDHCI_SRA; *((volatile uint32_t *)(base + STD_SDHCI_RESET_REG)) = reg; } break; default: /* HRS01, HRS04, HRS06 - not applicable on Versal, ignore */ break; } } /* Register access functions for generic SDHCI driver. * Translates Cadence SD4HC register offsets to standard Arasan SDHCI layout. */ uint32_t sdhci_reg_read(uint32_t offset) { volatile uint8_t *base = (volatile uint8_t *)VERSAL_SDHCI_BASE; /* Cadence SRS registers (0x200+) -> standard SDHCI (subtract 0x200) */ if (offset >= CADENCE_SRS_OFFSET) { return *((volatile uint32_t *)(base + offset - CADENCE_SRS_OFFSET)); } /* Cadence HRS registers (0x000-0x1FF) -> translate to standard equivalents */ return versal_sdhci_hrs_read(offset); } void sdhci_reg_write(uint32_t offset, uint32_t val) { volatile uint8_t *base = (volatile uint8_t *)VERSAL_SDHCI_BASE; /* Cadence SRS registers (0x200+) -> standard SDHCI (subtract 0x200) */ if (offset >= CADENCE_SRS_OFFSET) { *((volatile uint32_t *)(base + offset - CADENCE_SRS_OFFSET)) = val; return; } /* Cadence HRS registers (0x000-0x1FF) -> translate to standard equivalents */ versal_sdhci_hrs_write(offset, val); } /* Platform initialization - called from sdhci_init() * PLM already initializes the SD controller on Versal when booting from SD card, * so we don't need to configure clocks/reset (CRL registers are protected at EL2). * We verify the SDHCI controller is accessible via standard register reads. */ void sdhci_platform_init(void) { #ifdef DEBUG_SDHCI volatile uint8_t *base = (volatile uint8_t *)VERSAL_SDHCI_BASE; uint32_t val; wolfBoot_printf("sdhci_platform_init: SD1 at 0x%x\n", (unsigned int)VERSAL_SDHCI_BASE); /* Read standard SDHCI registers to verify controller access */ val = *((volatile uint32_t *)(base + 0x24)); /* Present State */ wolfBoot_printf(" Present State: 0x%x\n", (unsigned int)val); val = *((volatile uint32_t *)(base + 0x40)); /* Capabilities */ wolfBoot_printf(" Capabilities: 0x%x\n", (unsigned int)val); (void)val; #endif /* PLM already configured SD1 - no clock/reset setup needed */ } /* Platform interrupt setup - called from sdhci_init() * Using polling mode for simplicity - no GIC setup needed */ void sdhci_platform_irq_init(void) { /* Polling mode: no interrupt setup required * GIC interrupt support can be added later if needed */ #ifdef DEBUG_SDHCI wolfBoot_printf("sdhci_platform_irq_init: Using polling mode\n"); #endif } /* Platform bus mode selection - called from sdhci_init() */ void sdhci_platform_set_bus_mode(int is_emmc) { (void)is_emmc; #ifdef DEBUG_SDHCI wolfBoot_printf("sdhci_platform_set_bus_mode: is_emmc=%d\n", is_emmc); #endif /* Nothing additional needed for Versal - mode is set in generic driver */ } #endif /* DISK_SDCARD || DISK_EMMC */ #endif /* TARGET_versal */