From a8c8c19fe8ddbf160bb441c4350728b7ea71760c Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 9 Sep 2026 09:31:50 -0700 Subject: [PATCH] Add LS1028A eSDHC SD card disk boot support --- .github/workflows/test-configs.yml | 8 ++ arch.mk | 20 +++- config/examples/nxp-ls1028a-sdcard.config | 88 +++++++++++++++ docs/Targets.md | 5 + hal/nxp_esdhc.c | 131 +++++++++++++++++----- options.mk | 12 +- 6 files changed, 230 insertions(+), 34 deletions(-) create mode 100644 config/examples/nxp-ls1028a-sdcard.config diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 0d5c9edb..4b973482 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -274,6 +274,14 @@ jobs: config-file: ./config/examples/nxp-ls1028a-tpm.config make-args: CROSS_COMPILE=aarch64-linux-gnu- + # SD card disk boot via the Freescale eSDHC driver (hal/nxp_esdhc.o) + nxp_ls1028a_sdcard_test: + uses: ./.github/workflows/test-build.yml + with: + arch: aarch64 + config-file: ./config/examples/nxp-ls1028a-sdcard.config + make-args: CROSS_COMPILE=aarch64-linux-gnu- + nxp_mcxa_test: uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml with: diff --git a/arch.mk b/arch.mk index ad20c6d6..6d405f03 100644 --- a/arch.mk +++ b/arch.mk @@ -181,6 +181,18 @@ ifeq ($(ARCH),AARCH64) CFLAGS+=-fno-builtin-printf endif + # SD card disk boot uses the Freescale eSDHC driver, not the Cadence + # SDHCI one (the shared AARCH64 block below wires update_disk.o). + # hal/nxp_esdhc.c is SD card only, so DISK_EMMC has no driver here: + # reject it rather than silently link the wrong controller driver. + ifeq ($(DISK_EMMC),1) + $(error DISK_EMMC is not supported on nxp_ls1028a (hal/nxp_esdhc.c is SD card only)) + endif + ifeq ($(DISK_SDCARD),1) + override DISK_DRIVER=esdhc + OBJS+=hal/nxp_esdhc.o + endif + SPI_TARGET=nxp endif @@ -1598,10 +1610,16 @@ ifneq ($(filter nxp_t1024 nxp_t1040,$(TARGET)),) # Disk boot from SD card (eSDHC controller, driver hal/nxp_esdhc.c). # src/gpt.o is already linked for all PPC targets above. The driver is # kept out of the size-constrained stage1 loader. - ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),) + # hal/nxp_esdhc.c drives SD cards only: it has no eMMC (CMD1/EXT_CSD) + # initialization, so DISK_EMMC has no driver on this arch. + ifeq ($(DISK_EMMC),1) + $(error DISK_EMMC is not supported on PPC (hal/nxp_esdhc.c is SD card only)) + endif + ifeq ($(DISK_SDCARD),1) CFLAGS+=-D"WOLFBOOT_UPDATE_DISK" -D"MAX_DISKS=1" UPDATE_OBJS:=src/update_disk.o OBJS+=src/disk.o + override DISK_DRIVER=esdhc ifneq ($(STAGE1),1) OBJS+=hal/nxp_esdhc.o endif diff --git a/config/examples/nxp-ls1028a-sdcard.config b/config/examples/nxp-ls1028a-sdcard.config new file mode 100644 index 00000000..86103d3b --- /dev/null +++ b/config/examples/nxp-ls1028a-sdcard.config @@ -0,0 +1,88 @@ +# NXP LS1028A with SD card disk boot +# +# Same as nxp-ls1028a.config, but the signed application image is loaded +# from the SD card slot (eSDHC1 at 0x02140000) instead of XSPI NOR. The +# card is GPT (or MBR) partitioned; the signed image sits at offset 0 of +# the first two partitions (BOOT_PART_A / BOOT_PART_B, 0-based indexes +# into the partition table - same card layout as the T1040 eSDHC target). +# wolfBoot picks the slot with the higher version, verifies it and boots +# it from DDR (src/update_disk.c). + +ARCH=AARCH64 +TARGET=nxp_ls1028a +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +DEBUG_UART?=1 +VTOR?=0 +CORTEX_M0?=0 +NO_ASM?=0 +EXT_FLASH?=1 +SPI_FLASH?=0 +## Force app to be copied into ram +NO_XIP?=1 +UART_FLASH?=0 +ALLOW_DOWNGRADE?=0 +NVM_FLASH_WRITEONCE?=0 +WOLFBOOT_VERSION?=0 +V?=0 +NO_MPU?=0 +SPMATH?=1 +RAM_CODE?=0 +DUALBANK_SWAP?=0 +PKA?=0 +ELF?=1 + +# SD card disk boot (Freescale eSDHC driver, hal/nxp_esdhc.c) +DISK_SDCARD=1 +# Upper bound for the unauthenticated image size read from disk +WOLFBOOT_RAMBOOT_MAX_SIZE?=0x1000000 +# Boot slots: partition table indexes (0-based) and read chunk size +CFLAGS_EXTRA+=-DBOOT_PART_A=0 -DBOOT_PART_B=1 +CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x8000 +# do_boot keeps the MMU and caches on (ENETC coherency) and cleans from the +# entry point before the jump. Pin the clean length to the RAMBOOT window so +# it covers the area images are loaded into regardless of what +# WOLFBOOT_PARTITION_SIZE is set to. Note this cleans one span from the +# entry point: an ELF or FIT payload that relocates a segment outside this +# window would need its own cache maintenance. +CFLAGS_EXTRA+=-DWOLFBOOT_MMU_FLUSH_APP_SIZE=0x1000000 +# eSDHC bring-up trace on the DUART console +#CFLAGS_EXTRA+=-DDEBUG_ESDHC + +# NOR Base Address +ARCH_FLASH_OFFSET?=0x20000000 + +# Flash Sector Size (128 KB) +WOLFBOOT_SECTOR_SIZE=0x20000 + +# wolfBoot start address +WOLFBOOT_ORIGIN=0x20020000 + +# wolfBoot partition size +BOOTLOADER_PARTITION_SIZE=0x20000 + +# Application Partition size. Matches the 16MB SD card slots; the sign +# tool bounds the image against this even though disk boot does not use +# the NOR partitions. +WOLFBOOT_PARTITION_SIZE?=0x1000000 + +# Location in Flash for Application Partition (unused for disk boot, +# kept for target.h consistency) +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x20100000 + +# Load the image to DDR, not the default OCRAM: OCRAM also holds +# wolfBoot's own .data/.bss/.stack and cannot hold a disk-sized image. +WOLFBOOT_LOAD_ADDRESS?=0x80100000 + +# Location in Flash for Update Partition +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x21100000 + +# Location of temporary sector used during updates +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x22100000 + +# DTS (Device Tree). The load address must sit above +# WOLFBOOT_LOAD_ADDRESS + WOLFBOOT_RAMBOOT_MAX_SIZE. +WOLFBOOT_LOAD_DTS_ADDRESS?=0x82000000 +WOLFBOOT_DTS_BOOT_ADDRESS?=0x20F00000 +WOLFBOOT_DTS_UPDATE_ADDRESS?=0x20F00000 diff --git a/docs/Targets.md b/docs/Targets.md index 90f645bf..3289930b 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -3698,6 +3698,11 @@ The LS1028A is a AARCH64 armv8-a Cortex-A72 processor. Support has been tested w Example configurations for this target are provided in: * NXP LS1028A: [/config/examples/nxp-ls1028a.config](/config/examples/nxp-ls1028a.config). * NXP LS1028A with TPM: [/config/examples/nxp-ls1028a-tpm.config](/config/examples/nxp-ls1028a-tpm.config). +* NXP LS1028A with SD card boot: [/config/examples/nxp-ls1028a-sdcard.config](/config/examples/nxp-ls1028a-sdcard.config). + +### LS1028A SD Card Boot (eSDHC) + +The LS1028A can load the signed application image from the SD card slot (eSDHC1) using the same Freescale eSDHC driver as the T1040 (`hal/nxp_esdhc.c`, built as its own object). The card layout is identical to the T1040 SD target: GPT (or MBR) partitioned, with the signed image at offset 0 of the first two partitions (`BOOT_PART_A`/`BOOT_PART_B`, 0-based). wolfBoot reads both headers, picks the higher version, loads it to DDR (`WOLFBOOT_LOAD_ADDRESS=0x80100000`), verifies the signature and boots it. The driver reprograms the eSDHC source clock (HWA2) at init, because the NOR-boot RCW leaves it on a source too fast for card identification. Define `DEBUG_ESDHC` (see the config) for controller bring-up trace on the DUART console. Validated on the LS1028ARDB booting a signed image from SD. ### Building wolfBoot for NXP LS1028A diff --git a/hal/nxp_esdhc.c b/hal/nxp_esdhc.c index 1d15e6d3..a6fc4998 100644 --- a/hal/nxp_esdhc.c +++ b/hal/nxp_esdhc.c @@ -19,7 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ -/* Freescale/NXP eSDHC block driver for QorIQ (T1040, T1024, T2080). +/* Freescale/NXP eSDHC block driver for QorIQ PPC (T1040, T1024, T2080) + * and Layerscape (LS1028A). * * Provides the four entry points src/disk.c expects (disk_init, disk_read, * disk_write, disk_close) so the disk boot path, and therefore DISK_FS, can @@ -30,9 +31,9 @@ * one XFERTYP register, combines block size and count into BLKATTR, and has * a watermark register with no standard-SDHCI equivalent. * - * Compiled as its own object (arch.mk adds hal/nxp_esdhc.o when - * DISK_SDCARD or DISK_EMMC is set); the clock helpers it needs are - * exported by nxp_ppc.h. + * Compiled as its own object (the target's arch.mk block adds + * hal/nxp_esdhc.o and sets DISK_DRIVER=esdhc when DISK_SDCARD is set); + * per-target base address, clocks and byte order are selected below. * * Transfers use PIO through DATPORT rather than DMA. On e5500 with the MMU * enabled a DMA descriptor would need cache maintenance on the destination, @@ -47,7 +48,38 @@ #include "disk.h" #include "printf.h" + +/* Per-target selection. The eSDHC block is the same IP on big-endian QorIQ + * PPC and little-endian Layerscape; the register file follows the + * integration, so a native 32-bit access reads it correctly on both. Only + * the base address, source clock and DATPORT byte order move per target. */ +#if defined(TARGET_nxp_ls1028a) + +#include "nxp_ls1028a.h" +/* eSDHC1 is the SD card slot; eSDHC2 is eMMC, not supported by this driver. */ +#define ESDHC_CTRL_BASE ESDHC_BASE(0) +#define ESDHC_EMODE_SEL ESDHC_PROCTL_EMODE_LE +#ifndef ESDHC_REF_CLK +#define ESDHC_REF_CLK 400000000UL +#endif +/* The block is clocked by hardware-accelerator mux HWA2 (the device tree + * binds mmc@2140000 to QORIQ_CLK_HWACCEL index 1). The NOR-boot RCW leaves + * it on CGA_PLL2 (1.2 GHz), which overruns the card during identification; + * CLKSEL=7 selects CGA_PLL1/3. Set at runtime so SD works whichever RCW + * booted us. hwaccel[idx] = clockgen + 0x20*idx + 0x10, eSDHC is idx 1. */ +#define LS1028A_HWA2CSR (CGUCGA_BASE + 0x30) +#define HWA_CLKSEL_MASK 0x78000000U +#define HWA_CLKSEL_SHIFT 27 +#define HWA_CLKSEL_ESDHC 7U + +#else + +/* QorIQ PPC T-series: big-endian core and registers. */ #include "nxp_ppc.h" +#define ESDHC_CTRL_BASE (CCSRBAR + 0x114000) +#define ESDHC_EMODE_SEL ESDHC_PROCTL_EMODE_BE + +#endif /* target selection */ #ifdef DEBUG_ESDHC #define ESDHC_DBG(_f_, ...) wolfBoot_printf(_f_, ##__VA_ARGS__) @@ -55,18 +87,7 @@ #define ESDHC_DBG(_f_, ...) do{}while(0) #endif -/* --------------------------------------------------------------------- - * Register map. CCSRBAR comes from nxp_ppc.h for the selected target. - * - * The block presents its registers big-endian and the e5500 is big-endian, - * so a native 32-bit access reads them correctly with no swapping. The one - * exception is DATPORT, whose byte order is selected by PROCTL[EMODE] and is - * configured below. - * --------------------------------------------------------------------- */ -#ifndef ESDHC_BASE -#define ESDHC_BASE (CCSRBAR + 0x114000) -#endif -#define ESDHC_REG(off) ((volatile uint32_t*)(ESDHC_BASE + (off))) +#define ESDHC_REG(off) ((volatile uint32_t*)(ESDHC_CTRL_BASE + (off))) #define ESDHC_DSADDR 0x00 #define ESDHC_BLKATTR 0x04 @@ -117,11 +138,11 @@ #define ESDHC_PROCTL_DTW_1BIT (0U << 1) #define ESDHC_PROCTL_DTW_4BIT (1U << 1) #define ESDHC_PROCTL_DTW_MASK (3U << 1) -/* EMODE selects the byte order of DATPORT. Big-endian mode delivers bytes - * in media order when the word is stored natively by this big-endian core. - * Verified on T1040D4RDB silicon: little-endian mode read every aligned - * 4-byte group byte-reversed (MBR signature came back AA55). */ +/* EMODE sets DATPORT byte order; ESDHC_EMODE_SEL picks the mode giving + * media order for a native word store. Silicon-verified: BE on the T1040 + * (LE there returned every aligned 4-byte group reversed). */ #define ESDHC_PROCTL_EMODE_BE (0U << 4) +#define ESDHC_PROCTL_EMODE_LE (2U << 4) #define ESDHC_PROCTL_EMODE_MASK (3U << 4) /* SYSCTL */ @@ -186,6 +207,27 @@ static int g_esdhc_ready; * 37500000 Hz (600 MHz / 16). * --------------------------------------------------------------------- */ +#if defined(TARGET_nxp_ls1028a) + +/* ARM generic timer; hal_init() enables the system counter first. */ +static uint64_t esdhc_timebase(void) +{ + uint64_t cnt; + + __asm__ __volatile__("isb; mrs %0, cntpct_el0" : "=r"(cnt)); + return cnt; +} + +static uint32_t esdhc_read_tb_hz(void) +{ + uint64_t frq; + + __asm__ __volatile__("mrs %0, cntfrq_el0" : "=r"(frq)); + return (uint32_t)frq; +} + +#else + static uint64_t esdhc_timebase(void) { uint32_t hi, lo, hi2; @@ -201,6 +243,13 @@ static uint64_t esdhc_timebase(void) return ((uint64_t)hi << 32) | (uint64_t)lo; } +static uint32_t esdhc_read_tb_hz(void) +{ + return TIMEBASE_HZ; +} + +#endif /* target timebase */ + /* Timebase frequency, cached by disk_init(). TIMEBASE_HZ reads clock * registers and divides on every use; the value cannot change at runtime, * and disk_init() rejects a zero reading before any other driver path can @@ -311,7 +360,11 @@ static int esdhc_send_cmd(uint32_t idx, uint32_t arg, uint32_t xfertyp, /* Set the SD clock. The divider is SDCLKFS (base 2 prescaler) times DVS. */ static void esdhc_set_clock(uint32_t target_hz) { +#if defined(TARGET_nxp_ls1028a) + uint32_t base = ESDHC_REF_CLK; +#else uint32_t base = hal_get_bus_clk(); +#endif uint32_t pre = 2, div = 1, sysctl; if (target_hz == 0U) { @@ -341,8 +394,9 @@ static void esdhc_set_clock(uint32_t target_hz) ESDHC_SYSCTL_SDCLKEN; esdhc_udelay(100); - ESDHC_DBG("esdhc: clock %u Hz (pre %u, div %u)\r\n", - (base / pre) / div, pre, div); + ESDHC_DBG("esdhc: clock %u Hz (pre %u, div %u) SYSCTL %x PROCTL %x\r\n", + (base / pre) / div, pre, div, + *ESDHC_REG(ESDHC_SYSCTL), *ESDHC_REG(ESDHC_PROCTL)); } @@ -376,7 +430,7 @@ static int esdhc_host_init(void) /* 1-bit bus for identification, and set the data-port byte order. */ proctl = *ESDHC_REG(ESDHC_PROCTL); proctl &= ~(ESDHC_PROCTL_DTW_MASK | ESDHC_PROCTL_EMODE_MASK); - proctl |= ESDHC_PROCTL_DTW_1BIT | ESDHC_PROCTL_EMODE_BE; + proctl |= ESDHC_PROCTL_DTW_1BIT | ESDHC_EMODE_SEL; *ESDHC_REG(ESDHC_PROCTL) = proctl; esdhc_set_clock(400000U); @@ -403,6 +457,7 @@ static int esdhc_send_acmd(uint32_t idx, uint32_t arg, uint32_t xfertyp, ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN | ESDHC_XFERTYP_CCCEN, NULL); if (ret != 0) { + ESDHC_DBG("esdhc: CMD55 (for ACMD%u) failed\r\n", idx); return ret; } return esdhc_send_cmd(idx, arg, xfertyp, resp); @@ -442,8 +497,10 @@ static int esdhc_card_init(void) return -1; } v2 = 1; + ESDHC_DBG("esdhc: CMD8 ok resp %x\r\n", resp[0]); } else { + ESDHC_DBG("esdhc: CMD8 no response (v1 or signalling)\r\n"); /* CMD8 leaves the command line in error state on a v1 card. */ *ESDHC_REG(ESDHC_SYSCTL) = *ESDHC_REG(ESDHC_SYSCTL) | ESDHC_SYSCTL_RSTC; @@ -529,10 +586,12 @@ static int esdhc_card_init(void) /* Drain one block from the data port. * - * PROCTL[EMODE] is set to big-endian above, so a native 32-bit read of - * DATPORT returns the four media bytes already in order and they can be - * stored as-is. Silicon-verified: little-endian mode returned every - * aligned 4-byte group byte-reversed. */ + * ESDHC_EMODE_SEL (set per target above) makes a native 32-bit read of + * DATPORT return the four media bytes already in order, so they are + * stored as-is. The correct mode differs by integration and both are + * silicon-verified: big-endian on the big-endian T1040, where + * little-endian returned every aligned 4-byte group reversed, and + * little-endian on the little-endian LS1028A. */ static int esdhc_read_block(uint8_t *buf) { uint32_t i, word; @@ -662,6 +721,19 @@ static int esdhc_read_blocks(uint64_t lba, uint32_t count, uint8_t *buf) * disk.c interface * --------------------------------------------------------------------- */ +#if defined(TARGET_nxp_ls1028a) +/* Route a usable source clock to the eSDHC block (see HWA2CSR above). */ +static void esdhc_clock_src_init(void) +{ + volatile uint32_t *hwa2 = (volatile uint32_t*)LS1028A_HWA2CSR; + uint32_t val = (*hwa2 & ~HWA_CLKSEL_MASK) | + (HWA_CLKSEL_ESDHC << HWA_CLKSEL_SHIFT); + + *hwa2 = val; + ESDHC_DBG("esdhc: HWA2CSR %x\r\n", *hwa2); +} +#endif + int disk_init(int drv) { if (drv != 0) { @@ -673,7 +745,10 @@ int disk_init(int drv) /* Cache the timebase frequency for every delay and timeout below. A * zero reading means no timeout in this driver could ever expire, so * fail here and let the caller panic instead of spinning forever. */ - g_esdhc_tb_hz = TIMEBASE_HZ; +#if defined(TARGET_nxp_ls1028a) + esdhc_clock_src_init(); +#endif + g_esdhc_tb_hz = esdhc_read_tb_hz(); if (g_esdhc_tb_hz == 0U) { return -1; } diff --git a/options.mk b/options.mk index 06e5e638..05a03a57 100644 --- a/options.mk +++ b/options.mk @@ -801,12 +801,14 @@ ifeq ($(DISK_EMMC),1) CFLAGS+=-D"DISK_EMMC=1" endif -# Add SDHCI driver if SD card or eMMC is enabled (only add once). -# PPC targets provide their own eSDHC driver (hal/nxp_esdhc.o, added in -# arch.mk), so the Cadence SDHCI driver must not be linked there (its -# disk_* entry points would collide). +# Add the SD/eMMC block driver if SD card or eMMC is enabled (only once). +# DISK_DRIVER selects which one: the Cadence SDHCI driver (src/sdhci.c, +# the default) or the Freescale eSDHC driver (hal/nxp_esdhc.o, added by +# the target's arch.mk block, which also sets DISK_DRIVER=esdhc). Exactly +# one may link: both define the disk_* entry points. +DISK_DRIVER?=cadence ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),) - ifneq ($(ARCH),PPC) + ifeq ($(DISK_DRIVER),cadence) OBJS+= src/sdhci.o endif endif