wolfBoot/hal/zynq7000.c

1276 lines
44 KiB
C

/* zynq7000.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
*/
#ifdef TARGET_zynq7000
#include <stdint.h>
#include <string.h>
#include <target.h>
#include "image.h"
#include "printf.h"
#include "hal/zynq7000.h"
#include "hal_fpga.h"
#ifndef ARCH_ARM
# error "wolfBoot zynq7000 HAL: wrong architecture selected. Please compile with ARCH=ARM."
#endif
#ifdef DEBUG_UART
void uart_init(void)
{
/* Disable interrupts */
Z7_UART_IDR = Z7_UART_ISR_MASK;
/* Disable TX/RX */
Z7_UART_CR = (Z7_UART_CR_TX_DIS | Z7_UART_CR_RX_DIS);
/* Clear ISR */
Z7_UART_ISR = Z7_UART_ISR_MASK;
/* 8N1 */
Z7_UART_MR = Z7_UART_MR_8N1;
/* Half-FIFO trigger levels (XUartPs FIFO depth = 64) */
Z7_UART_RXWM = 32;
Z7_UART_TXWM = 32;
/* RX timeout disabled */
Z7_UART_RXTOUT = 0;
/* baud = ref_clk / (BR_GEN * (BR_DIV + 1)) */
Z7_UART_BR_GEN = UART_CLK_REF / (DEBUG_UART_BAUD * (DEBUG_UART_DIV + 1));
Z7_UART_BR_DIV = DEBUG_UART_DIV;
/* Reset TX/RX paths */
Z7_UART_CR = (Z7_UART_CR_TXRST | Z7_UART_CR_RXRST);
/* Enable TX/RX */
Z7_UART_CR = (Z7_UART_CR_TX_EN | Z7_UART_CR_RX_EN);
}
void uart_write(const char* buf, unsigned int sz)
{
unsigned int pos = 0;
while (sz-- > 0) {
char c = buf[pos++];
if (c == '\n') {
while (Z7_UART_SR & Z7_UART_SR_TXFULL)
;
Z7_UART_FIFO = (uint32_t)'\r';
}
while (Z7_UART_SR & Z7_UART_SR_TXFULL)
;
Z7_UART_FIFO = (uint32_t)c;
}
while (!(Z7_UART_SR & Z7_UART_SR_TXEMPTY))
;
}
#endif /* DEBUG_UART */
#ifdef EXT_FLASH
/* ===================== QSPI flash driver (XQspiPs) =====================
* Bare-metal driver for the Zynq-7000 "Linear/Static" QSPI controller.
* The driver mixes two operating modes by purpose:
* - I/O mode (single-bit SPI through the controller's TX/RX FIFOs):
* used for all short commands - JEDEC ID, status, write enable,
* sector erase, page program. Bytes go in/out of the FIFOs one
* transfer at a time, fully software-driven.
* - Linear/XIP mode (memory-mapped reads at 0xFC000000+): used by
* spi_flash_read() for bulk reads. The controller drives CS, sends
* cmd+addr+dummy, and exposes the flash as a memory window so
* image loading is a plain memcpy from that window. Single-byte
* accesses to the window are unreliable (the linear engine is
* burst-aware), so spi_flash_read() issues 32-bit AXI loads.
*
* qspi_init() resets and reconfigures the controller, leaving it in
* I/O mode; spi_flash_read() switches into linear mode for the duration
* of the read and returns the controller to I/O mode afterwards. The
* driver assumes FSBL has already configured QSPI ref clock and MIO
* pins (typical when wolfBoot is loaded by FSBL). */
/* JEDEC SPI-NOR command codes (subset used by wolfBoot) */
#define SPI_CMD_RDID 0x9F
#define SPI_CMD_RDSR 0x05
#define SPI_CMD_WREN 0x06
#define SPI_CMD_WRDI 0x04
#define SPI_CMD_READ 0x03
#define SPI_CMD_FAST_READ 0x0B /* requires 8 dummy clocks */
#define SPI_CMD_PAGE_PROGRAM 0x02
#define SPI_CMD_SECTOR_ERASE 0xD8 /* 64 KB erase */
#define SPI_STATUS_WIP 0x01 /* write-in-progress */
#define SPI_STATUS_WEL 0x02 /* write-enable latch */
#define SPI_NOR_PAGE_SIZE 256U
#define SPI_NOR_SECTOR_SIZE 0x10000U /* 64 KB */
static void qspi_drain_rxfifo(void)
{
while (Z7_QSPI_ISR & Z7_QSPI_ISR_RXNEMPTY)
(void)Z7_QSPI_RXD;
}
static void qspi_cs_assert(void)
{
/* PCS [13:10] = 0b1110 -> CS0 asserted. */
Z7_QSPI_CR = (Z7_QSPI_CR & ~Z7_QSPI_CR_PCS_MASK) | Z7_QSPI_CR_PCS_CS0;
}
static void qspi_cs_release(void)
{
/* PCS [13:10] = 0b1111 -> all CS deasserted. */
Z7_QSPI_CR |= Z7_QSPI_CR_PCS_NONE;
}
/* Transfer up to 4 bytes. Uses TXD(n) for partial sends WITHOUT RX (so the
* flash sees exactly n clock cycles of MOSI), and TXD0 (4-byte) when RX is
* needed (so the controller pushes a full 4-byte RX FIFO entry we can
* decode). Mirrors u-boot zynq_qspi.c (offsets[3] when rx_buf, offsets[len-1]
* otherwise). */
static void qspi_xfer4(const uint8_t *tx, uint8_t *rx, unsigned int nbytes)
{
uint32_t txw = 0xFFFFFFFFU;
uint32_t rxw;
unsigned int i;
if (nbytes > 4)
nbytes = 4;
if (nbytes == 0)
return;
if (tx != NULL) {
for (i = 0; i < nbytes; i++) {
txw &= ~((uint32_t)0xFFU << (i * 8));
txw |= ((uint32_t)tx[i]) << (i * 8);
}
}
qspi_drain_rxfifo();
if (rx != NULL || nbytes == 4) {
/* Receive path or full 4-byte send: use TXD0. */
Z7_QSPI_TXD0 = txw;
} else {
/* Send-only short transfer: pick TXD1/TXD2/TXD3 to clock exactly
* nbytes out the wire (no padding). The TX byte(s) are at the LSB. */
switch (nbytes) {
case 1: Z7_QSPI_TXD1 = txw; break;
case 2: Z7_QSPI_TXD2 = txw; break;
case 3: Z7_QSPI_TXD3 = txw; break;
default: Z7_QSPI_TXD0 = txw; break;
}
}
while (!(Z7_QSPI_ISR & Z7_QSPI_ISR_RXNEMPTY))
;
rxw = Z7_QSPI_RXD;
#ifdef DEBUG_QSPI_BYTE
{
const char hex[] = "0123456789abcdef";
char line[48];
unsigned int p = 0;
line[p++] = '[';
for (i = 0; i < nbytes; i++) {
uint8_t b = (uint8_t)(txw >> (i * 8));
line[p++] = hex[(b >> 4) & 0xF];
line[p++] = hex[(b >> 0) & 0xF];
}
line[p++] = ' ';
line[p++] = '/';
line[p++] = ' ';
for (i = 0; i < nbytes; i++) {
uint8_t b = (uint8_t)(rxw >> (i * 8));
line[p++] = hex[(b >> 4) & 0xF];
line[p++] = hex[(b >> 0) & 0xF];
}
line[p++] = ']';
line[p++] = '\n';
uart_write(line, p);
}
#endif
if (rx != NULL) {
for (i = 0; i < nbytes; i++)
rx[i] = (uint8_t)(rxw >> (i * 8));
}
}
static int qspi_xfer(const uint8_t *tx, uint8_t *rx, unsigned int len)
{
unsigned int off = 0;
unsigned int chunk;
qspi_cs_assert();
while (off < len) {
chunk = len - off;
if (chunk > 4)
chunk = 4;
qspi_xfer4((tx != NULL) ? &tx[off] : NULL,
(rx != NULL) ? &rx[off] : NULL,
chunk);
off += chunk;
}
qspi_cs_release();
return 0;
}
/* I/O mode: used for short cmd-only ops (JEDEC, RDSR, WREN, sector erase,
* page program initiation). Reads use Linear/XIP mode separately. */
static void qspi_io_mode_setup(void)
{
Z7_QSPI_EN = 0;
Z7_QSPI_IDR = Z7_QSPI_ISR_MASK;
qspi_drain_rxfifo();
Z7_QSPI_ISR = Z7_QSPI_ISR_MASK;
Z7_QSPI_LQSPI_CR = 0; /* leave linear mode */
Z7_QSPI_TXTHR = 1;
Z7_QSPI_RXTHR = 1;
Z7_QSPI_CR = Z7_QSPI_CR_IFMODE
| Z7_QSPI_CR_HOLD_B
| Z7_QSPI_CR_SSFORCE
| Z7_QSPI_CR_PCS_NONE
| Z7_QSPI_CR_FIFO_WIDTH
| Z7_QSPI_CR_BAUD_DIV_8
| Z7_QSPI_CR_MSTREN;
Z7_QSPI_EN = Z7_QSPI_EN_VAL;
}
/* Linear (XIP) mode: hardware-managed reads. Controller asserts CS, sends
* cmd+addr+dummy, returns data via memory-mapped accesses at 0xFC000000+.
* Matches XQspiPs_LinearInit() in qspips_v3_14/src/xqspips_hw.c. */
static void qspi_linear_mode_setup(void)
{
Z7_QSPI_EN = 0;
Z7_QSPI_IDR = Z7_QSPI_ISR_MASK;
qspi_drain_rxfifo();
Z7_QSPI_ISR = Z7_QSPI_ISR_MASK;
/* CR: IFMODE=1, FIFO=32-bit, MSTREN=1, SSFORCE=1, HOLD_B=1, /4 baud,
* MANSTRTEN=0 (auto-start), CPHA/CPOL=0, PCS=CS0 asserted explicitly
* (the linear-mode controller still drives PCS even with SSFORCE; we
* set the field deterministically rather than relying on the reset
* default). */
Z7_QSPI_CR = Z7_QSPI_CR_IFMODE
| Z7_QSPI_CR_HOLD_B
| Z7_QSPI_CR_SSFORCE
| Z7_QSPI_CR_PCS_CS0
| Z7_QSPI_CR_FIFO_WIDTH
| Z7_QSPI_CR_BAUD_DIV_4
| Z7_QSPI_CR_MSTREN;
/* Single-bit FAST_READ (0x0B) with 1 dummy byte. Avoids needing QE
* bit set in the flash status register. */
Z7_QSPI_LQSPI_CR = 0x8000010BU;
Z7_QSPI_EN = Z7_QSPI_EN_VAL;
}
static void qspi_init(void)
{
qspi_io_mode_setup();
}
static int spi_flash_read_id(uint8_t out[3])
{
uint8_t cmd[4] = { SPI_CMD_RDID, 0, 0, 0 };
uint8_t rx[4] = { 0, 0, 0, 0 };
int rc = qspi_xfer(cmd, rx, sizeof(cmd));
if (rc == 0) {
out[0] = rx[1];
out[1] = rx[2];
out[2] = rx[3];
}
return rc;
}
static int spi_flash_status(uint8_t *status)
{
uint8_t cmd[2] = { SPI_CMD_RDSR, 0 };
uint8_t rx[2] = { 0, 0 };
int rc = qspi_xfer(cmd, rx, sizeof(cmd));
if (rc == 0)
*status = rx[1];
return rc;
}
static int spi_flash_wait_ready(void)
{
uint8_t status = 0xFF;
/* Spin until WIP clears. No timeout: a stuck flash is a board issue. */
do {
if (spi_flash_status(&status) != 0)
return -1;
} while ((status & SPI_STATUS_WIP) != 0);
return 0;
}
static int spi_flash_write_enable(void)
{
uint8_t cmd = SPI_CMD_WREN;
int rc;
rc = qspi_xfer(&cmd, NULL, 1);
if (rc != 0)
return rc;
/* Optional: confirm WEL bit set */
{
uint8_t status = 0;
if (spi_flash_status(&status) != 0)
return -1;
if ((status & SPI_STATUS_WEL) == 0)
return -1;
}
return 0;
}
static int spi_flash_sector_erase(uint32_t address)
{
uint8_t cmd[4];
int rc;
rc = spi_flash_write_enable();
if (rc != 0)
return rc;
cmd[0] = SPI_CMD_SECTOR_ERASE;
cmd[1] = (uint8_t)((address >> 16) & 0xFFU);
cmd[2] = (uint8_t)((address >> 8) & 0xFFU);
cmd[3] = (uint8_t)((address >> 0) & 0xFFU);
rc = qspi_xfer(cmd, NULL, sizeof(cmd));
if (rc != 0)
return rc;
return spi_flash_wait_ready();
}
static int spi_flash_page_program(uint32_t address,
const uint8_t *data,
unsigned int len)
{
/* len must be <= SPI_NOR_PAGE_SIZE and not cross a page boundary */
uint8_t hdr[4];
int rc;
if (len == 0 || len > SPI_NOR_PAGE_SIZE)
return -1;
rc = spi_flash_write_enable();
if (rc != 0)
return rc;
hdr[0] = SPI_CMD_PAGE_PROGRAM;
hdr[1] = (uint8_t)((address >> 16) & 0xFFU);
hdr[2] = (uint8_t)((address >> 8) & 0xFFU);
hdr[3] = (uint8_t)((address >> 0) & 0xFFU);
qspi_cs_assert();
qspi_xfer4(hdr, NULL, 4);
{
unsigned int off = 0;
while (off < len) {
unsigned int chunk = len - off;
if (chunk > 4)
chunk = 4;
qspi_xfer4(&data[off], NULL, chunk);
off += chunk;
}
}
qspi_cs_release();
return spi_flash_wait_ready();
}
/* Reads use Linear/XIP mode: switch the controller to linear mode, do a
* memcpy from the XIP window at 0xFC000000+offset, then return the
* controller to I/O mode. Mirrors how the ZynqMP HAL splits CMD17 (single
* block PIO) vs CMD18 (multi-block DMA) on SDHCI - here, short cmd ops use
* I/O mode and bulk reads use linear/XIP. */
static int spi_flash_read(uint32_t address, uint8_t *data, unsigned int len)
{
/* Issue 32-bit AXI reads to the XIP window. Single-byte loads are
* unreliable - the linear-mode controller is burst-aware and wants
* word transfers. Decompose each 32-bit word into bytes at the
* destination so unaligned addr / unaligned dst / unaligned len all
* work without faulting on devices with strict alignment. */
const volatile uint32_t *xipw;
uint32_t aligned_addr;
uint32_t w;
unsigned int byte_off;
unsigned int i;
if (len == 0)
return 0;
qspi_linear_mode_setup();
aligned_addr = address & ~3U;
byte_off = address & 3U;
xipw = (const volatile uint32_t *)(Z7_QSPI_LINEAR_BASE + aligned_addr);
/* Sacrificial first 32-bit read primes the controller pipeline. */
(void)xipw[0];
i = 0;
if (byte_off != 0) {
w = *xipw++;
for (; byte_off < 4U && i < len; byte_off++, i++)
data[i] = (uint8_t)(w >> (byte_off * 8U));
}
while (i + 4U <= len) {
w = *xipw++;
data[i++] = (uint8_t)(w >> 0);
data[i++] = (uint8_t)(w >> 8);
data[i++] = (uint8_t)(w >> 16);
data[i++] = (uint8_t)(w >> 24);
}
if (i < len) {
w = *xipw;
for (byte_off = 0; i < len; byte_off++, i++)
data[i] = (uint8_t)(w >> (byte_off * 8U));
}
qspi_io_mode_setup();
return 0;
}
#if defined(TEST_EXT_FLASH) || defined(TEST_QSPI)
/* QSPI self-test (enable with -DTEST_EXT_FLASH or -DTEST_QSPI):
* 1) Read JEDEC ID and print it
* 2) Erase a 64 KB sector at TEST_EXT_FLASH_ADDR (default 2 MB offset)
* 3) Page-program a 256 B pattern (i & 0xFF)
* 4) Read back and verify
* Mirrors the existing src/spi_flash.c test_ext_flash() logic. Output via
* UART. Wired to fire from qspi_init() / hal_init() below. */
#ifndef TEST_EXT_FLASH_ADDR
#define TEST_EXT_FLASH_ADDR (2U * 1024U * 1024U)
#endif
static void qspi_print_hex_byte(uint8_t b)
{
static const char hex[] = "0123456789abcdef";
char buf[2];
buf[0] = hex[(b >> 4) & 0xFU];
buf[1] = hex[(b >> 0) & 0xFU];
uart_write(buf, 2);
}
static void qspi_print_hex32(uint32_t v)
{
qspi_print_hex_byte((uint8_t)(v >> 24));
qspi_print_hex_byte((uint8_t)(v >> 16));
qspi_print_hex_byte((uint8_t)(v >> 8));
qspi_print_hex_byte((uint8_t)(v >> 0));
}
static void qspi_selftest(void)
{
uint8_t patbuf[SPI_NOR_PAGE_SIZE];
uint8_t rdback[SPI_NOR_PAGE_SIZE];
uint8_t id[3] = { 0, 0, 0 };
unsigned int i;
int rc;
for (i = 0; i < SPI_NOR_PAGE_SIZE; i++)
patbuf[i] = (uint8_t)(i & 0xFFU);
uart_write("qspi: --- TEST_EXT_FLASH start ---\n", 35);
/* 1) JEDEC ID */
rc = spi_flash_read_id(id);
uart_write("qspi: JEDEC ID = 0x", 19);
qspi_print_hex_byte(id[0]);
qspi_print_hex_byte(id[1]);
qspi_print_hex_byte(id[2]);
uart_write(" rc=", 5);
qspi_print_hex_byte((uint8_t)rc);
uart_write("\n", 1);
if (id[0] == 0x00 || id[0] == 0xFF) {
uart_write("qspi: JEDEC read returned blank - driver broken\n", 48);
return;
}
/* 1b) Sanity read of known-programmed area at 0x100000 (signed image
* staged via Vitis program_flash). Should start with 'WOLF' magic. */
{
uint8_t boot[8] = { 0 };
spi_flash_read(0x00100000U, boot, sizeof(boot));
uart_write("qspi: read @0x100000 = ", 23);
for (i = 0; i < 8; i++) qspi_print_hex_byte(boot[i]);
uart_write("\n", 1);
}
/* 2) Erase */
uart_write("qspi: erase sector @ 0x", 23);
qspi_print_hex32(TEST_EXT_FLASH_ADDR);
uart_write(" ...\n", 5);
rc = spi_flash_sector_erase(TEST_EXT_FLASH_ADDR);
if (rc != 0) {
uart_write("qspi: erase FAILED\n", 19);
return;
}
/* 3) Page program */
uart_write("qspi: page program ...\n", 23);
rc = spi_flash_page_program(TEST_EXT_FLASH_ADDR, patbuf, SPI_NOR_PAGE_SIZE);
if (rc != 0) {
uart_write("qspi: program FAILED\n", 21);
return;
}
/* 4a) Re-read JEDEC ID after program to confirm controller is alive */
{
uint8_t id2[3] = { 0, 0, 0 };
spi_flash_read_id(id2);
uart_write("qspi: post-program JEDEC = 0x", 29);
qspi_print_hex_byte(id2[0]);
qspi_print_hex_byte(id2[1]);
qspi_print_hex_byte(id2[2]);
uart_write("\n", 1);
}
/* 4b) Read back at TEST_EXT_FLASH_ADDR + compare */
for (i = 0; i < SPI_NOR_PAGE_SIZE; i++)
rdback[i] = 0;
spi_flash_read(TEST_EXT_FLASH_ADDR, rdback, SPI_NOR_PAGE_SIZE);
uart_write("qspi: rdback[0..7] = ", 21);
for (i = 0; i < 8; i++) qspi_print_hex_byte(rdback[i]);
uart_write("\n", 1);
/* 4c) Linear-mode XIP sanity check: read 32-bit words then decode bytes.
* Single-byte AXI accesses to the linear window confuse the controller -
* the burst-aware controller wants 32-bit reads. */
uart_write("qspi: xip32@0x200000 = ", 23);
{
volatile uint32_t *xipw;
unsigned int j;
uint32_t w;
Z7_QSPI_EN = 0;
Z7_QSPI_LQSPI_CR = 0x80000003U; /* LQ_MODE=1, INST=0x03 (READ), no dummy */
Z7_QSPI_EN = Z7_QSPI_EN_VAL;
xipw = (volatile uint32_t*)(Z7_QSPI_LINEAR_BASE + TEST_EXT_FLASH_ADDR);
for (j = 0; j < 2; j++) {
w = xipw[j];
qspi_print_hex_byte((uint8_t)(w >> 0));
qspi_print_hex_byte((uint8_t)(w >> 8));
qspi_print_hex_byte((uint8_t)(w >> 16));
qspi_print_hex_byte((uint8_t)(w >> 24));
}
uart_write(" xip32@0x100000 = ", 19);
xipw = (volatile uint32_t*)(Z7_QSPI_LINEAR_BASE + 0x100000U);
for (j = 0; j < 2; j++) {
w = xipw[j];
qspi_print_hex_byte((uint8_t)(w >> 0));
qspi_print_hex_byte((uint8_t)(w >> 8));
qspi_print_hex_byte((uint8_t)(w >> 16));
qspi_print_hex_byte((uint8_t)(w >> 24));
}
uart_write("\n", 1);
/* Restore I/O mode for any later transfers. */
Z7_QSPI_EN = 0;
Z7_QSPI_LQSPI_CR = 0;
Z7_QSPI_EN = Z7_QSPI_EN_VAL;
}
for (i = 0; i < SPI_NOR_PAGE_SIZE; i++) {
if (rdback[i] != patbuf[i]) {
uart_write("qspi: MISMATCH @ idx 0x", 23);
qspi_print_hex_byte((uint8_t)(i >> 8));
qspi_print_hex_byte((uint8_t)(i & 0xFFU));
uart_write(" got 0x", 8);
qspi_print_hex_byte(rdback[i]);
uart_write(" expected 0x", 12);
qspi_print_hex_byte(patbuf[i]);
uart_write("\n", 1);
return;
}
}
uart_write("qspi: --- TEST_EXT_FLASH PASS ---\n", 34);
}
#endif /* TEST_EXT_FLASH || TEST_QSPI */
#endif /* EXT_FLASH (qspi block) */
void hal_init(void)
{
#ifdef DEBUG_UART
uart_init();
{
const char banner[] = "wolfBoot Zynq-7000 (ZC702) hal_init\n";
uart_write(banner, sizeof(banner) - 1);
}
/* One-line boot config summary so the UART log identifies which
* config / storage / payload / clock plan the running wolfBoot was
* built for. Useful when juggling QSPI vs SD images. */
#if defined(DISK_SDCARD) || defined(DISK_EMMC)
wolfBoot_printf(" storage : SDCard (Arasan SDHCI v2.0, %d MHz post-init)\n",
SDHCI_CLK_50MHZ / 1000);
#elif defined(EXT_FLASH)
wolfBoot_printf(" storage : QSPI (XQspiPs N25Q128, IO_PLL/40 ~12.5 MHz)\n");
#else
wolfBoot_printf(" storage : NONE (JTAG-loaded)\n");
#endif
/* do_boot always emits the ARM Linux boot ABI on this target; bare-
* metal payloads simply ignore r0..r3, so one ABI covers both. */
wolfBoot_printf(" payload : ARM Linux ABI (r0=0, r1=~0, r2=DTB)\n");
wolfBoot_printf(" partns : load=0x%x boot=0x%x update=0x%x\n",
(unsigned int)WOLFBOOT_LOAD_ADDRESS,
(unsigned int)WOLFBOOT_PARTITION_BOOT_ADDRESS,
(unsigned int)WOLFBOOT_PARTITION_UPDATE_ADDRESS);
wolfBoot_printf(" cputmr : Cortex-A9 GTimer @ %u Hz\n",
(unsigned int)Z7_GTIMER_FREQ_HZ);
#endif /* DEBUG_UART */
#ifdef EXT_FLASH
qspi_init();
#if defined(TEST_EXT_FLASH) || defined(TEST_QSPI)
qspi_selftest();
#endif
#ifdef DEBUG_BOOTPART
/* Dump first 16 bytes of the BOOT partition so we can see if the
* QSPI driver is returning the signed-image header (magic 'WOLF'). */
{
uint8_t buf[16];
const char hex[] = "0123456789abcdef";
char line[3*16 + 2];
unsigned int i;
spi_flash_read(0x00100000U, buf, sizeof(buf));
for (i = 0; i < sizeof(buf); i++) {
line[i*3 + 0] = hex[(buf[i] >> 4) & 0xF];
line[i*3 + 1] = hex[(buf[i] >> 0) & 0xF];
line[i*3 + 2] = ' ';
}
line[sizeof(line) - 2] = '\n';
line[sizeof(line) - 1] = 0;
uart_write("QSPI[0x100000]: ", 16);
uart_write(line, sizeof(line) - 1);
}
#endif
#endif
}
/* Cortex-A9 cache teardown sequence used before do_boot(). FSBL hands off
* with MMU+L1+L2 enabled; we clean and disable them so the next stage sees
* a deterministic CPU state. Order follows ARM ARM B2.2.5: clean D-cache,
* disable MMU, invalidate I-cache, ISB. */
static inline void z7_dsb(void) { __asm__ volatile("dsb sy" ::: "memory"); }
static inline void z7_isb(void) { __asm__ volatile("isb sy" ::: "memory"); }
static void z7_l1_dcache_clean_invalidate_all(void)
{
/* v7-A clean+invalidate by set/way - iterates the data cache levels in
* CLIDR and walks each (set, way) issuing DCCISW. Adapted from ARMv7-A
* Architecture Reference Manual B2.2.4 example. */
__asm__ volatile (
"dmb sy \n"
"mrc p15, 1, r0, c0, c0, 1 \n" /* CLIDR */
"ands r3, r0, #0x07000000 \n"
"mov r3, r3, lsr #23 \n"
"beq 5f \n"
"mov r10, #0 \n"
"1: \n"
"add r2, r10, r10, lsr #1 \n"
"mov r1, r0, lsr r2 \n"
"and r1, r1, #7 \n"
"cmp r1, #2 \n"
"blt 4f \n"
"mcr p15, 2, r10, c0, c0, 0\n" /* CSSELR */
"isb \n"
"mrc p15, 1, r1, c0, c0, 0 \n" /* CCSIDR */
"and r2, r1, #7 \n"
"add r2, r2, #4 \n" /* line size */
"ldr r4, =0x3FF \n"
"ands r4, r4, r1, lsr #3 \n" /* assoc */
"clz r5, r4 \n"
"ldr r7, =0x7FFF \n"
"ands r7, r7, r1, lsr #13 \n" /* num sets */
"2: \n"
"mov r9, r4 \n"
"3: \n"
"orr r11, r10, r9, lsl r5 \n"
"orr r11, r11, r7, lsl r2 \n"
"mcr p15, 0, r11, c7, c14, 2 \n" /* DCCISW */
"subs r9, r9, #1 \n"
"bge 3b \n"
"subs r7, r7, #1 \n"
"bge 2b \n"
"4: \n"
"add r10, r10, #2 \n"
"cmp r3, r10 \n"
"bgt 1b \n"
"5: \n"
"dsb sy \n"
"isb \n"
:
:
: "r0","r1","r2","r3","r4","r5","r7","r9","r10","r11","memory","cc"
);
}
static void z7_l1_icache_invalidate_all(void)
{
/* ICIALLU + branch predictor invalidate */
__asm__ volatile (
"mov r0, #0 \n"
"mcr p15, 0, r0, c7, c5, 0 \n" /* ICIALLU */
"mcr p15, 0, r0, c7, c5, 6 \n" /* BPIALL */
"dsb sy \n"
"isb \n"
: : : "r0","memory"
);
}
static void z7_disable_mmu_and_caches(void)
{
/* SCTLR: clear M (bit0), C (bit2), I (bit12). Leaves Z (branch predict)
* alone since we cleared BPIALL above. */
__asm__ volatile (
"mrc p15, 0, r0, c1, c0, 0 \n"
"bic r0, r0, #(1 << 0) \n"
"bic r0, r0, #(1 << 2) \n"
"bic r0, r0, #(1 << 12) \n"
"mcr p15, 0, r0, c1, c0, 0 \n"
"dsb sy \n"
"isb \n"
: : : "r0","memory"
);
}
void hal_prepare_boot(void)
{
/* Disable IRQ + FIQ */
__asm__ volatile("cpsid if" ::: "memory");
z7_dsb();
z7_l1_dcache_clean_invalidate_all();
z7_disable_mmu_and_caches();
z7_l1_icache_invalidate_all();
z7_isb();
/* PL310 L2: leave alone for first cut. FSBL on ZC702 typically does
* not enable PL310 unless explicitly configured; if your FSBL does,
* extend this routine with L2x0 clean-invalidate + disable. */
}
/* Internal flash operations are no-ops on Zynq-7000:
* QSPI is treated as external flash via ext_flash_*. */
int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
{
(void)address; (void)data; (void)len;
return 0;
}
int RAMFUNCTION hal_flash_erase(uint32_t address, int len)
{
(void)address; (void)len;
return 0;
}
void RAMFUNCTION hal_flash_unlock(void) { }
void RAMFUNCTION hal_flash_lock(void) { }
#ifdef EXT_FLASH
int ext_flash_read(uintptr_t address, uint8_t *data, int len)
{
if (len <= 0)
return 0;
if (spi_flash_read((uint32_t)address, data, (unsigned int)len) != 0)
return -1;
return len; /* wolfBoot's update_ram.c expects bytes-read on success */
}
int ext_flash_write(uintptr_t address, const uint8_t *data, int len)
{
/* Split writes on SPI-NOR page boundaries (256 B). */
uint32_t addr = (uint32_t)address;
unsigned int remain = (unsigned int)((len > 0) ? len : 0);
unsigned int off = 0;
while (remain > 0) {
unsigned int page_off = addr & (SPI_NOR_PAGE_SIZE - 1U);
unsigned int chunk = SPI_NOR_PAGE_SIZE - page_off;
if (chunk > remain)
chunk = remain;
if (spi_flash_page_program(addr, data + off, chunk) != 0)
return -1;
addr += chunk;
off += chunk;
remain -= chunk;
}
return 0;
}
int ext_flash_erase(uintptr_t address, int len)
{
/* Erase whole sectors covering [address, address+len). The caller is
* expected to align to WOLFBOOT_SECTOR_SIZE (= SPI_NOR_SECTOR_SIZE). */
uint32_t addr = (uint32_t)address;
int remain = len;
while (remain > 0) {
if (spi_flash_sector_erase(addr) != 0)
return -1;
addr += SPI_NOR_SECTOR_SIZE;
remain -= (int)SPI_NOR_SECTOR_SIZE;
}
return 0;
}
void ext_flash_lock(void) { }
void ext_flash_unlock(void) { }
#endif /* EXT_FLASH */
#ifdef MMU
/* Memory-mapped DTB fallback. Not used on Zynq-7000 with EXT_FLASH=1: the
* signed DTB is opened as PART_DTS_BOOT in update_ram.c and read out of
* QSPI via ext_flash_check_read. Return NULL so the fallback path is a
* no-op when the DTS partition is missing. */
void *hal_get_dts_address(void)
{
return NULL;
}
void *hal_get_dts_update_address(void)
{
return NULL;
}
#endif /* MMU */
/* Microsecond timer using the Cortex-A9 Global Timer at PERIPHBASE+0x200.
* 64-bit free-running counter, increments at PERIPHCLK = CPU_3x2x =
* 333.33 MHz on ZC702 with the default FSBL clock plan (ARM_PLL =
* 1.333 GHz, CPU_6x4x = 666.67 MHz). The actual divide constant lives
* in Z7_GTIMER_FREQ_HZ in hal/zynq7000.h - override there if you reclock
* the CPU. The Global Timer is started by the FSBL; if it isn't running
* yet we kick it here. */
uint64_t hal_get_timer_us(void)
{
uint32_t hi1, lo, hi2;
uint64_t count;
/* If the Global Timer hasn't been enabled yet, enable it. The Control
* Register's bit 0 is the Timer Enable; one-time per power cycle. */
if ((Z7_GTIMER_CTRL & Z7_GTIMER_CTRL_EN) == 0) {
Z7_GTIMER_CTRL = Z7_GTIMER_CTRL_EN;
}
/* Read low/high atomically: read high, low, high again - if high changed,
* a wrap happened mid-read and we must retry. */
do {
hi1 = Z7_GTIMER_HI;
lo = Z7_GTIMER_LO;
hi2 = Z7_GTIMER_HI;
} while (hi1 != hi2);
count = ((uint64_t)hi2 << 32) | (uint64_t)lo;
/* Convert ticks to microseconds. PERIPHCLK is fixed (FSBL clock plan),
* so the divide is by a known constant - no 64x64 division needed. */
return (count * 1000000ULL) / (uint64_t)Z7_GTIMER_FREQ_HZ;
}
#ifdef WOLFBOOT_FPGA_BITSTREAM
/* PL programming timeout (microseconds). */
#ifndef Z7_FPGA_TIMEOUT_US
#define Z7_FPGA_TIMEOUT_US 1000000ULL /* 1 second */
#endif
/* Clean the D-cache over [start, start+len) by MVA so the DevC DMA sees
* the committed bitstream bytes (DCCMVAC, L1 line = 32B). Mirrors the
* SDMA coherency path. */
static void z7_dcache_clean_range(uintptr_t start, uint32_t len)
{
uintptr_t addr;
uintptr_t end = (start + len + 31U) & ~31U;
start &= ~31U;
for (addr = start; addr < end; addr += 32U) {
__asm__ volatile("mcr p15, 0, %0, c7, c10, 1" : : "r"(addr) : "memory");
}
__asm__ volatile("dsb sy" : : : "memory");
}
/* Program the PL from a bootgen .bin bitstream resident in DDR using the
* DevC PCAP DMA engine (UG585 ch.6 / Xilinx XDcfg full-bitstream flow).
* Only the full-bitstream path is implemented; partial reconfiguration
* returns an error. */
int hal_fpga_load(uint32_t flags, uintptr_t addr, size_t size)
{
uint32_t sts;
uint32_t words;
uint64_t t0;
if (flags == HAL_FPGA_PARTIAL) {
/* Partial reconfig leaves PROG_B asserted and routes via PCAP_PR;
* not implemented in this initial full-bitstream path. */
wolfBoot_printf("Z7 FPGA: partial reconfig not implemented\n");
return -1;
}
if (addr > 0xFFFFFFFFU || size == 0) {
return -1;
}
#if defined(SIZE_MAX) && SIZE_MAX > 0xFFFFFFFFU
/* words and the DMA length register are 32-bit; reject a size that would
* truncate. Compiled out where size_t is already 32-bit. */
if (size > 0xFFFFFFFFU) {
wolfBoot_printf("Z7 FPGA: bitstream size too large\n");
return -1;
}
#endif
/* The DevC DMA descriptor encodes the "last" flag in the source
* address LSB, so the bitstream buffer must be word-aligned. */
if ((addr & 0x3U) != 0U) {
wolfBoot_printf("Z7 FPGA: bitstream addr not word-aligned\n");
return -1;
}
/* Round byte size up to whole words without overflowing size + 3. */
words = (uint32_t)(size / 4U);
if ((size & 3U) != 0U) {
words++;
}
/* 1. Unlock DevC and select PCAP (not ICAP). PCAP_MODE=1 enables the
* PCAP interface; PCAP_PR=1 selects PCAP (0 would select ICAP) per
* UG585 devcfg.CTRL bit 27. This matches Xilinx FSBL pcap.c, which
* sets both bits for full-bitstream programming. */
Z7_DEVC_UNLOCK = Z7_DEVC_UNLOCK_KEY;
Z7_DEVC_CTRL |= (Z7_DEVC_CTRL_PCAP_MODE | Z7_DEVC_CTRL_PCAP_PR);
/* Disable internal PCAP loopback. */
Z7_DEVC_MCTRL &= ~Z7_DEVC_MCTRL_PCAP_LPBK;
/* 2. Clear sticky interrupts. */
Z7_DEVC_INT_STS = Z7_DEVC_INT_ALL;
/* 3. Pulse PROG_B low then high to clear the PL (full bitstream). */
Z7_DEVC_CTRL &= ~Z7_DEVC_CTRL_PCFG_PROG_B;
t0 = hal_get_timer_us();
while (Z7_DEVC_STATUS & Z7_DEVC_STATUS_PCFG_INIT) {
if (hal_get_timer_us() - t0 > Z7_FPGA_TIMEOUT_US) {
wolfBoot_printf("Z7 FPGA: timeout waiting PCFG_INIT clear\n");
return -1;
}
}
Z7_DEVC_CTRL |= Z7_DEVC_CTRL_PCFG_PROG_B;
t0 = hal_get_timer_us();
while (!(Z7_DEVC_STATUS & Z7_DEVC_STATUS_PCFG_INIT)) {
if (hal_get_timer_us() - t0 > Z7_FPGA_TIMEOUT_US) {
wolfBoot_printf("Z7 FPGA: timeout waiting PCFG_INIT set\n");
return -1;
}
}
/* 4. Clear interrupts. */
Z7_DEVC_INT_STS = Z7_DEVC_INT_ALL;
/* 5. Make the bitstream coherent in DDR for the DMA. Clean the same
* rounded-up word length the DMA reads (words * 4), not just the
* byte size, so a non-word-aligned tail is covered. (Xilinx .bin
* bitstreams are word streams, so this normally equals size.) */
z7_dcache_clean_range(addr, words * 4U);
/* 6. Wait for room in the DevC DMA command queue, then program the DMA.
* Writing a descriptor while the queue is full silently drops it,
* leaving step 7 to spin to the timeout. */
t0 = hal_get_timer_us();
while (Z7_DEVC_STATUS & Z7_DEVC_STATUS_DMA_CMD_FULL) {
if (hal_get_timer_us() - t0 > Z7_FPGA_TIMEOUT_US) {
wolfBoot_printf("Z7 FPGA: timeout waiting DMA queue space\n");
return -1;
}
}
/* src = DDR bitstream (LSB=1 marks last descriptor),
* dst = PCAP sentinel. Lengths are in 32-bit words. */
Z7_DEVC_DMA_SRC = ((uint32_t)addr) | Z7_DEVC_DMA_LAST;
Z7_DEVC_DMA_DST = Z7_DEVC_DMA_DEST_PCAP;
Z7_DEVC_DMA_SRC_LEN = words;
Z7_DEVC_DMA_DST_LEN = words;
/* 7. Wait for DMA done (and check error bits). */
t0 = hal_get_timer_us();
do {
sts = Z7_DEVC_INT_STS;
if (sts & Z7_DEVC_INT_ERR_MASK) {
wolfBoot_printf("Z7 FPGA: DMA error, INT_STS=0x%x\n", sts);
return -1;
}
if (hal_get_timer_us() - t0 > Z7_FPGA_TIMEOUT_US) {
wolfBoot_printf("Z7 FPGA: timeout waiting DMA done\n");
return -1;
}
} while (!(sts & Z7_DEVC_INT_DMA_DONE));
/* 8. Wait for the PL to report configuration complete (and surface a
* config error immediately rather than spinning to the timeout). */
t0 = hal_get_timer_us();
do {
sts = Z7_DEVC_INT_STS;
if (sts & Z7_DEVC_INT_ERR_MASK) {
wolfBoot_printf("Z7 FPGA: config error, INT_STS=0x%x\n", sts);
return -1;
}
if (hal_get_timer_us() - t0 > Z7_FPGA_TIMEOUT_US) {
wolfBoot_printf("Z7 FPGA: timeout waiting PCFG_DONE\n");
return -1;
}
} while (!(sts & Z7_DEVC_INT_PCFG_DONE));
return 0;
}
#endif /* WOLFBOOT_FPGA_BITSTREAM */
#if defined(DISK_SDCARD) || defined(DISK_EMMC)
/* ============================================================================
* SDHCI (SD Card / eMMC) Platform Support
* ============================================================================
* The Zynq-7000 SDIO controller is an Arasan SDHCI v2.0, same IP family as
* the v3.0 used on ZynqMP - register layout matches the SD Host Controller
* Standard 1.00, mapped at 0xE0100000 (SD0) / 0xE0101000 (SD1).
*
* The generic SDHCI driver (src/sdhci.c) targets the Cadence SD4HC layout
* which adds a HRS register block at 0x000-0x01F and shifts the standard
* SRS registers to 0x200+. We translate between the two here, mirroring
* the ZynqMP HAL block in hal/zynq.c.
*
* Differences from ZynqMP's translation:
* - Base addr: SD0 at 0xE0100000 (ZC702 SD card slot)
* - Clock/reset is via SLCR.SDIO_{CLK,RST} (Z7) instead of CRL_APB (ZynqMP)
* - No HV4E mode (32-bit ARM, only legacy SDMA via SRS00)
* - DMA cache ops use ARMv7 CP15 (mcr p15,0,Rt,c7,c10,1 etc.) instead of
* AArch64 dc cvac. Caches are typically off in our hal_prepare_boot
* path, but if a future config keeps them on, the generic-SDMA
* coherency path needs the ops.
*/
#include "sdhci.h"
#ifndef Z7_SDHCI_BASE
#define Z7_SDHCI_BASE Z7_SDIO0_BASE
#endif
#define CADENCE_SRS_OFFSET 0x200
/* Standard SDHCI register offsets (byte addresses within the controller)
* matching the Arasan v2.0 register map. */
#define STD_SDHCI_SDMA_ADDR 0x00 /* SDMA System Address (32-bit) */
#define STD_SDHCI_HOST_CTRL1 0x28 /* Host Control 1 (8-bit) */
#define STD_SDHCI_POWER_CTRL 0x29 /* Power Control (8-bit) */
#define STD_SDHCI_BLKGAP_CTRL 0x2A /* Block Gap Control (8-bit) */
#define STD_SDHCI_WAKEUP_CTRL 0x2B /* Wakeup Control (8-bit) */
#define STD_SDHCI_CLK_CTRL 0x2C /* Clock Control (16-bit) */
#define STD_SDHCI_TIMEOUT_CTRL 0x2E /* Timeout Control (8-bit) */
#define STD_SDHCI_SW_RESET 0x2F /* Software Reset (8-bit) */
#define STD_SDHCI_HOST_CTRL2 0x3C /* Auto CMD Err(16) + Host Ctrl 2(16) */
/* Software Reset register bits (at offset 0x2F, 8-bit register) */
#define STD_SDHCI_SRA 0x01 /* Software Reset for All */
/* Cadence HRS faux-read translation (the driver pokes HRS00/HRS04 during
* init even though there's no HRS block on Arasan; emulate just enough). */
static uint32_t z7_sdhci_hrs_read(uint32_t hrs_offset)
{
volatile uint8_t *base = (volatile uint8_t *)Z7_SDHCI_BASE;
switch (hrs_offset) {
case 0x000: { /* HRS00 - Software Reset (mirror std SRA) */
uint8_t val = *((volatile uint8_t *)(base + STD_SDHCI_SW_RESET));
return (val & STD_SDHCI_SRA) ? 1U : 0U;
}
case 0x010: /* HRS04 - PHY access ACK (Cadence-specific). Return ACK so
* the driver's wait-for-ack loops complete. */
return (1U << 26);
default:
return 0;
}
}
static void z7_sdhci_hrs_write(uint32_t hrs_offset, uint32_t val)
{
volatile uint8_t *base = (volatile uint8_t *)Z7_SDHCI_BASE;
if (hrs_offset == 0x000 && (val & 1U)) {
/* Software Reset for All - byte write to std SRA. */
*((volatile uint8_t *)(base + STD_SDHCI_SW_RESET)) = STD_SDHCI_SRA;
}
/* HRS01 (debounce), HRS02, HRS06 (eMMC mode) etc. - not applicable on
* Arasan; ignore. */
(void)val;
}
uint32_t sdhci_reg_read(uint32_t offset)
{
volatile uint8_t *base = (volatile uint8_t *)Z7_SDHCI_BASE;
if (offset >= CADENCE_SRS_OFFSET) {
uint32_t std_off = offset - CADENCE_SRS_OFFSET;
/* SRS22 (0x58) -> SRS00: Legacy SDMA address register */
if (std_off == 0x58) {
return *((volatile uint32_t *)(base + STD_SDHCI_SDMA_ADDR));
}
/* SRS23 (0x5C) -> 0: no 64-bit SDMA on Arasan v2.0 */
if (std_off == 0x5C) {
return 0;
}
return *((volatile uint32_t *)(base + std_off));
}
return z7_sdhci_hrs_read(offset);
}
void sdhci_reg_write(uint32_t offset, uint32_t val)
{
volatile uint8_t *base = (volatile uint8_t *)Z7_SDHCI_BASE;
if (offset >= CADENCE_SRS_OFFSET) {
uint32_t std_off = offset - CADENCE_SRS_OFFSET;
/* Cadence SRS10 = std 0x28..0x2B (HostCtrl1/PowerCtrl/BlkGap/Wakeup).
* Mask out HSE (High Speed Enable, bit 2) before writing HostCtrl1
* - Arasan v2.0 + 3.3V SDHC cards stay in single-edge default-speed
* timing (max 25 MHz); the driver tries to push HSE+50MHz which the
* card can't follow, causing DTOE on data transfers. */
if (std_off == 0x28) {
uint8_t host_ctrl1 = (uint8_t)(val & 0xFF);
host_ctrl1 &= (uint8_t)~0x04U; /* clear HSE */
*((volatile uint8_t *)(base + STD_SDHCI_HOST_CTRL1)) = host_ctrl1;
*((volatile uint8_t *)(base + STD_SDHCI_POWER_CTRL)) =
(uint8_t)((val >> 8) & 0xFF);
*((volatile uint8_t *)(base + STD_SDHCI_BLKGAP_CTRL)) =
(uint8_t)((val >> 16) & 0xFF);
*((volatile uint8_t *)(base + STD_SDHCI_WAKEUP_CTRL)) =
(uint8_t)((val >> 24) & 0xFF);
return;
}
/* Cadence SRS11 = std 0x2C..0x2F (ClkCtrl/TimeoutCtrl/SwReset) */
if (std_off == 0x2C) {
*((volatile uint16_t *)(base + STD_SDHCI_CLK_CTRL)) =
(uint16_t)(val & 0xFFFF);
*((volatile uint8_t *)(base + STD_SDHCI_TIMEOUT_CTRL)) =
(uint8_t)((val >> 16) & 0xFF);
*((volatile uint8_t *)(base + STD_SDHCI_SW_RESET)) =
(uint8_t)((val >> 24) & 0xFF);
return;
}
/* SRS22 (0x58) -> SRS00: Legacy SDMA address register */
if (std_off == 0x58) {
*((volatile uint32_t *)(base + STD_SDHCI_SDMA_ADDR)) = val;
return;
}
if (std_off == 0x5C) {
return; /* no 64-bit SDMA */
}
/* SRS15 (0x3C): mask out v3-only bits the driver tries to set.
* Arasan SDHCI v2.0 (Zynq-7000) is 3.3V-only with no UHS-I
* support; writing UHS Mode Select / 1.8V Enable / Sampling Clock
* Select / HV4E / A64 either reserved-faults or puts the
* controller into an invalid signaling mode that breaks all
* subsequent data transfers (DTOE on the first PIO read). */
if (std_off == STD_SDHCI_HOST_CTRL2) {
val &= ~((7U << 0) /* UMS[2:0] (UHS Mode Select) */
| (1U << 3) /* 1.8V Signaling Enable */
| (7U << 4) /* Driver Strength Select */
| (1U << 7) /* Sampling Clock Select */
| (1U << 12) /* HV4E (Host Version 4 Enable) */
| (1U << 13)); /* A64 (64-bit Addressing) */
}
*((volatile uint32_t *)(base + std_off)) = val;
return;
}
z7_sdhci_hrs_write(offset, val);
}
/* SDHCI controller bring-up. ZC702 boots from SD with FSBL having already
* configured SDIO_CLK / pinmux / power, so this is mostly a no-op. For
* JTAG-loaded development we kick the SLCR clock + reset just in case. */
void sdhci_platform_init(void)
{
volatile int i;
/* Unlock SLCR. */
Z7_SLCR_UNLOCK = Z7_SLCR_UNLOCK_KEY;
/* APER clock for SDIO0 (AHB bus clock to the controller). */
Z7_SLCR_APER_CLK |= Z7_SLCR_APER_SDIO0;
/* SDIO_CLK_CTRL: enable CLKACT0, IO_PLL source, divisor for ~50 MHz
* ref. IO_PLL=1 GHz default, /20 = 50 MHz. Skip if FSBL already set
* something sensible. */
if ((Z7_SLCR_SDIO_CLK & Z7_SLCR_SDIO_CLK_ACT0) == 0) {
uint32_t v = Z7_SLCR_SDIO_CLK;
v &= ~Z7_SLCR_SDIO_CLK_DIV_MSK;
v |= (20U << Z7_SLCR_SDIO_CLK_DIV_SH);
v |= Z7_SLCR_SDIO_CLK_ACT0;
Z7_SLCR_SDIO_CLK = v;
}
/* Pulse SDIO0 reset (REF + CPU) so the controller picks up clock and
* caps cleanly. */
Z7_SLCR_SDIO_RST |= (Z7_SLCR_SDIO_RST_REF0 | Z7_SLCR_SDIO_RST_CPU0);
for (i = 0; i < 256; i++) { /* short delay */ }
Z7_SLCR_SDIO_RST &= ~(Z7_SLCR_SDIO_RST_REF0 | Z7_SLCR_SDIO_RST_CPU0);
for (i = 0; i < 1024; i++) { /* wait for controller ready */ }
Z7_SLCR_LOCK = Z7_SLCR_LOCK_KEY;
}
void sdhci_platform_irq_init(void)
{
/* Polling-mode driver: nothing to do. */
}
void sdhci_platform_set_bus_mode(int is_emmc)
{
(void)is_emmc;
/* SD vs eMMC bus mode: nothing extra needed for SD - the generic driver
* sets up SD bus width / clock via the standard SDHCI registers, which
* our reg_read/write translation handles transparently. */
}
/* DMA cache maintenance for SDMA. wolfBoot's hal_prepare_boot disables D-cache
* before do_boot, but SDHCI runs BEFORE that during the load+verify phase
* with caches potentially live. ARMv7 cache ops (clean / clean+invalidate
* by MVA) keep DMA buffers coherent. */
void sdhci_platform_dma_prepare(void *buf, uint32_t sz, int is_write)
{
uintptr_t start = (uintptr_t)buf & ~31U; /* L1 D-cache line = 32B */
uintptr_t end = ((uintptr_t)buf + sz + 31U) & ~31U;
uintptr_t addr;
if (is_write) {
/* DCCMVAC - clean by MVA: dirty CPU lines flushed so DMA reads them */
for (addr = start; addr < end; addr += 32U) {
__asm__ volatile("mcr p15, 0, %0, c7, c10, 1" : : "r"(addr) : "memory");
}
} else {
/* DCCIMVAC - clean+invalidate by MVA: discard stale CPU lines so
* DMA write data shows up on subsequent CPU reads */
for (addr = start; addr < end; addr += 32U) {
__asm__ volatile("mcr p15, 0, %0, c7, c14, 1" : : "r"(addr) : "memory");
}
}
__asm__ volatile("dsb sy" : : : "memory");
}
void sdhci_platform_dma_complete(void *buf, uint32_t sz, int is_write)
{
if (!is_write) {
/* Post-DMA-read: invalidate-only (DCIMVAC, c7,c6,1). The DMA
* controller has just written fresh data into DRAM; we must
* discard any stale CPU lines (including ones the prefetcher
* may have pulled in between dma_prepare and dma_complete) so
* subsequent CPU reads see the new data. clean+invalidate
* (DCCIMVAC) would write back the stale lines first, partially
* overwriting the controller's data. */
uintptr_t start = (uintptr_t)buf & ~31U;
uintptr_t end = ((uintptr_t)buf + sz + 31U) & ~31U;
uintptr_t addr;
for (addr = start; addr < end; addr += 32U) {
__asm__ volatile("mcr p15, 0, %0, c7, c6, 1" : : "r"(addr) : "memory");
}
__asm__ volatile("dsb sy" : : : "memory");
}
}
#endif /* DISK_SDCARD || DISK_EMMC */
#endif /* TARGET_zynq7000 */