mirror of https://github.com/wolfSSL/wolfBoot.git
2107 lines
75 KiB
C
2107 lines
75 KiB
C
/* mpfs250.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
|
|
*/
|
|
|
|
/* Microchip PolarFire SoC MPFS250T HAL for wolfBoot */
|
|
/* Supports:
|
|
* RISC-V 64-bit architecture
|
|
* External flash operations
|
|
* UART communication
|
|
* System initialization
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
|
|
#include "target.h"
|
|
|
|
#include "mpfs250.h"
|
|
#include "riscv.h"
|
|
#include "image.h"
|
|
#ifndef ARCH_RISCV64
|
|
# error "wolfBoot mpfs250 HAL: wrong architecture selected. Please compile with ARCH=RISCV64."
|
|
#endif
|
|
|
|
#include "printf.h"
|
|
#include "loader.h"
|
|
#include "hal.h"
|
|
#include "gpt.h"
|
|
#include "fdt.h"
|
|
|
|
|
|
#if defined(DISK_SDCARD) || defined(DISK_EMMC)
|
|
#include "sdhci.h"
|
|
|
|
/* Forward declaration of SDHCI IRQ handler */
|
|
extern void sdhci_irq_handler(void);
|
|
#endif
|
|
|
|
/* Video Kit DDR/Clock configuration is included in mpfs250.h */
|
|
|
|
/* ------------------------------------------------------------------------
|
|
* File-scope globals
|
|
* ---------------------------------------------------------------------- */
|
|
/* APB (PCLK) frequency for UART baud divisors. Starts at the mode's
|
|
* compile-time value (40 MHz E51 reset clock in M-mode, 150 MHz under
|
|
* HSS) and is updated when M-mode wolfBoot raises the MSS PLL. */
|
|
uint32_t mpfs_apb_clk_hz = MSS_APB_AHB_CLK;
|
|
|
|
#ifdef WOLFBOOT_RISCV_MMODE
|
|
/* CPU frequency in MHz for mcycle-based udelay(); seeded at the E51 reset
|
|
* clock and bumped to the PLL rate after mss_pll_init() (hal/mpfs250_ddr.c).
|
|
* A wrong value here skews timing-sensitive paths (e.g. SD power-up). */
|
|
uint32_t mpfs_cpu_freq_mhz = MPFS_CPU_FREQ_RESET_MHZ;
|
|
|
|
/* Saved boot ROM watchdog values, restored in hal_prepare_boot(). */
|
|
static uint32_t mpfs_wdt_default_mvrp = 0;
|
|
static uint32_t mpfs_wdt_default_ctrl = 0;
|
|
|
|
/* Snapshots captured at hal_init entry, printed after uart_init;
|
|
* RESET_SR shows the cause of the most-recent reset. */
|
|
static uint32_t mpfs_boot_wdt_snap[6];
|
|
static uint32_t mpfs_boot_reset_sr_snap;
|
|
|
|
/* Configure L2 cache: enable ways 0,1,3 (0x0B) and set way masks for all masters */
|
|
static void mpfs_config_l2_cache(void)
|
|
{
|
|
L2_WAY_ENABLE = 0x0B; /* WayEnable INDEX (not a mask): ways 0..11 are
|
|
* cache-capable; the masters' way masks (0xFF)
|
|
* restrict cache fills to ways 0-7, leaving
|
|
* 8-11 as the scratchpad carve-out. Matches
|
|
* HSS/Libero (LIBERO_SETTING_WAY_ENABLE=0xB). */
|
|
SYSREG_L2_SHUTDOWN_CR = 0;
|
|
L2_WAY_MASK_DMA = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_AXI4_PORT0 = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_AXI4_PORT1 = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_AXI4_PORT2 = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_AXI4_PORT3 = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_E51_DCACHE = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_E51_ICACHE = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_U54_1_DCACHE = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_U54_1_ICACHE = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_U54_2_DCACHE = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_U54_2_ICACHE = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_U54_3_DCACHE = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_U54_3_ICACHE = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_U54_4_DCACHE = L2_WAY_MASK_CACHE_ONLY;
|
|
L2_WAY_MASK_U54_4_ICACHE = L2_WAY_MASK_CACHE_ONLY;
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
}
|
|
|
|
/* mcycle-based microsecond delay. MTIME is not running in M-mode without
|
|
* HSS, but mcycle ticks at the CPU clock rate and is monotonic. */
|
|
__attribute__((noinline)) void udelay(uint32_t us)
|
|
{
|
|
uint64_t start, now, target;
|
|
__asm__ volatile("rdcycle %0" : "=r"(start));
|
|
target = (uint64_t)us * (uint64_t)mpfs_cpu_freq_mhz;
|
|
do {
|
|
__asm__ volatile("rdcycle %0" : "=r"(now));
|
|
} while ((now - start) < target);
|
|
}
|
|
|
|
#endif /* WOLFBOOT_RISCV_MMODE */
|
|
|
|
|
|
/* Multi-Hart Support */
|
|
#ifdef WOLFBOOT_RISCV_MMODE
|
|
|
|
extern uint8_t _main_hart_hls; /* linker-provided address symbol; typed as uint8_t to avoid size confusion */
|
|
|
|
/* Watchdog timeout configuration.
|
|
* WATCHDOG=0 (default): WDT disabled in hal_init() then restored to boot
|
|
* ROM defaults in hal_prepare_boot() before do_boot.
|
|
* WATCHDOG=1: WDT kept enabled with WATCHDOG_TIMEOUT_MS during wolfBoot.
|
|
* Verify is bounded at ~5s; default 30s leaves ample headroom and avoids
|
|
* the need to pet the WDT during the long ECDSA verify call. */
|
|
#ifdef WATCHDOG
|
|
# ifndef WATCHDOG_TIMEOUT_MS
|
|
# define WATCHDOG_TIMEOUT_MS 30000U
|
|
# endif
|
|
/* MPFS MSS WDT clock is AHB / 256 ~= 150 MHz / 256 ~= 585 kHz at S-mode rate
|
|
* but ~80 MHz / 256 ~= 312 kHz on E51 reset clocks. Use a conservative
|
|
* 300 ticks/ms; the actual rate may be a bit higher but a slightly longer
|
|
* timeout is safe. Caller can override WATCHDOG_TIMEOUT_MS at build time. */
|
|
# define WATCHDOG_TIMEOUT_TICKS ((WATCHDOG_TIMEOUT_MS) * 300U)
|
|
#endif
|
|
|
|
/* CLINT MSIP register for IPI delivery */
|
|
#define CLINT_MSIP_REG(hart) (*(volatile uint32_t*)(CLINT_BASE + (hart) * 4))
|
|
/* CLINT machine-timer comparator (per hart) and MTIME counter */
|
|
#define CLINT_MTIMECMP_REG(hart) \
|
|
(*(volatile uint64_t*)(CLINT_BASE + 0x4000UL + (hart) * 8UL))
|
|
#define CLINT_MTIME_REG (*(volatile uint64_t*)(CLINT_BASE + 0xBFF8UL))
|
|
|
|
/* Signal secondary harts that E51 (main hart) is ready. */
|
|
static void mpfs_signal_main_hart_started(void)
|
|
{
|
|
HLS_DATA* hls = (HLS_DATA*)&_main_hart_hls;
|
|
hls->in_wfi_indicator = HLS_MAIN_HART_STARTED;
|
|
hls->my_hart_id = MPFS_FIRST_HART;
|
|
/* The eNVM secondary-hart gate polls the DTIM copy of this flag, not
|
|
* the L2-scratch HLS above: a cacheable store to the scratchpad can
|
|
* be lost on dirty-line eviction (layout-dependent), which parked
|
|
* the secondaries until the kernel's hart_start IPI -- too late for
|
|
* its 1s online window. DTIM is uncached and visible to all harts. */
|
|
*(volatile uint32_t *)MPFS_DTIM_MAIN_STARTED_ADDR =
|
|
(uint32_t)HLS_MAIN_HART_STARTED;
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
}
|
|
|
|
#if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
|
|
/* Per-hart S-mode start mailboxes, written by the E51 (boot hart release)
|
|
* or by the SBI HSM hart_start backend (on the calling U54), and consumed
|
|
* by the target hart's park loop in secondary_hart_entry().
|
|
*
|
|
* These live in the E51 DTIM, NOT in L2-scratch BSS: cacheable stores to
|
|
* the scratchpad can be lost on cache-line eviction, so cross-hart
|
|
* mailboxes written by a U54 would silently vanish (observed: the SBI
|
|
* HSM hart-state writes never became visible). The DTIM is small,
|
|
* uncached and coherent for every hart. The SBI shared state occupies
|
|
* DTIM+0x000 (see src/riscv_sbi.c); the mailboxes sit at +0x100. */
|
|
typedef struct {
|
|
volatile uint32_t marker; /* MPFS_KERNEL_HANDOFF_MARKER when valid */
|
|
volatile uint64_t entry; /* S-mode entry point */
|
|
volatile uint64_t opaque; /* a1 at entry (dtb for the boot hart) */
|
|
} mpfs_kernel_handoff_t;
|
|
|
|
#define MPFS_KERNEL_HANDOFF_MARKER 0x4C4E5858UL /* "LNXX" */
|
|
|
|
#define mpfs_kernel_handoff \
|
|
((mpfs_kernel_handoff_t *)(0x01000000UL + 0x100UL))
|
|
|
|
/* Provided by src/boot_riscv.c. */
|
|
extern void riscv_mmode_to_smode(unsigned long entry, unsigned long hartid,
|
|
unsigned long dtb) __attribute__((noreturn));
|
|
#endif /* MPFS_DDR_INIT && WOLFBOOT_MMODE_SMODE_BOOT */
|
|
|
|
/* Secondary hart (U54) entry: jump into the waiting Linux kernel (when a
|
|
* hand-off context has been staged for us) or park in WFI waiting for an
|
|
* SBI/Linux IPI.
|
|
*
|
|
* Keep this path FAST and free of UART access: the secondaries reach it
|
|
* via the kernel's HSM hart_start IPI, inside the kernel's 1s online
|
|
* window. A per-hart UART banner here once spent multiple seconds
|
|
* spinning on LSR (layout-dependent), making harts miss that window. */
|
|
void secondary_hart_entry(unsigned long hartid, HLS_DATA* hls)
|
|
{
|
|
(void)hls;
|
|
|
|
while (1) {
|
|
#if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
|
|
/* Check the hand-off context BEFORE sleeping: the release IPI was
|
|
* already consumed (MSIP cleared) by the eNVM wake path, so a
|
|
* wfi-first loop sleeps through an already-staged hand-off. The
|
|
* old wfi-first order only appeared to work because mtimecmp's
|
|
* reset value of 0 left MTIP permanently pending, making the wfi
|
|
* fall through; parking the comparators exposed it. */
|
|
if (hartid < (unsigned long)MPFS_NUM_HARTS &&
|
|
mpfs_kernel_handoff[hartid].marker
|
|
== MPFS_KERNEL_HANDOFF_MARKER) {
|
|
unsigned long kentry;
|
|
unsigned long opq;
|
|
/* Acquire fence: pair with the writer's release fence so we
|
|
* are guaranteed to observe entry / opaque after seeing
|
|
* marker. Without this, RISC-V's relaxed memory model
|
|
* permits the reader to use stale field values cached before
|
|
* marker was published. */
|
|
__asm__ volatile("fence r,rw" ::: "memory");
|
|
kentry = (unsigned long)mpfs_kernel_handoff[hartid].entry;
|
|
opq = (unsigned long)mpfs_kernel_handoff[hartid].opaque;
|
|
riscv_mmode_to_smode(kentry, hartid, opq);
|
|
/* never returns */
|
|
}
|
|
/* Sleep until the next IPI (e.g. a future SBI HSM hart_start),
|
|
* then clear it and re-check the mailbox. */
|
|
__asm__ volatile("wfi");
|
|
CLINT_MSIP_REG(hartid) = 0;
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
#else
|
|
__asm__ volatile("wfi");
|
|
#endif /* MPFS_DDR_INIT && WOLFBOOT_MMODE_SMODE_BOOT */
|
|
}
|
|
}
|
|
|
|
#if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
|
|
/* Enable the CLINT MTIME counter via the SYSREG RTC/time-base clock divider
|
|
* (HSS set_RTC_divisor equivalent). Without this MTIME never advances and an
|
|
* S-mode OS has no time source for its scheduler tick. */
|
|
static void mpfs_enable_mtime(void)
|
|
{
|
|
volatile uint32_t *rtc_cr = (volatile uint32_t *)(SYSREG_BASE + 0x0CUL);
|
|
uint32_t div;
|
|
#ifdef LIBERO_SETTING_MSS_RTC_CLOCK_CR
|
|
div = (uint32_t)LIBERO_SETTING_MSS_RTC_CLOCK_CR & 0xFFFU;
|
|
#else
|
|
div = 125U; /* 125 MHz reference / 1 MHz RTC */
|
|
#endif
|
|
*rtc_cr = div; /* program divider (bits 11:0), enable off */
|
|
*rtc_cr |= (1UL << 16); /* enable RTC/time-base clock */
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
}
|
|
|
|
/* SBI HSM hart_start backend (called from src/riscv_sbi.c on the boot
|
|
* hart): stage the target hart's start mailbox and ring its MSIP; the
|
|
* parked hart consumes it in secondary_hart_entry() and enters S-mode at
|
|
* saddr with a0=hartid, a1=opaque. */
|
|
int sbi_hal_hart_start(unsigned long hartid, unsigned long saddr,
|
|
unsigned long opaque)
|
|
{
|
|
if (hartid < (unsigned long)MPFS_FIRST_U54_HART ||
|
|
hartid > (unsigned long)MPFS_LAST_U54_HART) {
|
|
return -1;
|
|
}
|
|
mpfs_kernel_handoff[hartid].entry = (uint64_t)saddr;
|
|
mpfs_kernel_handoff[hartid].opaque = (uint64_t)opaque;
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
mpfs_kernel_handoff[hartid].marker = MPFS_KERNEL_HANDOFF_MARKER;
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
CLINT_MSIP_REG(hartid) = 0x01;
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
return 0;
|
|
}
|
|
|
|
/* Override of the weak hal_smode_boot in src/boot_riscv.c. The E51 cannot
|
|
* run Linux (cpu@0 is marked disabled in the Yocto MPFS DTB), so instead of
|
|
* dropping to S-mode on hart 0 we stage the kernel/DTB pointers, IPI a U54,
|
|
* and park hart 0 in M-mode. The released U54 picks up the context from
|
|
* its WFI loop in secondary_hart_entry() and performs the actual M->S jump. */
|
|
void __attribute__((noreturn))
|
|
hal_smode_boot(unsigned long entry, unsigned long hartid, unsigned long dtb)
|
|
{
|
|
static const unsigned long park_wdt_bases[5] = {
|
|
MSS_WDT_E51_BASE, MSS_WDT_U54_1_BASE, MSS_WDT_U54_2_BASE,
|
|
MSS_WDT_U54_3_BASE, MSS_WDT_U54_4_BASE
|
|
};
|
|
unsigned int w;
|
|
|
|
(void)hartid; /* the calling E51 hart is not the kernel boot hart */
|
|
|
|
/* Bring up the MTIME time base before releasing the U54 into S-mode. */
|
|
mpfs_enable_mtime();
|
|
|
|
/* Enable the clocks and release the soft resets of MMUART1-4 for the
|
|
* OS: the kernel's mpfs clock driver gates SUBBLK_CLOCK_CR but does
|
|
* not release peripheral soft resets (HSS normally does), so without
|
|
* this the serial console (MMUART1) stays dead. Done here on the
|
|
* E51, single-threaded, so no SYSREG read-modify-write races. */
|
|
SYSREG_SUBBLK_CLOCK_CR |= (MSS_PERIPH_MMUART0 << 1) |
|
|
(MSS_PERIPH_MMUART0 << 2) |
|
|
(MSS_PERIPH_MMUART0 << 3) |
|
|
(MSS_PERIPH_MMUART0 << 4);
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
SYSREG_SOFT_RESET_CR &= ~((MSS_PERIPH_MMUART0 << 1) |
|
|
(MSS_PERIPH_MMUART0 << 2) |
|
|
(MSS_PERIPH_MMUART0 << 3) |
|
|
(MSS_PERIPH_MMUART0 << 4));
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
|
|
mpfs_kernel_handoff[MPFS_FIRST_U54_HART].entry = (uint64_t)entry;
|
|
mpfs_kernel_handoff[MPFS_FIRST_U54_HART].opaque = (uint64_t)dtb;
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
mpfs_kernel_handoff[MPFS_FIRST_U54_HART].marker =
|
|
MPFS_KERNEL_HANDOFF_MARKER;
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
|
|
wolfBoot_printf("Releasing hart %d into S-mode at 0x%lx (dtb=0x%lx)\n",
|
|
MPFS_FIRST_U54_HART, entry, dtb);
|
|
|
|
CLINT_MSIP_REG(MPFS_FIRST_U54_HART) = 0x01;
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
|
|
/* Park the E51 as the platform monitor (HSS's watchdog-service role):
|
|
* doze on the machine timer and pet all watchdogs every few seconds.
|
|
* The MSS watchdogs always count and reset the chip on timeout
|
|
* (~28 s at the boot-time settings), so the parked E51 must keep
|
|
* them refreshed while the OS boots and runs.
|
|
*
|
|
* Wake only on the machine timer; mstatus.MIE stays clear so the
|
|
* pending timer wakes WFI without vectoring into the trap path.
|
|
* Pet ALL watchdogs (HSS's WDog-service role): the MSS WDTs always
|
|
* count and RESET the chip on timeout even with CONTROL=0, and the
|
|
* OS watchdog driver is disabled in the dtb fixup (when it owned
|
|
* them, its refresh-forbidden window made our blind refreshes trip
|
|
* it). With MVRP at maximum a refresh is always permitted. */
|
|
__asm__ volatile("csrw mie, %0" :: "r"(0x80UL)); /* MTIE */
|
|
while (1) {
|
|
for (w = 0; w < 5U; w++) {
|
|
MSS_WDT_REFRESH(park_wdt_bases[w]) = 0xDEADC0DEU;
|
|
}
|
|
CLINT_MTIMECMP_REG(MPFS_FIRST_HART) =
|
|
CLINT_MTIME_REG + (5UL * RTC_CLOCK_FREQ);
|
|
__asm__ volatile("wfi");
|
|
}
|
|
__builtin_unreachable();
|
|
}
|
|
#endif /* MPFS_DDR_INIT && WOLFBOOT_MMODE_SMODE_BOOT */
|
|
|
|
#endif /* WOLFBOOT_RISCV_MMODE */
|
|
|
|
#if defined(EXT_FLASH) && defined(TEST_EXT_FLASH) && defined(__WOLFBOOT)
|
|
static int test_ext_flash(void);
|
|
#endif
|
|
#if defined(EXT_FLASH) && defined(UART_QSPI_PROGRAM) && defined(__WOLFBOOT)
|
|
static void qspi_uart_program(void);
|
|
#endif
|
|
|
|
void hal_init(void)
|
|
{
|
|
#ifdef WOLFBOOT_RISCV_MMODE
|
|
volatile uint32_t *wdt_e51 = (volatile uint32_t *)0x20001000UL;
|
|
volatile uint32_t *sysreg_reset_sr = (volatile uint32_t *)0x20002020UL;
|
|
int h;
|
|
#if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
|
|
volatile uint32_t *dtim = (volatile uint32_t *)0x01000000UL;
|
|
unsigned int k;
|
|
#endif
|
|
#ifndef WATCHDOG
|
|
static const unsigned long wdt_bases[5] = {
|
|
MSS_WDT_E51_BASE, MSS_WDT_U54_1_BASE, MSS_WDT_U54_2_BASE,
|
|
MSS_WDT_U54_3_BASE, MSS_WDT_U54_4_BASE
|
|
};
|
|
unsigned int w;
|
|
#endif
|
|
#ifdef MPFS_DDR_INIT
|
|
unsigned int outer_retry;
|
|
int ddr_ok = 0;
|
|
#endif
|
|
|
|
/* Park every hart's machine-timer comparator at maximum. CLINT MTIME
|
|
* is 0 (the RTC time base is not running yet) and mtimecmp resets to 0,
|
|
* so MTIP is pending on every hart out of reset. A pending interrupt
|
|
* makes WFI return immediately, so the parked secondary harts' eNVM
|
|
* wait loop SPINS continuously (fetching from eNVM for the entire
|
|
* boot) instead of sleeping. Parking the comparators clears MTIP so
|
|
* WFI really waits. */
|
|
for (h = 0; h < MPFS_NUM_HARTS; h++) {
|
|
CLINT_MTIMECMP_REG(h) = ~(uint64_t)0;
|
|
}
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
|
|
#if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
|
|
/* Clear the DTIM-resident cross-hart state (start mailboxes + SBI
|
|
* shared block): DTIM content is undefined at power-on. */
|
|
for (k = 0; k < (0x200U / sizeof(uint32_t)); k++) {
|
|
dtim[k] = 0;
|
|
}
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
#endif
|
|
|
|
/* Capture boot ROM WDT defaults for restoration in hal_prepare_boot() */
|
|
mpfs_wdt_default_mvrp = MSS_WDT_MVRP(MSS_WDT_E51_BASE);
|
|
mpfs_wdt_default_ctrl = MSS_WDT_CONTROL(MSS_WDT_E51_BASE);
|
|
/* Snapshot boot-ROM WDT state and SYSREG RESET_SR (reset status
|
|
* cause) so we can print them AFTER uart_init. RESET_SR is W1C --
|
|
* we clear after reading. */
|
|
mpfs_boot_wdt_snap[0] = wdt_e51[0];
|
|
mpfs_boot_wdt_snap[1] = wdt_e51[1];
|
|
mpfs_boot_wdt_snap[2] = wdt_e51[2];
|
|
mpfs_boot_wdt_snap[3] = wdt_e51[3];
|
|
mpfs_boot_wdt_snap[4] = wdt_e51[4];
|
|
mpfs_boot_wdt_snap[5] = wdt_e51[5];
|
|
mpfs_boot_reset_sr_snap = *sysreg_reset_sr;
|
|
*sysreg_reset_sr = mpfs_boot_reset_sr_snap; /* W1C: clear seen bits */
|
|
|
|
#ifndef WATCHDOG
|
|
/* WATCHDOG=0 (default): disable WDT for the duration of wolfBoot; it is
|
|
* re-enabled in hal_prepare_boot() before do_boot. The MPFS MSS WDOG
|
|
* cannot be disabled outright (it always counts), so clear DEVRST
|
|
* (bit 5) -- timeout then raises an NMI instead of a chip reset -- set
|
|
* TIME/MVRP to max for the longest window, and refresh with the magic
|
|
* key (a refresh inside the triggered window would arm a reset). */
|
|
for (w = 0; w < 5; w++) {
|
|
MSS_WDT_REFRESH(wdt_bases[w]) = 0xDEADC0DEU;
|
|
MSS_WDT_TIME(wdt_bases[w]) = 0x00FFFFFFUL;
|
|
MSS_WDT_MVRP(wdt_bases[w]) = 0x00FFFFFFUL;
|
|
MSS_WDT_CONTROL(wdt_bases[w]) = 0;
|
|
MSS_WDT_REFRESH(wdt_bases[w]) = 0xDEADC0DEU;
|
|
}
|
|
#else
|
|
/* WATCHDOG=1: keep WDT enabled with a generous timeout for crypto.
|
|
* Verify is bounded at ~5s; configure a much larger timeout so we
|
|
* never have to pet the WDT during ECDSA verify. */
|
|
MSS_WDT_REFRESH(MSS_WDT_E51_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_MVRP(MSS_WDT_E51_BASE) = WATCHDOG_TIMEOUT_TICKS;
|
|
MSS_WDT_CONTROL(MSS_WDT_E51_BASE) |= MSS_WDT_CTRL_ENABLE;
|
|
#endif
|
|
|
|
mpfs_config_l2_cache();
|
|
mpfs_signal_main_hart_started();
|
|
#endif
|
|
|
|
#ifdef DEBUG_UART
|
|
SYSREG_SUBBLK_CLOCK_CR |= (MSS_PERIPH_MMUART0 << DEBUG_UART_PORT);
|
|
SYSREG_SOFT_RESET_CR &= ~(MSS_PERIPH_MMUART0 << DEBUG_UART_PORT);
|
|
uart_init();
|
|
#endif
|
|
|
|
#ifdef WOLFBOOT_REPRODUCIBLE_BUILD
|
|
wolfBoot_printf("wolfBoot Version: %s\n", LIBWOLFBOOT_VERSION_STRING);
|
|
#else
|
|
wolfBoot_printf("wolfBoot Version: %s (%s %s)\n",
|
|
LIBWOLFBOOT_VERSION_STRING, __DATE__, __TIME__);
|
|
#endif
|
|
|
|
#ifdef WOLFBOOT_RISCV_MMODE
|
|
wolfBoot_printf("Running on E51 (hart 0) in M-mode\n");
|
|
DBG_DDR("Boot WDT_E51: REFRESH=%x CTRL=%x STATUS=%x TIME=%x MVRP=%x TRIG=%x\n",
|
|
mpfs_boot_wdt_snap[0], mpfs_boot_wdt_snap[1], mpfs_boot_wdt_snap[2],
|
|
mpfs_boot_wdt_snap[3], mpfs_boot_wdt_snap[4], mpfs_boot_wdt_snap[5]);
|
|
wolfBoot_printf("Boot RESET_SR: %x (bit0=PERIPH bit1=MSS bit2=CPU bit3=DBG "
|
|
"bit4=FABRIC bit5=WDOG bit6=GPIO bit7=BUS bit8=SOFT)\n",
|
|
mpfs_boot_reset_sr_snap);
|
|
|
|
#ifdef MPFS_DDR_INIT
|
|
/* Bring up LPDDR4 before any DDR-resident operations.
|
|
*
|
|
* Outer retry loop: each call to mpfs_ddr_init() does a SYSREG DDRC
|
|
* soft-reset pulse, which clears the MTC engine state. If the
|
|
* inner retry inside mpfs_ddr_init() exhausts (typically because
|
|
* MTC wedged after the first failure), come back here for a full
|
|
* controller re-init. Empirical: per-attempt failure rate ~30%, so
|
|
* MPFS_DDR_MAX_OUTER_RETRY (6) outer attempts cover ~99.9% of boots. */
|
|
for (outer_retry = 0; outer_retry < MPFS_DDR_MAX_OUTER_RETRY;
|
|
outer_retry++) {
|
|
if (outer_retry > 0) {
|
|
wolfBoot_printf(
|
|
"DDR: Outer retry %u/%u (full DDRC re-init)\n",
|
|
outer_retry, MPFS_DDR_MAX_OUTER_RETRY);
|
|
}
|
|
if (mpfs_ddr_init(outer_retry) == 0) {
|
|
ddr_ok = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!ddr_ok) {
|
|
/* No safe path forward: WOLFBOOT_LOAD_ADDRESS is in DDR, so a
|
|
* subsequent disk-load would write to a non-functional
|
|
* controller and hang silently inside the AXI master. Halt
|
|
* with a clear message so the operator can power-cycle. */
|
|
wolfBoot_printf(
|
|
"DDR: Init FAILED after %u outer retries -- halting.\n"
|
|
"DDR: Power-cycle the board and retry.\n",
|
|
MPFS_DDR_MAX_OUTER_RETRY);
|
|
while (1) {
|
|
/* spin */
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef EXT_FLASH
|
|
if (qspi_init() != 0) {
|
|
wolfBoot_printf("QSPI: Init failed\n");
|
|
} else {
|
|
#if defined(TEST_EXT_FLASH) && defined(__WOLFBOOT)
|
|
test_ext_flash();
|
|
#endif
|
|
#if defined(UART_QSPI_PROGRAM) && defined(__WOLFBOOT)
|
|
qspi_uart_program();
|
|
#endif
|
|
}
|
|
#endif /* EXT_FLASH */
|
|
}
|
|
|
|
/* System Controller Mailbox */
|
|
|
|
static int mpfs_scb_mailbox_busy(void)
|
|
{
|
|
return (SCBCTRL_REG(SERVICES_SR_OFFSET) & SERVICES_SR_BUSY_MASK);
|
|
}
|
|
|
|
/* Read 16-byte device serial number via SCB system service (opcode 0x00). */
|
|
int mpfs_read_serial_number(uint8_t *serial)
|
|
{
|
|
uint32_t cmd, status;
|
|
int i, timeout;
|
|
|
|
if (serial == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
/* Check if mailbox is busy */
|
|
if (mpfs_scb_mailbox_busy()) {
|
|
wolfBoot_printf("SCB mailbox busy\n");
|
|
return -2;
|
|
}
|
|
|
|
/* Send serial number request command (opcode 0x00)
|
|
* Command format: [31:16] = opcode, [0] = request bit */
|
|
cmd = (SYS_SERV_CMD_SERIAL_NUMBER << SERVICES_CR_COMMAND_SHIFT) |
|
|
SERVICES_CR_REQ_MASK;
|
|
SCBCTRL_REG(SERVICES_CR_OFFSET) = cmd;
|
|
|
|
/* Wait for request bit to clear (command accepted) */
|
|
timeout = MPFS_SCB_TIMEOUT;
|
|
while ((SCBCTRL_REG(SERVICES_CR_OFFSET) & SERVICES_CR_REQ_MASK) && timeout > 0) {
|
|
timeout--;
|
|
}
|
|
if (timeout == 0) {
|
|
wolfBoot_printf("SCB mailbox request timeout\n");
|
|
return -3;
|
|
}
|
|
|
|
/* Wait for busy bit to clear (command completed) */
|
|
timeout = MPFS_SCB_TIMEOUT;
|
|
while (mpfs_scb_mailbox_busy() && timeout > 0) {
|
|
timeout--;
|
|
}
|
|
if (timeout == 0) {
|
|
wolfBoot_printf("SCB mailbox busy timeout\n");
|
|
return -4;
|
|
}
|
|
|
|
/* Check status (upper 16 bits of status register) */
|
|
status = (SCBCTRL_REG(SERVICES_SR_OFFSET) >> SERVICES_SR_STATUS_SHIFT) & 0xFFFF;
|
|
if (status != 0) {
|
|
wolfBoot_printf("SCB mailbox error: 0x%x\n", status);
|
|
return -5;
|
|
}
|
|
|
|
/* Read serial number from mailbox RAM (16 bytes) */
|
|
for (i = 0; i < DEVICE_SERIAL_NUMBER_SIZE; i++) {
|
|
serial[i] = SCBMBOX_BYTE(i);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Linux kernel command line arguments */
|
|
#ifndef LINUX_BOOTARGS
|
|
#ifndef LINUX_BOOTARGS_ROOT
|
|
/* wolfBoot SD layout (tools/scripts/program-sdcard.sh): p1=boot FIT,
|
|
* p2=update, p3=rootfs. */
|
|
#define LINUX_BOOTARGS_ROOT "/dev/mmcblk0p3"
|
|
#endif
|
|
|
|
#define LINUX_BOOTARGS \
|
|
"earlycon=sbi root="LINUX_BOOTARGS_ROOT" rootwait uio_pdrv_genirq.of_id=generic-uio"
|
|
#endif
|
|
|
|
/* Microchip OUI (Organizationally Unique Identifier) for MAC address */
|
|
#define MICROCHIP_OUI_0 0x00
|
|
#define MICROCHIP_OUI_1 0x04
|
|
#define MICROCHIP_OUI_2 0xA3
|
|
|
|
static int mpfs_dts_fixup_inplace(void* dts_addr)
|
|
{
|
|
int off, ret;
|
|
struct fdt_header *fdt = (struct fdt_header *)dts_addr;
|
|
uint8_t device_serial_number[DEVICE_SERIAL_NUMBER_SIZE];
|
|
uint8_t mac_addr[6];
|
|
#if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
|
|
/* Nodes disabled below. Watchdogs: the MSS WDTs always count; the OS
|
|
* driver arms them at probe but nothing pings them (no userspace
|
|
* watchdog daemon in the default image), so the system would reset
|
|
* ~28 s into boot. Disabled, they only latch a harmless tripped
|
|
* status and the parked E51 monitor keeps them refreshed. Re-enable
|
|
* when an OS-side petting story exists. */
|
|
static const char *const cpu_off[] = {
|
|
"watchdog@20001000", "watchdog@20101000", "watchdog@20103000",
|
|
"watchdog@20105000", "watchdog@20107000" };
|
|
unsigned int i;
|
|
#endif
|
|
|
|
/* Verify FDT header */
|
|
ret = fdt_check_header(dts_addr);
|
|
if (ret != 0) {
|
|
wolfBoot_printf("FDT: Invalid header! %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
wolfBoot_printf("FDT: Version %d, Size %d\n",
|
|
fdt_version(fdt), fdt_totalsize(fdt));
|
|
|
|
/* Expand total size to allow adding/modifying properties.
|
|
* Sizing comes from WOLFBOOT_FDT_FIXUP_HEADROOM in include/fdt.h. */
|
|
fdt_set_totalsize(fdt,
|
|
fdt_totalsize(fdt) + WOLFBOOT_FDT_FIXUP_HEADROOM);
|
|
|
|
/* Find /chosen node */
|
|
off = fdt_find_node_offset(fdt, -1, "chosen");
|
|
if (off < 0) {
|
|
/* Create /chosen node if it doesn't exist */
|
|
off = fdt_add_subnode(fdt, 0, "chosen");
|
|
}
|
|
|
|
if (off >= 0) {
|
|
/* Set bootargs property */
|
|
fdt_fixup_str(fdt, off, "chosen", "bootargs", LINUX_BOOTARGS);
|
|
}
|
|
|
|
#if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
|
|
/* Disable the MSS watchdog dtb nodes BEFORE the serial-number read: this
|
|
* does not depend on the serial number and MUST run even if the SC serial
|
|
* read below fails. Otherwise the OS watchdog driver would arm them at
|
|
* probe while the parked E51 monitor (hal_smode_boot) also refreshes all
|
|
* five every cycle -- a conflict that can trip a watchdog-driven reset.
|
|
*
|
|
* Do NOT override the stock /memory nodes: the 32-bit cached DDR window at
|
|
* 0x80000000 is only 1 GB wide (0xC0000000 up is the NON-CACHED alias onto
|
|
* the same DDR) and the stock Video Kit DTB already describes the full
|
|
* 2 GB; an earlier fixup that forced memory@80000000 to 2 GB made the
|
|
* kernel treat the alias window as extra RAM (self-aliasing corruption;
|
|
* boot hung at the first deep memblock allocation).
|
|
*
|
|
* cpu@2..cpu@4 stay ENABLED: the SBI HSM hart_start backend releases the
|
|
* parked harts on the kernel's request (SMP). cpu@0 (E51) is already
|
|
* disabled in the Yocto DTB; cpu@1 stays enabled so Linux boots on it. */
|
|
for (i = 0; i < sizeof(cpu_off) / sizeof(cpu_off[0]); i++) {
|
|
off = fdt_find_node_offset(fdt, -1, cpu_off[i]);
|
|
if (off >= 0) {
|
|
ret = fdt_fixup_str(fdt, off, cpu_off[i], "status",
|
|
"disabled");
|
|
if (ret != 0) {
|
|
wolfBoot_printf("FDT: Failed to disable %s (%d)\n",
|
|
cpu_off[i], ret);
|
|
}
|
|
}
|
|
else {
|
|
wolfBoot_printf("FDT: %s not found\n", cpu_off[i]);
|
|
}
|
|
}
|
|
#endif /* MPFS_DDR_INIT && WOLFBOOT_MMODE_SMODE_BOOT */
|
|
|
|
/* Read device serial number from system controller */
|
|
ret = mpfs_read_serial_number(device_serial_number);
|
|
if (ret != 0) {
|
|
wolfBoot_printf("FDT: Failed to read serial number (%d)\n", ret);
|
|
/* Continue without setting MAC addresses */
|
|
return 0;
|
|
}
|
|
|
|
wolfBoot_printf("FDT: Device serial: %02x%02x%02x%02x-%02x%02x%02x%02x-"
|
|
"%02x%02x%02x%02x-%02x%02x%02x%02x\n",
|
|
device_serial_number[15], device_serial_number[14],
|
|
device_serial_number[13], device_serial_number[12],
|
|
device_serial_number[11], device_serial_number[10],
|
|
device_serial_number[9], device_serial_number[8],
|
|
device_serial_number[7], device_serial_number[6],
|
|
device_serial_number[5], device_serial_number[4],
|
|
device_serial_number[3], device_serial_number[2],
|
|
device_serial_number[1], device_serial_number[0]);
|
|
|
|
/* Build MAC address: Microchip OUI + lower 3 bytes of serial number
|
|
* Format: {0x00, 0x04, 0xA3, serial[2], serial[1], serial[0]} */
|
|
mac_addr[0] = MICROCHIP_OUI_0;
|
|
mac_addr[1] = MICROCHIP_OUI_1;
|
|
mac_addr[2] = MICROCHIP_OUI_2;
|
|
mac_addr[3] = device_serial_number[2];
|
|
mac_addr[4] = device_serial_number[1];
|
|
mac_addr[5] = device_serial_number[0];
|
|
|
|
wolfBoot_printf("FDT: MAC0 = %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
mac_addr[0], mac_addr[1], mac_addr[2],
|
|
mac_addr[3], mac_addr[4], mac_addr[5]);
|
|
|
|
/* Set local-mac-address for ethernet@20110000 (mac0) */
|
|
off = fdt_find_node_offset(fdt, -1, "ethernet@20110000");
|
|
if (off >= 0) {
|
|
ret = fdt_setprop(fdt, off, "local-mac-address", mac_addr, 6);
|
|
if (ret != 0) {
|
|
wolfBoot_printf("FDT: Failed to set mac0 address (%d)\n", ret);
|
|
}
|
|
}
|
|
else {
|
|
wolfBoot_printf("FDT: ethernet@20110000 not found\n");
|
|
}
|
|
|
|
/* Set local-mac-address for ethernet@20112000 (mac1)
|
|
* Use MAC address + 1 for the second interface */
|
|
mac_addr[5] = device_serial_number[0] + 1;
|
|
|
|
wolfBoot_printf("FDT: MAC1 = %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
mac_addr[0], mac_addr[1], mac_addr[2],
|
|
mac_addr[3], mac_addr[4], mac_addr[5]);
|
|
|
|
off = fdt_find_node_offset(fdt, -1, "ethernet@20112000");
|
|
if (off >= 0) {
|
|
ret = fdt_setprop(fdt, off, "local-mac-address", mac_addr, 6);
|
|
if (ret != 0) {
|
|
wolfBoot_printf("FDT: Failed to set mac1 address (%d)\n", ret);
|
|
}
|
|
}
|
|
else {
|
|
wolfBoot_printf("FDT: ethernet@20112000 not found\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(WOLFBOOT_RISCV_MMODE) && defined(MPFS_DDR_INIT)
|
|
/* FIT subimage copy via PDMA (overrides the weak default in src/fdt.c).
|
|
* CPU writes to DDR do not land on this board, so route kernel/dtb copies
|
|
* through the PDMA master. A DDR source is read via its non-cached alias so
|
|
* PDMA sees real DDR; mpfs_pdma_memcpy remaps the dst 0x8x->0xCx and flushes
|
|
* L2. Chunked + WDT-petted for kernel-sized copies. */
|
|
int wolfBoot_fit_memcpy(void *dst, const void *src, uint32_t len)
|
|
{
|
|
uintptr_t d = (uintptr_t)dst;
|
|
uintptr_t s = (uintptr_t)src;
|
|
volatile const uint8_t *ncd;
|
|
const uint8_t *ncs;
|
|
uint32_t off = 0;
|
|
uint32_t chunk;
|
|
uint32_t k;
|
|
int retry;
|
|
int mism;
|
|
int rc = 0;
|
|
|
|
if ((s & 0xF0000000UL) == 0x80000000UL) {
|
|
s |= 0x40000000UL; /* non-cached source alias */
|
|
}
|
|
while (off < len) {
|
|
chunk = len - off;
|
|
if (chunk > (1024U * 1024U)) {
|
|
chunk = 1024U * 1024U;
|
|
}
|
|
/* mpfs_pdma_memcpy always returns 0, so the read-back verify below is
|
|
* the authoritative success check for this chunk. The PDMA->DDR write
|
|
* intermittently drops a block, so re-PDMA on a mismatch (same pattern
|
|
* as sdhci_platform_block_copy). A DDR destination (0x8xxxxxxx) is
|
|
* read back through its non-cached alias (| 0x40000000) so we compare
|
|
* what actually landed in DDR, not stale L2; this makes the caller's
|
|
* fail-closed rc real for the signature-uncovered kernel/dtb copies.
|
|
* A non-DDR destination (e.g. an L2 scratch buffer) lands directly, so
|
|
* a single copy suffices. */
|
|
mism = 1;
|
|
for (retry = 0; retry < 8 && mism != 0; retry++) {
|
|
(void)mpfs_pdma_memcpy((void *)(d + off),
|
|
(const void *)(s + off), chunk);
|
|
/* Refresh all five MSS watchdogs (they always count and reset the
|
|
* chip and cannot be disabled) during the multi-MB kernel copy
|
|
* and its read-back verify. */
|
|
MSS_WDT_REFRESH(MSS_WDT_E51_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_1_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_2_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_3_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_4_BASE) = 0xDEADC0DEU;
|
|
if ((d & 0xF0000000UL) != 0x80000000UL) {
|
|
mism = 0; /* non-DDR dst lands on the first copy */
|
|
break;
|
|
}
|
|
__asm__ volatile("fence iorw,iorw" ::: "memory");
|
|
ncd = (volatile const uint8_t *)((d + off) | 0x40000000UL);
|
|
ncs = (const uint8_t *)(s + off);
|
|
mism = 0;
|
|
for (k = 0; k < chunk; k++) {
|
|
if (ncd[k] != ncs[k]) {
|
|
mism = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (mism != 0) {
|
|
/* Copy could not be verified within the retry budget; remember the
|
|
* failure so the caller fails closed rather than boot corrupt,
|
|
* no-longer-signature-covered data. */
|
|
rc = -1;
|
|
}
|
|
off += chunk;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/* L2 round-trip wrapper around mpfs_dts_fixup_inplace(). The dtb lives in DDR
|
|
* (WOLFBOOT_LOAD_DTS_ADDRESS) but CPU writes to DDR do not land here, so copy
|
|
* it (non-cached read) into an L2 scratch buffer, run the FDT fixups there
|
|
* (CPU L2 writes work), then PDMA the result back to DDR. */
|
|
int hal_dts_fixup(void* dts_addr)
|
|
{
|
|
static uint8_t l2_dtb[64 * 1024] __attribute__((aligned(8)));
|
|
const uint8_t *ddr_nc;
|
|
uint32_t sz;
|
|
int ret;
|
|
|
|
if (dts_addr == NULL) {
|
|
return -1;
|
|
}
|
|
ddr_nc = (const uint8_t *)((uintptr_t)dts_addr | 0x40000000UL);
|
|
if (fdt_check_header((void *)ddr_nc) != 0) {
|
|
wolfBoot_printf("FDT: invalid header at %p\n", dts_addr);
|
|
return -1;
|
|
}
|
|
sz = (uint32_t)fdt_totalsize((void *)ddr_nc);
|
|
/* Overflow-safe bound: compare without adding. A near-UINT32_MAX
|
|
* totalsize (fdt_check_header does not bound it) would make
|
|
* sz + WOLFBOOT_FDT_FIXUP_HEADROOM wrap to a small value that passes the
|
|
* check, after which memcpy(l2_dtb, ., sz) overruns the 64 KB buffer.
|
|
* sizeof(l2_dtb) (64 KB) is always greater than the headroom. */
|
|
if (sz > sizeof(l2_dtb) - WOLFBOOT_FDT_FIXUP_HEADROOM) {
|
|
wolfBoot_printf("FDT: dtb too large for L2 fixup (%u > %u)\n",
|
|
(unsigned)sz,
|
|
(unsigned)(sizeof(l2_dtb) - WOLFBOOT_FDT_FIXUP_HEADROOM));
|
|
return -1;
|
|
}
|
|
/* DDR (non-cached) -> L2 */
|
|
memcpy(l2_dtb, ddr_nc, sz);
|
|
/* fixup in the CPU-writable L2 buffer */
|
|
ret = mpfs_dts_fixup_inplace(l2_dtb);
|
|
/* L2 -> DDR via PDMA (expanded totalsize) */
|
|
if (wolfBoot_fit_memcpy(dts_addr, l2_dtb,
|
|
(uint32_t)fdt_totalsize(l2_dtb)) != 0) {
|
|
wolfBoot_printf("FDT: dtb copy-back to DDR failed\n");
|
|
return -1;
|
|
}
|
|
return ret;
|
|
}
|
|
#else
|
|
/* Without the M-mode DDR constraints the dtb buffer is CPU-writable, so
|
|
* run the fixups directly in place (the original behavior, kept so
|
|
* FDT-enabled non-DDR builds do not silently fall back to the weak
|
|
* no-op hal_dts_fixup). */
|
|
int hal_dts_fixup(void* dts_addr)
|
|
{
|
|
if (dts_addr == NULL) {
|
|
return -1;
|
|
}
|
|
return mpfs_dts_fixup_inplace(dts_addr);
|
|
}
|
|
#endif /* WOLFBOOT_RISCV_MMODE && MPFS_DDR_INIT */
|
|
|
|
void hal_prepare_boot(void)
|
|
{
|
|
#ifdef WOLFBOOT_RISCV_MMODE
|
|
#ifndef WOLFBOOT_MMODE_SMODE_BOOT
|
|
/* Restore boot ROM WDT defaults so the application sees a normal WDT.
|
|
* Refresh first so the timer doesn't fire immediately after we apply
|
|
* the new MVRP. Restore the original CONTROL value (including the
|
|
* enable bit) rather than unconditionally enabling. */
|
|
MSS_WDT_REFRESH(MSS_WDT_E51_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_MVRP(MSS_WDT_E51_BASE) = mpfs_wdt_default_mvrp;
|
|
MSS_WDT_CONTROL(MSS_WDT_E51_BASE) = mpfs_wdt_default_ctrl;
|
|
#else
|
|
/* Booting an S-mode OS: keep the watchdogs in the safe state set in
|
|
* hal_init (no device reset, maximum window) and give every hart's
|
|
* watchdog one final refresh so the OS inherits a full window (the
|
|
* OS watchdog driver hangs at probe if it finds an already-tripped
|
|
* watchdog). The OS watchdog nodes are disabled in the dtb, so after
|
|
* hand-off the parked E51 monitor loop in hal_smode_boot refreshes all
|
|
* five each cycle for the life of the OS. */
|
|
MSS_WDT_REFRESH(MSS_WDT_E51_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_1_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_2_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_3_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_4_BASE) = 0xDEADC0DEU;
|
|
|
|
/* Hand the OS a clean SD controller: wolfBoot just used it for the
|
|
* image load, and the leftover state makes the OS driver's re-init
|
|
* and tuning intermittently fail ("Waiting for root device"). Guarded
|
|
* on the disk backend: an S-mode-boot config that loads the kernel from
|
|
* a non-SDHCI source (e.g. QSPI) pulls in no sdhci.h declaration. */
|
|
#if defined(DISK_SDCARD) || defined(DISK_EMMC)
|
|
sdhci_shutdown();
|
|
#endif
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
void RAMFUNCTION hal_flash_unlock(void)
|
|
{
|
|
|
|
}
|
|
|
|
void RAMFUNCTION hal_flash_lock(void)
|
|
{
|
|
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/* Wait for SCB register bits to clear, with timeout */
|
|
static int mpfs_scb_wait_clear(uint32_t reg_offset, uint32_t mask,
|
|
uint32_t timeout)
|
|
{
|
|
while ((SCBCTRL_REG(reg_offset) & mask) && --timeout)
|
|
;
|
|
return (timeout == 0) ? -1 : 0;
|
|
}
|
|
|
|
#ifdef EXT_FLASH
|
|
/* ==========================================================================
|
|
* QSPI Flash Controller Implementation
|
|
*
|
|
* Both MSS QSPI (0x21000000) and SC QSPI (0x37020100) use CoreQSPI v2
|
|
* with identical register layouts. The controller is selected at build
|
|
* time via MPFS_SC_SPI, which changes QSPI_BASE in the header.
|
|
* ========================================================================== */
|
|
|
|
/* Microsecond delay using RISC-V time CSR (1 MHz tick rate) */
|
|
#ifndef WOLFBOOT_RISCV_MMODE
|
|
static void udelay(uint32_t us)
|
|
{
|
|
uint64_t start = csr_read(time);
|
|
while ((uint64_t)(csr_read(time) - start) < us)
|
|
;
|
|
}
|
|
#endif
|
|
/* Forward declarations */
|
|
static int qspi_transfer_block(uint8_t read_mode, const uint8_t *cmd,
|
|
uint32_t cmd_len, uint8_t *data,
|
|
uint32_t data_len, uint8_t dummy_cycles);
|
|
static int qspi_read_id(uint8_t *id_buf);
|
|
static int qspi_enter_4byte_mode(void);
|
|
|
|
/* Send Release from Deep Power-Down / Wake up command */
|
|
static void qspi_flash_wakeup(void)
|
|
{
|
|
uint8_t cmd = 0xAB; /* Release from Deep Power-Down */
|
|
qspi_transfer_block(QSPI_MODE_WRITE, &cmd, 1, NULL, 0, 0);
|
|
/* Flash needs tRES1 (3us typ) to wake up */
|
|
udelay(10);
|
|
}
|
|
|
|
int qspi_init(void)
|
|
{
|
|
uint8_t id[3];
|
|
uint32_t timeout;
|
|
|
|
#ifdef MPFS_SC_SPI
|
|
wolfBoot_printf("QSPI: Using SC QSPI Controller (0x%x)\n", QSPI_BASE);
|
|
|
|
/* Wait for system controller to finish any pending operations before
|
|
* taking direct control of the SC QSPI peripheral */
|
|
mpfs_scb_wait_clear(SERVICES_SR_OFFSET, SERVICES_SR_BUSY_MASK,
|
|
QSPI_TIMEOUT_TRIES);
|
|
|
|
#ifdef DEBUG_QSPI
|
|
wolfBoot_printf("QSPI: Initial CTRL=0x%x, STATUS=0x%x, DIRECT=0x%x\n",
|
|
QSPI_CONTROL, QSPI_STATUS, QSPI_DIRECT);
|
|
#endif
|
|
|
|
/* Disable direct access / XIP mode (SC may have left it enabled) */
|
|
QSPI_DIRECT = 0;
|
|
#else
|
|
wolfBoot_printf("QSPI: Using MSS QSPI Controller (0x%x)\n", QSPI_BASE);
|
|
|
|
/* Enable QSPI peripheral clock (MSS only) */
|
|
SYSREG_SUBBLK_CLOCK_CR |= SYSREG_SUBBLK_CLOCK_CR_QSPI;
|
|
udelay(1);
|
|
|
|
/* Release MSS QSPI from reset (MSS only) */
|
|
SYSREG_SOFT_RESET_CR &= ~SYSREG_SOFT_RESET_CR_QSPI;
|
|
udelay(10);
|
|
#endif
|
|
|
|
/* Disable controller before configuration */
|
|
QSPI_CONTROL = 0;
|
|
|
|
/* Disable all interrupts */
|
|
QSPI_IEN = 0;
|
|
|
|
/* Configure QSPI Control Register:
|
|
* - Clock divider for ~5MHz (conservative)
|
|
* - CPOL=1 (clock idle high) for SPI Mode 3
|
|
* - Sample on SCK edge
|
|
* - Enable controller
|
|
*/
|
|
QSPI_CONTROL =
|
|
(QSPI_CLK_DIV_30 << QSPI_CTRL_CLKRATE_OFFSET) |
|
|
QSPI_CTRL_CLKIDLE |
|
|
QSPI_CTRL_SAMPLE_SCK |
|
|
QSPI_CTRL_EN;
|
|
|
|
/* Wait for controller to be ready */
|
|
timeout = QSPI_TIMEOUT_TRIES;
|
|
while (!(QSPI_STATUS & QSPI_STATUS_READY) && --timeout);
|
|
if (timeout == 0) {
|
|
wolfBoot_printf("QSPI: Controller not ready\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Wake up flash from deep power-down (if applicable) */
|
|
qspi_flash_wakeup();
|
|
|
|
/* Read and display JEDEC ID for verification */
|
|
if (qspi_read_id(id) == 0) {
|
|
wolfBoot_printf("QSPI: Flash ID = 0x%02x 0x%02x 0x%02x\n",
|
|
id[0], id[1], id[2]);
|
|
}
|
|
|
|
/* Enter 4-byte addressing mode for >16MB flash */
|
|
qspi_enter_4byte_mode();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* QSPI Block Transfer Function
|
|
* Modeled after Microchip's MSS_QSPI_polled_transfer_block reference driver.
|
|
*
|
|
* read_mode: 0=write (QSPI_MODE_WRITE), 1=read (QSPI_MODE_READ)
|
|
* cmd: Command buffer (opcode + address bytes)
|
|
* cmd_len: Length of command (opcode + address, NOT including opcode separately)
|
|
* data: Data buffer for read/write
|
|
* data_len: Length of data phase
|
|
* dummy_cycles: Number of idle cycles between command and data phase
|
|
*/
|
|
static int qspi_transfer_block(uint8_t read_mode, const uint8_t *cmd,
|
|
uint32_t cmd_len, uint8_t *data,
|
|
uint32_t data_len, uint8_t dummy_cycles)
|
|
{
|
|
uint32_t total_bytes = cmd_len + data_len;
|
|
uint32_t frames;
|
|
uint32_t i;
|
|
uint32_t timeout;
|
|
uint32_t frame_cmd;
|
|
|
|
/* Wait for controller to be ready before starting */
|
|
timeout = QSPI_TIMEOUT_TRIES;
|
|
while (!(QSPI_STATUS & QSPI_STATUS_READY) && --timeout);
|
|
if (timeout == 0) {
|
|
#ifdef DEBUG_QSPI
|
|
wolfBoot_printf("QSPI: Timeout waiting for READY\n");
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
/* Drain RX FIFO of any stale data from previous transfers. */
|
|
timeout = QSPI_TIMEOUT_TRIES;
|
|
while ((QSPI_STATUS & QSPI_STATUS_RXAVAIL) && --timeout) {
|
|
(void)QSPI_RX_DATA;
|
|
}
|
|
#ifdef DEBUG_QSPI
|
|
if (timeout == 0) {
|
|
/* log warning and continue trying to transfer data */
|
|
wolfBoot_printf("QSPI: Timeout draining RX FIFO\n");
|
|
}
|
|
#endif
|
|
|
|
/* Configure FRAMES register:
|
|
* - Total bytes: command + data (idle cycles handled by hardware)
|
|
* - Command bytes: TX-only bytes before data phase
|
|
* - Idle cycles: inserted by hardware between command and data
|
|
* - FBYTE: status flags (RXAVAIL/TXAVAIL) refer to individual bytes
|
|
*
|
|
* For write-mode transfers, set CMDBYTES = TOTALBYTES so the entire
|
|
* transfer occurs in the command phase (TX-only). The CoreQSPI data
|
|
* phase shifts TX FIFO output by a fixed offset on writes, causing
|
|
* data rotation in the programmed page. Keeping everything in the
|
|
* command phase avoids this. The flash determines command vs data
|
|
* boundaries from the opcode, not the controller's phase. */
|
|
frame_cmd = read_mode ? cmd_len : total_bytes;
|
|
frames = ((total_bytes & 0xFFFF) << QSPI_FRAMES_TOTALBYTES_OFFSET) |
|
|
((frame_cmd & 0x1FF) << QSPI_FRAMES_CMDBYTES_OFFSET) |
|
|
((dummy_cycles & 0xF) << QSPI_FRAMES_IDLE_OFFSET) |
|
|
(1u << QSPI_FRAMES_FBYTE_OFFSET);
|
|
|
|
QSPI_FRAMES = frames;
|
|
|
|
/* Send command bytes (opcode + address).
|
|
* Use TXAVAIL (bit 3) to check for FIFO space -- CoreQSPI v2 does NOT
|
|
* have a TXFULL status bit (bit 5 is reserved/always 0).
|
|
* A fence (iorw, iorw) after each TX write ensures the store reaches the
|
|
* peripheral before we read STATUS again (RISC-V RVWMO allows posted
|
|
* stores that could cause stale TXAVAIL reads and FIFO overflow). */
|
|
for (i = 0; i < cmd_len; i++) {
|
|
timeout = QSPI_TIMEOUT_TRIES;
|
|
while (!(QSPI_STATUS & QSPI_STATUS_TXAVAIL) && --timeout);
|
|
if (timeout == 0) {
|
|
#ifdef DEBUG_QSPI
|
|
wolfBoot_printf("QSPI: TX FIFO full timeout\n");
|
|
#endif
|
|
return -2;
|
|
}
|
|
QSPI_TX_DATA = cmd[i];
|
|
QSPI_IO_FENCE();
|
|
}
|
|
|
|
if (read_mode) {
|
|
/* Read mode: poll RXAVAIL for each data byte. */
|
|
for (i = 0; i < data_len; i++) {
|
|
timeout = QSPI_RX_TIMEOUT_TRIES;
|
|
while (!(QSPI_STATUS & QSPI_STATUS_RXAVAIL) && --timeout);
|
|
if (timeout == 0) {
|
|
#ifdef DEBUG_QSPI
|
|
wolfBoot_printf("QSPI: RX timeout at byte %d, status=0x%x\n",
|
|
i, QSPI_STATUS);
|
|
#endif
|
|
return -3;
|
|
}
|
|
data[i] = QSPI_RX_DATA;
|
|
}
|
|
/* Wait for receive complete */
|
|
timeout = QSPI_RX_TIMEOUT_TRIES;
|
|
while (!(QSPI_STATUS & QSPI_STATUS_RXDONE) && --timeout);
|
|
if (timeout == 0) {
|
|
#ifdef DEBUG_QSPI
|
|
wolfBoot_printf("QSPI: RXDONE timeout\n");
|
|
#endif
|
|
return -5;
|
|
}
|
|
} else {
|
|
/* Write mode: send data bytes.
|
|
* Must push bytes without delay -- any gap causes FIFO underflow
|
|
* since CoreQSPI continues clocking with empty FIFO.
|
|
* Fence (iorw, iorw) after each write ensures the store reaches the
|
|
* FIFO before we re-read STATUS (prevents FIFO overflow from posted
|
|
* stores). */
|
|
if (data && data_len > 0) {
|
|
for (i = 0; i < data_len; i++) {
|
|
timeout = QSPI_TIMEOUT_TRIES;
|
|
while (!(QSPI_STATUS & QSPI_STATUS_TXAVAIL) && --timeout);
|
|
if (timeout == 0) {
|
|
#ifdef DEBUG_QSPI
|
|
wolfBoot_printf("QSPI: TX data timeout\n");
|
|
#endif
|
|
return -4;
|
|
}
|
|
QSPI_TX_DATA = data[i];
|
|
QSPI_IO_FENCE();
|
|
}
|
|
}
|
|
/* Wait for transmit complete */
|
|
timeout = QSPI_TIMEOUT_TRIES;
|
|
while (!(QSPI_STATUS & QSPI_STATUS_TXDONE) && --timeout);
|
|
if (timeout == 0) {
|
|
#ifdef DEBUG_QSPI
|
|
wolfBoot_printf("QSPI: TXDONE timeout, status=0x%x\n",
|
|
QSPI_STATUS);
|
|
#endif
|
|
return -5;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_QSPI
|
|
wolfBoot_printf("QSPI: cmd[0]=0x%x, cmd_len=%d, data_len=%d, frames=0x%x\n",
|
|
cmd[0], cmd_len, data_len, frames);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Read JEDEC ID from flash */
|
|
static int qspi_read_id(uint8_t *id_buf)
|
|
{
|
|
uint8_t cmd = QSPI_CMD_READ_ID_OPCODE;
|
|
return qspi_transfer_block(QSPI_MODE_READ, &cmd, 1, id_buf, 3, 0);
|
|
}
|
|
|
|
/* Send Write Enable command */
|
|
static int qspi_write_enable(void)
|
|
{
|
|
uint8_t cmd = QSPI_CMD_WRITE_ENABLE_OPCODE;
|
|
return qspi_transfer_block(QSPI_MODE_WRITE, &cmd, 1, NULL, 0, 0);
|
|
}
|
|
|
|
/* Wait for flash to be ready (poll status register) */
|
|
static int qspi_wait_ready(uint32_t timeout_ms)
|
|
{
|
|
uint8_t cmd = QSPI_CMD_READ_STATUS_OPCODE;
|
|
uint8_t status;
|
|
uint32_t count = 0;
|
|
uint32_t max_count = timeout_ms * 1000; /* Rough timing */
|
|
int ret;
|
|
|
|
do {
|
|
ret = qspi_transfer_block(QSPI_MODE_READ, &cmd, 1, &status, 1, 0);
|
|
if (ret != 0) {
|
|
return ret; /* Propagate transfer error */
|
|
}
|
|
if (!(status & 0x01)) { /* Bit 0 = WIP (Write In Progress) */
|
|
return 0; /* Ready */
|
|
}
|
|
count++;
|
|
} while (count < max_count);
|
|
|
|
return -1; /* Timeout */
|
|
}
|
|
|
|
/* Enter 4-byte addressing mode (required for >32MB flash) */
|
|
static int qspi_enter_4byte_mode(void)
|
|
{
|
|
uint8_t cmd = QSPI_CMD_ENTER_4BYTE_MODE;
|
|
return qspi_transfer_block(QSPI_MODE_WRITE, &cmd, 1, NULL, 0, 0);
|
|
}
|
|
|
|
/* Read from QSPI flash (4-byte addressing) */
|
|
static int qspi_flash_read(uint32_t address, uint8_t *data, uint32_t len)
|
|
{
|
|
const uint32_t max_chunk = 0xFFFF - 5; /* total_bytes is 16-bit, cmd is 5 */
|
|
uint8_t cmd[5];
|
|
uint32_t remaining = len;
|
|
uint32_t chunk_len;
|
|
int ret;
|
|
|
|
while (remaining > 0) {
|
|
chunk_len = (remaining > max_chunk) ? max_chunk : remaining;
|
|
|
|
/* Build 4-byte read command */
|
|
cmd[0] = QSPI_CMD_4BYTE_READ_OPCODE;
|
|
cmd[1] = (address >> 24) & 0xFF;
|
|
cmd[2] = (address >> 16) & 0xFF;
|
|
cmd[3] = (address >> 8) & 0xFF;
|
|
cmd[4] = address & 0xFF;
|
|
|
|
ret = qspi_transfer_block(QSPI_MODE_READ, cmd, 5, data, chunk_len, 0);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
address += chunk_len;
|
|
data += chunk_len;
|
|
remaining -= chunk_len;
|
|
}
|
|
|
|
return (int)len;
|
|
}
|
|
|
|
/* Write to QSPI flash - single page (max 256 bytes) */
|
|
static int qspi_flash_write_page(uint32_t address, const uint8_t *data, uint32_t len)
|
|
{
|
|
uint8_t cmd[5];
|
|
int ret;
|
|
|
|
/* Ensure page alignment and length */
|
|
if (len > FLASH_PAGE_SIZE) {
|
|
len = FLASH_PAGE_SIZE;
|
|
}
|
|
|
|
/* Enable write */
|
|
ret = qspi_write_enable();
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* Build 4-byte page program command */
|
|
cmd[0] = QSPI_CMD_4BYTE_PAGE_PROG_OPCODE;
|
|
cmd[1] = (address >> 24) & 0xFF;
|
|
cmd[2] = (address >> 16) & 0xFF;
|
|
cmd[3] = (address >> 8) & 0xFF;
|
|
cmd[4] = address & 0xFF;
|
|
|
|
/* Send command + data */
|
|
ret = qspi_transfer_block(QSPI_MODE_WRITE, cmd, 5, (uint8_t *)data, len, 0);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* Wait for write to complete */
|
|
return qspi_wait_ready(1000); /* 1 second timeout */
|
|
}
|
|
|
|
/* Erase 64KB sector */
|
|
static int qspi_flash_sector_erase(uint32_t address)
|
|
{
|
|
uint8_t cmd[5];
|
|
int ret;
|
|
|
|
/* Enable write */
|
|
ret = qspi_write_enable();
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* Build 4-byte sector erase command */
|
|
cmd[0] = QSPI_CMD_4BYTE_SECTOR_ERASE;
|
|
cmd[1] = (address >> 24) & 0xFF;
|
|
cmd[2] = (address >> 16) & 0xFF;
|
|
cmd[3] = (address >> 8) & 0xFF;
|
|
cmd[4] = address & 0xFF;
|
|
|
|
ret = qspi_transfer_block(QSPI_MODE_WRITE, cmd, 5, NULL, 0, 0);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* Wait for erase to complete (64KB erase can take several seconds) */
|
|
return qspi_wait_ready(10000); /* 10 second timeout */
|
|
}
|
|
|
|
/* ==========================================================================
|
|
* External Flash API Implementation
|
|
* ========================================================================== */
|
|
void ext_flash_lock(void)
|
|
{
|
|
/* Optional: Could implement write protection here */
|
|
}
|
|
|
|
void ext_flash_unlock(void)
|
|
{
|
|
}
|
|
|
|
int ext_flash_write(uintptr_t address, const uint8_t *data, int len)
|
|
{
|
|
uint32_t page_offset;
|
|
uint32_t chunk_len;
|
|
int ret;
|
|
int remaining = len;
|
|
int total = len;
|
|
|
|
#ifdef DEBUG_QSPI
|
|
wolfBoot_printf("QSPI: Write 0x%x, len %d\n", (uint32_t)address, len);
|
|
#endif
|
|
|
|
/* Write data page by page */
|
|
while (remaining > 0) {
|
|
/* Calculate bytes to write in this page */
|
|
page_offset = address & (FLASH_PAGE_SIZE - 1);
|
|
chunk_len = FLASH_PAGE_SIZE - page_offset;
|
|
if (chunk_len > (uint32_t)remaining) {
|
|
chunk_len = remaining;
|
|
}
|
|
|
|
/* Write page */
|
|
ret = qspi_flash_write_page(address, data, chunk_len);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* Update pointers */
|
|
address += chunk_len;
|
|
data += chunk_len;
|
|
remaining -= chunk_len;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
int ext_flash_read(uintptr_t address, uint8_t *data, int len)
|
|
{
|
|
#ifdef DEBUG_QSPI
|
|
wolfBoot_printf("QSPI: Read 0x%x -> 0x%lx, len %d\n",
|
|
(uint32_t)address, (unsigned long)data, len);
|
|
#endif
|
|
return qspi_flash_read((uint32_t)address, data, (uint32_t)len);
|
|
}
|
|
|
|
int ext_flash_erase(uintptr_t address, int len)
|
|
{
|
|
uint32_t sector_addr;
|
|
uint32_t end_addr;
|
|
int ret;
|
|
int total = len;
|
|
|
|
#ifdef DEBUG_QSPI
|
|
wolfBoot_printf("QSPI: Erase 0x%x, len %d\n", (uint32_t)address, len);
|
|
#endif
|
|
|
|
/* Check for invalid length or integer overflow */
|
|
if (len <= 0 || (uint32_t)len > UINT32_MAX - (uint32_t)address) {
|
|
return -1;
|
|
}
|
|
|
|
/* Align to sector boundaries */
|
|
sector_addr = address & ~(FLASH_SECTOR_SIZE - 1);
|
|
end_addr = (uint32_t)address + (uint32_t)len;
|
|
|
|
/* Erase sectors */
|
|
while (sector_addr < end_addr) {
|
|
#ifdef DEBUG_QSPI
|
|
wolfBoot_printf("QSPI: Erasing sector at 0x%08X\n", sector_addr);
|
|
#endif
|
|
|
|
ret = qspi_flash_sector_erase(sector_addr);
|
|
if (ret != 0) {
|
|
wolfBoot_printf("QSPI: Erase failed\n");
|
|
return ret;
|
|
}
|
|
|
|
sector_addr += FLASH_SECTOR_SIZE;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
/* ============================================================================
|
|
* UART QSPI Programmer
|
|
*
|
|
* Allows programming the QSPI flash over the debug UART without a JTAG/Libero
|
|
* tool. Enabled at build time with UART_QSPI_PROGRAM=1 in the .config.
|
|
*
|
|
* Protocol (after wolfBoot prints the "QSPI-PROG" prompt):
|
|
* 1. Host sends 'P' within the timeout window to enter programming mode
|
|
* 2. wolfBoot sends "READY\r\n"
|
|
* 3. Host sends [4-byte LE QSPI address][4-byte LE data length]
|
|
* 4. wolfBoot erases required sectors, sends "ERASED\r\n"
|
|
* 5. For each 256-byte chunk:
|
|
* wolfBoot sends ACK byte (0x06) -> host sends chunk -> wolfBoot writes
|
|
* 6. wolfBoot sends "DONE\r\n" and continues normal boot
|
|
*
|
|
* Host side: tools/scripts/mpfs_qspi_prog.py
|
|
* ============================================================================ */
|
|
#if defined(UART_QSPI_PROGRAM) && defined(__WOLFBOOT)
|
|
|
|
#define QSPI_PROG_CHUNK 256
|
|
#define QSPI_PROG_ACK 0x06
|
|
#define QSPI_RX_TIMEOUT_MS 10000U /* 10 s per byte -- aborts if host disappears */
|
|
|
|
|
|
/* Returns 0-255 on success, -1 on timeout (so the boot path is never deadlocked). */
|
|
static int uart_qspi_rx(void)
|
|
{
|
|
uint32_t t;
|
|
for (t = 0; t < QSPI_RX_TIMEOUT_MS; t++) {
|
|
if (MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_DR)
|
|
return (int)(uint8_t)MMUART_RBR(DEBUG_UART_BASE);
|
|
udelay(1000);
|
|
}
|
|
return -1; /* timeout */
|
|
}
|
|
|
|
static void uart_qspi_tx(uint8_t c)
|
|
{
|
|
while (!(MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_THRE))
|
|
;
|
|
MMUART_THR(DEBUG_UART_BASE) = c;
|
|
}
|
|
|
|
static void uart_qspi_puts(const char *s)
|
|
{
|
|
while (*s)
|
|
uart_qspi_tx((uint8_t)*s++);
|
|
}
|
|
|
|
static void qspi_uart_program(void)
|
|
{
|
|
uint8_t ch = 0;
|
|
uint32_t addr, size, n_sectors, written, t;
|
|
uint32_t i, s;
|
|
uint8_t chunk[QSPI_PROG_CHUNK];
|
|
|
|
/* Use uart_qspi_puts (direct UART) for ALL programmer output.
|
|
* wolfBoot_printf uses uart_write which adds \r before \n and may
|
|
* leave stale bytes in the UART TX pipeline that corrupt the
|
|
* binary ACK/data protocol after ERASED. */
|
|
uart_qspi_puts("QSPI-PROG: Press 'P' within 3s to program flash\r\n");
|
|
|
|
/* Drain any stale RX bytes before opening the window */
|
|
while (MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_DR)
|
|
(void)MMUART_RBR(DEBUG_UART_BASE);
|
|
|
|
/* Wait up to 3s: 3000 iterations of 1ms each */
|
|
for (t = 0; t < 3000U; t++) {
|
|
udelay(1000);
|
|
if (MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_DR) {
|
|
ch = MMUART_RBR(DEBUG_UART_BASE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ch != 'P' && ch != 'p') {
|
|
uart_qspi_puts("QSPI-PROG: No trigger, booting\r\n");
|
|
return;
|
|
}
|
|
|
|
uart_qspi_puts("READY\r\n");
|
|
|
|
/* Receive destination address then data length (4 bytes LE each) */
|
|
addr = 0;
|
|
for (i = 0; i < 4; i++) {
|
|
int b = uart_qspi_rx();
|
|
if (b < 0) {
|
|
uart_qspi_puts("QSPI-PROG: RX timeout (addr)\r\n");
|
|
return;
|
|
}
|
|
addr |= ((uint32_t)(uint8_t)b << (i * 8));
|
|
}
|
|
size = 0;
|
|
for (i = 0; i < 4; i++) {
|
|
int b = uart_qspi_rx();
|
|
if (b < 0) {
|
|
uart_qspi_puts("QSPI-PROG: RX timeout (size)\r\n");
|
|
return;
|
|
}
|
|
size |= ((uint32_t)(uint8_t)b << (i * 8));
|
|
}
|
|
|
|
if (size == 0 || size > 0x200000U) {
|
|
uart_qspi_puts("QSPI-PROG: Invalid size\r\n");
|
|
return;
|
|
}
|
|
|
|
/* Reject writes to unaligned or out-of-partition addresses */
|
|
if ((addr & (FLASH_SECTOR_SIZE - 1U)) != 0U) {
|
|
uart_qspi_puts("QSPI-PROG: Not sector-aligned\r\n");
|
|
return;
|
|
}
|
|
if (!((addr >= WOLFBOOT_PARTITION_BOOT_ADDRESS &&
|
|
addr < WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE &&
|
|
size <= WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE - addr) ||
|
|
(addr >= WOLFBOOT_PARTITION_UPDATE_ADDRESS &&
|
|
addr < WOLFBOOT_PARTITION_UPDATE_ADDRESS + WOLFBOOT_PARTITION_SIZE &&
|
|
size <= WOLFBOOT_PARTITION_UPDATE_ADDRESS + WOLFBOOT_PARTITION_SIZE - addr))) {
|
|
uart_qspi_puts("QSPI-PROG: Outside partition\r\n");
|
|
return;
|
|
}
|
|
|
|
/* Erase all required sectors */
|
|
n_sectors = (size + FLASH_SECTOR_SIZE - 1) / FLASH_SECTOR_SIZE;
|
|
uart_qspi_puts("QSPI-PROG: Erasing...\r\n");
|
|
ext_flash_unlock();
|
|
for (s = 0; s < n_sectors; s++) {
|
|
int ret;
|
|
/* The MSS WDTs always count and reset the chip at timeout (~28.6s
|
|
* at the reset divisor) and cannot be disabled, so a transfer of
|
|
* more than a few tens of KB outlives the period: refresh all
|
|
* five per sector here and per chunk below. */
|
|
MSS_WDT_REFRESH(MSS_WDT_E51_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_1_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_2_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_3_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_4_BASE) = 0xDEADC0DEU;
|
|
ret = ext_flash_erase(addr + s * FLASH_SECTOR_SIZE,
|
|
FLASH_SECTOR_SIZE);
|
|
if (ret < 0) {
|
|
uart_qspi_puts("QSPI-PROG: Erase failed\r\n");
|
|
ext_flash_lock();
|
|
return;
|
|
}
|
|
}
|
|
|
|
uart_qspi_puts("ERASED\r\n");
|
|
|
|
/* Chunk transfer: wolfBoot requests each 256-byte block with ACK 0x06.
|
|
* No wolfBoot_printf allowed in this loop -- only direct UART via
|
|
* uart_qspi_tx/uart_qspi_puts to avoid protocol corruption. */
|
|
written = 0;
|
|
while (written < size) {
|
|
int ret;
|
|
uint32_t chunk_len = size - written;
|
|
if (chunk_len > QSPI_PROG_CHUNK)
|
|
chunk_len = QSPI_PROG_CHUNK;
|
|
|
|
MSS_WDT_REFRESH(MSS_WDT_E51_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_1_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_2_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_3_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_4_BASE) = 0xDEADC0DEU;
|
|
|
|
uart_qspi_tx(QSPI_PROG_ACK); /* request next chunk */
|
|
|
|
for (i = 0; i < chunk_len; i++) {
|
|
int b = uart_qspi_rx();
|
|
if (b < 0) {
|
|
uart_qspi_puts("QSPI-PROG: RX timeout\r\n");
|
|
ext_flash_lock();
|
|
return;
|
|
}
|
|
chunk[i] = (uint8_t)b;
|
|
}
|
|
|
|
ret = ext_flash_write(addr + written, chunk, (int)chunk_len);
|
|
if (ret < 0) {
|
|
uart_qspi_puts("QSPI-PROG: Write failed\r\n");
|
|
ext_flash_lock();
|
|
return;
|
|
}
|
|
written += chunk_len;
|
|
}
|
|
ext_flash_lock();
|
|
|
|
uart_qspi_puts("DONE\r\n");
|
|
}
|
|
|
|
#endif /* UART_QSPI_PROGRAM */
|
|
|
|
/* Test for external QSPI flash erase/write/read */
|
|
#ifdef TEST_EXT_FLASH
|
|
|
|
#ifndef TEST_EXT_ADDRESS
|
|
#define TEST_EXT_ADDRESS WOLFBOOT_PARTITION_UPDATE_ADDRESS
|
|
#endif
|
|
|
|
static int test_ext_flash(void)
|
|
{
|
|
int ret;
|
|
uint32_t i;
|
|
uint8_t pageData[FLASH_PAGE_SIZE];
|
|
|
|
wolfBoot_printf("Ext Flash Test 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("Sector Erase: Ret %d\n", ret);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Verify erase (should be all 0xFF) */
|
|
memset(pageData, 0, sizeof(pageData));
|
|
ret = ext_flash_read(TEST_EXT_ADDRESS, pageData, sizeof(pageData));
|
|
if (ret < 0) {
|
|
wolfBoot_printf("Erase verify read failed: Ret %d\n", ret);
|
|
return ret;
|
|
}
|
|
wolfBoot_printf("Erase verify: ");
|
|
for (i = 0; i < 16; i++) {
|
|
wolfBoot_printf("%02x ", pageData[i]);
|
|
}
|
|
wolfBoot_printf("\n");
|
|
|
|
/* Write Page */
|
|
for (i = 0; i < sizeof(pageData); i++) {
|
|
pageData[i] = (i & 0xff);
|
|
}
|
|
ret = ext_flash_write(TEST_EXT_ADDRESS, pageData, sizeof(pageData));
|
|
wolfBoot_printf("Page Write: Ret %d\n", ret);
|
|
if (ret < 0)
|
|
return ret;
|
|
#endif /* !TEST_FLASH_READONLY */
|
|
|
|
/* Read page */
|
|
memset(pageData, 0, sizeof(pageData));
|
|
ret = ext_flash_read(TEST_EXT_ADDRESS, pageData, sizeof(pageData));
|
|
wolfBoot_printf("Page Read: Ret %d\n", ret);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Check data */
|
|
for (i = 0; i < sizeof(pageData); i++) {
|
|
if (pageData[i] != (i & 0xff)) {
|
|
wolfBoot_printf("Check Data @ %d failed (0x%02x != 0x%02x)\n",
|
|
i, pageData[i], (i & 0xff));
|
|
wolfBoot_printf("First 16 bytes: ");
|
|
for (i = 0; i < 16; i++) {
|
|
wolfBoot_printf("%02x ", pageData[i]);
|
|
}
|
|
wolfBoot_printf("\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
wolfBoot_printf("Ext Flash Test Passed\n");
|
|
return 0;
|
|
}
|
|
#endif /* TEST_EXT_FLASH */
|
|
|
|
#else /* !EXT_FLASH */
|
|
|
|
/* Stubs for when QSPI is disabled */
|
|
void ext_flash_lock(void)
|
|
{
|
|
}
|
|
|
|
void ext_flash_unlock(void)
|
|
{
|
|
}
|
|
|
|
int ext_flash_write(uintptr_t address, const uint8_t *data, int len)
|
|
{
|
|
(void)address;
|
|
(void)data;
|
|
(void)len;
|
|
return 0;
|
|
}
|
|
|
|
int ext_flash_read(uintptr_t address, uint8_t *data, int len)
|
|
{
|
|
(void)address;
|
|
(void)data;
|
|
(void)len;
|
|
return 0;
|
|
}
|
|
|
|
int ext_flash_erase(uintptr_t address, int len)
|
|
{
|
|
(void)address;
|
|
(void)len;
|
|
return 0;
|
|
}
|
|
|
|
#endif /* EXT_FLASH */
|
|
|
|
#if defined(MMU) && !defined(WOLFBOOT_NO_PARTITIONS)
|
|
void* hal_get_dts_address(void)
|
|
{
|
|
#if defined(EXT_FLASH) && defined(NO_XIP)
|
|
/* Flash is not memory-mapped when using NO_XIP with external flash
|
|
* (e.g. SC SPI). DTS must be loaded via ext_flash_read, not direct
|
|
* dereference. Return NULL so the caller skips the direct-access path. */
|
|
return NULL;
|
|
#else
|
|
return (void*)WOLFBOOT_DTS_BOOT_ADDRESS;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/* PLIC: E51(hart 0)->ctx 0 (M-mode only); U54(1-4)->ctx hart*2-1 (M), hart*2 (S) */
|
|
#ifdef WOLFBOOT_RISCV_MMODE
|
|
uint32_t plic_get_context(void)
|
|
{
|
|
uint32_t hart_id;
|
|
__asm__ volatile("csrr %0, mhartid" : "=r"(hart_id));
|
|
return (hart_id == 0) ? 0 : (hart_id * 2) - 1;
|
|
}
|
|
#else
|
|
extern unsigned long get_boot_hartid(void);
|
|
uint32_t plic_get_context(void)
|
|
{
|
|
return (uint32_t)get_boot_hartid() * 2;
|
|
}
|
|
#endif
|
|
|
|
/* Dispatch IRQ to appropriate platform handler */
|
|
void plic_dispatch_irq(uint32_t irq)
|
|
{
|
|
switch (irq) {
|
|
#if defined(DISK_SDCARD) || defined(DISK_EMMC)
|
|
case PLIC_INT_MMC_MAIN:
|
|
sdhci_irq_handler();
|
|
break;
|
|
#endif
|
|
default:
|
|
/* Unknown interrupt - ignore */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* MSSIO IOMUX + bank-config register offsets.
|
|
* IOMUX0..IOMUX6_CR and the per-pad IO_CFG_*_*_CR registers live in SYSREG
|
|
* (base 0x20002000). The two MSSIO_BANK*_CFG_CR registers that set
|
|
* bank-wide pcode/ncode/voltage live in the *SCB* register space
|
|
* (SYSREGSCB_BASE 0x20003000) at offsets 0x1C4/0x1C8 - HSS writes them via
|
|
* SCB_REGS, not SYSREG. */
|
|
#define SYSREG_IOMUX0_CR_OFFSET 0x200u
|
|
#define SYSREG_IOMUX1_CR_OFFSET 0x204u
|
|
#define SYSREG_IOMUX2_CR_OFFSET 0x208u
|
|
#define SYSREG_IOMUX3_CR_OFFSET 0x20Cu
|
|
#define SYSREG_IOMUX4_CR_OFFSET 0x210u
|
|
#define SYSREG_IOMUX5_CR_OFFSET 0x214u
|
|
#define SYSREG_IOMUX6_CR_OFFSET 0x218u
|
|
#define SYSREG_MSSIO_BANK4_IO_CFG_0_1_CR 0x234u /* +4 each pair */
|
|
#define SYSREG_MSSIO_BANK2_IO_CFG_0_1_CR 0x254u /* +4 each pair */
|
|
#define SCB_MSSIO_BANK2_CFG_CR_OFFSET 0x1C4u
|
|
#define SCB_MSSIO_BANK4_CFG_CR_OFFSET 0x1C8u
|
|
|
|
/* Configure the MSSIO IOMUX so the SDHCI controller's pins are actually
|
|
* routed to the SD/eMMC slot pads. In S-mode builds HSS does this as
|
|
* part of mssio_setup() during nwc init; M-mode wolfBoot has to do it
|
|
* itself or the controller talks to floating pads and SD command
|
|
* responses come back as garbage (CMD_INDEX_ERR + CMD_END_BIT_ERR).
|
|
*
|
|
* All values come straight from the Libero/HSS-generated
|
|
* fpga_design_config.h that LIBERO_FPGA_CONFIG_DIR points at. */
|
|
#if defined(WOLFBOOT_RISCV_MMODE) && defined(MPFS_DDR_INIT)
|
|
void mpfs_iomux_init(void)
|
|
{
|
|
uint32_t iomux4, iomux5;
|
|
|
|
SYSREG_REG(SYSREG_IOMUX0_CR_OFFSET) = LIBERO_SETTING_IOMUX0_CR;
|
|
SYSREG_REG(SYSREG_IOMUX1_CR_OFFSET) = LIBERO_SETTING_IOMUX1_CR;
|
|
SYSREG_REG(SYSREG_IOMUX2_CR_OFFSET) = LIBERO_SETTING_IOMUX2_CR;
|
|
SYSREG_REG(SYSREG_IOMUX3_CR_OFFSET) = LIBERO_SETTING_IOMUX3_CR;
|
|
|
|
/* IOMUX4 + IOMUX5 need MPFS-Video-Kit-specific GPIO drive overrides
|
|
* on top of the Libero values to steer the board's external SD/eMMC
|
|
* demux mux into SD-card mode and pull a USB pin low. This logic
|
|
* is implemented in HSS as a board hook in
|
|
* boards/mpfs-video-kit/hss_board_init.c::switch_demux_using_fabric_ip
|
|
* and is NOT visible from the generic Libero IOMUX_CR values alone.
|
|
* Without this, the SDHCI controller talks to bank4 pads but those
|
|
* signals never reach the SD card slot, producing the "CMD8 timeout
|
|
* regardless of card insertion" symptom we observed for many runs.
|
|
*
|
|
* IOMUX4 bits[19:16] (USB pin): 0xD = drive logic 0
|
|
* IOMUX5 bits[3:0] (pad 30): 0xE = drive logic 1
|
|
* IOMUX5 bits[19:16] (pad 34): 0xE = drive logic 1
|
|
* IOMUX5 bits[31:28] (pad 37): 0xD = drive logic 0
|
|
*/
|
|
iomux4 = LIBERO_SETTING_IOMUX4_CR;
|
|
iomux5 = LIBERO_SETTING_IOMUX5_CR;
|
|
iomux4 &= ~(0xFu << 16);
|
|
iomux4 |= (0xDu << 16);
|
|
iomux5 &= ~((0xFu << 0) | (0xFu << 16) | (0xFu << 28));
|
|
iomux5 |= ((0xEu << 0) | (0xEu << 16) | (0xDu << 28));
|
|
SYSREG_REG(SYSREG_IOMUX4_CR_OFFSET) = iomux4;
|
|
SYSREG_REG(SYSREG_IOMUX5_CR_OFFSET) = iomux5;
|
|
SYSREG_REG(SYSREG_IOMUX6_CR_OFFSET) = LIBERO_SETTING_IOMUX6_CR;
|
|
|
|
/* Bank-wide config goes via SCB; per-pad IO_CFG goes via SYSREG. */
|
|
SYSREGSCB_REG(SCB_MSSIO_BANK4_CFG_CR_OFFSET) =
|
|
LIBERO_SETTING_MSSIO_BANK4_CFG_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK4_IO_CFG_0_1_CR + 0x00u) =
|
|
LIBERO_SETTING_MSSIO_BANK4_IO_CFG_0_1_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK4_IO_CFG_0_1_CR + 0x04u) =
|
|
LIBERO_SETTING_MSSIO_BANK4_IO_CFG_2_3_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK4_IO_CFG_0_1_CR + 0x08u) =
|
|
LIBERO_SETTING_MSSIO_BANK4_IO_CFG_4_5_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK4_IO_CFG_0_1_CR + 0x0Cu) =
|
|
LIBERO_SETTING_MSSIO_BANK4_IO_CFG_6_7_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK4_IO_CFG_0_1_CR + 0x10u) =
|
|
LIBERO_SETTING_MSSIO_BANK4_IO_CFG_8_9_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK4_IO_CFG_0_1_CR + 0x14u) =
|
|
LIBERO_SETTING_MSSIO_BANK4_IO_CFG_10_11_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK4_IO_CFG_0_1_CR + 0x18u) =
|
|
LIBERO_SETTING_MSSIO_BANK4_IO_CFG_12_13_CR;
|
|
|
|
SYSREGSCB_REG(SCB_MSSIO_BANK2_CFG_CR_OFFSET) =
|
|
LIBERO_SETTING_MSSIO_BANK2_CFG_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK2_IO_CFG_0_1_CR + 0x00u) =
|
|
LIBERO_SETTING_MSSIO_BANK2_IO_CFG_0_1_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK2_IO_CFG_0_1_CR + 0x04u) =
|
|
LIBERO_SETTING_MSSIO_BANK2_IO_CFG_2_3_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK2_IO_CFG_0_1_CR + 0x08u) =
|
|
LIBERO_SETTING_MSSIO_BANK2_IO_CFG_4_5_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK2_IO_CFG_0_1_CR + 0x0Cu) =
|
|
LIBERO_SETTING_MSSIO_BANK2_IO_CFG_6_7_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK2_IO_CFG_0_1_CR + 0x10u) =
|
|
LIBERO_SETTING_MSSIO_BANK2_IO_CFG_8_9_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK2_IO_CFG_0_1_CR + 0x14u) =
|
|
LIBERO_SETTING_MSSIO_BANK2_IO_CFG_10_11_CR;
|
|
SYSREG_REG(SYSREG_MSSIO_BANK2_IO_CFG_0_1_CR + 0x18u) =
|
|
LIBERO_SETTING_MSSIO_BANK2_IO_CFG_12_13_CR;
|
|
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
}
|
|
#endif /* WOLFBOOT_RISCV_MMODE && MPFS_DDR_INIT */
|
|
|
|
#if defined(DISK_SDCARD) || defined(DISK_EMMC)
|
|
/* SDHCI Platform HAL */
|
|
|
|
|
|
/* MSS MPU base + per-master offset. Each AXI master (FIC0/1/2, CRYPTO,
|
|
* GEM0/1, USB, MMC, SCB, TRACE) has 16 PMPCFG entries (uint64_t each) at
|
|
* 0x20005000 + (master_index << 8). HSS calls mpu_configure() during early
|
|
* boot to load these from LIBERO_SETTING_*_MPU_CFG_PMP* defaults; without
|
|
* that, the master may be locked out of memory regions it needs and stalls
|
|
* silently mid-transaction. MMC is master index 7. */
|
|
#define MSS_MPU_BASE 0x20005000UL
|
|
#define MSS_MPU_MMC_BASE (MSS_MPU_BASE + (7UL << 8))
|
|
|
|
#ifdef MPFS_DDR_INIT
|
|
/* Only available when LIBERO_FPGA_CONFIG_DIR is set (which also enables
|
|
* MPFS_DDR_INIT in arch.mk). HSS already configures these PMP entries
|
|
* during its own boot, so non-DDR / HSS-loaded builds don't need this. */
|
|
static void mpfs_mpu_init_mmc(void)
|
|
{
|
|
volatile uint64_t *pmp = (volatile uint64_t *)MSS_MPU_MMC_BASE;
|
|
pmp[0] = LIBERO_SETTING_MMC_MPU_CFG_PMP0;
|
|
pmp[1] = LIBERO_SETTING_MMC_MPU_CFG_PMP1;
|
|
pmp[2] = LIBERO_SETTING_MMC_MPU_CFG_PMP2;
|
|
pmp[3] = LIBERO_SETTING_MMC_MPU_CFG_PMP3;
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
}
|
|
#endif /* MPFS_DDR_INIT */
|
|
|
|
#ifdef SDHCI_BLOCK_VIA_PDMA
|
|
/* Pet all five MSS watchdogs during the (long) per-block SDHCI read loop.
|
|
* Overrides the weak no-op in src/sdhci.c. The MSS watchdogs always count
|
|
* and reset the chip at timeout and cannot be disabled, so the multi-second
|
|
* load of a large image must keep refreshing them. */
|
|
void sdhci_platform_wdt_pet(void)
|
|
{
|
|
MSS_WDT_REFRESH(MSS_WDT_E51_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_1_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_2_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_3_BASE) = 0xDEADC0DEU;
|
|
MSS_WDT_REFRESH(MSS_WDT_U54_4_BASE) = 0xDEADC0DEU;
|
|
}
|
|
|
|
/* Copy a staged SDHCI block to its final destination (overrides the weak
|
|
* memcpy default in src/sdhci.c). Direct CPU writes to DDR do not land on
|
|
* this board, so a DDR destination (0x8xxxxxxx) is written through the PDMA
|
|
* master and verified via its non-cached alias (| 0x40000000), re-PDMA'ing on
|
|
* a drop (the PDMA->DDR write intermittently drops a block when interleaved
|
|
* with SDHCI reads). A non-DDR destination (L2 header/GPT buffers) is a plain
|
|
* CPU copy, which lands. Returns 0 on success, -1 if a DDR write cannot be
|
|
* verified within the retry budget. */
|
|
int sdhci_platform_block_copy(void *dst, const void *src, uint32_t len)
|
|
{
|
|
volatile const uint8_t *ncv;
|
|
const uint8_t *s = (const uint8_t *)src;
|
|
int retry;
|
|
int mism;
|
|
uint32_t k;
|
|
|
|
if (((uintptr_t)dst & 0xF0000000UL) != 0x80000000UL) {
|
|
memcpy(dst, src, len);
|
|
return 0;
|
|
}
|
|
ncv = (volatile const uint8_t *)((uintptr_t)dst | 0x40000000UL);
|
|
mism = 1;
|
|
for (retry = 0; retry < 8 && mism != 0; retry++) {
|
|
/* The read-back verify below is the authoritative success check, so
|
|
* a PDMA-engine error is caught there and retried like any drop. */
|
|
(void)mpfs_pdma_memcpy(dst, src, len);
|
|
sdhci_platform_wdt_pet();
|
|
__asm__ volatile("fence iorw,iorw" ::: "memory");
|
|
mism = 0;
|
|
for (k = 0; k < len; k++) {
|
|
if (ncv[k] != s[k]) {
|
|
mism = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (mism != 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif /* SDHCI_BLOCK_VIA_PDMA */
|
|
|
|
void sdhci_platform_init(void)
|
|
{
|
|
/* IOMUX/MSSIO routing was already programmed in nwc_init() before the
|
|
* MSSIO_CONTROL_CR 4-phase sequence committed the pad config. Here:
|
|
* 1. Configure the MMC AXI master MPU so the controller can access
|
|
* the regions Libero expects (default state may be all-deny).
|
|
* 2. Enable the MMC peripheral clock.
|
|
* 3. Deassert the MMC soft reset.
|
|
* Mirrors the DDRC sequence in mpfs_ddr_init(). */
|
|
wolfBoot_printf("SDHCI: platform init\n");
|
|
#ifdef MPFS_DDR_INIT
|
|
/* MMC AXI master MPU config requires LIBERO_SETTING_MMC_MPU_CFG_*
|
|
* which is only defined when LIBERO_FPGA_CONFIG_DIR is set. When
|
|
* not set, this build is intended to run UNDER HSS (which has
|
|
* already configured the MPU), so this step is a no-op. */
|
|
mpfs_mpu_init_mmc();
|
|
#endif
|
|
SYSREG_REG(SYSREG_SUBBLK_CLOCK_CR_OFF) |= MSS_PERIPH_MMC;
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
SYSREG_SOFT_RESET_CR &= ~MSS_PERIPH_MMC;
|
|
__asm__ volatile("fence iorw, iorw" ::: "memory");
|
|
}
|
|
|
|
void sdhci_platform_irq_init(void)
|
|
{
|
|
#ifdef DEBUG_SDHCI
|
|
extern unsigned long get_boot_hartid(void);
|
|
#endif
|
|
|
|
/* Set priority for MMC main interrupt */
|
|
plic_set_priority(PLIC_INT_MMC_MAIN, PLIC_PRIORITY_DEFAULT);
|
|
|
|
/* Set threshold to 0 (allow all priorities > 0) */
|
|
plic_set_threshold(0);
|
|
|
|
/* Enable MMC interrupt for this hart */
|
|
plic_enable_interrupt(PLIC_INT_MMC_MAIN);
|
|
|
|
#ifdef DEBUG_SDHCI
|
|
wolfBoot_printf("sdhci_platform_irq_init: hart %lu, context %u, irq %u enabled\n",
|
|
get_boot_hartid(), (unsigned)plic_get_context(),
|
|
(unsigned)PLIC_INT_MMC_MAIN);
|
|
#endif
|
|
}
|
|
|
|
void sdhci_platform_set_bus_mode(int is_emmc)
|
|
{
|
|
(void)is_emmc;
|
|
}
|
|
|
|
uint32_t sdhci_reg_read(uint32_t offset)
|
|
{
|
|
return *((volatile uint32_t*)(EMMC_SD_BASE + offset));
|
|
}
|
|
|
|
void sdhci_reg_write(uint32_t offset, uint32_t val)
|
|
{
|
|
*((volatile uint32_t*)(EMMC_SD_BASE + offset)) = val;
|
|
}
|
|
#endif /* DISK_SDCARD || DISK_EMMC */
|
|
|
|
/* DEBUG UART */
|
|
#ifdef DEBUG_UART
|
|
|
|
/* Baud divisor: integer = PCLK/(baudrate*16), fractional (0-63) via 128x
|
|
* scaling. Uses the RUNTIME APB clock so divisors computed after the MSS
|
|
* PLL raise stay correct (the compile-time MSS_APB_AHB_CLK garbled every
|
|
* post-raise reinit). */
|
|
static void uart_config_baud(unsigned long base, uint32_t baudrate)
|
|
{
|
|
const uint64_t pclk = mpfs_apb_clk_hz;
|
|
uint32_t div_x128 = (uint32_t)((8UL * pclk) / baudrate);
|
|
uint32_t div_x64 = div_x128 / 2u;
|
|
uint32_t div_int = div_x64 / 64u;
|
|
uint32_t div_frac = div_x64 - (div_int * 64u);
|
|
div_frac += (div_x128 - (div_int * 128u)) - (div_frac * 2u);
|
|
if (div_frac > 63u)
|
|
div_frac = 63u;
|
|
if (div_int > (uint32_t)UINT16_MAX)
|
|
return;
|
|
MMUART_LCR(base) |= DLAB_MASK;
|
|
MMUART_DMR(base) = (uint8_t)(div_int >> 8);
|
|
MMUART_DLR(base) = (uint8_t)div_int;
|
|
MMUART_LCR(base) &= ~DLAB_MASK;
|
|
if (div_int > 1u) {
|
|
MMUART_MM0(base) |= EFBR_MASK;
|
|
MMUART_DFR(base) = (uint8_t)div_frac;
|
|
} else {
|
|
MMUART_MM0(base) &= ~EFBR_MASK;
|
|
}
|
|
}
|
|
|
|
static void uart_init_base(unsigned long base)
|
|
{
|
|
MMUART_MM0(base) &= ~ELIN_MASK;
|
|
MMUART_MM1(base) &= ~EIRD_MASK;
|
|
MMUART_MM2(base) &= ~EERR_MASK;
|
|
MMUART_IER(base) = 0u;
|
|
MMUART_FCR(base) = CLEAR_RX_FIFO_MASK | CLEAR_TX_FIFO_MASK | RXRDY_TXRDYN_EN_MASK;
|
|
MMUART_MCR(base) &= ~(LOOP_MASK | RLOOP_MASK);
|
|
MMUART_MCR(base) |= RTS_MASK; /* Assert RTS -- required for USB-UART bridge CTS */
|
|
MMUART_MM1(base) &= ~(E_MSB_TX_MASK | E_MSB_RX_MASK);
|
|
MMUART_MM2(base) &= ~(EAFM_MASK | ESWM_MASK);
|
|
MMUART_MM0(base) &= ~(ETTG_MASK | ERTO_MASK | EFBR_MASK);
|
|
MMUART_GFR(base) = 0u;
|
|
MMUART_TTG(base) = 0u;
|
|
MMUART_RTO(base) = 0u;
|
|
uart_config_baud(base, 115200);
|
|
MMUART_LCR(base) = MSS_UART_DATA_8_BITS | MSS_UART_NO_PARITY | MSS_UART_ONE_STOP_BIT;
|
|
}
|
|
|
|
void uart_init(void)
|
|
{
|
|
uart_init_base(DEBUG_UART_BASE);
|
|
}
|
|
|
|
void uart_write(const char* buf, unsigned int sz)
|
|
{
|
|
uint32_t pos = 0;
|
|
while (sz-- > 0) {
|
|
char c = buf[pos++];
|
|
if (c == '\n') {
|
|
while ((MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_THRE) == 0);
|
|
MMUART_THR(DEBUG_UART_BASE) = '\r';
|
|
}
|
|
while ((MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_THRE) == 0);
|
|
MMUART_THR(DEBUG_UART_BASE) = c;
|
|
}
|
|
}
|
|
|
|
#ifdef WOLFBOOT_RISCV_MMODE
|
|
/* Reinitialize the UART baud divisor after mss_pll_init() raises the
|
|
* APB clock (the divisor was computed for the 40 MHz boot clock). */
|
|
void hal_uart_reinit(void)
|
|
{
|
|
/* mpfs_apb_clk_hz was updated by mss_pll_init; just reprogram the
|
|
* divisor (uart_config_baud reads the runtime APB clock). */
|
|
uart_config_baud(DEBUG_UART_BASE, 115200);
|
|
}
|
|
#endif /* WOLFBOOT_RISCV_MMODE */
|
|
#endif /* DEBUG_UART */
|
|
|