wolfBoot/hal/nxp_t2080.c

1092 lines
38 KiB
C

/* nxp_t2080.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
*/
#include <stdint.h>
#include <stddef.h>
#include "target.h"
#include "printf.h"
#include "image.h" /* for RAMFUNCTION */
#include "nxp_ppc.h"
#include "nxp_t2080.h"
#define ENABLE_IFC
#define ENABLE_BUS_CLK_CALC
/* #define DEBUG_FLASH */
#ifndef BUILD_LOADER_STAGE1
#define ENABLE_MP /* multi-core support */
#endif
/* generic shared NXP QorIQ driver code */
#include "nxp_ppc.c"
/* Forward declarations */
static void RAMFUNCTION hal_flash_unlock_sector(uint32_t sector);
#ifdef ENABLE_MP
static void hal_mp_init(void);
#endif
/* AMD CFI Commands (Spansion/Cypress) */
#define AMD_CMD_RESET 0xF0
#define AMD_CMD_WRITE 0xA0
#define AMD_CMD_ERASE_START 0x80
#define AMD_CMD_ERASE_SECTOR 0x30
#define AMD_CMD_UNLOCK_START 0xAA
#define AMD_CMD_UNLOCK_ACK 0x55
#define AMD_CMD_WRITE_TO_BUFFER 0x25
#define AMD_CMD_WRITE_BUFFER_CONFIRM 0x29
#define AMD_CMD_SET_PPB_ENTRY 0xC0
#define AMD_CMD_SET_PPB_EXIT_BC1 0x90
#define AMD_CMD_SET_PPB_EXIT_BC2 0x00
#define AMD_CMD_PPB_UNLOCK_BC1 0x80
#define AMD_CMD_PPB_UNLOCK_BC2 0x30
#define AMD_CMD_PPB_LOCK_BC1 0xA0
#define AMD_CMD_PPB_LOCK_BC2 0x00
#define AMD_STATUS_TOGGLE 0x40
#define AMD_STATUS_ERROR 0x20
/* Flash unlock addresses */
#if FLASH_CFI_WIDTH == 16
#define FLASH_UNLOCK_ADDR1 0x555
#define FLASH_UNLOCK_ADDR2 0x2AA
#else
#define FLASH_UNLOCK_ADDR1 0xAAA
#define FLASH_UNLOCK_ADDR2 0x555
#endif
/* Flash IO Helpers */
#if FLASH_CFI_WIDTH == 16
#define FLASH_IO8_WRITE(sec, n, val) *((volatile uint16_t*)(FLASH_BASE_ADDR + (FLASH_SECTOR_SIZE * (sec)) + ((n) * 2))) = (((val) << 8) | (val))
#define FLASH_IO16_WRITE(sec, n, val) *((volatile uint16_t*)(FLASH_BASE_ADDR + (FLASH_SECTOR_SIZE * (sec)) + ((n) * 2))) = (val)
#define FLASH_IO8_READ(sec, n) (uint8_t)(*((volatile uint16_t*)(FLASH_BASE_ADDR + (FLASH_SECTOR_SIZE * (sec)) + ((n) * 2))))
#define FLASH_IO16_READ(sec, n) *((volatile uint16_t*)(FLASH_BASE_ADDR + (FLASH_SECTOR_SIZE * (sec)) + ((n) * 2)))
#else
#define FLASH_IO8_WRITE(sec, n, val) *((volatile uint8_t*)(FLASH_BASE_ADDR + (FLASH_SECTOR_SIZE * (sec)) + (n))) = (val)
#define FLASH_IO8_READ(sec, n) *((volatile uint8_t*)(FLASH_BASE_ADDR + (FLASH_SECTOR_SIZE * (sec)) + (n)))
#endif
void law_init(void)
{
/* Buffer Manager (BMan) (control) - probably not required */
set_law(3, 0xF, 0xF4000000, LAW_TRGT_BMAN, LAW_SIZE_32MB, 1);
}
/* Note: AMD Autoselect (READ_ID) mode is not used here because entering it
* affects the entire flash bank. Since wolfBoot runs XIP from the same
* bank (CS0), entering Autoselect would crash instruction fetch. */
static void hal_flash_init(void)
{
#ifdef ENABLE_IFC
uint32_t cspr;
/* IFC CS0 - NOR Flash
* Do NOT reprogram IFC CS0 base address, port size, AMASK, CSOR, or
* FTIM while executing from flash (XIP). The boot ROM already
* configured CS0 correctly.
*
* However, the boot ROM may set IFC_CSPR_WP (write-protect), which
* blocks all write cycles to the flash. This prevents AMD command
* sequences (erase/program) from reaching the chips. Clearing just
* the WP bit is safe during XIP — it doesn't change chip-select
* decode, only enables write forwarding. */
cspr = get32(IFC_CSPR(0));
#ifdef DEBUG_UART
wolfBoot_printf("IFC CSPR0: 0x%x%s\n", cspr,
(cspr & IFC_CSPR_WP) ? " (WP set)" : "");
#endif
/* WP clearing is done in hal_flash_clear_wp() from RAMFUNCTION code.
* T2080RM requires V=0 before modifying IFC_CSPR, which is not safe
* during XIP. The RAMFUNCTION code runs from DDR with flash TLB
* guarded, so it can safely toggle V=0 -> modify -> V=1. */
#endif /* ENABLE_IFC */
}
void hal_ddr_init(void)
{
#ifdef ENABLE_DDR
uint32_t reg;
/* Map LAW for DDR */
set_law(4, 0, DDR_ADDRESS, LAW_TRGT_DDR_1, LAW_SIZE_2GB, 0);
/* If DDR is already enabled then just return */
reg = get32(DDR_SDRAM_CFG);
if (reg & DDR_SDRAM_CFG_MEM_EN) {
return;
}
/* Set clock early for clock / pin */
set32(DDR_SDRAM_CLK_CNTL, DDR_SDRAM_CLK_CNTL_VAL);
/* Setup DDR CS (chip select) bounds */
set32(DDR_CS_BNDS(0), DDR_CS0_BNDS_VAL);
set32(DDR_CS_CONFIG(0), DDR_CS0_CONFIG_VAL);
set32(DDR_CS_CONFIG_2(0), DDR_CS_CONFIG_2_VAL);
set32(DDR_CS_BNDS(1), DDR_CS1_BNDS_VAL);
set32(DDR_CS_CONFIG(1), DDR_CS1_CONFIG_VAL);
set32(DDR_CS_CONFIG_2(1), DDR_CS_CONFIG_2_VAL);
set32(DDR_CS_BNDS(2), DDR_CS2_BNDS_VAL);
set32(DDR_CS_CONFIG(2), DDR_CS2_CONFIG_VAL);
set32(DDR_CS_CONFIG_2(2), DDR_CS_CONFIG_2_VAL);
set32(DDR_CS_BNDS(3), DDR_CS3_BNDS_VAL);
set32(DDR_CS_CONFIG(3), DDR_CS3_CONFIG_VAL);
set32(DDR_CS_CONFIG_2(3), DDR_CS_CONFIG_2_VAL);
/* DDR SDRAM timing configuration */
set32(DDR_TIMING_CFG_3, DDR_TIMING_CFG_3_VAL);
set32(DDR_TIMING_CFG_0, DDR_TIMING_CFG_0_VAL);
set32(DDR_TIMING_CFG_1, DDR_TIMING_CFG_1_VAL);
set32(DDR_TIMING_CFG_2, DDR_TIMING_CFG_2_VAL);
set32(DDR_TIMING_CFG_4, DDR_TIMING_CFG_4_VAL);
set32(DDR_TIMING_CFG_5, DDR_TIMING_CFG_5_VAL);
set32(DDR_ZQ_CNTL, DDR_ZQ_CNTL_VAL);
/* DDR SDRAM mode configuration */
set32(DDR_SDRAM_MODE, DDR_SDRAM_MODE_VAL);
set32(DDR_SDRAM_MODE_2, DDR_SDRAM_MODE_2_VAL);
set32(DDR_SDRAM_MODE_3, DDR_SDRAM_MODE_3_8_VAL);
set32(DDR_SDRAM_MODE_4, DDR_SDRAM_MODE_3_8_VAL);
set32(DDR_SDRAM_MODE_5, DDR_SDRAM_MODE_3_8_VAL);
set32(DDR_SDRAM_MODE_6, DDR_SDRAM_MODE_3_8_VAL);
set32(DDR_SDRAM_MODE_7, DDR_SDRAM_MODE_3_8_VAL);
set32(DDR_SDRAM_MODE_8, DDR_SDRAM_MODE_3_8_VAL);
set32(DDR_SDRAM_MD_CNTL, DDR_SDRAM_MD_CNTL_VAL);
/* DDR Configuration */
set32(DDR_SDRAM_INTERVAL, DDR_SDRAM_INTERVAL_VAL);
set32(DDR_DATA_INIT, DDR_DATA_INIT_VAL);
set32(DDR_WRLVL_CNTL, DDR_WRLVL_CNTL_VAL);
set32(DDR_WRLVL_CNTL_2, DDR_WRLVL_CNTL_2_VAL);
set32(DDR_WRLVL_CNTL_3, DDR_WRLVL_CNTL_3_VAL);
set32(DDR_SR_CNTR, 0);
set32(DDR_SDRAM_RCW_1, 0);
set32(DDR_SDRAM_RCW_2, 0);
set32(DDR_DDRCDR_1, DDR_DDRCDR_1_VAL);
set32(DDR_SDRAM_CFG_2, (DDR_SDRAM_CFG_2_VAL | DDR_SDRAM_CFG_2_D_INIT));
set32(DDR_INIT_ADDR, 0);
set32(DDR_INIT_EXT_ADDR, 0);
set32(DDR_DDRCDR_2, DDR_DDRCDR_2_VAL);
set32(DDR_ERR_DISABLE, 0);
set32(DDR_ERR_INT_EN, DDR_ERR_INT_EN_VAL);
set32(DDR_ERR_SBE, DDR_ERR_SBE_VAL);
/* Set values, but do not enable the DDR yet */
set32(DDR_SDRAM_CFG, DDR_SDRAM_CFG_VAL & ~DDR_SDRAM_CFG_MEM_EN);
__asm__ __volatile__("sync;isync");
/* busy wait for ~500us */
udelay(500);
__asm__ __volatile__("sync;isync");
/* Enable controller */
reg = get32(DDR_SDRAM_CFG) & ~DDR_SDRAM_CFG_BI;
set32(DDR_SDRAM_CFG, reg | DDR_SDRAM_CFG_MEM_EN);
__asm__ __volatile__("sync;isync");
/* Wait for data initialization to complete */
while (get32(DDR_SDRAM_CFG_2) & DDR_SDRAM_CFG_2_D_INIT) {
/* busy wait loop - throttle polling */
udelay(10000);
}
#endif /* ENABLE_DDR */
}
void hal_early_init(void)
{
/* Enable timebase on core 0 */
set32(RCPM_PCTBENR, (1 << 0));
/* Only invalidate the CPC if it is NOT configured as SRAM.
* When CPC SRAM is active (used as stack), writing CPCFI|CPCLFC
* without preserving CPCE would disable the CPC and corrupt the
* stack. Skip invalidation when SRAMEN is set (T2080RM 8.4.2.2). */
if (!(get32((volatile uint32_t*)(CPC_BASE + CPCSRCR0)) & CPCSRCR0_SRAMEN)) {
set32((volatile uint32_t*)(CPC_BASE + CPCCSR0),
(CPCCSR0_CPCFI | CPCCSR0_CPCLFC));
/* Wait for self-clearing invalidate bits */
while (get32((volatile uint32_t*)(CPC_BASE + CPCCSR0)) &
(CPCCSR0_CPCFI | CPCCSR0_CPCLFC));
}
/* Set DCSR space = 1G */
set32(DCFG_DCSR, (get32(DCFG_DCSR) | CORENET_DCSR_SZ_1G));
get32(DCFG_DCSR); /* read back to sync */
hal_ddr_init();
}
static void hal_cpld_init(void)
{
#ifdef ENABLE_CPLD
/* CPLD IFC Timing Parameters */
set32(IFC_FTIM0(3), (IFC_FTIM0_GPCM_TACSE(16UL) |
IFC_FTIM0_GPCM_TEADC(16UL) |
IFC_FTIM0_GPCM_TEAHC(16UL)));
set32(IFC_FTIM1(3), (IFC_FTIM1_GPCM_TACO(16UL) |
IFC_FTIM1_GPCM_TRAD(31UL)));
set32(IFC_FTIM2(3), (IFC_FTIM2_GPCM_TCS(16UL) |
IFC_FTIM2_GPCM_TCH(8UL) |
IFC_FTIM2_GPCM_TWP(31UL)));
set32(IFC_FTIM3(3), 0);
/* CPLD IFC Definitions (CS3) */
set32(IFC_CSPR_EXT(3), CPLD_BASE_PHYS_HIGH);
set32(IFC_CSPR(3), (IFC_CSPR_PHYS_ADDR(CPLD_BASE) |
IFC_CSPR_PORT_SIZE_16 |
IFC_CSPR_MSEL_GPCM |
IFC_CSPR_V));
set32(IFC_AMASK(3), IFC_AMASK_64KB);
set32(IFC_CSOR(3), 0);
/* IFC - CPLD (use LAW 5; LAW 2 is used for CPC SRAM) */
set_law(5, CPLD_BASE_PHYS_HIGH, CPLD_BASE,
LAW_TRGT_IFC, LAW_SIZE_4KB, 1);
/* CPLD - TBL=1, Entry 17 */
set_tlb(1, 17, CPLD_BASE, CPLD_BASE, CPLD_BASE_PHYS_HIGH,
MAS3_SX | MAS3_SW | MAS3_SR, MAS2_I | MAS2_G,
0, BOOKE_PAGESZ_4K, 1);
#endif
}
#ifdef ENABLE_DDR
/* Release CPC SRAM back to L2 cache mode.
* Call after stack is relocated to DDR (done in boot_entry_C).
* This gives us the full 2MB CPC as L3 cache for better performance.
*
* Before releasing CPC SRAM, .ramcode (RAMFUNCTION) is copied to DDR
* and TLB9 is remapped: VA 0xF8F00000 -> PA DDR_RAMCODE_ADDR so that
* RAMFUNCTION code (memcpy, wolfBoot_start, etc.) continues to work. */
static void hal_reconfigure_cpc_as_cache(void)
{
volatile uint32_t *cpc_csr0 = (volatile uint32_t *)(CPC_BASE + CPCCSR0);
volatile uint32_t *cpc_srcr0 = (volatile uint32_t *)(CPC_BASE + CPCSRCR0);
uint32_t reg;
/* Linker symbols for .ramcode section boundaries */
extern unsigned int _start_ramcode;
extern unsigned int _end_ramcode;
uint32_t ramcode_size = (uint32_t)&_end_ramcode - (uint32_t)&_start_ramcode;
/* Step 1: Copy .ramcode from CPC SRAM to DDR.
* Must use volatile loop — memcpy itself is in .ramcode! */
if (ramcode_size > 0) {
volatile const uint32_t *src = (volatile const uint32_t *)&_start_ramcode;
volatile uint32_t *dst = (volatile uint32_t *)DDR_RAMCODE_ADDR;
volatile uint32_t *end = (volatile uint32_t *)(DDR_RAMCODE_ADDR +
ramcode_size);
while (dst < end) {
*dst++ = *src++;
}
/* Flush D-cache and invalidate I-cache for the DDR copy */
flush_cache(DDR_RAMCODE_ADDR, ramcode_size);
/* Step 2: Remap TLB9: same VA (0xF8F00000) -> DDR physical address.
* All .ramcode references use VA 0xF8F00000, so this makes them
* transparently access the DDR copy instead of CPC SRAM. */
set_tlb(1, 9,
L2SRAM_ADDR, DDR_RAMCODE_ADDR, 0,
MAS3_SX | MAS3_SW | MAS3_SR, MAS2_M, 0,
INITIAL_SRAM_BOOKE_SZ, 1);
/* Ensure TLB update and I-cache pick up new mapping */
invalidate_icache();
}
#ifdef DEBUG_UART
wolfBoot_printf("Ramcode: copied %d bytes to DDR, TLB9 remapped\n",
ramcode_size);
#endif
/* Step 3: Flush the CPC to push any dirty SRAM data out.
* Read-modify-write to preserve CPCE/CPCPE enable bits. */
reg = *cpc_csr0;
reg |= CPCCSR0_CPCFL;
*cpc_csr0 = reg;
__asm__ __volatile__("sync; isync" ::: "memory");
/* Step 4: Poll until flush completes (CPCFL clears) */
while (*cpc_csr0 & CPCCSR0_CPCFL);
/* Step 5: Disable SRAM mode - release all ways back to cache */
*cpc_srcr0 = 0;
__asm__ __volatile__("sync; isync" ::: "memory");
/* Step 6: Disable CPC SRAM LAW (no longer needed — TLB9 now routes
* to DDR via LAW4, not CPC SRAM via LAW2).
* Keep TLB9 — it's remapped to DDR and still in use. */
set32(LAWAR(2), 0);
/* Step 7: Flash invalidate CPC to start fresh as cache */
reg = *cpc_csr0;
reg |= CPCCSR0_CPCFI;
*cpc_csr0 = reg;
__asm__ __volatile__("sync; isync" ::: "memory");
while (*cpc_csr0 & CPCCSR0_CPCFI);
/* Step 8: Enable parity/ECC now that SRAM is released and cache is clean.
* CPCPE was intentionally omitted during ASM init to avoid ECC machine
* checks on uninitialized SRAM (cold power cycle). Safe to enable here:
* SRAM mode is off, CPC is freshly invalidated, no stale data. */
reg = *cpc_csr0;
reg |= CPCCSR0_CPCPE;
*cpc_csr0 = reg;
__asm__ __volatile__("sync; isync" ::: "memory");
/* CPC is now fully enabled (CPCE|CPCPE), all 2MB as L3 cache */
#ifdef DEBUG_UART
wolfBoot_printf("CPC: Released SRAM, full 2MB L3 CPC cache enabled\n");
#endif
}
/* Make flash TLB cacheable for XIP code performance.
* Changes TLB Entry 2 (flash) from MAS2_I|MAS2_G to MAS2_M.
* This enables L1 I-cache + L2 + CPC to cache flash instructions. */
static void hal_flash_enable_caching(void)
{
/* Rewrite flash TLB entry with cacheable attributes.
* MAS2_M = memory coherent, enables caching */
set_tlb(1, 2,
FLASH_BASE_ADDR, FLASH_BASE_ADDR, FLASH_BASE_PHYS_HIGH,
MAS3_SX | MAS3_SW | MAS3_SR, MAS2_M, 0,
FLASH_TLB_PAGESZ, 1);
/* Invalidate L1 I-cache so new TLB attributes take effect */
invalidate_icache();
#ifdef DEBUG_UART
wolfBoot_printf("Flash: caching enabled (L1+L2+CPC)\n");
#endif
}
#endif /* ENABLE_DDR */
void hal_init(void)
{
#if defined(DEBUG_UART) && defined(ENABLE_CPLD)
uint32_t fw;
#endif
/* Enable timebase on core 0 */
set32(RCPM_PCTBENR, (1 << 0));
law_init();
#ifdef DEBUG_UART
uart_init();
uart_write("wolfBoot Init\n", 14);
#ifndef WOLFBOOT_REPRODUCIBLE_BUILD
wolfBoot_printf("Build: %s %s\n", __DATE__, __TIME__);
#endif
wolfBoot_printf("System Clock: %lu MHz\n",
(unsigned long)(SYS_CLK / 1000000));
wolfBoot_printf("Platform Clock: %lu MHz\n",
(unsigned long)(hal_get_plat_clk() / 1000000));
wolfBoot_printf("Core Clock: %lu MHz\n",
(unsigned long)(hal_get_core_clk() / 1000000));
wolfBoot_printf("Bus Clock: %lu MHz\n",
(unsigned long)(hal_get_bus_clk() / 1000000));
wolfBoot_printf("Timebase: %lu MHz\n",
(unsigned long)(TIMEBASE_HZ / 1000000));
#endif
hal_flash_init();
hal_cpld_init();
#ifdef ENABLE_CPLD
set8(CPLD_DATA(CPLD_PROC_STATUS), 1); /* Enable proc reset */
set8(CPLD_DATA(CPLD_WR_TEMP_ALM_OVRD), 0); /* Enable temp alarm */
#ifdef DEBUG_UART
fw = get8(CPLD_DATA(CPLD_FW_REV));
wolfBoot_printf("CPLD FW Rev: 0x%x\n", fw);
#endif
#endif /* ENABLE_CPLD */
#ifdef ENABLE_DDR
/* Stack is already in DDR (relocated in boot_entry_C via
* ddr_call_with_stack trampoline before main() was called).
*
* Now release CPC SRAM back to L2 cache and enable flash caching.
* This dramatically improves ECC signature verification performance:
* - CPC (2MB) becomes L3 cache for all memory accesses
* - Flash code is cached by L1 I-cache + L2 + CPC
* - Stack/data in DDR is cached by L1 D-cache + L2 + CPC */
hal_reconfigure_cpc_as_cache();
hal_flash_enable_caching();
/* Enable branch prediction now that DDR stack and cache hierarchy
* are fully configured. Disabled during early ASM boot to avoid
* speculative fetches during hardware init. */
{
uint32_t bucsr = BUCSR_STAC_EN | BUCSR_LS_EN | BUCSR_BBFI | BUCSR_BPEN;
__asm__ __volatile__("mtspr %0, %1; isync" :: "i"(SPRN_BUCSR), "r"(bucsr));
}
#endif
#ifdef ENABLE_MP
/* Start secondary cores AFTER CPC release and flash caching.
* Secondary cores' L2 flash-invalidate on the shared cluster L2
* must not disrupt the CPC SRAM→cache transition. Starting them
* after ensures the cache hierarchy is fully stable. */
hal_mp_init();
#endif
}
/* Switch flash TLB to cache-inhibited + guarded for direct flash chip access.
* AMD flash commands require writes to reach the chip immediately and status
* reads to come directly from the chip. With MAS2_M (cacheable), stores go
* through the CPC coherency fabric; IFC does not support coherent writes and
* returns a bus error (DSI). */
static void RAMFUNCTION hal_flash_cache_disable(void)
{
set_tlb(1, 2, FLASH_BASE_ADDR, FLASH_BASE_ADDR, FLASH_BASE_PHYS_HIGH,
MAS3_SX | MAS3_SW | MAS3_SR, MAS2_I | MAS2_G, 0, FLASH_TLB_PAGESZ, 1);
}
/* Restore flash TLB to cacheable mode after flash operation.
* Flash must be back in read-array mode before calling (AMD_CMD_RESET sent).
* Invalidate caches afterward so stale pre-erase data is not served. */
static void RAMFUNCTION hal_flash_cache_enable(void)
{
set_tlb(1, 2, FLASH_BASE_ADDR, FLASH_BASE_ADDR, FLASH_BASE_PHYS_HIGH,
MAS3_SX | MAS3_SW | MAS3_SR, MAS2_M, 0, FLASH_TLB_PAGESZ, 1);
invalidate_dcache();
invalidate_icache();
}
/* Clear IFC write-protect. T2080RM says IFC_CSPR should only be written
* when V=0. Must be called from RAMFUNCTION (DDR) with flash TLB set to
* guarded (MAS2_G) so no speculative access occurs while V is briefly 0. */
static void RAMFUNCTION hal_flash_clear_wp(void)
{
uint32_t cspr = get32(IFC_CSPR(0));
if (cspr & IFC_CSPR_WP) {
/* Clear V first, then modify WP, then re-enable V */
set32(IFC_CSPR(0), cspr & ~(IFC_CSPR_WP | IFC_CSPR_V));
__asm__ __volatile__("sync; isync");
set32(IFC_CSPR(0), (cspr & ~IFC_CSPR_WP) | IFC_CSPR_V);
__asm__ __volatile__("sync; isync");
/* Verify WP cleared */
cspr = get32(IFC_CSPR(0));
#ifdef DEBUG_FLASH
wolfBoot_printf("WP clear: CSPR0=0x%x%s\n", cspr,
(cspr & IFC_CSPR_WP) ? " (FAILED)" : " (OK)");
#endif
}
}
static void RAMFUNCTION hal_flash_unlock_sector(uint32_t sector)
{
/* AMD unlock sequence */
FLASH_IO8_WRITE(sector, FLASH_UNLOCK_ADDR1, AMD_CMD_UNLOCK_START);
FLASH_IO8_WRITE(sector, FLASH_UNLOCK_ADDR2, AMD_CMD_UNLOCK_ACK);
}
/* Check and clear PPB (Persistent Protection Bits) for a sector.
* S29GL01GS has per-sector non-volatile protection bits. If set, erase/program
* fails with DQ5 error. PPB erase is chip-wide (clears ALL sectors).
* Returns: 0 if unprotected or successfully cleared, -1 on failure. */
static int RAMFUNCTION hal_flash_ppb_unlock(uint32_t sector)
{
uint16_t ppb_status;
uint16_t read1, read2;
uint32_t timeout;
/* Enter PPB ASO (Address Space Overlay) */
FLASH_IO8_WRITE(0, FLASH_UNLOCK_ADDR1, AMD_CMD_UNLOCK_START);
FLASH_IO8_WRITE(0, FLASH_UNLOCK_ADDR2, AMD_CMD_UNLOCK_ACK);
FLASH_IO8_WRITE(0, FLASH_UNLOCK_ADDR1, AMD_CMD_SET_PPB_ENTRY);
/* Read PPB status for target sector: DQ0=0 means protected.
* On 16-bit bus, must read both chip lanes to check both devices. */
#if FLASH_CFI_WIDTH == 16
ppb_status = FLASH_IO16_READ(sector, 0);
if ((ppb_status & 0x0101) == 0x0101) {
#else
ppb_status = FLASH_IO8_READ(sector, 0);
if ((ppb_status & 0x01) == 0x01) {
#endif
/* Both chips report unprotected — exit PPB mode and return */
FLASH_IO8_WRITE(0, 0, AMD_CMD_SET_PPB_EXIT_BC1);
FLASH_IO8_WRITE(0, 0, AMD_CMD_SET_PPB_EXIT_BC2);
return 0;
}
/* Exit PPB ASO before calling printf (flash must be in read-array
* mode for I-cache misses to fetch valid instructions) */
FLASH_IO8_WRITE(0, 0, AMD_CMD_SET_PPB_EXIT_BC1);
FLASH_IO8_WRITE(0, 0, AMD_CMD_SET_PPB_EXIT_BC2);
FLASH_IO8_WRITE(0, 0, AMD_CMD_RESET);
udelay(50);
#ifdef DEBUG_FLASH
wolfBoot_printf("PPB: sector %d protected (0x%x), erasing all PPBs\n",
sector, ppb_status);
#endif
/* Re-enter PPB ASO for erase */
FLASH_IO8_WRITE(0, FLASH_UNLOCK_ADDR1, AMD_CMD_UNLOCK_START);
FLASH_IO8_WRITE(0, FLASH_UNLOCK_ADDR2, AMD_CMD_UNLOCK_ACK);
FLASH_IO8_WRITE(0, FLASH_UNLOCK_ADDR1, AMD_CMD_SET_PPB_ENTRY);
/* PPB Erase All (clears all sectors' PPBs) */
FLASH_IO8_WRITE(0, 0, AMD_CMD_PPB_UNLOCK_BC1); /* 0x80 */
FLASH_IO8_WRITE(0, 0, AMD_CMD_PPB_UNLOCK_BC2); /* 0x30 */
/* Wait for PPB erase completion — poll for toggle stop.
* On 16-bit bus, read both chip lanes to ensure both complete. */
timeout = 0;
do {
#if FLASH_CFI_WIDTH == 16
read1 = FLASH_IO16_READ(0, 0);
read2 = FLASH_IO16_READ(0, 0);
#else
read1 = FLASH_IO8_READ(0, 0);
read2 = FLASH_IO8_READ(0, 0);
#endif
if (read1 == read2)
break;
udelay(10);
} while (timeout++ < 100000); /* 1 second */
/* Exit PPB ASO */
FLASH_IO8_WRITE(0, 0, AMD_CMD_SET_PPB_EXIT_BC1);
FLASH_IO8_WRITE(0, 0, AMD_CMD_SET_PPB_EXIT_BC2);
/* Reset to read-array mode */
FLASH_IO8_WRITE(0, 0, AMD_CMD_RESET);
udelay(50);
if (timeout >= 100000) {
#ifdef DEBUG_FLASH
wolfBoot_printf("PPB: erase timeout\n");
#endif
return -1;
}
#ifdef DEBUG_FLASH
wolfBoot_printf("PPB: erase complete\n");
#endif
return 0;
}
/* wait for DQ6 toggle to stop within microsecond timeout.
* RAMFUNCTION: executes from DDR while flash is in program/erase command mode. */
static int RAMFUNCTION hal_flash_status_wait(uint32_t sector, uint32_t timeout_us)
{
int ret = 0;
uint32_t timeout = 0;
uint16_t read1, read2;
/* Replicate 8-bit AMD toggle/error bits to both bytes for parallel chips */
#if FLASH_CFI_WIDTH == 16
uint16_t toggle16 = (AMD_STATUS_TOGGLE << 8) | AMD_STATUS_TOGGLE;
uint16_t error16 = (AMD_STATUS_ERROR << 8) | AMD_STATUS_ERROR;
#else
uint16_t toggle16 = AMD_STATUS_TOGGLE;
uint16_t error16 = AMD_STATUS_ERROR;
#endif
do {
/* AMD toggle detection: DQ6 toggles on consecutive reads during
* program/erase. When the operation completes, DQ6 reflects actual
* data and consecutive reads return the same value.
* NOTE: Do NOT check programmed data bits against a mask here —
* after write completes, the data depends on what was written, not
* on any fixed status bits. Only erase guarantees 0xFF data. */
#if FLASH_CFI_WIDTH == 16
read1 = FLASH_IO16_READ(sector, 0);
read2 = FLASH_IO16_READ(sector, 0);
#else
read1 = FLASH_IO8_READ(sector, 0);
read2 = FLASH_IO8_READ(sector, 0);
#endif
#ifdef DEBUG_FLASH
wolfBoot_printf("Wait toggle %x -> %x\n", read1, read2);
#endif
/* DQ6 stopped toggling → operation complete */
if (((read1 ^ read2) & toggle16) == 0)
break;
/* Check DQ5 (error) on both chips while still toggling */
if (read1 & error16) {
/* Read one more time to confirm it's not a false DQ5 */
#if FLASH_CFI_WIDTH == 16
read1 = FLASH_IO16_READ(sector, 0);
read2 = FLASH_IO16_READ(sector, 0);
#else
read1 = FLASH_IO8_READ(sector, 0);
read2 = FLASH_IO8_READ(sector, 0);
#endif
if (((read1 ^ read2) & toggle16) == 0)
break; /* toggle stopped — was a race, not an error */
ret = -2; /* DQ5 error — program/erase failed */
break;
}
udelay(1);
} while (timeout++ < timeout_us);
if (timeout >= timeout_us) {
ret = -1; /* timeout */
}
#ifdef DEBUG_FLASH
wolfBoot_printf("Wait done (%d tries): %x -> %x\n",
timeout, read1, read2);
#endif
return ret;
}
int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
{
int ret = 0;
uint32_t i, sector, offset, nwords;
const uint32_t width_bytes = FLASH_CFI_WIDTH / 8;
/* Enforce alignment to flash bus width */
if ((address % width_bytes) != 0 || (len % width_bytes) != 0) {
#ifdef DEBUG_FLASH
wolfBoot_printf("Flash Write: unaligned addr 0x%x or len %d "
"(need %d-byte alignment)\n", address, len, width_bytes);
#endif
return -1;
}
/* adjust for flash base */
if (address >= FLASH_BASE_ADDR)
address -= FLASH_BASE_ADDR;
#ifdef DEBUG_FLASH
wolfBoot_printf("Flash Write: Ptr %p -> Addr 0x%x (len %d)\n",
data, address, len);
#endif
/* Disable flash caching — AMD commands must reach the chip directly */
hal_flash_cache_disable();
hal_flash_clear_wp();
/* Reset flash to read-array mode in case previous operation left it
* in command mode (e.g. after a timeout or incomplete operation) */
FLASH_IO8_WRITE(0, 0, AMD_CMD_RESET);
udelay(50);
/* Program one word at a time using AMD single-word program (0xA0).
* Each word requires: unlock + 0xA0 + data → poll.
* Typical program time: 60-120us per word.
* This is simpler and more reliable than Write-Buffer-Program (WBP),
* which had DQ1 abort/timeout issues on this IFC + S29GL01GS
* combination. WBP can be re-enabled as an optimization once
* single-word program is verified working on hardware. */
nwords = (uint32_t)len / width_bytes;
for (i = 0; i < nwords; i++) {
sector = address / FLASH_SECTOR_SIZE;
offset = (address - (sector * FLASH_SECTOR_SIZE)) / width_bytes;
hal_flash_unlock_sector(sector);
FLASH_IO8_WRITE(sector, FLASH_UNLOCK_ADDR1, AMD_CMD_WRITE);
#if FLASH_CFI_WIDTH == 16
{
/* Build 16-bit value from bytes to avoid unaligned access */
const uint8_t *p = &data[i * 2];
uint16_t val = ((uint16_t)p[0] << 8) | (uint16_t)p[1];
FLASH_IO16_WRITE(sector, offset, val);
}
#else
FLASH_IO8_WRITE(sector, offset, data[i]);
#endif
/* Poll for program completion (typical 60-120us, max 200ms) */
ret = hal_flash_status_wait(sector, 200 * 1000);
if (ret != 0) {
FLASH_IO8_WRITE(sector, 0, AMD_CMD_RESET);
udelay(50);
#ifdef DEBUG_FLASH
wolfBoot_printf("Flash Write: %s at addr 0x%x\n",
ret == -2 ? "DQ5 error" : "Timeout",
(uint32_t)(FLASH_BASE_ADDR + address));
#endif
break;
}
address += width_bytes;
}
/* Restore flash caching — flash is back in read-array mode */
hal_flash_cache_enable();
return ret;
}
int RAMFUNCTION hal_flash_erase(uint32_t address, int len)
{
int ret = 0;
uint32_t sector;
/* adjust for flash base */
if (address >= FLASH_BASE_ADDR)
address -= FLASH_BASE_ADDR;
/* Disable flash caching — AMD commands must reach the chip directly */
hal_flash_cache_disable();
hal_flash_clear_wp();
/* Reset flash to read-array mode in case previous operation left it
* in command mode (e.g. after a timeout or incomplete operation) */
FLASH_IO8_WRITE(0, 0, AMD_CMD_RESET);
udelay(50);
while (len > 0) {
/* determine sector address */
sector = (address / FLASH_SECTOR_SIZE);
#ifdef DEBUG_FLASH
wolfBoot_printf("Flash Erase: Sector %d, Addr 0x%x, Len %d\n",
sector, address, len);
#endif
/* Check and clear PPB protection if set */
if (hal_flash_ppb_unlock(sector) != 0) {
#ifdef DEBUG_FLASH
wolfBoot_printf("Flash Erase: PPB unlock failed sector %d\n", sector);
#endif
ret = -1;
break;
}
#ifdef DEBUG_FLASH
wolfBoot_printf("Erasing sector %d...\n", sector);
#endif
hal_flash_unlock_sector(sector);
FLASH_IO8_WRITE(sector, FLASH_UNLOCK_ADDR1, AMD_CMD_ERASE_START);
hal_flash_unlock_sector(sector);
FLASH_IO8_WRITE(sector, 0, AMD_CMD_ERASE_SECTOR);
/* block erase timeout = 50us - for additional sectors */
/* Typical is 200ms (max 1100ms) */
/* poll for erase completion - max 1.1 sec
* NOTE: Do NOT call wolfBoot_printf while flash is in erase mode.
* With cache-inhibited TLB, I-cache misses fetch from flash which
* returns status data instead of instructions. */
ret = hal_flash_status_wait(sector, 1100*1000);
if (ret != 0) {
/* Reset flash to read-array mode BEFORE calling printf */
FLASH_IO8_WRITE(sector, 0, AMD_CMD_RESET);
udelay(50);
#ifdef DEBUG_FLASH
wolfBoot_printf("Flash Erase: Timeout at sector %d\n", sector);
#endif
break;
}
/* Erase succeeded — flash is back in read-array mode.
* Reset to be safe before any printf (I-cache may miss) */
FLASH_IO8_WRITE(sector, 0, AMD_CMD_RESET);
udelay(10);
#ifdef DEBUG_FLASH
wolfBoot_printf("Erase sector %d: OK\n", sector);
#endif
address += FLASH_SECTOR_SIZE;
len -= FLASH_SECTOR_SIZE;
}
/* Restore flash caching — flash is back in read-array mode */
hal_flash_cache_enable();
return ret;
}
void RAMFUNCTION hal_flash_unlock(void)
{
/* Per-sector unlock is done in hal_flash_write/erase before each operation.
* The previous non-volatile PPB protection mode (C0h) approach caused
* unnecessary wear on PPB cells since it was called on every boot. */
hal_flash_unlock_sector(0);
}
void hal_flash_lock(void)
{
/* intentional no-op: per-sector unlock is done in hal_flash_write/erase */
}
/* SMP Multi-Processor Driver */
#ifdef ENABLE_MP
/* from boot_ppc_mp.S */
extern uint32_t _secondary_start_page;
extern uint32_t _second_half_boot_page;
extern uint32_t _spin_table[];
extern uint32_t _spin_table_addr;
/* DDR address of the spin table, set during hal_mp_init() and reused in
* hal_dts_fixup() for cpu-release-addr fixups. */
static uint32_t g_spin_table_ddr = 0;
extern uint32_t _bootpg_addr;
/* Startup additional cores with spin table and synchronize the timebase.
* spin_table_ddr: DDR address of the spin table (for checking status) */
static void hal_mp_up(uint32_t bootpg, uint32_t spin_table_ddr)
{
uint32_t all_cores, active_cores, whoami;
int timeout = 50, i;
whoami = get32(PIC_WHOAMI); /* Get current running core number */
all_cores = ((1 << CPU_NUMCORES) - 1); /* mask of all cores */
active_cores = (1 << whoami); /* current running cores */
wolfBoot_printf("MP: Starting cores (boot page %p, spin table %p)\n",
bootpg, spin_table_ddr);
/* Set the boot page translation register */
set32(LCC_BSTRH, 0);
set32(LCC_BSTRL, bootpg);
set32(LCC_BSTAR, (LCC_BSTAR_EN |
LCC_BSTAR_LAWTRGT(LAW_TRGT_DDR_1) |
LAW_SIZE_4KB));
(void)get32(LCC_BSTAR); /* read back to sync */
/* Enable time base on current core only */
set32(RCPM_PCTBENR, (1 << whoami));
/* Release the CPU core(s) */
set32(DCFG_BRR, all_cores);
__asm__ __volatile__("sync; isync; msync");
/* wait for other core(s) to start */
while (timeout) {
for (i = 0; i < CPU_NUMCORES; i++) {
volatile uint32_t* entry = (volatile uint32_t*)(
spin_table_ddr + (i * ENTRY_SIZE) + ENTRY_ADDR_LOWER);
if (*entry) {
active_cores |= (1 << i);
}
}
if ((active_cores & all_cores) == all_cores) {
break;
}
udelay(100);
timeout--;
}
if (timeout == 0) {
wolfBoot_printf("MP: Timeout enabling additional cores!\n");
}
/* Synchronize and reset timebase across all cores.
* On e6500, mtspr to TBL/TBU (SPR 284/285) may cause an illegal
* instruction exception — skip timebase reset if secondary cores
* did not start (timebase sync only matters for multi-core). */
if ((active_cores & all_cores) == all_cores) {
/* Disable all timebases */
set32(RCPM_PCTBENR, 0);
/* Reset our timebase */
mtspr(SPRN_TBWU, 0);
mtspr(SPRN_TBWL, 0);
/* Enable timebase for all cores */
set32(RCPM_PCTBENR, all_cores);
} else {
/* Only re-enable timebase for boot core */
set32(RCPM_PCTBENR, (1 << whoami));
}
}
static void hal_mp_init(void)
{
uint32_t *fixup = (uint32_t*)&_secondary_start_page;
uint32_t bootpg, second_half_ddr, spin_table_ddr;
int i_tlb = 0; /* always 0 */
size_t i;
const volatile uint32_t *s;
volatile uint32_t *d;
/* Assign virtual boot page at end of LAW-mapped DDR region.
* DDR LAW maps 2GB (LAW_SIZE_2GB) starting at DDR_ADDRESS.
* DDR_SIZE may exceed 32-bit range (e.g. 8GB), so use the LAW-mapped
* size to ensure bootpg fits in 32 bits and is accessible. */
bootpg = DDR_ADDRESS + 0x80000000UL - BOOT_ROM_SIZE;
/* Second half boot page (spin loop + spin table) goes just below.
* For XIP flash builds, .bootmp is in flash — secondary cores can't
* write to flash, so the spin table MUST be in DDR. */
second_half_ddr = bootpg - BOOT_ROM_SIZE;
/* DDR addresses for second half symbols */
spin_table_ddr = second_half_ddr +
((uint32_t)_spin_table - (uint32_t)&_second_half_boot_page);
/* Flush DDR destination before copying */
flush_cache(bootpg, BOOT_ROM_SIZE);
flush_cache(second_half_ddr, BOOT_ROM_SIZE);
/* Map reset page to bootpg so we can copy code there.
* Boot page translation will redirect secondary core fetches from
* 0xFFFFF000 to bootpg in DDR. */
disable_tlb1(i_tlb);
set_tlb(1, i_tlb, BOOT_ROM_ADDR, bootpg, 0, /* tlb, epn, rpn, urpn */
(MAS3_SX | MAS3_SW | MAS3_SR), (MAS2_I | MAS2_G), /* perms, wimge */
0, BOOKE_PAGESZ_4K, 1); /* ts, esel, tsize, iprot */
/* Copy first half (startup code) to DDR via BOOT_ROM_ADDR mapping.
* Uses cache-inhibited TLB to ensure data reaches DDR immediately. */
s = (const uint32_t*)fixup;
d = (uint32_t*)BOOT_ROM_ADDR;
for (i = 0; i < BOOT_ROM_SIZE/4; i++) {
d[i] = s[i];
}
/* Write _bootpg_addr and _spin_table_addr into the DDR first-half copy.
* These variables are .long 0 in the linked .bootmp (flash), and direct
* stores to their flash addresses silently fail on XIP builds.
* Calculate offsets within the boot page and write via BOOT_ROM_ADDR. */
{
volatile uint32_t *bp = (volatile uint32_t*)(BOOT_ROM_ADDR +
((uint32_t)&_bootpg_addr - (uint32_t)&_secondary_start_page));
volatile uint32_t *st = (volatile uint32_t*)(BOOT_ROM_ADDR +
((uint32_t)&_spin_table_addr - (uint32_t)&_secondary_start_page));
*bp = second_half_ddr;
*st = spin_table_ddr;
}
/* Copy second half (spin loop + spin table) directly to DDR.
* Master has DDR TLB (entry 12, MAS2_M). Flush cache after copy
* to ensure secondary cores see the data. */
s = (const uint32_t*)&_second_half_boot_page;
d = (uint32_t*)second_half_ddr;
for (i = 0; i < BOOT_ROM_SIZE/4; i++) {
d[i] = s[i];
}
flush_cache(second_half_ddr, BOOT_ROM_SIZE);
/* Persist DDR spin-table base for use in hal_dts_fixup() */
g_spin_table_ddr = spin_table_ddr;
/* start cores and wait for them to be enabled */
hal_mp_up(bootpg, spin_table_ddr);
}
#endif /* ENABLE_MP */
void hal_prepare_boot(void)
{
}
#ifdef MMU
void* hal_get_dts_address(void)
{
return (void*)WOLFBOOT_DTS_BOOT_ADDRESS;
}
int hal_dts_fixup(void* dts_addr)
{
#ifndef BUILD_LOADER_STAGE1
struct fdt_header *fdt = (struct fdt_header *)dts_addr;
int off;
uint32_t *reg;
/* verify the FDT is valid */
off = fdt_check_header(dts_addr);
if (off != 0) {
wolfBoot_printf("FDT: Invalid header! %d\n", off);
return off;
}
/* display FDT information */
wolfBoot_printf("FDT: Version %d, Size %d\n",
fdt_version(fdt), fdt_totalsize(fdt));
/* expand total size */
{
uint32_t new_size = (uint32_t)fdt_totalsize(fdt) + 2048U;
fdt_set_totalsize(fdt, new_size);
wolfBoot_printf("FDT: Expanded (2KB) to %d bytes\n",
fdt_totalsize(fdt));
}
/* fixup the memory region - single bank */
off = fdt_find_devtype(fdt, -1, "memory");
if (off >= 0) {
/* build addr/size as aligned 64-bit values */
uint64_t ranges[2];
ranges[0] = cpu_to_fdt64(DDR_ADDRESS);
ranges[1] = cpu_to_fdt64(DDR_SIZE);
wolfBoot_printf("FDT: Set memory, start=0x%x, size=0x%x\n",
DDR_ADDRESS, (uint32_t)DDR_SIZE);
fdt_setprop(fdt, off, "reg", ranges, sizeof(ranges));
}
/* fixup CPU status and release address and enable method */
off = fdt_find_devtype(fdt, -1, "cpu");
while (off >= 0) {
int core;
#ifdef ENABLE_MP
uint64_t core_spin_table;
#endif
reg = (uint32_t*)fdt_getprop(fdt, off, "reg", NULL);
if (reg == NULL)
break;
core = (int)fdt32_to_cpu(*reg);
if (core >= CPU_NUMCORES) {
break; /* invalid core index */
}
#ifdef ENABLE_MP
/* Calculate DDR address of this core's spin table entry.
* Must use g_spin_table_ddr (the DDR copy), NOT _spin_table which
* is the flash/VMA address — Linux writes the release word to this
* address, and XIP flash is read-only. */
core_spin_table = (uint64_t)(g_spin_table_ddr + (core * ENTRY_SIZE));
fdt_fixup_str(fdt, off, "cpu", "status", (core == 0) ? "okay" : "disabled");
fdt_fixup_val64(fdt, off, "cpu", "cpu-release-addr", core_spin_table);
fdt_fixup_str(fdt, off, "cpu", "enable-method", "spin-table");
#endif
fdt_fixup_val(fdt, off, "cpu", "timebase-frequency", TIMEBASE_HZ);
fdt_fixup_val(fdt, off, "cpu", "clock-frequency", hal_get_core_clk());
fdt_fixup_val(fdt, off, "cpu", "bus-frequency", hal_get_plat_clk());
off = fdt_find_devtype(fdt, off, "cpu");
}
/* fixup the soc clock */
off = fdt_find_devtype(fdt, -1, "soc");
if (off >= 0) {
fdt_fixup_val(fdt, off, "soc", "bus-frequency", hal_get_plat_clk());
}
/* fixup the serial clocks */
off = fdt_find_devtype(fdt, -1, "serial");
while (off >= 0) {
fdt_fixup_val(fdt, off, "serial", "clock-frequency", hal_get_bus_clk());
off = fdt_find_devtype(fdt, off, "serial");
}
#endif /* !BUILD_LOADER_STAGE1 */
(void)dts_addr;
return 0;
}
#endif /* MMU */