From 1904d894bae7eb55dcdef76e8a67d43ca496806f Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 11 Nov 2026 15:28:04 -0800 Subject: [PATCH] Add NXP S32G2 (S32G274A GoldBox) target: wolfBoot as the Boot ROM image, booting a signed FIT from SD, eMMC or QuadSPI NOR --- .github/workflows/test-configs.yml | 32 + .gitignore | 6 + Makefile | 5 + arch.mk | 48 + config/examples/s32g2-goldbox-qspi.config | 40 + config/examples/s32g2-goldbox.config | 41 + docs/Targets.md | 122 ++ hal/imx_usdhc_sdhci.c | 2 + hal/s32g2.c | 2057 +++++++++++++++++++++ hal/s32g2.h | 252 +++ hal/s32g2.its | 28 + hal/s32g2.ld | 68 + hal/s32g2_ddr.c | 485 +++++ src/boot_aarch64.c | 9 + src/boot_aarch64_start.S | 210 ++- src/update_disk.c | 3 + tools/scripts/s32g2/s32g2-ddr-blob.py | 134 ++ tools/scripts/s32g2/s32g2-mkfit.sh | 35 + tools/scripts/s32g2/s32g2_mkimage.py | 157 ++ 19 files changed, 3733 insertions(+), 1 deletion(-) create mode 100644 config/examples/s32g2-goldbox-qspi.config create mode 100644 config/examples/s32g2-goldbox.config create mode 100644 hal/s32g2.c create mode 100644 hal/s32g2.h create mode 100644 hal/s32g2.its create mode 100644 hal/s32g2.ld create mode 100644 hal/s32g2_ddr.c create mode 100755 tools/scripts/s32g2/s32g2-ddr-blob.py create mode 100755 tools/scripts/s32g2/s32g2-mkfit.sh create mode 100755 tools/scripts/s32g2/s32g2_mkimage.py diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index cdd2f3cb60..36ff386e64 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -873,6 +873,38 @@ jobs: config-file: ./config/examples/imx8mm-pico.config make-args: IMX8MM_BL33=1 DISK_EMMC=0 IMX8MM_MMU=0 wolfboot.bin test-app/image_v1_signed.bin + s32g2_test: + uses: ./.github/workflows/test-build-aarch64.yml + with: + arch: aarch64 + # Boot ROM image in SRAM, signed FIT from the SD card. + config-file: ./config/examples/s32g2-goldbox.config + make-args: wolfboot.bin + + s32g2_emmc_test: + uses: ./.github/workflows/test-build-aarch64.yml + with: + arch: aarch64 + # FIT from the eMMC behind the board's SW3, no console. + config-file: ./config/examples/s32g2-goldbox.config + make-args: DISK_EMMC=1 DISK_SDCARD=0 DEBUG_UART=0 wolfboot.bin + + s32g2_qspi_test: + uses: ./.github/workflows/test-build-aarch64.yml + with: + arch: aarch64 + # Boot from the QuadSPI NOR with the A/B partitions in the NOR. + config-file: ./config/examples/s32g2-goldbox-qspi.config + make-args: wolfboot.bin + + s32g2_qspi_program_test: + uses: ./.github/workflows/test-build-aarch64.yml + with: + arch: aarch64 + # NOR programmer build: image file from the SD card to the NOR. + config-file: ./config/examples/s32g2-goldbox.config + make-args: S32G2_QSPI=1 S32G2_QSPI_PROGRAM_FILE=/s32g2_qspi_boot.bin wolfboot.bin + disk_boot_confirm_test: uses: ./.github/workflows/test-build-aarch64.yml with: diff --git a/.gitignore b/.gitignore index fa29746e8b..2f9538fdef 100644 --- a/.gitignore +++ b/.gitignore @@ -694,3 +694,9 @@ tools/unit-tests/stm32wb_write_extract.h tools/unit-tests/t10xx_flash_status_extract.h tools/unit-tests/ti_hercules_erase_extract.h tools/unit-tests/versal_host_write.c +# S32G2 GoldBox FIT build outputs (tools/scripts/s32g2/s32g2-mkfit.sh) +Image.gz +s32g2-fitImage* +boot.scr +s32g2.dtb +s32g2_ddr.bin diff --git a/Makefile b/Makefile index e05344fc39..889aab8eaa 100644 --- a/Makefile +++ b/Makefile @@ -511,6 +511,11 @@ ifeq ($(TARGET),imx8mm) MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin endif +# S32G2 runs from SRAM and boots a signed FIT; there is no factory.bin. +ifeq ($(TARGET),s32g2) + MAIN_TARGET:=wolfboot.bin +endif + # i.MX95 M7 runs from ITCM (loaded by the Linux remoteproc driver); the payload # lives in DDR at 0x80100000, so there is no contiguous flash image to assemble. ifeq ($(TARGET),imx95_m7) diff --git a/arch.mk b/arch.mk index b5eccfc0ee..3d7a250848 100644 --- a/arch.mk +++ b/arch.mk @@ -359,6 +359,54 @@ ifeq ($(ARCH),AARCH64) endif endif + ifeq ($(TARGET),s32g2) + # NXP S32G274A GoldBox: wolfBoot is the Boot ROM's application image, + # copied into SRAM and entered at EL3. It runs its own identity MMU there + # and tears it down at the handoff with the stack-free flush routine. + ARCH_FLAGS=-mcpu=cortex-a53+crypto -march=armv8-a+crypto -mstrict-align + CFLAGS+=$(ARCH_FLAGS) -DCORTEX_A53 + CFLAGS+=-DWOLFBOOT_AARCH64_MMU_TEARDOWN + # The RVBAR write in the full startup path is a ZynqMP register. + CFLAGS+=-DSKIP_RVBAR=1 + WOLFBOOT_ORIGIN=0x340A0000 + OBJS+=hal/s32g2_ddr.o + # wolfBoot adds /memory and the spin-table addresses to the FIT's DTB, + # which needs more room than the default headroom. + CFLAGS+=-DWOLFBOOT_FDT_FIXUP_HEADROOM=4096 + # LPDDR4 register tables and PHY training firmware, generated by + # tools/scripts/s32g2/s32g2-ddr-blob.py. Without the file the image + # still builds, and reports the missing blob instead of training. + S32G2_DDR_BLOB ?= s32g2_ddr.bin + ifneq ($(wildcard $(S32G2_DDR_BLOB)),) + CFLAGS+=-DS32G2_DDR_BLOB='"$(abspath $(S32G2_DDR_BLOB))"' + endif + # uSDHC0 board facts. Here rather than in the config because a command-line + # CFLAGS_EXTRA= would replace the config's, whereas CFLAGS is additive. + ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),) + # uSDHC -> SDHCI shim, shared with the i.MX 8 targets + OBJS+=hal/imx_usdhc_sdhci.o + # Card detect is a GPIO, so PRES_STATE bit 16 never sets. + CFLAGS+=-DSDHCI_FORCE_CARD_DETECT + # Read the payload in 64 KB chunks rather than the 512-byte default. + DISK_BLOCK_SIZE ?= 65536 + CFLAGS+=-DDISK_BLOCK_SIZE=$(DISK_BLOCK_SIZE) + # The slot is 3.3 V only (no-1-8-v). + S32G2_USDHC_MAX_CLK_KHZ ?= 25000 + CFLAGS+=-DS32G2_USDHC_MAX_CLK_KHZ=$(S32G2_USDHC_MAX_CLK_KHZ) + endif + # QuadSPI NOR driver (the ext_flash API with EXT_FLASH=1) + ifeq ($(S32G2_QSPI),1) + CFLAGS+=-DS32G2_QSPI + # NOR programmer build: writes the named file from the SD/eMMC FAT + # partition to the NOR at S32G2_QSPI_PROGRAM_OFFSET, then halts. + ifneq ($(S32G2_QSPI_PROGRAM_FILE),) + S32G2_QSPI_PROGRAM_OFFSET ?= 0 + CFLAGS+=-DS32G2_QSPI_PROGRAM_FILE='"$(S32G2_QSPI_PROGRAM_FILE)"' + CFLAGS+=-DS32G2_QSPI_PROGRAM_OFFSET=$(S32G2_QSPI_PROGRAM_OFFSET) + endif + endif + endif + ifeq ($(TARGET),imx8mm) # uSDHC -> SDHCI shim, shared with imx8qm ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),) diff --git a/config/examples/s32g2-goldbox-qspi.config b/config/examples/s32g2-goldbox-qspi.config new file mode 100644 index 0000000000..3f7d8e2638 --- /dev/null +++ b/config/examples/s32g2-goldbox-qspi.config @@ -0,0 +1,40 @@ +# NXP S32G2 GoldBox (S32G-VNP-GLDBOX, S32G274A-RDB2) - wolfBoot as the Boot +# ROM's application image booting from the 64 MB QuadSPI NOR, with the A/B +# image partitions in the same NOR and the signed FIT loaded into DDR. No SD +# card is needed (DISK_SDCARD=0). +# +# NOR layout: wolfBoot at 0 (IVT, ROM QSPI parameters, code), BOOT partition +# at 1 MB, UPDATE at 17 MB, SWAP sector at 33 MB; 4 KB erase sectors. +# tools/scripts/s32g2/s32g2_mkimage.py wolfboot.bin s32g2_qspi_boot.bin --qspi +# The NOR is written by a programmer build of the SD config +# (S32G2_QSPI=1 S32G2_QSPI_PROGRAM_FILE=/); the board must be strapped +# for QuadSPI boot. +# See docs/Targets.md. +# +# Never pass CFLAGS_EXTRA= on the command line - it replaces, not appends. +ARCH=AARCH64 +TARGET=s32g2 +S32G2_QSPI=1 +SIGN?=ECC384 +HASH?=SHA384 +DEBUG?=1 +DEBUG_UART?=1 +SPMATH?=1 +NO_XIP?=1 +EXT_FLASH?=1 +SKIP_GIC_INIT?=1 +GZIP?=1 +DISK_SDCARD?=0 +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_PARTITION_SIZE?=0x1000000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x100000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x1100000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x2100000 +# Separate DTB partitions are not used (the FIT carries the DTB) but the +# RAM-boot flow needs them defined; they sit after the swap sector. +WOLFBOOT_DTS_BOOT_ADDRESS?=0x2110000 +WOLFBOOT_DTS_UPDATE_ADDRESS?=0x2120000 + +WOLFBOOT_RAMBOOT_MAX_SIZE=0x4000000 +WOLFBOOT_LOAD_ADDRESS?=0xA0000000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0xA5000000 diff --git a/config/examples/s32g2-goldbox.config b/config/examples/s32g2-goldbox.config new file mode 100644 index 0000000000..998c13b4f5 --- /dev/null +++ b/config/examples/s32g2-goldbox.config @@ -0,0 +1,41 @@ +# NXP S32G2 GoldBox (S32G-VNP-GLDBOX, S32G274A-RDB2) - wolfBoot as the Boot +# ROM's application image: copied into SRAM at 0x340A0000 and entered at EL3 +# with only FIRC running, in place of U-Boot. The signed FIT (kernel + DTB, +# hal/s32g2.its) is read from a FAT32 partition of the SD card, or of the +# eMMC with DISK_EMMC=1 DISK_SDCARD=0. +# +# The LPDDR4 tables and PHY firmware come from the NXP BSP U-Boot sources: +# tools/scripts/s32g2/s32g2-ddr-blob.py s32g2_ddr.bin +# Wrap wolfboot.bin for the Boot ROM and write it at SD byte offset 0x1000: +# tools/scripts/s32g2/s32g2_mkimage.py wolfboot.bin s32g2_boot.bin +# dd if=s32g2_boot.bin of=/dev/sdX bs=512 seek=8 +# See docs/Targets.md. +# +# Never pass CFLAGS_EXTRA= on the command line - it replaces, not appends. +ARCH=AARCH64 +TARGET=s32g2 +SIGN?=ECC384 +HASH?=SHA384 +DEBUG?=1 +DEBUG_UART?=1 +SPMATH?=1 +NO_XIP?=1 +SKIP_GIC_INIT?=1 +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_NO_PARTITIONS=1 +GZIP?=1 +WOLFBOOT_PARTITION_SIZE?=0x100000 +DISK_SDCARD?=1 +DISK_FS?=fat32 + +# A/B slots: two files on the third partition (FAT32). The higher version +# boots. An older version is not used when the newer one fails to verify +# (anti-rollback), unless built with ALLOW_DOWNGRADE=1. +CFLAGS_EXTRA+=-DBOOT_PART_A=2 +CFLAGS_EXTRA+=-DBOOT_PART_B=2 +CFLAGS_EXTRA+=-DBOOT_FILE_A='"/fitImage_a.bin"' +CFLAGS_EXTRA+=-DBOOT_FILE_B='"/fitImage_b.bin"' + +WOLFBOOT_RAMBOOT_MAX_SIZE=0x4000000 +WOLFBOOT_LOAD_ADDRESS?=0xA0000000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0xA5000000 diff --git a/docs/Targets.md b/docs/Targets.md index 82706c6e1a..d5a4a3b597 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -28,6 +28,7 @@ This README describes configuration of supported targets. * [Nordic nRF54LM20](#nordic-nrf54lm20) * [NXP i.MX 8M Mini](#nxp-imx-8m-mini) * [NXP i.MX 8QuadMax](#nxp-imx-8quadmax) +* [NXP S32G2 (S32G274A GoldBox)](#nxp-s32g2-s32g274a-goldbox) * [NXP i.MX95 Cortex-M7](#nxp-imx95-cortex-m7) * [NXP i.MX95 Cortex-A55 (BL33)](#nxp-imx95-cortex-a55-bl33) * [NXP iMX-RT](#nxp-imx-rt) @@ -10438,6 +10439,127 @@ Register bases used by the HAL, from the upstream device tree (`imx8-ss-{lsio,co `-DDEBUG_SDHCI -DDEBUG_DISK -DDEBUG_GPT` add command-level tracing to the storage paths, and `-DDEBUG_FS` traces the filesystem layer when `DISK_FS` is enabled. +## NXP S32G2 (S32G274A GoldBox) + +wolfBoot runs on the NXP S32G274A (4x Cortex-A53 + 3x Cortex-M7 lock-step pairs) as the Boot ROM's application image, in place of U-Boot: the ROM copies it into the 8 MB system SRAM at 0x340A0000 and enters it at EL3 with only the 48 MHz FIRC running. wolfBoot brings the SoC up, verifies a signed FIT (kernel plus device tree) and enters Linux at EL2. Developed against the S32G-VNP-GLDBOX, which is an S32G274A-RDB2 in an enclosure. + +``` +Boot ROM -> wolfBoot in SRAM (EL3) -> signed FIT from SD, eMMC or QuadSPI NOR -> Linux (EL2) +``` + +### Example configurations + +| Config | Boot ROM loads wolfBoot from | Signed FIT from | +|---|---|---| +| [/config/examples/s32g2-goldbox.config](/config/examples/s32g2-goldbox.config) | SD card (or eMMC) | FAT32 partition of the SD card; of the eMMC with `DISK_EMMC=1 DISK_SDCARD=0` | +| [/config/examples/s32g2-goldbox-qspi.config](/config/examples/s32g2-goldbox-qspi.config) | QuadSPI NOR | BOOT / UPDATE partitions in the same NOR | + +Both use `SIGN=ECC384 HASH=SHA384`. The disk config has two file slots on the third partition, `/fitImage_a.bin` and `/fitImage_b.bin`: the higher version boots. With wolfBoot's anti-rollback default an older version is not booted when the newer one fails to verify; that needs `ALLOW_DOWNGRADE=1`. The NOR config has BOOT at 1 MB, UPDATE at 17 MB and a 4 KB SWAP sector at 33 MB, with the FIT read into DDR through the `ext_flash` API. + +### LPDDR4 data blob + +wolfBoot trains the LPDDR4 itself ([/hal/s32g2_ddr.c](/hal/s32g2_ddr.c)) but does not carry the board's controller and PHY register tables or the Synopsys PHY training firmware. NXP publishes both in the BSP U-Boot tree, and [/tools/scripts/s32g2/s32g2-ddr-blob.py](/tools/scripts/s32g2/s32g2-ddr-blob.py) packs them into the binary the build links in: + +``` +git clone -b release/bsp29.0-2020.04 https://github.com/nxp-auto-linux/u-boot.git +tools/scripts/s32g2/s32g2-ddr-blob.py u-boot s32g2_ddr.bin +``` + +`S32G2_DDR_BLOB` names the file (default `s32g2_ddr.bin` in the wolfBoot root). Without it the image still builds, and reports the missing blob on the console instead of training. The blob is checked by magic, version and CRC before use. Inline ECC is not supported. + +### Building + +``` +cp config/examples/s32g2-goldbox.config .config +make +tools/scripts/s32g2/s32g2_mkimage.py wolfboot.bin s32g2_boot.bin +dd if=/dev/sdX bs=512 count=8192 of=sd_raw_backup.bin # keep the stock boot area +dd if=s32g2_boot.bin of=/dev/sdX bs=512 seek=8 conv=fsync # IVT at byte 0x1000 +tools/scripts/s32g2/s32g2-mkfit.sh Image 1 wolfboot_signing_private_key.der fsl-s32g274a-rdb2.dtb +mcopy -i /dev/sdX3 s32g2-fitImage_v1_signed.bin ::/fitImage_a.bin +``` + +[/tools/scripts/s32g2/s32g2_mkimage.py](/tools/scripts/s32g2/s32g2_mkimage.py) writes the IVT (tag 0xD1), a no-op DCD and the application boot code header (tag 0xD5) in the stock BSP's layout. The card must stay MBR: the IVT sits where GPT entries would be. Restoring the backup puts the stock chain back. The stock card's first partition is FAT16, which wolfBoot does not read, so the FIT goes on an added FAT32 partition (`mkfs.vfat -F 32 -s 8`). + +`s32g2-mkfit.sh` builds the FIT from [/hal/s32g2.its](/hal/s32g2.its): the gzip-compressed kernel, loaded at 0x84080000 (2 MB-aligned base plus the image's text offset), and the board DTB, both under the wolfBoot signature. + +### What wolfBoot does before the FIT + +In this order, in [/hal/s32g2.c](/hal/s32g2.c) and the S32G2 block of [/src/boot_aarch64_start.S](/src/boot_aarch64_start.S): + +- ECC-initialises `.bss`, the stack and the spin-table page through SRAM controller 0. The ROM only initialises the bytes it copies, and the controller works in 512-byte granules, so `.data` ends on a granule boundary. +- Routes the UART0 pads and programs LINFlexD_0. +- Services the VR5510 PMIC over I2C4: fail-safe watchdog and FCCU monitoring off. Unserviced, the PMIC power-cycles the board about a second after power-up, and on any clock change. +- Clock tree: FXOSC, ARM PLL (A53 at 1 GHz), XBAR 800 MHz, PERIPH PLL (LINFlexD 125 MHz, uSDHC 400 MHz, QuadSPI), DDR PLL 800 MHz. +- Ncore coherent interconnect, then an identity MMU at EL3 with the caches on: SRAM Normal, peripherals and the QuadSPI window Device. Cluster 1's interconnect port only joins when its cores are released, since a port admitted with its cores in reset stalls the barrier after a TLB invalidate. +- LPDDR4 training (1D and 2D), a read-back check through a Device mapping, then DRAM mapped Normal. +- MSCM interrupt routing, GICv3 distributor and redistributors, and the three secondary cores released through MC_ME and parked at EL2 on a spin table at 0x34000000. +- uSDHC and QuadSPI pads, whichever medium holds the FIT, as the OS expects them routed. + +`hal_dts_fixup` adds the `/memory` node for both DRAM banks, each core's `enable-method = "spin-table"` and `cpu-release-addr`, the bootargs, and clears `linux,initrd-start/end` (the stock DTB's range overlaps the kernel). The handoff flushes the caches, turns the MMU off and enters the kernel at EL2 with `SCR_EL3` leaving IRQ and FIQ to EL2. EL3 exception vectors report ESR, ELR and FAR on the console. + +### QuadSPI NOR + +`S32G2_QSPI=1` builds the driver for the QuadSPI controller at 0x40134000 and the GoldBox's 64 MB Macronix MX25UW51245G. The flash runs in plain SPI mode with 4-byte addresses: single commands are IP commands described by a LUT sequence (page program 0x12, 4 KB sector erase 0x21), bulk reads go through the memory-mapped AHB window (fast read 0x0C), the DLL slave is bypassed and data is sampled from the internal DQS loopback. The serial clock comes from PERIPH DFS1 through MC_CGM0 mux 12 (`S32G2_QSPI_2X_DIV`, 8 = 50 MHz). If the Boot ROM left the flash in octal DTR mode, the init resets it to SPI mode with the two-byte OPI reset commands. + +``` +cp config/examples/s32g2-goldbox-qspi.config .config +make +tools/scripts/s32g2/s32g2_mkimage.py wolfboot.bin s32g2_qspi_boot.bin --qspi +``` + +`--qspi` lays the image out for QuadSPI boot: the IVT at flash offset 0, the 0x200-byte QuadSPI parameter block the ROM programs the controller with at 0x200 (`sdr`, the default, reads in plain SPI mode with no mode switch; `--qspi-params octal` is the stock BSP block that moves the flash to octal DTR), the DCD and the application. Strap the RDB2 for QuadSPI boot with SW4 all OFF, SW9 OFF, SW10 1 ON / 2 OFF. + +The NOR is written by a programmer build of the SD config: it reads the named file from the FAT32 partition into DDR, erases, programs and reads it back, then halts. Boot it from the SD card once per image: + +``` +cp config/examples/s32g2-goldbox.config .config +make S32G2_QSPI=1 S32G2_QSPI_PROGRAM_FILE=/s32g2_qspi_boot.bin # wolfBoot at 0 +make S32G2_QSPI=1 S32G2_QSPI_PROGRAM_FILE=/fitImage_a.bin S32G2_QSPI_PROGRAM_OFFSET=0x100000 # FIT in BOOT +``` + +### eMMC + +The GoldBox routes uSDHC0 either to the microSD slot or to the on-board 32 GB eMMC with the RDB2's SW3 (ON = microSD, OFF = eMMC). `make DISK_EMMC=1 DISK_SDCARD=0` drives the eMMC through the same uSDHC shim. The factory eMMC carries the BSP image (FAT16 boot partition, ext3 rootfs), so a FAT32 partition is appended from the board's Linux (`sfdisk --append`, `mkfs.vfat -F 32 -s 8`) for the FIT files. With wolfBoot in the NOR and SW3 OFF the box boots with no card inserted. + +### Hardware setup (GoldBox) + +- Console: UART0 micro-USB (FTDI FT232R), LINFlexD_0, 115200 8N1. The second micro-USB is UART1 and prints nothing. +- Boot source switches on the RDB2: SD boot is SW4 position 7 ON only; QuadSPI boot is SW4 all OFF; SW9 OFF and SW10 1 ON / 2 OFF for both. +- Expected console, booting from the SD card: + +``` +wolfBoot S32G2 (EL3, running from SRAM) +s32g2: VR5510 watchdog disabled +s32g2: A53 1000 MHz, XBAR_2X 800 MHz, LIN 125 MHz, ... +s32g2: MMU on at EL3, caches enabled +DDR: 1D training +DDR: 2D training +s32g2: DDR up, 4096 MB, erratum polling 0 +s32g2: core 1 parked +p2: fat32, /fitImage_a.bin +Firmware Valid. +FDT: memory and spin-table set +``` + +### Peripheral map + +| Block | Base | Notes | +|---|---|---| +| LINFlexD_0 | 0x401C8000 | FIFO mode; TX pin PC09 (MSCR41), RX PC10 (MSCR42, IMCR512) | +| uSDHC0 | 0x402F0000 | i.MX uSDHC v3, shared shim [/hal/imx_usdhc_sdhci.c](/hal/imx_usdhc_sdhci.c), per clock 400 MHz | +| QuadSPI | 0x40134000 | AHB window at 0x0 | +| I2C4 | 0x402DC000 | VR5510 fail-safe unit at 0x21 | +| SRAM | 0x34000000 | 8 MB; controllers at 0x4019C000 / 0x401A0000 | +| DRAM | 0x80000000 + 0x880000000 | 2 GB + 2 GB | +| Ncore / GICv3 | 0x50400000 / 0x50800000 | | + +### Known limitations + +- The NOR UPDATE partition has not been written from Linux: the BSP29 5.4 kernel's QuadSPI driver blocks on its first flash read under this boot chain, so the NOR update and rollback flow is untested and the NOR is programmed through the programmer build. +- QuadSPI boot with the octal DTR parameter block is untested. +- Inline DRAM ECC, HSE secure boot and chain-loading BL31 (for kernels that need PSCI or SCMI) are not implemented. + ## NXP i.MX95 Cortex-M7 The i.MX95 pairs an A55 cluster running Linux with a real-time Cortex-M7 and a Cortex-M33 System Manager. The M7 has no dedicated flash: it is loaded into TCM by the Linux `remoteproc` driver on the A55 side, so wolfBoot is the ELF that `remoteproc` loads, and the images it verifies live in the DDR region the device tree reserves for the M7. diff --git a/hal/imx_usdhc_sdhci.c b/hal/imx_usdhc_sdhci.c index 77a2ba9278..58c0f5fd3b 100644 --- a/hal/imx_usdhc_sdhci.c +++ b/hal/imx_usdhc_sdhci.c @@ -37,6 +37,8 @@ #include "hal/imx8qm.h" #elif defined(TARGET_imx8mm) #include "hal/imx8mm.h" +#elif defined(TARGET_s32g2) +#include "hal/s32g2.h" #else #error "hal/imx_usdhc_sdhci.c: unsupported target" #endif diff --git a/hal/s32g2.c b/hal/s32g2.c new file mode 100644 index 0000000000..cbe99b3bce --- /dev/null +++ b/hal/s32g2.c @@ -0,0 +1,2057 @@ +/* s32g2.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 + */ + +/* NXP S32G2 (S32G274A) on the S32G-VNP-GLDBOX / RDB2: wolfBoot as the Boot + * ROM's application image, copied into SRAM and entered at EL3 with only the + * FIRC running. It brings the SoC up (PMIC, clocks, LPDDR4 in hal/s32g2_ddr.c, + * interconnect, GIC, secondary cores), verifies a signed FIT from SD, eMMC or + * the QuadSPI NOR and enters Linux at EL2. See docs/Targets.md. */ + +#include +#include +#include +#include + +#if defined(DEBUG_UART) + #define PRINTF_ENABLED +#endif + +#include "image.h" +#include "loader.h" +#include "printf.h" +#include "hal/s32g2.h" +#ifdef S32G2_QSPI_PROGRAM_FILE +#include "disk.h" +#include "disk_fs.h" +#ifndef BOOT_PART_A +#define BOOT_PART_A 2 +#endif +#ifndef S32G2_QSPI_PROGRAM_OFFSET +#define S32G2_QSPI_PROGRAM_OFFSET 0 +#endif +#define S32G2_QSPI_PROGRAM_MAX 0x1000000 /* 16 MB, the NOR partition size */ +#endif +#include "fdt.h" +#if defined(DISK_SDCARD) || defined(DISK_EMMC) +#include "sdhci.h" +#endif + +#ifndef ARCH_AARCH64 +# error "wolfBoot s32g2 HAL: wrong architecture. Compile with ARCH=AARCH64." +#endif + +/* MMIO is Device-nGnRnE, with the MMU off and in the identity map, so + * accesses are strongly ordered by the memory type and need no barriers. */ +static inline uint32_t rd32(uintptr_t a) { return *(volatile uint32_t*)a; } +static inline void wr32(uintptr_t a, uint32_t v) { *(volatile uint32_t*)a = v; } +static inline void wr8(uintptr_t a, uint8_t v) { *(volatile uint8_t*)a = v; } +static inline uint8_t rd8(uintptr_t a) { return *(volatile uint8_t*)a; } +static inline void wr16(uintptr_t a, uint16_t v) { *(volatile uint16_t*)a = v; } +static inline uint64_t rd64(uintptr_t a) { return *(volatile uint64_t*)a; } + +#include "aarch64_arch.h" + +uint64_t hal_get_timer_us(void) +{ + return (timer_get_count() * 1000000ULL) / timer_get_freq(); +} + +void hal_delay_us(uint32_t us) +{ + uint64_t deadline = timer_deadline_us(us); + + while (!timer_expired(deadline)) + ; +} + +/* Clock tree. Out of reset everything runs from the 48 MHz FIRC; this takes + * the A53 cluster, the XBAR, the LIN, SDHC, QuadSPI and DDR clocks to the + * rates the NXP BSP runs them at. */ +/* MC_CGM: clock source select and dividers, one 0x40 block per mux. */ +#define CGM_MUX_CSC(cgm, mux) ((cgm) + 0x300 + (mux) * 0x40) +#define CGM_MUX_CSS(cgm, mux) ((cgm) + 0x304 + (mux) * 0x40) +#define CGM_MUX_DC(cgm, mux, n) ((cgm) + 0x308 + (mux) * 0x40 + (n) * 4) +#define CGM_MUX_DIV_UPD_STAT(cgm, mux) ((cgm) + 0x33C + (mux) * 0x40) +#define CGM_CSC_SELCTL_MASK 0x3F000000 +#define CGM_CSC_SELCTL(src) (((uint32_t)(src) << 24) & CGM_CSC_SELCTL_MASK) +#define CGM_CSC_CLK_SW (1U << 2) +#define CGM_CSS_SELSTAT(css) (((css) >> 24) & 0x3F) +#define CGM_CSS_SWTRG(css) (((css) >> 17) & 0x7) +#define CGM_CSS_SWTRG_SUCCESS 1 +#define CGM_CSS_SWIP (1U << 16) +#define CGM_DC_DE (1U << 31) +#define CGM_DC_DIV(dc) ((((uint32_t)(dc) - 1) << 16) & 0x00FF0000) + +/* Mux source IDs */ +#define CGM_SRC_FIRC 0 +#define CGM_SRC_ARM_PLL_PHI0 4 +#define CGM_SRC_ARM_DFS1 12 +#define CGM_SRC_PERIPH_PLL_PHI3 21 +#define CGM_SRC_PERIPH_DFS1 26 +#define CGM_SRC_PERIPH_DFS3 28 +#define CGM_SRC_DDR_PLL_PHI0 36 + +/* PLLDIG */ +#define PLL_PLLCR 0x00 +#define PLL_PLLSR 0x04 +#define PLL_PLLDV 0x08 +#define PLL_PLLFD 0x10 +#define PLL_PLLCLKMUX 0x20 +#define PLL_PLLODIV(n) (0x80 + (n) * 4) +#define PLL_PLLCR_PLLPD (1U << 31) +#define PLL_PLLSR_LOCK (1U << 2) +#define PLL_PLLDV_RDIV(r) (((uint32_t)(r) & 0x7) << 12) +#define PLL_PLLDV_MFI(m) ((uint32_t)(m) & 0xFF) +#define PLL_PLLFD_SMDEN (1U << 30) +#define PLL_PLLFD_MFN(m) ((uint32_t)(m) & 0x7FFF) +#define PLL_PLLCLKMUX_FXOSC 1 +#define PLL_PLLODIV_DE (1U << 31) +#define PLL_PLLODIV_DIV(dc) ((((uint32_t)(dc) - 1) << 16) & 0x00FF0000) +/* Output dividers per PLL: the register blocks end right after the last one. */ +#define ARM_PLL_NODIVS 2 +#define PERIPH_PLL_NODIVS 8 +#define DDR_PLL_NODIVS 1 + +/* DFS */ +#define DFS_PORTSR 0x0C +#define DFS_PORTOLSR 0x10 +#define DFS_PORTRESET 0x14 +#define DFS_CTL 0x18 +#define DFS_DVPORT(n) (0x1C + (n) * 4) +#define DFS_CTL_RESET (1U << 1) +#define DFS_PORTS_MASK 0x3F +#define DFS_DVPORT_VAL(mfi, mfn) ((((uint32_t)(mfi) & 0xFF) << 8) | ((uint32_t)(mfn) & 0x3F)) + +/* FXOSC */ +#define FXOSC_CTRL 0x00 +#define FXOSC_STAT 0x04 +#define FXOSC_CTRL_OSCON (1U << 0) +#define FXOSC_CTRL_GM_SEL(s) (((uint32_t)(s) & 0xF) << 4) +#define FXOSC_CTRL_EOCV(v) (((uint32_t)(v) & 0xFF) << 16) +#define FXOSC_CTRL_COMP_EN (1U << 24) +#define FXOSC_STAT_OSC_STAT (1U << 31) + +/* MC_ME partition registers */ +#define MC_ME_CTL_KEY 0x000 +#define MC_ME_CTL_KEY_KEY 0x5AF0 +#define MC_ME_CTL_KEY_INVKEY 0xA50F +#define MC_ME_PRTN_PCONF(n) (0x100 + (n) * 0x200) +#define MC_ME_PRTN_PUPD(n) (0x104 + (n) * 0x200) +#define MC_ME_PRTN_STAT(n) (0x108 + (n) * 0x200) +#define MC_ME_PRTN_COFB0_STAT(n) (0x110 + (n) * 0x200) +#define MC_ME_PRTN_COFB0_CLKEN(n) (0x130 + (n) * 0x200) +#define MC_ME_PRTN_PCONF_PCE (1U << 0) +#define MC_ME_PRTN_PUPD_PCUD (1U << 0) + +/* Bounded polls: the loops below spin on hardware that could be polled without a + * timeout; a bound turns a wedge into an error report instead of a hang. */ +#define CLK_POLL_MAX 2000000U + +static int poll_set(uintptr_t a, uint32_t mask) +{ + uint32_t n; + + for (n = 0; n < CLK_POLL_MAX; n++) { + if ((rd32(a) & mask) != 0) + return 0; + } + return -1; +} + +static int poll_clear(uintptr_t a, uint32_t mask) +{ + uint32_t n; + + for (n = 0; n < CLK_POLL_MAX; n++) { + if ((rd32(a) & mask) == 0) + return 0; + } + return -1; +} + +/* Currently selected source of a clock mux */ +uint32_t s32g2_cgm_mux_get(uintptr_t cgm, uint32_t mux) +{ + return (rd32(CGM_MUX_CSS(cgm, mux)) >> 24) & 0x3F; +} + +int s32g2_cgm_mux_set(uintptr_t cgm, uint32_t mux, uint32_t src) +{ + uint32_t css, csc; + + css = rd32(CGM_MUX_CSS(cgm, mux)); + if (CGM_CSS_SELSTAT(css) == src && CGM_CSS_SWTRG(css) == CGM_CSS_SWTRG_SUCCESS && + (css & CGM_CSS_SWIP) == 0) + return 0; + if (poll_clear(CGM_MUX_CSS(cgm, mux), CGM_CSS_SWIP) != 0) + return -1; + csc = rd32(CGM_MUX_CSC(cgm, mux)) & ~CGM_CSC_SELCTL_MASK; + csc |= CGM_CSC_SELCTL(src) | CGM_CSC_CLK_SW; + wr32(CGM_MUX_CSC(cgm, mux), csc); + if (poll_clear(CGM_MUX_CSC(cgm, mux), CGM_CSC_CLK_SW) != 0) + return -1; + if (poll_clear(CGM_MUX_CSS(cgm, mux), CGM_CSS_SWIP) != 0) + return -1; + css = rd32(CGM_MUX_CSS(cgm, mux)); + if (CGM_CSS_SWTRG(css) != CGM_CSS_SWTRG_SUCCESS || CGM_CSS_SELSTAT(css) != src) + return -1; + return 0; +} + +static int s32g2_cgm_div_set(uintptr_t cgm, uint32_t mux, uint32_t n, uint32_t dc) +{ + uint32_t val = CGM_DC_DE | CGM_DC_DIV(dc); + + if ((rd32(CGM_MUX_DC(cgm, mux, n)) & (CGM_DC_DE | 0x00FF0000)) == val) + return 0; + wr32(CGM_MUX_DC(cgm, mux, n), val); + return poll_clear(CGM_MUX_DIV_UPD_STAT(cgm, mux), 1); +} + +static int fxosc_enable(void) +{ + uintptr_t b = S32G2_FXOSC_BASE; + uint32_t ctrl; + + /* The Boot ROM may have started it already; its inputs must not change + * while it runs. */ + if ((rd32(b + FXOSC_CTRL) & FXOSC_CTRL_OSCON) != 0) + return 0; + ctrl = FXOSC_CTRL_COMP_EN | FXOSC_CTRL_EOCV(1) | FXOSC_CTRL_GM_SEL(7); + wr32(b + FXOSC_CTRL, ctrl); + wr32(b + FXOSC_CTRL, ctrl | FXOSC_CTRL_OSCON); + return poll_set(b + FXOSC_STAT, FXOSC_STAT_OSC_STAT); +} + +/* VCO = FXOSC * MFI / RDIV with RDIV 1, fractional part off; output dividers + * are disabled around the reprogramming as the PLL is powered down. */ +static int pll_program(uintptr_t pll, uint32_t mfi, uint32_t nodivs) +{ + uint32_t n, dv; + + for (n = 0; n < nodivs; n++) + wr32(pll + PLL_PLLODIV(n), rd32(pll + PLL_PLLODIV(n)) & ~PLL_PLLODIV_DE); + wr32(pll + PLL_PLLCR, PLL_PLLCR_PLLPD); + wr32(pll + PLL_PLLCLKMUX, PLL_PLLCLKMUX_FXOSC); + dv = rd32(pll + PLL_PLLDV) & ~(PLL_PLLDV_RDIV(7) | PLL_PLLDV_MFI(0xFF)); + wr32(pll + PLL_PLLDV, dv | PLL_PLLDV_RDIV(1) | PLL_PLLDV_MFI(mfi)); + wr32(pll + PLL_PLLFD, PLL_PLLFD_SMDEN | PLL_PLLFD_MFN(0)); + wr32(pll + PLL_PLLCR, 0); + return poll_set(pll + PLL_PLLSR, PLL_PLLSR_LOCK); +} + +static void pll_odiv_set(uintptr_t pll, uint32_t n, uint32_t dc) +{ + wr32(pll + PLL_PLLODIV(n), PLL_PLLODIV_DIV(dc)); + wr32(pll + PLL_PLLODIV(n), PLL_PLLODIV_DIV(dc) | PLL_PLLODIV_DE); +} + +/* DFS output = VCO / (2 * (MFI + MFN / 36)). Ports are reset all together + * the first time the DFS is touched, singly afterwards. */ +static int dfs_port_set(uintptr_t dfs, uint32_t port, uint32_t mfi, uint32_t mfn) +{ + uint32_t portsr, mask; + int first; + + portsr = rd32(dfs + DFS_PORTSR); + if ((portsr & (1U << port)) != 0 && + (rd32(dfs + DFS_PORTOLSR) & (1U << port)) == 0 && + rd32(dfs + DFS_DVPORT(port)) == DFS_DVPORT_VAL(mfi, mfn)) + return 0; + first = (portsr == 0); + mask = first ? DFS_PORTS_MASK : (1U << port); + wr32(dfs + DFS_PORTOLSR, mask); + wr32(dfs + DFS_PORTRESET, mask); + if (poll_clear(dfs + DFS_PORTSR, mask) != 0) + return -1; + if (first) + wr32(dfs + DFS_CTL, DFS_CTL_RESET); + wr32(dfs + DFS_DVPORT(port), DFS_DVPORT_VAL(mfi, mfn)); + if (first) + wr32(dfs + DFS_CTL, ~DFS_CTL_RESET); + wr32(dfs + DFS_PORTRESET, rd32(dfs + DFS_PORTRESET) & ~(1U << port)); + if (poll_set(dfs + DFS_PORTSR, 1U << port) != 0) + return -1; + if ((rd32(dfs + DFS_PORTOLSR) & (1U << port)) != 0) + return -1; + return 0; +} + +/* Clock a block of an MC_ME partition: COFB enable, partition clock enable, + * then the keyed process update. */ +static int s32g2_mc_me_block_enable(uint32_t part, uint32_t block) +{ + uintptr_t me = S32G2_MC_ME_BASE; + uint32_t n, pconf; + + wr32(me + MC_ME_PRTN_COFB0_CLKEN(part), + rd32(me + MC_ME_PRTN_COFB0_CLKEN(part)) | (1U << block)); + wr32(me + MC_ME_PRTN_PCONF(part), + rd32(me + MC_ME_PRTN_PCONF(part)) | MC_ME_PRTN_PCONF_PCE); + wr32(me + MC_ME_PRTN_PUPD(part), + rd32(me + MC_ME_PRTN_PUPD(part)) | MC_ME_PRTN_PUPD_PCUD); + wr32(me + MC_ME_CTL_KEY, MC_ME_CTL_KEY_KEY); + wr32(me + MC_ME_CTL_KEY, MC_ME_CTL_KEY_INVKEY); + pconf = rd32(me + MC_ME_PRTN_PCONF(part)) & MC_ME_PRTN_PUPD_PCUD; + for (n = 0; n < CLK_POLL_MAX; n++) { + if ((rd32(me + MC_ME_PRTN_STAT(part)) & MC_ME_PRTN_PUPD_PCUD) == pconf) + break; + } + if (n == CLK_POLL_MAX) + return -1; + return poll_set(me + MC_ME_PRTN_COFB0_STAT(part), 1U << block); +} + +/* A53 at 1 GHz, XBAR_2X 800 MHz, LIN 125 MHz, SDHC 400 MHz, DDR 800 MHz. + * trace() is called with the step number before each step; it is where the + * caller reports progress and reprograms its console once the LIN clock has + * moved (after step 8). Returns the number of the step that failed, 0 on + * success. */ +static int s32g2_clk_init(void (*trace)(int)) +{ +#define STEP(n) do { if (trace != NULL) trace(n); } while (0) + /* Park the XBAR on FIRC while its PLL and DFS are reprogrammed. */ + STEP(1); + if (s32g2_cgm_mux_set(S32G2_MC_CGM0_BASE, 0, CGM_SRC_FIRC) != 0) + return 1; + STEP(2); + if (fxosc_enable() != 0) + return 2; + + /* ARM PLL: 40 MHz * 50 = 2000 MHz VCO, PHI0 = /2 = 1000 MHz -> A53. The + * Boot ROM already runs the cluster from this PLL, so park the cluster + * on FIRC first or powering the PLL down stops the core. */ + STEP(3); + if (s32g2_cgm_mux_set(S32G2_MC_CGM1_BASE, 0, CGM_SRC_FIRC) != 0) + return 3; + if (pll_program(S32G2_ARM_PLL_BASE, 50, ARM_PLL_NODIVS) != 0) + return 3; + pll_odiv_set(S32G2_ARM_PLL_BASE, 0, 2); + STEP(4); + if (s32g2_cgm_mux_set(S32G2_MC_CGM1_BASE, 0, CGM_SRC_ARM_PLL_PHI0) != 0) + return 4; + + /* ARM DFS port 0 (DFS1): 2000 / (2 * 1.25) = 800 MHz -> XBAR_2X. */ + STEP(5); + if (dfs_port_set(S32G2_ARM_DFS_BASE, 0, 1, 9) != 0) + return 5; + STEP(6); + if (s32g2_cgm_mux_set(S32G2_MC_CGM0_BASE, 0, CGM_SRC_ARM_DFS1) != 0) + return 6; + + /* PERIPH PLL: 2000 MHz VCO, PHI3 = /16 = 125 MHz -> LIN_BAUD. Same + * precaution for the LIN mux, which the console hangs off. */ + STEP(7); + if (s32g2_cgm_mux_set(S32G2_MC_CGM0_BASE, 8, CGM_SRC_FIRC) != 0) + return 7; + if (pll_program(S32G2_PERIPH_PLL_BASE, 50, PERIPH_PLL_NODIVS) != 0) + return 7; + pll_odiv_set(S32G2_PERIPH_PLL_BASE, 3, 16); + STEP(8); + if (s32g2_cgm_mux_set(S32G2_MC_CGM0_BASE, 8, CGM_SRC_PERIPH_PLL_PHI3) != 0) + return 8; + + /* PERIPH DFS port 2 (DFS3): 800 MHz, /2 at the mux -> SDHC 400 MHz. */ + STEP(9); + if (dfs_port_set(S32G2_PERIPH_DFS_BASE, 2, 1, 9) != 0) + return 9; + STEP(10); + if (s32g2_cgm_div_set(S32G2_MC_CGM0_BASE, 14, 0, 2) != 0) + return 10; + STEP(11); + if (s32g2_cgm_mux_set(S32G2_MC_CGM0_BASE, 14, CGM_SRC_PERIPH_DFS3) != 0) + return 11; + STEP(12); + if (s32g2_mc_me_block_enable(0, S32G2_MC_ME_USDHC_BLOCK) != 0) + return 12; + + /* DDR PLL: 40 MHz * 40 = 1600 MHz VCO, PHI0 = /2 = 800 MHz -> DDR. */ + STEP(13); + if (pll_program(S32G2_DDR_PLL_BASE, 40, DDR_PLL_NODIVS) != 0) + return 13; + pll_odiv_set(S32G2_DDR_PLL_BASE, 0, 2); + STEP(14); + if (s32g2_cgm_mux_set(S32G2_MC_CGM5_BASE, 0, CGM_SRC_DDR_PLL_PHI0) != 0) + return 14; + STEP(15); + if (s32g2_mc_me_block_enable(0, S32G2_MC_ME_DDR_BLOCK) != 0) + return 15; + STEP(16); +#ifdef S32G2_QSPI + /* PERIPH DFS port 0 (DFS1): 800 MHz, divided at the mux to QSPI_2X; + * the serial clock is half of that (/16 = 25 MHz, /8 = 50 MHz). */ + if (dfs_port_set(S32G2_PERIPH_DFS_BASE, 0, 1, 9) != 0) + return 16; + STEP(17); + if (s32g2_cgm_div_set(S32G2_MC_CGM0_BASE, 12, 0, S32G2_QSPI_2X_DIV) != 0) + return 17; + STEP(18); + if (s32g2_cgm_mux_set(S32G2_MC_CGM0_BASE, 12, CGM_SRC_PERIPH_DFS1) != 0) + return 18; + STEP(19); +#endif + return 0; +#undef STEP +} + +/* VR5510 PMIC on I2C4: take the fail-safe unit out of its INIT_FS watchdog + * expectations so the board is not reset once the challenger window elapses. + * The I2C controller is the byte-register (IBFD/IBCR/IBSR/IBDR) type, driven + * in polled master mode. */ +#define I2C_IBFD 0x1 +#define I2C_IBCR 0x2 +#define I2C_IBSR 0x3 +#define I2C_IBDR 0x4 +#define IBCR_MDIS (1U << 7) +#define IBCR_MSSL (1U << 5) +#define IBCR_TX (1U << 4) +#define IBCR_NOACK (1U << 3) +#define IBCR_RSTA (1U << 2) +#define IBSR_IBB (1U << 5) +#define IBSR_IBAL (1U << 4) +#define IBSR_IBIF (1U << 1) +#define IBSR_RXAK (1U << 0) + +#define I2C_POLL_MAX 200000U + +/* IBFD divider table of the controller; the first entry at or below the + * target rate from the module clock is used. */ +static const uint16_t i2c_div[] = { + 20, 22, 24, 26, 28, 30, 34, 40, 28, 32, + 36, 40, 44, 48, 58, 64, 48, 56, 64, 72, + 80, 88, 104, 128, 80, 96, 112, 128, 144, 160, + 192, 240, 160, 192, 224, 256, 288, 320, 384, 480, + 320, 384, 448, 512, 576, 640, 768, 960, 640, 768, + 896, 1024, 1152, 1280, 1536, 1920, 1280, 1536, 1792, 2048, + 2304, 2560, 3072, 3840 +}; + +static void i2c_init(uintptr_t b, uint32_t clk_hz) +{ + uint32_t i; + + for (i = 0; i < (sizeof(i2c_div) / sizeof(i2c_div[0])) - 1; i++) { + if ((clk_hz / i2c_div[i]) <= S32G2_I2C_SPEED_HZ) + break; + } + wr8(b + I2C_IBFD, (uint8_t)i); + wr8(b + I2C_IBCR, IBCR_MDIS); + wr8(b + I2C_IBSR, 0); +} + +static int i2c_wait_idle(uintptr_t b) +{ + uint32_t n; + + for (n = 0; n < I2C_POLL_MAX; n++) { + if ((rd8(b + I2C_IBSR) & IBSR_IBB) == 0) + return 0; + } + return -1; +} + +static int i2c_wait_done(uintptr_t b) +{ + uint32_t n; + uint8_t sr; + + for (n = 0; n < I2C_POLL_MAX; n++) { + sr = rd8(b + I2C_IBSR); + if ((sr & IBSR_IBAL) != 0) { + wr8(b + I2C_IBSR, IBSR_IBAL); + return -1; + } + if ((sr & IBSR_IBIF) != 0) { + wr8(b + I2C_IBSR, IBSR_IBIF); + return 0; + } + } + return -1; +} + +static int i2c_tx_byte(uintptr_t b, uint8_t v) +{ + wr8(b + I2C_IBDR, v); + if (i2c_wait_done(b) != 0) + return -1; + return ((rd8(b + I2C_IBSR) & IBSR_RXAK) != 0) ? -1 : 0; +} + +static int i2c_stop(uintptr_t b) +{ + wr8(b + I2C_IBCR, rd8(b + I2C_IBCR) & ~IBCR_MSSL); + return i2c_wait_idle(b); +} + +/* START, chip address in write mode, register address. */ +static int i2c_start(uintptr_t b, uint8_t chip, uint8_t reg) +{ + wr8(b + I2C_IBCR, IBCR_MDIS); + wr8(b + I2C_IBSR, 0); + wr8(b + I2C_IBCR, 0); + wr8(b + I2C_IBSR, IBSR_IBIF); + if (i2c_wait_idle(b) != 0) + return -1; + wr8(b + I2C_IBCR, rd8(b + I2C_IBCR) | IBCR_MSSL); + wr8(b + I2C_IBCR, rd8(b + I2C_IBCR) | IBCR_TX | IBCR_NOACK); + if (i2c_tx_byte(b, (uint8_t)(chip << 1)) != 0) + return -1; + return i2c_tx_byte(b, reg); +} + +static int i2c_write(uintptr_t b, uint8_t chip, uint8_t reg, + const uint8_t* buf, int len) +{ + int i, ret = 0; + + if (i2c_start(b, chip, reg) != 0) { + i2c_stop(b); + return -1; + } + for (i = 0; i < len; i++) { + ret = i2c_tx_byte(b, buf[i]); + if (ret != 0) + break; + } + i2c_stop(b); + return ret; +} + +static int i2c_read(uintptr_t b, uint8_t chip, uint8_t reg, uint8_t* buf, int len) +{ + int i; + uint8_t cr; + + if (i2c_start(b, chip, reg) != 0) { + i2c_stop(b); + return -1; + } + /* Repeated START, then the chip address in read mode. */ + wr8(b + I2C_IBCR, rd8(b + I2C_IBCR) | IBCR_RSTA); + if (i2c_tx_byte(b, (uint8_t)((chip << 1) | 1)) != 0) { + i2c_stop(b); + return -1; + } + cr = rd8(b + I2C_IBCR) & ~IBCR_TX; + if (len == 1) + cr |= IBCR_NOACK; + else + cr &= ~IBCR_NOACK; + wr8(b + I2C_IBCR, cr); + (void)rd8(b + I2C_IBDR); /* dummy read starts the receive */ + for (i = 0; i < len; i++) { + if (i2c_wait_done(b) != 0) { + i2c_stop(b); + return -1; + } + if (i == len - 2) + wr8(b + I2C_IBCR, rd8(b + I2C_IBCR) | IBCR_NOACK); + else if (i == len - 1) + wr8(b + I2C_IBCR, rd8(b + I2C_IBCR) & ~IBCR_MSSL); + buf[i] = rd8(b + I2C_IBDR); + } + i2c_stop(b); + return 0; +} + +/* VR5510 register access: 16-bit registers, big-endian on the wire, followed + * by a CRC-8 (poly 0x1D, seed 0xFF) over {chip<<1|rw, reg, data_hi, data_lo}. */ +#define VR5510_CRC_POLY 0x1D +#define VR5510_CRC_SEED 0xFF + +static uint8_t vr5510_crc(const uint8_t* d, int len) +{ + uint8_t crc = VR5510_CRC_SEED; + int i, bit; + + for (i = 0; i < len; i++) { + crc ^= d[i]; + for (bit = 0; bit < 8; bit++) { + crc = (uint8_t)((crc & 0x80) ? ((crc << 1) ^ VR5510_CRC_POLY) : (crc << 1)); + } + } + return crc; +} + +static int vr5510_read(uint8_t chip, uint8_t reg, uint16_t* val) +{ + uint8_t frame[5]; + + if (i2c_read(S32G2_I2C4_BASE, chip, reg, &frame[2], 3) != 0) + return -1; + frame[0] = (uint8_t)((chip << 1) | 1); + frame[1] = reg; + if (vr5510_crc(frame, 4) != frame[4]) + return -2; + *val = (uint16_t)(((uint16_t)frame[2] << 8) | frame[3]); + return 0; +} + +static int vr5510_write(uint8_t chip, uint8_t reg, uint16_t val) +{ + uint8_t frame[5]; + + frame[0] = (uint8_t)(chip << 1); + frame[1] = reg; + frame[2] = (uint8_t)(val >> 8); + frame[3] = (uint8_t)val; + frame[4] = vr5510_crc(frame, 4); + return i2c_write(S32G2_I2C4_BASE, chip, reg, &frame[2], 3); +} + +/* Fail-safe unit registers */ +#define VR5510_FS_I_WD_CFG 7 +#define VR5510_FS_I_SAFE_INPUTS 9 +#define VR5510_FS_I_NOT_SAFE_INPUTS 10 +#define VR5510_FS_WD_WINDOW 15 +#define VR5510_FS_NOT_WD_WINDOW 16 +#define VR5510_FS_WD_SEED 17 +#define VR5510_FS_WD_ANSWER 18 +#define VR5510_FS_DIAG_SAFETY 22 +#define VR5510_FS_STATES 24 +#define VR5510_STATE_MASK 0x1F +#define VR5510_STATE_INIT_FS 6 +#define VR5510_WD_WINDOW_MASK 0xF000 +#define VR5510_FCCU_CFG_MASK (3U << 14) +#define VR5510_ABIST1_OK (1U << 10) +#define VR5510_ERR_CNT_MASK 0xF + +/* Answer the challenger watchdog once: the seed is transformed as the + * datasheet prescribes and the error counter must read zero afterwards. */ +static int vr5510_wd_refresh(void) +{ + uint16_t reg; + uint32_t answer; + + if (vr5510_read(S32G2_VR5510_FSU_ADDR, VR5510_FS_WD_SEED, ®) != 0) + return -1; + answer = ((~((uint32_t)reg * 4 + 2)) / 4) & 0xFFFFU; + if (vr5510_write(S32G2_VR5510_FSU_ADDR, VR5510_FS_WD_ANSWER, (uint16_t)answer) != 0) + return -2; + if (vr5510_read(S32G2_VR5510_FSU_ADDR, VR5510_FS_I_WD_CFG, ®) != 0) + return -3; + return ((reg & VR5510_ERR_CNT_MASK) != 0) ? -4 : 0; +} + +static int s32g2_pmic_init(uint32_t i2c_clk_hz) +{ + uint16_t reg; + int ret; + + /* I2C4 pads (PC01 SDA, PC02 SCL): open drain, both directions. */ + wr32(S32G2_SIUL2_0_MSCR_BASE + 4 * S32G2_I2C4_SDA_MSCR, S32G2_I2C4_SDA_MSCR_VAL); + wr32(S32G2_SIUL2_0_MSCR_BASE + 4 * S32G2_I2C4_SCL_MSCR, S32G2_I2C4_SCL_MSCR_VAL); + wr32(S32G2_SIUL2_1_IMCR_BASE + 4 * (S32G2_I2C4_SDA_IMCR - S32G2_SIUL2_1_IMCR_START), + S32G2_I2C4_IMCR_VAL); + wr32(S32G2_SIUL2_1_IMCR_BASE + 4 * (S32G2_I2C4_SCL_IMCR - S32G2_SIUL2_1_IMCR_START), + S32G2_I2C4_IMCR_VAL); + i2c_init(S32G2_I2C4_BASE, i2c_clk_hz); + + ret = vr5510_read(S32G2_VR5510_FSU_ADDR, VR5510_FS_STATES, ®); + if (ret != 0) { + wolfBoot_printf("s32g2: VR5510 FS_STATES read failed (%d)\n", ret); + return -1; + } + wolfBoot_printf("s32g2: VR5510 FS state 0x%02x\n", reg & VR5510_STATE_MASK); + if ((reg & VR5510_STATE_MASK) != VR5510_STATE_INIT_FS) + return 0; /* an earlier stage already took it out of INIT_FS */ + + /* Watchdog window off, written with its complement as the FSU requires. */ + if (vr5510_read(S32G2_VR5510_FSU_ADDR, VR5510_FS_WD_WINDOW, ®) != 0) + return -2; + reg &= (uint16_t)~VR5510_WD_WINDOW_MASK; + if (vr5510_write(S32G2_VR5510_FSU_ADDR, VR5510_FS_WD_WINDOW, reg) != 0 || + vr5510_write(S32G2_VR5510_FSU_ADDR, VR5510_FS_NOT_WD_WINDOW, + (uint16_t)~reg) != 0) + return -3; + + if (vr5510_read(S32G2_VR5510_FSU_ADDR, VR5510_FS_DIAG_SAFETY, ®) != 0) + return -4; + if ((reg & VR5510_ABIST1_OK) == 0) { + wolfBoot_printf("s32g2: VR5510 ABIST1 not OK (0x%04x)\n", reg); + return -5; + } + + /* FCCU monitoring off, same complement write. */ + if (vr5510_read(S32G2_VR5510_FSU_ADDR, VR5510_FS_I_SAFE_INPUTS, ®) != 0) + return -6; + reg &= (uint16_t)~VR5510_FCCU_CFG_MASK; + if (vr5510_write(S32G2_VR5510_FSU_ADDR, VR5510_FS_I_SAFE_INPUTS, reg) != 0 || + vr5510_write(S32G2_VR5510_FSU_ADDR, VR5510_FS_I_NOT_SAFE_INPUTS, + (uint16_t)~reg) != 0) + return -7; + + ret = vr5510_wd_refresh(); + if (ret != 0) { + wolfBoot_printf("s32g2: VR5510 watchdog refresh failed (%d)\n", ret); + return -8; + } + return 0; +} + +/* What the OS expects an EL3 firmware to have done for a four-core A53 + * system: the Ncore interconnect and the MSCM interrupt router, the GICv3 + * secure-side setup on each core, and the three secondary cores released + * into a spin table in SRAM for the kernel's spin-table enable-method. */ +#define SMP_POLL_MAX 2000000U + +/* Ncore coherent interconnect: directory snoop filters online, then the + * cluster's CAIU admitted to snooping and DVM. */ +#define NCORE_CSR (S32G2_NCORE_BASE + 0xFF000) +#define NCORE_CSADSER (NCORE_CSR + 0x40) +#define NCORE_CSUIDR (NCORE_CSR + 0xFF8) +#define NCORE_CSIDR (NCORE_CSR + 0xFFC) +#define NCORE_DIRU(n) (S32G2_NCORE_BASE + 0x80000 + (n) * 0x1000) +#define NCORE_DIRUSFER(n) (NCORE_DIRU(n) + 0x10) +#define NCORE_DIRUCASER(n) (NCORE_DIRU(n) + 0x40) +#define NCORE_DIRUSFMCR(n) (NCORE_DIRU(n) + 0x80) +#define NCORE_DIRUSFMAR(n) (NCORE_DIRU(n) + 0x84) +#define NCORE_CAIUIDR(n) (S32G2_NCORE_BASE + (n) * 0x1000 + 0xFFC) +#define CSUIDR_NUMDIRUS(v) (((v) >> 16) & 0x3F) +#define CSIDR_NUMSFS(v) (((v) >> 18) & 0x1F) +#define CAIUIDR_TYPE_MASK (0xFU << 16) +#define CAIUIDR_TYPE_ACE_DVM (0x0U << 16) + +static void ncore_caiu_online(uint32_t caiu) +{ + uint32_t numdirus = CSUIDR_NUMDIRUS(rd32(NCORE_CSUIDR)); + uint32_t diru; + + for (diru = 0; diru < numdirus; diru++) + wr32(NCORE_DIRUCASER(diru), rd32(NCORE_DIRUCASER(diru)) | (1U << caiu)); + if ((rd32(NCORE_CAIUIDR(caiu)) & CAIUIDR_TYPE_MASK) == CAIUIDR_TYPE_ACE_DVM) + wr32(NCORE_CSADSER, rd32(NCORE_CSADSER) | (1U << caiu)); +} + +static int s32g2_ncore_init(void) +{ + uint32_t numdirus = CSUIDR_NUMDIRUS(rd32(NCORE_CSUIDR)); + uint32_t numsfs = CSIDR_NUMSFS(rd32(NCORE_CSIDR)) + 1; + uint32_t diru, sf, n; + + /* The ROM isolated cluster 0 from the interconnect; drop that first. */ + wr32(S32G2_NCORE_CAIUTC, rd32(S32G2_NCORE_CAIUTC) & ~S32G2_NCORE_CAIUTC_ISOLEN); + for (diru = 0; diru < numdirus; diru++) { + for (sf = 0; sf < numsfs; sf++) { + wr32(NCORE_DIRUSFMCR(diru), sf << 16); /* initialise all entries */ + for (n = 0; n < SMP_POLL_MAX; n++) { + if ((rd32(NCORE_DIRUSFMAR(diru)) & 1U) == 0) + break; + } + if (n == SMP_POLL_MAX) + return -1; + wr32(NCORE_DIRUSFER(diru), rd32(NCORE_DIRUSFER(diru)) | (1U << sf)); + } + } + /* Cluster 1's port joins when its cores are released: a port admitted + * to DVM with its cores in reset never answers a DVM sync, and the DSB + * after a TLB invalidate then hangs. */ + ncore_caiu_online(0); + return 0; +} + +/* MSCM interrupt router: every shared peripheral interrupt to all four cores. */ +static void s32g2_mscm_init(void) +{ + uint32_t i; + + for (i = 0; i < S32G2_MSCM_IRSPRC_NUM; i++) + wr16(S32G2_MSCM_BASE + S32G2_MSCM_IRSPRC + i * 2, 0xF); +} + +/* GICv3 distributor: affinity routing on, every SPI in Group 1 non-secure. */ +static void s32g2_gic_init_dist(void) +{ + uintptr_t d = S32G2_GICD_BASE; + uint32_t lines = rd32(d + GICD_TYPER) & 0x1F; + uint32_t i; + + wr32(d + GICD_CTLR, GICD_CTLR_ARE_NS | GICD_CTLR_ARE_S | GICD_CTLR_ENGRP1S | + GICD_CTLR_ENGRP1NS | GICD_CTLR_ENGRP0); + for (i = 1; i <= lines; i++) { + wr32(d + GICD_IGROUPR + i * 4, 0xFFFFFFFFU); + wr32(d + GICD_IGROUPMODR + i * 4, 0); + } +} + +/* GICv3 redistributor and CPU interface of the calling core, at EL3. */ +static void s32g2_gic_init_cpu(void) +{ + uintptr_t r = S32G2_GICR_BASE; + uint64_t mpidr, aff; + uint32_t n; + + __asm__ volatile("mrs %0, mpidr_el1" : "=r"(mpidr)); + aff = ((mpidr >> 32) & 0xFF) << 24 | (mpidr & 0x00FFFFFF); + for (n = 0; n < 8; n++, r += S32G2_GICR_STRIDE) { + if ((rd64(r + GICR_TYPER) >> 32) == aff) + break; + } + if (n == 8) + return; + wr32(r + GICR_WAKER, rd32(r + GICR_WAKER) & ~GICR_WAKER_PROCESSORSLEEP); + __asm__ volatile("dsb st; isb"); + for (n = 0; n < SMP_POLL_MAX; n++) { + if ((rd32(r + GICR_WAKER) & GICR_WAKER_CHILDRENASLEEP) == 0) + break; + } + wr32(r + GICR_SGI_BASE + GICR_IGROUPR0, 0xFFFFFFFFU); + wr32(r + GICR_SGI_BASE + GICR_IGROUPMODR0, 0); + wr32(r + GICR_SGI_BASE + GICR_ISENABLER0, 1); + + __asm__ volatile( + "mrs x0, icc_sre_el3\n" + "orr x0, x0, #0xf\n" + "msr icc_sre_el3, x0\n" + "isb\n" + "mov x0, #0x3\n" + "msr icc_igrpen1_el3, x0\n" + "isb\n" + "msr icc_ctlr_el3, xzr\n" + "isb\n" + "mrs x0, icc_sre_el2\n" + "orr x0, x0, #0xf\n" + "msr icc_sre_el2, x0\n" + "isb\n" + "msr icc_ctlr_el1, xzr\n" + "isb\n" + "mov x0, #0x80\n" + "msr icc_pmr_el1, x0\n" + "isb\n" + ::: "x0", "memory"); +} + +/* A53 coherency: CPUECTLR.SMPEN, required before the caches are used. */ +static void s32g2_smpen(void) +{ + __asm__ volatile( + "mrs x0, S3_1_C15_C2_1\n" + "orr x0, x0, #(1 << 6)\n" + "msr S3_1_C15_C2_1, x0\n" + "isb\n" + ::: "x0"); +} + +/* Called by each secondary core from the startup code, on its own SRAM + * stack, before it drops to EL2 and parks on its spin-table slot. */ +void s32g2_secondary_main(uint32_t id); +void s32g2_secondary_main(uint32_t id) +{ + uintptr_t slot = S32G2_SPIN_TABLE_BASE + id * S32G2_SPIN_TABLE_STRIDE; + + __asm__ volatile("msr cntfrq_el0, %0" :: "r"((uint64_t)S32G2_CNTFRQ_HZ)); + s32g2_smpen(); + s32g2_gic_init_cpu(); + *(volatile uint64_t*)(slot + S32G2_SPIN_TABLE_STATUS) = 1; + __asm__ volatile("dsb sy"); +} + +/* MC_ME core enable and MC_RGM reset release for the secondaries. The core + * start address is this image's entry; the startup code sends secondaries + * to s32g2_secondary_entry. Cluster 1 (cores 2, 3) first needs its CAIU. */ +#define MC_ME_PRTN_CORE(n, m) (S32G2_MC_ME_BASE + 0x140 + (n) * 0x200 + (m) * 0x20) +#define MC_ME_CORE_PCONF 0x0 +#define MC_ME_CORE_PUPD 0x4 +#define MC_ME_CORE_STAT 0x8 +#define MC_ME_CORE_ADDR 0xC +#define MC_ME_CORE_CCE 1U +#define MC_ME_CORE_CCUPD 1U +#define MC_ME_CORE_CCS 1U +#define MC_ME_A53_PART 1 +#define MC_RGM_PRST(g) (S32G2_MC_RGM_BASE + 0x40 + (g) * 8) +#define MC_RGM_PSTAT(g) (S32G2_MC_RGM_BASE + 0x140 + (g) * 8) +#define MC_RGM_A53_GROUP 1 + +static void mc_me_apply(void) +{ + wr32(S32G2_MC_ME_BASE + MC_ME_CTL_KEY, MC_ME_CTL_KEY_KEY); + wr32(S32G2_MC_ME_BASE + MC_ME_CTL_KEY, MC_ME_CTL_KEY_INVKEY); +} + +static int s32g2_core_release(uint32_t core, uintptr_t entry) +{ + uintptr_t cl = MC_ME_PRTN_CORE(MC_ME_A53_PART, core & ~1U); + uint32_t bit = 1U << (core + 1); + uint32_t n; + + wr32(MC_ME_PRTN_CORE(MC_ME_A53_PART, core) + MC_ME_CORE_ADDR, + (uint32_t)entry & 0xFFFFFFFCU); + /* Clock enable is per cluster, defined on its first core's registers. */ + if ((rd32(cl + MC_ME_CORE_STAT) & MC_ME_CORE_CCS) == 0) { + wr32(cl + MC_ME_CORE_PCONF, MC_ME_CORE_CCE); + wr32(cl + MC_ME_CORE_PUPD, MC_ME_CORE_CCUPD); + mc_me_apply(); + for (n = 0; n < SMP_POLL_MAX; n++) { + if ((rd32(cl + MC_ME_CORE_STAT) & MC_ME_CORE_CCS) != 0) + break; + } + if (n == SMP_POLL_MAX) + return -1; + } + wr32(MC_RGM_PRST(MC_RGM_A53_GROUP), rd32(MC_RGM_PRST(MC_RGM_A53_GROUP)) & ~bit); + for (n = 0; n < SMP_POLL_MAX; n++) { + if ((rd32(MC_RGM_PSTAT(MC_RGM_A53_GROUP)) & bit) == 0) + return 0; + } + return -2; +} + +static int s32g2_smp_release(uintptr_t entry) +{ + uint32_t core, n; + int ret; + + /* Clear the spin table: entry 0 means "wait" to the parked cores. */ + for (core = 0; core < S32G2_NUM_CORES; core++) { + uintptr_t slot = (uintptr_t)S32G2_SPIN_TABLE_BASE + core * S32G2_SPIN_TABLE_STRIDE; + *(volatile uint64_t*)slot = 0; + *(volatile uint64_t*)(slot + S32G2_SPIN_TABLE_STATUS) = 0; + /* The parked cores run with their caches off. */ + __asm__ volatile("dc civac, %0" :: "r"(slot) : "memory"); + } + __asm__ volatile("dsb sy"); + + wr32(S32G2_MC_ME_BASE + MC_ME_PRTN_PCONF(MC_ME_A53_PART), 1U); + wr32(S32G2_MC_ME_BASE + MC_ME_PRTN_PUPD(MC_ME_A53_PART), 1U); + mc_me_apply(); + + for (core = 1; core < S32G2_NUM_CORES; core++) { + if (core == 2) + ncore_caiu_online(1); + ret = s32g2_core_release(core, entry); + if (ret != 0) { + wolfBoot_printf("s32g2: core %u release failed (%d)\n", core, ret); + return ret; + } + for (n = 0; n < SMP_POLL_MAX; n++) { + uintptr_t st = (uintptr_t)S32G2_SPIN_TABLE_BASE + + core * S32G2_SPIN_TABLE_STRIDE + S32G2_SPIN_TABLE_STATUS; + __asm__ volatile("dc ivac, %0; dsb sy" :: "r"(st) : "memory"); + if (*(volatile uint64_t*)st != 0) + break; + } + wolfBoot_printf("s32g2: core %u %s\n", core, + (n == SMP_POLL_MAX) ? "did not check in" : "parked"); + } + return 0; +} + +/* QuadSPI controller driving the GoldBox's 64 MB Macronix MX25UW51245G NOR + * in plain SPI mode (one data line, SDR, 4-byte addresses). Single commands + * are IP commands described by a LUT sequence; bulk reads go through the + * memory-mapped AHB window. */ +#ifdef S32G2_QSPI + +#define QSPI_MCR 0x000 +#define QSPI_MCR_SWRSTSD (1U << 0) +#define QSPI_MCR_SWRSTHD (1U << 1) +#define QSPI_MCR_END_CFG (3U << 2) +#define QSPI_MCR_DQS_EN (1U << 6) +#define QSPI_MCR_CLR_RXF (1U << 10) +#define QSPI_MCR_CLR_TXF (1U << 11) +#define QSPI_MCR_MDIS (1U << 14) +#define QSPI_MCR_RESERVED (0xFU << 16) +#define QSPI_MCR_DQS_INT_PAD (1U << 24) +#define QSPI_IPCR 0x008 +#define QSPI_IPCR_SEQID(s) ((uint32_t)(s) << 24) +#define QSPI_FLSHCR 0x00C +#define QSPI_FLSHCR_TCSS(n) ((uint32_t)(n) << 0) +#define QSPI_FLSHCR_TCSH(n) ((uint32_t)(n) << 8) +#define QSPI_BUF3CR 0x01C +#define QSPI_BUF3CR_ALLMST (1U << 31) +#define QSPI_BUF3CR_ADATSZ(n) ((uint32_t)(n) << 8) +#define QSPI_BFGENCR 0x020 +#define QSPI_BFGENCR_SEQID(s) ((uint32_t)(s) << 12) +#define QSPI_BUF0IND 0x030 +#define QSPI_BUF1IND 0x034 +#define QSPI_BUF2IND 0x038 +#define QSPI_DLLCRA 0x060 +#define QSPI_DLLCR_SLV_UPD_EN (1U << 0) +#define QSPI_DLLCR_SLV_BYPASS_EN (1U << 1) +#define QSPI_DLLCR_SLV_EN (1U << 2) +#define QSPI_SFAR 0x100 +#define QSPI_SFACR 0x104 +#define QSPI_SMPR 0x108 +#define QSPI_SMPR_FSPHS (1U << 5) +#define QSPI_RBCT 0x110 +#define QSPI_RBCT_WMRK_MASK 0x1FU +#define QSPI_RBCT_RXBRD_USEIPS (1U << 8) +#define QSPI_DLLSR 0x12C +#define QSPI_DLLSR_SLVA_LOCK (1U << 14) +#define QSPI_TBSR 0x150 +#define QSPI_TBSR_TRCTR(v) ((v) >> 16) +#define QSPI_TBSR_TRBFL(v) ((v) & 0xFFU) +#define QSPI_TBDR 0x154 +#define QSPI_SR 0x15C +#define QSPI_SR_BUSY (1U << 0) +#define QSPI_SR_IP_ACC (1U << 1) +#define QSPI_SR_AHB_ACC (1U << 2) +#define QSPI_SPTRCLR 0x16C +#define QSPI_SPTRCLR_BFPTRC (1U << 0) +#define QSPI_SPTRCLR_IPPTRC (1U << 8) +#define QSPI_SFA1AD 0x180 +#define QSPI_SFA2AD 0x184 +#define QSPI_SFB1AD 0x188 +#define QSPI_SFB2AD 0x18C +#define QSPI_RBDR(n) (0x200 + 4 * (n)) +#define QSPI_LUTKEY 0x300 +#define QSPI_LUTKEY_VALUE 0x5AF05AF0U +#define QSPI_LCKCR 0x304 +#define QSPI_LCKCR_LOCK 1U +#define QSPI_LCKCR_UNLOCK 2U +#define QSPI_LUT_BASE 0x310 +#define QSPI_LUT_WORDS 5 /* words per sequence on S32 */ +#define QSPI_LUT(seq, i) (QSPI_LUT_BASE + ((seq) * QSPI_LUT_WORDS + (i)) * 4) +#define QSPI_SEQ_IP 15 +#define QSPI_SEQ_AHB 14 + +#define LUT_STOP 0 +#define LUT_CMD 1 +#define LUT_ADDR 2 +#define LUT_DUMMY 3 +#define LUT_READ 7 +#define LUT_WRITE 8 +#define LUT_CMD_DDR 17 +/* instruction[15:10] | pad[9:8] (0 = one line, 3 = eight) | operand[7:0] */ +#define LUT_ENTRY(ins, opr) ((uint16_t)(((ins) << 10) | ((opr) & 0xFF))) +#define LUT_ENTRY_OCTAL(ins, opr) ((uint16_t)(((ins) << 10) | (3 << 8) | ((opr) & 0xFF))) +#define QSPI_MCR_DDR_EN (1U << 7) +#define QSPI_FLSHCR_TDH(n) ((uint32_t)(n) << 16) + +/* The RX buffer holds 128 bytes; one IP command moves at most that much. */ +#define QSPI_XFER_MAX 128U +#define QSPI_RX_WORDS (QSPI_XFER_MAX / 4) + +#define FLASH_ID_MX25UW51245G { 0xC2, 0x81, 0x3A } +#define FLASH_CMD_RSTEN 0x66 +#define FLASH_CMD_RST 0x99 +#define FLASH_CMD_RDID 0x9F +#define FLASH_CMD_RDSR 0x05 +#define FLASH_CMD_WREN 0x06 +#define FLASH_CMD_READ4B 0x0C /* fast read, 8 dummy cycles */ +#define FLASH_CMD_PP4B 0x12 +#define FLASH_CMD_SE4B 0x21 /* 4 KB sector */ +#define FLASH_SR_WIP 0x01 +#define FLASH_PAGE_SIZE 256U +#define FLASH_SECTOR_SIZE 4096U + +#define QSPI_POLL_MAX 2000000U +#define FLASH_WIP_POLL_MAX 200000U /* sector erase takes up to ~400 ms */ + +static int s32g2_qspi_read_id(uint8_t id[3]); +static void qspi_ahb_setup(void); + +static int qspi_wait_idle(void) +{ + uintptr_t b = S32G2_QSPI_BASE; + uint32_t spin; + + for (spin = 0; spin < QSPI_POLL_MAX; spin++) { + if ((rd32(b + QSPI_SR) & (QSPI_SR_BUSY | QSPI_SR_IP_ACC | QSPI_SR_AHB_ACC)) == 0) + return 0; + } + return -1; +} + +static void qspi_lut_program(uint32_t seq, const uint16_t* e, uint32_t n) +{ + uintptr_t b = S32G2_QSPI_BASE; + uint32_t i, w; + + wr32(b + QSPI_LUTKEY, QSPI_LUTKEY_VALUE); + wr32(b + QSPI_LCKCR, QSPI_LCKCR_UNLOCK); + for (i = 0; i < QSPI_LUT_WORDS; i++) { + w = 0; + if (2 * i < n) + w |= e[2 * i]; + if (2 * i + 1 < n) + w |= (uint32_t)e[2 * i + 1] << 16; + wr32(b + QSPI_LUT(seq, i), w); + } + wr32(b + QSPI_LUTKEY, QSPI_LUTKEY_VALUE); + wr32(b + QSPI_LCKCR, QSPI_LCKCR_LOCK); +} + +/* One IP command: opcode, optional 32-bit address, dummy cycles, then up to + * QSPI_XFER_MAX bytes out of tx or into rx. */ +static int qspi_op(uint8_t cmd, int has_addr, uint32_t addr, uint8_t dummy, + const uint8_t* tx, uint8_t* rx, uint32_t len) +{ + uintptr_t b = S32G2_QSPI_BASE; + uint16_t e[6]; + uint32_t n = 0, i, words, v, spin; + + if (len > QSPI_XFER_MAX) + return -1; + e[n++] = LUT_ENTRY(LUT_CMD, cmd); + if (has_addr) + e[n++] = LUT_ENTRY(LUT_ADDR, 0x20); /* 32 address bits */ + if (dummy != 0) + e[n++] = LUT_ENTRY(LUT_DUMMY, dummy); + if (len != 0) + e[n++] = LUT_ENTRY(rx != NULL ? LUT_READ : LUT_WRITE, len); + e[n++] = LUT_ENTRY(LUT_STOP, 0); + + if (qspi_wait_idle() != 0) + return -1; + wr32(b + QSPI_SFAR, addr); + wr32(b + QSPI_MCR, rd32(b + QSPI_MCR) | QSPI_MCR_CLR_RXF | QSPI_MCR_CLR_TXF); + wr32(b + QSPI_SPTRCLR, QSPI_SPTRCLR_BFPTRC | QSPI_SPTRCLR_IPPTRC); + qspi_lut_program(QSPI_SEQ_IP, e, n); + wr32(b + QSPI_RBCT, QSPI_RBCT_WMRK_MASK | QSPI_RBCT_RXBRD_USEIPS); + + words = (len + 3) / 4; + if (tx != NULL && len != 0) { + for (spin = 0; spin < QSPI_POLL_MAX; spin++) { + if (QSPI_TBSR_TRBFL(rd32(b + QSPI_TBSR)) == 0) + break; + } + if (spin == QSPI_POLL_MAX) + return -1; + for (i = 0; i < words; i++) { + v = 0; + memcpy(&v, tx + 4 * i, (len - 4 * i < 4) ? (len - 4 * i) : 4); + wr32(b + QSPI_TBDR, v); + } + } + wr32(b + QSPI_IPCR, len | QSPI_IPCR_SEQID(QSPI_SEQ_IP)); + if (tx != NULL && len != 0) { + for (spin = 0; spin < QSPI_POLL_MAX; spin++) { + v = rd32(b + QSPI_TBSR); + if (QSPI_TBSR_TRCTR(v) == words && QSPI_TBSR_TRBFL(v) == 0) + break; + } + if (spin == QSPI_POLL_MAX) + return -1; + } + if (qspi_wait_idle() != 0) + return -1; + if (rx != NULL && len != 0) { + for (i = 0; i < words; i++) { + v = rd32(b + QSPI_RBDR(i)); + memcpy(rx + 4 * i, &v, (len - 4 * i < 4) ? (len - 4 * i) : 4); + } + } + return 0; +} + +/* Reset both domains so a later AHB access does not see stale buffers. */ +static void qspi_invalidate(void) +{ + uintptr_t b = S32G2_QSPI_BASE; + uint32_t mcr = rd32(b + QSPI_MCR) & ~QSPI_MCR_MDIS; + + wr32(b + QSPI_MCR, mcr); + wr32(b + QSPI_MCR, mcr | QSPI_MCR_SWRSTHD | QSPI_MCR_SWRSTSD); + wr32(b + QSPI_MCR, mcr | QSPI_MCR_SWRSTHD | QSPI_MCR_SWRSTSD | QSPI_MCR_MDIS); + wr32(b + QSPI_MCR, mcr | QSPI_MCR_MDIS); + wr32(b + QSPI_MCR, mcr); +} + +/* The DLL slave in bypass mode, as the SDR configuration needs it. */ +static int qspi_dll_bypass(void) +{ + uintptr_t b = S32G2_QSPI_BASE; + uint32_t v = QSPI_DLLCR_SLV_EN | QSPI_DLLCR_SLV_BYPASS_EN; + uint32_t spin; + + wr32(b + QSPI_DLLCRA, v); + wr32(b + QSPI_DLLCRA, v | QSPI_DLLCR_SLV_UPD_EN); + for (spin = 0; spin < QSPI_POLL_MAX; spin++) { + if ((rd32(b + QSPI_DLLSR) & QSPI_DLLSR_SLVA_LOCK) != 0) + break; + } + wr32(b + QSPI_DLLCRA, v); + return (spin == QSPI_POLL_MAX) ? -1 : 0; +} + +/* A Boot ROM that loaded this image from the NOR with the stock parameters + * left the flash in octal DTR mode (volatile CR2). Software reset through + * two 8-line DDR commands puts it back into SPI mode. */ +static int qspi_octal_reset(void) +{ + uintptr_t b = S32G2_QSPI_BASE; + uint32_t mcr = rd32(b + QSPI_MCR); + uint16_t e[3]; + volatile uint32_t spin; + int ret; + + wr32(b + QSPI_FLSHCR, QSPI_FLSHCR_TCSS(3) | QSPI_FLSHCR_TCSH(3) | QSPI_FLSHCR_TDH(1)); + wr32(b + QSPI_MCR, mcr | QSPI_MCR_DDR_EN); + /* Macronix OPI commands are the opcode followed by its complement. */ + e[0] = LUT_ENTRY_OCTAL(LUT_CMD_DDR, FLASH_CMD_RSTEN); + e[1] = LUT_ENTRY_OCTAL(LUT_CMD_DDR, (uint8_t)~FLASH_CMD_RSTEN); + e[2] = LUT_ENTRY(LUT_STOP, 0); + ret = qspi_wait_idle(); + if (ret == 0) { + wr32(b + QSPI_SFAR, 0); + wr32(b + QSPI_SPTRCLR, QSPI_SPTRCLR_BFPTRC | QSPI_SPTRCLR_IPPTRC); + qspi_lut_program(QSPI_SEQ_IP, e, 3); + wr32(b + QSPI_IPCR, QSPI_IPCR_SEQID(QSPI_SEQ_IP)); + ret = qspi_wait_idle(); + } + if (ret == 0) { + e[0] = LUT_ENTRY_OCTAL(LUT_CMD_DDR, FLASH_CMD_RST); + e[1] = LUT_ENTRY_OCTAL(LUT_CMD_DDR, (uint8_t)~FLASH_CMD_RST); + wr32(b + QSPI_SPTRCLR, QSPI_SPTRCLR_BFPTRC | QSPI_SPTRCLR_IPPTRC); + qspi_lut_program(QSPI_SEQ_IP, e, 3); + wr32(b + QSPI_IPCR, QSPI_IPCR_SEQID(QSPI_SEQ_IP)); + ret = qspi_wait_idle(); + } + wr32(b + QSPI_MCR, mcr); + wr32(b + QSPI_FLSHCR, QSPI_FLSHCR_TCSS(3) | QSPI_FLSHCR_TCSH(3)); + /* tREADY after a software reset is well under a millisecond. */ + for (spin = 0; spin < 2000000U; spin++) + ; + qspi_invalidate(); + return ret; +} + +static int s32g2_qspi_init(void) +{ + uintptr_t b = S32G2_QSPI_BASE; + volatile uint32_t spin; + static const uint8_t expect[3] = FLASH_ID_MX25UW51245G; + uint8_t id[3] = { 0, 0, 0 }; + int ret; + + wr32(b + QSPI_MCR, QSPI_MCR_SWRSTSD | QSPI_MCR_SWRSTHD); + for (spin = 0; spin < 1000; spin++) + ; + wr32(b + QSPI_MCR, QSPI_MCR_MDIS | QSPI_MCR_RESERVED); + /* SDR timing: no data hold, three cycles of chip-select setup/hold. */ + wr32(b + QSPI_FLSHCR, QSPI_FLSHCR_TCSS(3) | QSPI_FLSHCR_TCSH(3)); + wr32(b + QSPI_SMPR, QSPI_SMPR_FSPHS); + wr32(b + QSPI_BUF0IND, 0); + wr32(b + QSPI_BUF1IND, 0); + wr32(b + QSPI_BUF2IND, 0); + wr32(b + QSPI_BFGENCR, QSPI_BFGENCR_SEQID(QSPI_SEQ_IP)); + wr32(b + QSPI_RBCT, QSPI_RBCT_WMRK_MASK); + wr32(b + QSPI_BUF3CR, QSPI_BUF3CR_ALLMST | QSPI_BUF3CR_ADATSZ(1024 / 8)); + /* One device on bus A, chip select 0. */ + wr32(b + QSPI_SFA1AD, S32G2_QSPI_FLASH_SIZE); + wr32(b + QSPI_SFA2AD, S32G2_QSPI_FLASH_SIZE); + wr32(b + QSPI_SFB1AD, S32G2_QSPI_FLASH_SIZE); + wr32(b + QSPI_SFB2AD, S32G2_QSPI_FLASH_SIZE); + wr32(b + QSPI_SFACR, 0); + wr32(b + QSPI_MCR, QSPI_MCR_RESERVED | QSPI_MCR_END_CFG | + QSPI_MCR_DQS_INT_PAD | QSPI_MCR_DQS_EN); + ret = qspi_dll_bypass(); + if (ret != 0) + return ret; + qspi_ahb_setup(); + /* The flash answers RDID only in SPI mode; otherwise it was left in + * octal DTR by the Boot ROM, so reset it and ask again. */ + if (s32g2_qspi_read_id(id) == 0 && memcmp(id, expect, 3) == 0) + return 0; + wolfBoot_printf("qspi: id %02x %02x %02x, resetting the flash out of octal mode\n", + id[0], id[1], id[2]); + if (qspi_octal_reset() != 0) + return -2; + if (s32g2_qspi_read_id(id) != 0 || memcmp(id, expect, 3) != 0) + return -3; + return 0; +} + +static int s32g2_qspi_read_id(uint8_t id[3]) +{ + return qspi_op(FLASH_CMD_RDID, 0, 0, 0, NULL, id, 3); +} + +/* Memory-mapped reads through the AHB window: the controller fetches with + * the sequence in BFGENCR into its 1 KB buffer. Only whole words, as the + * window is Device memory while the MMU is off. */ +static void qspi_ahb_setup(void) +{ + uint16_t e[5]; + + e[0] = LUT_ENTRY(LUT_CMD, FLASH_CMD_READ4B); + e[1] = LUT_ENTRY(LUT_ADDR, 0x20); + e[2] = LUT_ENTRY(LUT_DUMMY, 8); + e[3] = LUT_ENTRY(LUT_READ, QSPI_XFER_MAX); + e[4] = LUT_ENTRY(LUT_STOP, 0); + qspi_lut_program(QSPI_SEQ_AHB, e, 5); + wr32(S32G2_QSPI_BASE + QSPI_BFGENCR, QSPI_BFGENCR_SEQID(QSPI_SEQ_AHB)); +} + +__attribute__((optimize("no-delete-null-pointer-checks"))) +static int qspi_ahb_read(uint32_t addr, uint8_t* buf, uint32_t len) +{ + const volatile uint64_t* src = (const volatile uint64_t*)(uintptr_t)(S32G2_QSPI_AHB_BASE + addr); + uint64_t* dst = (uint64_t*)buf; + uint32_t i, n = len / 8; + + if (qspi_wait_idle() != 0) + return -1; + for (i = 0; i < n; i++) + dst[i] = src[i]; + return 0; +} + +static int s32g2_qspi_read(uint32_t addr, uint8_t* buf, uint32_t len) +{ + uint32_t n; + + /* Bulk through the window when source and destination are 8-byte + * aligned; the IP path takes the head and the tail. */ + if (len >= 1024 && (addr & 7) == 0 && (((uintptr_t)buf) & 7) == 0) { + n = len & ~7U; + if (qspi_ahb_read(addr, buf, n) != 0) + return -1; + addr += n; + buf += n; + len -= n; + } + while (len > 0) { + n = (len > QSPI_XFER_MAX) ? QSPI_XFER_MAX : len; + if (qspi_op(FLASH_CMD_READ4B, 1, addr, 8, NULL, buf, n) != 0) + return -1; + addr += n; + buf += n; + len -= n; + } + return 0; +} + +static int flash_wait_ready(void) +{ + uint8_t sr; + uint32_t spin; + + for (spin = 0; spin < FLASH_WIP_POLL_MAX; spin++) { + if (qspi_op(FLASH_CMD_RDSR, 0, 0, 0, NULL, &sr, 1) != 0) + return -1; + if ((sr & FLASH_SR_WIP) == 0) + return 0; + } + return -1; +} + +static int flash_write_enable(void) +{ + return qspi_op(FLASH_CMD_WREN, 0, 0, 0, NULL, NULL, 0); +} + +static int s32g2_qspi_write(uint32_t addr, const uint8_t* buf, uint32_t len) +{ + uint32_t n, page_left; + + while (len > 0) { + page_left = FLASH_PAGE_SIZE - (addr & (FLASH_PAGE_SIZE - 1)); + n = (len > page_left) ? page_left : len; + if (n > QSPI_XFER_MAX) + n = QSPI_XFER_MAX; + if (flash_write_enable() != 0 || + qspi_op(FLASH_CMD_PP4B, 1, addr, 0, buf, NULL, n) != 0 || + flash_wait_ready() != 0) + return -1; + addr += n; + buf += n; + len -= n; + } + qspi_invalidate(); + return 0; +} + +/* Erase whole 4 KB sectors covering [addr, addr + len). */ +static int s32g2_qspi_erase(uint32_t addr, uint32_t len) +{ + uint32_t end = addr + len; + + addr &= ~(FLASH_SECTOR_SIZE - 1); + while (addr < end) { + if (flash_write_enable() != 0 || + qspi_op(FLASH_CMD_SE4B, 1, addr, 0, NULL, NULL, 0) != 0 || + flash_wait_ready() != 0) + return -1; + addr += FLASH_SECTOR_SIZE; + } + qspi_invalidate(); + return 0; +} + +#ifdef EXT_FLASH +int ext_flash_read(uintptr_t address, uint8_t *data, int len) +{ + if (s32g2_qspi_read((uint32_t)address, data, (uint32_t)len) != 0) + return -1; + return len; +} + +int ext_flash_write(uintptr_t address, const uint8_t *data, int len) +{ + if (s32g2_qspi_write((uint32_t)address, data, (uint32_t)len) != 0) + return -1; + return len; +} + +int ext_flash_erase(uintptr_t address, int len) +{ + return s32g2_qspi_erase((uint32_t)address, (uint32_t)len); +} + +void ext_flash_lock(void) +{ +} + +void ext_flash_unlock(void) +{ +} +#endif /* EXT_FLASH */ + + +#endif /* S32G2_QSPI */ + +/* Console LINFlexD_0 */ + +#ifndef LINFLEX_TX_TIMEOUT_US +#define LINFLEX_TX_TIMEOUT_US 10000 +#endif + +#if defined(DEBUG_UART) +#define LINFLEX_INIT_POLL_MAX 1000000U +/* Program LINFlexD_0 for 8N1 FIFO mode with the given divisor. */ +static void s32g2_linflex_setup(uint32_t ibrr, uint32_t fbrr) +{ + uintptr_t b = S32G2_LINFLEX0_BASE; + uint32_t n; + + /* Enter init mode (INIT, then MME|INIT) and wait for LINS to report it. */ + wr32(b + LINFLEX_LINCR1, LINFLEX_LINCR1_INIT); + wr32(b + LINFLEX_LINCR1, LINFLEX_LINCR1_MME | LINFLEX_LINCR1_INIT); + for (n = 0; n < LINFLEX_INIT_POLL_MAX; n++) { + if ((rd32(b + LINFLEX_LINSR) & LINFLEX_LINSR_LINS_MASK) == + LINFLEX_LINSR_LINS_INIT) + break; + } + /* UART mode must be selected before the other UARTCR fields are written. */ + wr32(b + LINFLEX_UARTCR, LINFLEX_UARTCR_UART); + wr32(b + LINFLEX_LINIBRR, ibrr); + wr32(b + LINFLEX_LINFBRR, fbrr); + wr32(b + LINFLEX_UARTPTO, 0xF); + wr32(b + LINFLEX_UARTCR, LINFLEX_UARTCR_8N1_FIFO); + wr32(b + LINFLEX_LINCR1, LINFLEX_LINCR1_MME); +} + +void uart_init(void) +{ + /* The Boot ROM leaves the pads unrouted and the LIN clock on FIRC. */ + wr32(S32G2_SIUL2_0_MSCR_BASE + 4 * S32G2_UART0_TX_MSCR, + S32G2_UART0_TX_MSCR_VAL); + wr32(S32G2_SIUL2_0_MSCR_BASE + 4 * S32G2_UART0_RX_MSCR, + S32G2_UART0_RX_MSCR_VAL); + wr32(S32G2_SIUL2_0_IMCR_BASE + 4 * (S32G2_UART0_RX_IMCR - 512), + S32G2_UART0_RX_IMCR_VAL); + s32g2_linflex_setup(S32G2_LINIBRR_115200_FIRC, S32G2_LINFBRR_115200_FIRC); +} + +static void linflex_putc(char c) +{ + uintptr_t b = S32G2_LINFLEX0_BASE; + /* The generic timer is not guaranteed to count until FXOSC is up, so bound + * the wait with a plain spin instead of a timer deadline. */ + uint32_t spin = 0; + + while ((rd32(b + LINFLEX_UARTSR) & LINFLEX_UARTSR_DTF) != 0) { + if (++spin > 1000000U) + return; + } + /* Byte-wide write: in FIFO mode the access size is the byte count sent. */ + wr8(b + LINFLEX_BDRL, (uint8_t)c); +} + +void uart_write(const char* buf, unsigned int sz) +{ + unsigned int i; + + for (i = 0; i < sz; i++) { + if (buf[i] == '\n') + linflex_putc('\r'); + linflex_putc(buf[i]); + } +} +#endif /* DEBUG_UART */ + +/* Address hooks for builds without fixed partitions (the disk configs): + * DRAM staging addresses, so the update API has valid targets. */ + +void* hal_get_primary_address(void) +{ + return (void*)(uintptr_t)S32G2_PAYLOAD_ADDR; +} + +void* hal_get_update_address(void) +{ + return (void*)(uintptr_t)S32G2_UPDATE_ADDR; +} + +/* The device tree always comes from the signed FIT. */ +void* hal_get_dts_address(void) +{ + return NULL; +} + +void* hal_get_dts_update_address(void) +{ + return NULL; +} + +/* uSDHC -> SDHCI shim: hal/imx_usdhc_sdhci.c */ + + +#if (defined(DISK_SDCARD) || defined(DISK_EMMC)) && !defined(SDHCI_SDMA_DISABLED) +/* DMA cache maintenance for the SDMA path by address range, not the set/way + * walk used at handoff, which would cost more per transfer than the DMA saves. */ +static void s32g2_dcache_range(uintptr_t start, uint32_t sz, int invalidate) +{ + uintptr_t line, end; + uint64_t ctr; + unsigned int dminline; + + if (sz == 0) + return; + /* CTR_EL0.DminLine is log2 of the smallest data cache line, in words. */ + __asm__ volatile("mrs %0, ctr_el0" : "=r"(ctr)); + dminline = (unsigned int)((ctr >> 16) & 0xF); + line = (uintptr_t)4 << dminline; + + end = (start + sz + line - 1) & ~(line - 1); + start &= ~(line - 1); + __asm__ volatile("dsb sy"); + for (; start < end; start += line) { + if (invalidate) + __asm__ volatile("dc ivac, %0" :: "r"(start) : "memory"); + else + __asm__ volatile("dc civac, %0" :: "r"(start) : "memory"); + } + __asm__ volatile("dsb sy"); + __asm__ volatile("isb"); +} + +void sdhci_platform_dma_prepare(void* buf, uint32_t sz, int is_write) +{ + /* Outbound data must reach memory before the controller reads it. Clean on inbound + * too: a dirty line over the buffer could evict onto data the controller wrote. */ + s32g2_dcache_range((uintptr_t)buf, sz, 0); + (void)is_write; +} + +void sdhci_platform_dma_complete(void* buf, uint32_t sz, int is_write) +{ + /* Inbound data landed in memory behind the cache's back; drop any stale + * lines so the CPU reads what the controller wrote. */ + if (!is_write) + s32g2_dcache_range((uintptr_t)buf, sz, 1); +} +#endif /* (DISK_SDCARD || DISK_EMMC) && !SDHCI_SDMA_DISABLED */ + +/* EL3 exception report, called from s32g2_el3_fault_common + * (src/boot_aarch64_start.S); the vector halts in wfi after this. */ + +#if defined(DEBUG) && defined(DEBUG_UART) + +extern uint8_t _start_text[]; +extern uint8_t _end[]; + +void s32g2_el3_fault_handler(unsigned long esr, unsigned long elr, + unsigned long far, unsigned long vector); +void s32g2_el3_fault_handler(unsigned long esr, unsigned long elr, + unsigned long far, unsigned long vector) +{ + wolfBoot_printf("\n*** S32G2 EL3 EXCEPTION ***\n"); + wolfBoot_printf("vector=%d ESR=0x%08x EC=0x%02x ISS=0x%06x\n", (int)vector, + (uint32_t)esr, (uint32_t)((esr >> 26) & 0x3F), (uint32_t)(esr & 0x1FFFFFFUL)); + wolfBoot_printf("ELR=0x%08x%08x FAR=0x%08x%08x\n", + (uint32_t)(elr >> 32), (uint32_t)(elr & 0xFFFFFFFFUL), + (uint32_t)(far >> 32), (uint32_t)(far & 0xFFFFFFFFUL)); + if (elr >= (uintptr_t)_start_text && elr < (uintptr_t)_end) { + wolfBoot_printf("ELR is inside wolfBoot (+0x%x)\n", + (uint32_t)(elr - (uintptr_t)_start_text)); + } +} +#endif /* DEBUG && DEBUG_UART */ + +/* Device tree fixups on the FIT's DTB: memory, the spin-table release + * addresses, the kernel command line and the initrd range. */ + +#if defined(MMU) && defined(WOLFBOOT_FDT) + +/* Console LINFlexD_0 (ttyLF0); root is the stock card's second partition. */ +#ifndef LINUX_BOOTARGS +#ifndef LINUX_BOOTARGS_ROOT +#define LINUX_BOOTARGS_ROOT "/dev/mmcblk0p2" +#endif +#define LINUX_BOOTARGS \ + "console=ttyLF0,115200 earlycon root=" LINUX_BOOTARGS_ROOT \ + " rootwait rw" +#endif + +void* hal_get_boot_dts(void) +{ + return NULL; +} + +/* Describe the two DDR banks and point each cpu node's spin-table release + * address at its slot in SRAM. */ +static int s32g2_dts_fixup_cpus_memory(fdt_ctx* ctx) +{ + uint64_t reg[4]; + uint64_t release; + const uint32_t* cpureg; + uint32_t id; + int off, cpus, len, ret; + + off = fdt_find_devtype(ctx, 0, "memory"); + if (off < 0) { + off = fdt_add_subnode(ctx, 0, "memory@80000000"); + if (off < 0) { + wolfBoot_printf("FDT: cannot add /memory (%d)\n", off); + return off; + } + ret = fdt_setprop(ctx, off, "device_type", "memory", 7); + if (ret != 0) + return ret; + } + reg[0] = cpu_to_fdt64((uint64_t)S32G2_DRAM_BASE); + reg[1] = cpu_to_fdt64((uint64_t)S32G2_DRAM_BANK0_SIZE); + reg[2] = cpu_to_fdt64((uint64_t)S32G2_DRAM_BANK1_BASE); + reg[3] = cpu_to_fdt64((uint64_t)S32G2_DRAM_BANK1_SIZE); + ret = fdt_setprop(ctx, off, "reg", reg, (int)sizeof(reg)); + if (ret != 0) { + wolfBoot_printf("FDT: memory fixup failed (%d)\n", ret); + return ret; + } + + cpus = fdt_path_offset(ctx, "/cpus"); + if (cpus < 0) { + wolfBoot_printf("FDT: no /cpus (%d)\n", cpus); + return cpus; + } + off = fdt_find_devtype(ctx, cpus, "cpu"); + while (off >= 0) { + cpureg = (const uint32_t*)fdt_getprop(ctx, off, "reg", &len); + if (cpureg == NULL || len < 4) + return -1; + /* reg is the MPIDR affinity: aff1 at bits 8-15, aff0 at 0-1 */ + id = fdt32_to_cpu(cpureg[(len / 4) - 1]); + id = ((id >> 8) & 0xFF) * 2 + (id & 0x3); + if (id >= S32G2_NUM_CORES) + return -1; + release = cpu_to_fdt64((uint64_t)S32G2_SPIN_TABLE_BASE + + id * S32G2_SPIN_TABLE_STRIDE); + ret = fdt_setprop(ctx, off, "enable-method", "spin-table", 11); + if (ret == 0) + ret = fdt_setprop(ctx, off, "cpu-release-addr", &release, + (int)sizeof(release)); + if (ret != 0) { + wolfBoot_printf("FDT: cpu %u fixup failed (%d)\n", id, ret); + return ret; + } + off = fdt_find_devtype(ctx, off, "cpu"); + } + wolfBoot_printf("FDT: memory and spin-table set\n"); + return 0; +} + +int hal_dts_fixup(void* dts_addr, uint32_t capacity) +{ + fdt_ctx ctx; + int off; + int ret; + + ret = fdt_open(&ctx, dts_addr, capacity); + if (ret != 0) { + wolfBoot_printf("FDT: invalid header (%d)\n", ret); + return ret; + } + ret = fdt_grow(&ctx, WOLFBOOT_FDT_FIXUP_HEADROOM); + if (ret != 0) { + wolfBoot_printf("FDT: no headroom for fixups (%d)\n", ret); + return ret; + } + /* Before the /chosen edits, since inserting nodes moves the offsets + * after them. */ + ret = s32g2_dts_fixup_cpus_memory(&ctx); + if (ret != 0) + return ret; + off = fdt_subnode_offset(&ctx, 0, "chosen"); + if (off == -FDT_ERR_NOTFOUND) { + off = fdt_add_subnode(&ctx, 0, "chosen"); + } + if (off < 0) { + wolfBoot_printf("FDT: /chosen error (%d)\n", off); + return off; + } + ret = fdt_fixup_str(&ctx, off, "chosen", "bootargs", LINUX_BOOTARGS); + if (ret != 0) { + wolfBoot_printf("FDT: bootargs fixup failed (%d)\n", ret); + return ret; + } + /* The stock DTB carries linux,initrd-{start,end} and this path attaches no + * authenticated ramdisk: a stale range would point the signed kernel at + * unverified memory, and the stock values even overlap the kernel load + * address, where free_initrd_mem() then wipes the running kernel. */ + ret = fdt_fixup_val64(&ctx, off, "chosen", "linux,initrd-start", 0); + if (ret == 0) + ret = fdt_fixup_val64(&ctx, off, "chosen", "linux,initrd-end", 0); + if (ret != 0) { + wolfBoot_printf("FDT: initrd fixup failed (%d)\n", ret); + return ret; + } + wolfBoot_printf("FDT: bootargs set, initrd cleared\n"); + return 0; +} +#endif /* MMU && WOLFBOOT_FDT */ + +/* SoC bring-up: everything an earlier boot stage would otherwise have done. */ + +static int s32g2_ddr_test(uintptr_t base, uint32_t len) +{ + volatile uint64_t* p = (volatile uint64_t*)base; + uint32_t n = len / sizeof(uint64_t); + uint32_t i; + uint64_t v; + + for (i = 0; i < n; i++) + p[i] = ((uint64_t)(base + i * 8) * 0x9E3779B97F4A7C15ULL) ^ (uint64_t)i; + for (i = 0; i < n; i++) { + v = ((uint64_t)(base + i * 8) * 0x9E3779B97F4A7C15ULL) ^ (uint64_t)i; + if (p[i] != v) { + wolfBoot_printf("s32g2: DDR mismatch at 0x%08x%08x: 0x%08x%08x != 0x%08x%08x\n", + (uint32_t)((uint64_t)(uintptr_t)&p[i] >> 32), (uint32_t)(uintptr_t)&p[i], + (uint32_t)(p[i] >> 32), (uint32_t)p[i], (uint32_t)(v >> 32), (uint32_t)v); + return -1; + } + } + return 0; +} + +/* Let the 4-entry TX FIFO drain before the LIN clock under it changes or + * control leaves this image. Only used while the MMU is off, where one pass + * of the loop costs several microseconds: at least 15 ms. */ +static void linflex_drain(void) +{ + volatile uint32_t spin; + + for (spin = 0; spin < 4000U; spin++) + ; +} + + +/* Clock bring-up progress: a reset mid-sequence then points at the step. */ +static void s32g2_clk_trace(int step) +{ +#if defined(DEBUG_UART) + if (step == 9) + s32g2_linflex_setup(S32G2_LINIBRR_115200, S32G2_LINFBRR_115200); + wolfBoot_printf("s32g2: clk step %d\n", step); + linflex_drain(); +#else + (void)step; +#endif +} + +/* uSDHC0 pads: CLK, CMD/DATA0-7/VSELECT with pull-ups, RST, and the input + * muxes; the clock and MC_ME block come up with the clock tree. Routed + * whether or not wolfBoot reads the card itself, as the OS expects the boot + * firmware to have done it. */ +static void s32g2_sd_pads(void) +{ + uint32_t v; + + wr32(S32G2_SIUL2_0_MSCR_BASE + 4 * 46, S32G2_SD_CLK_MSCR_VAL); + for (v = 47; v <= 55; v++) + wr32(S32G2_SIUL2_0_MSCR_BASE + 4 * v, S32G2_SD_DATA_MSCR_VAL); + wr32(S32G2_SIUL2_0_MSCR_BASE + 4 * 56, S32G2_SD_RST_MSCR_VAL); + for (v = 515; v <= 524; v++) + wr32(S32G2_SIUL2_0_IMCR_BASE + 4 * (v - 512), S32G2_SD_IMCR_VAL); +} + +/* Identity map at EL3. With the MMU off every data access + * is Device-nGnRnE and costs about a microsecond on this interconnect, which + * makes the hash and the inflate step take tens of seconds. SRAM and both + * DRAM banks are Normal write-back; the QuadSPI window, the peripherals and + * everything unmapped stay Device. 4 KB granule, 36-bit VA (T0SZ 28, level 1 + * start): the first GB is split into 2 MB blocks for the SRAM. */ +#define EL3_BLOCK_NORMAL 0x0000000000000701ULL +#define EL3_BLOCK_DEVICE (0x0000000000000405ULL | (1ULL << 54) | (1ULL << 53)) +#define EL3_TABLE 0x3ULL +static uint64_t s32g2_el3_l1[64] __attribute__((aligned(4096))); +static uint64_t s32g2_el3_l2_gb0[512] __attribute__((aligned(4096))); + +static void s32g2_el3_mmu_enable(void) +{ + uint64_t a, sctlr; + uint32_t i; + + for (i = 0; i < 512; i++) { + a = (uint64_t)i << 21; + s32g2_el3_l2_gb0[i] = a | ((a >= S32G2_SRAM_BASE && + a < S32G2_SRAM_BASE + S32G2_SRAM_SIZE) ? EL3_BLOCK_NORMAL : EL3_BLOCK_DEVICE); + } + for (i = 0; i < 64; i++) + s32g2_el3_l1[i] = 0; + s32g2_el3_l1[0] = (uint64_t)(uintptr_t)s32g2_el3_l2_gb0 | EL3_TABLE; + s32g2_el3_l1[1] = (1ULL << 30) | EL3_BLOCK_DEVICE; + /* DRAM stays unmapped until it is trained (s32g2_el3_mmu_map_dram), so + * nothing can speculate into it. */ + __asm__ volatile("dsb sy"); + + /* Attr0 Normal WB/WA, Attr1 Device-nGnRnE; T0SZ 28, inner-shareable + * cacheable walks, 40-bit PA, bits 23 and 31 RES1. */ + __asm__ volatile("msr mair_el3, %0" :: "r"(0x00000000000000FFULL)); + __asm__ volatile("msr ttbr0_el3, %0" :: "r"((uint64_t)(uintptr_t)s32g2_el3_l1)); + __asm__ volatile("msr tcr_el3, %0" :: "r"(0x000000008082351CULL)); + __asm__ volatile("isb"); + __asm__ volatile("tlbi alle3"); + __asm__ volatile("dsb sy"); + __asm__ volatile("isb"); + aarch64_dcache_maint(0); + __asm__ volatile("ic iallu"); + __asm__ volatile("dsb sy"); + __asm__ volatile("isb"); + __asm__ volatile("mrs %0, sctlr_el3" : "=r"(sctlr)); + sctlr |= (1ULL << 0) | (1ULL << 2) | (1ULL << 12); + __asm__ volatile("msr sctlr_el3, %0" :: "r"(sctlr)); + __asm__ volatile("isb"); +} + +/* Both DRAM banks Normal write-back, once training and the uncached + * read-back check are done. With map 0 they are Device, for that check. */ +static void s32g2_el3_mmu_map_dram(int cached) +{ + uint64_t attr = cached ? EL3_BLOCK_NORMAL : EL3_BLOCK_DEVICE; + + s32g2_el3_l1[2] = (2ULL << 30) | attr; + s32g2_el3_l1[3] = (3ULL << 30) | attr; + s32g2_el3_l1[34] = (34ULL << 30) | attr; + s32g2_el3_l1[35] = (35ULL << 30) | attr; + __asm__ volatile("dsb sy"); + __asm__ volatile("tlbi alle3"); + __asm__ volatile("dsb sy"); + __asm__ volatile("isb"); +} + +static void s32g2_soc_init(void) +{ + uintptr_t rgm = S32G2_MC_RGM_BASE; + uint32_t des = rd32(rgm + MC_RGM_DES); + uint32_t fes = rd32(rgm + MC_RGM_FES); + uint64_t t0, t1; + uint32_t v; + int ret; + + (void)v; + (void)t0; + (void)t1; + wolfBoot_printf("s32g2: MIDR1=0x%08x MIDR2=0x%08x reset DES=0x%08x FES=0x%08x\n", + rd32(S32G2_SIUL2_0_BASE + SIUL2_MIDR1), + rd32(S32G2_SIUL2_0_BASE + SIUL2_MIDR2), des, fes); + /* Clear the cause flags so the next report is about the next reset. */ + wr32(rgm + MC_RGM_DES, des); + wr32(rgm + MC_RGM_FES, fes); + + /* PMIC fail-safe watchdog and FCCU monitoring, before any clock + * moves: the VR5510 power-cycles the board on an FCCU error-pin event, and + * the console is still on FIRC so a failure here is reported. */ + ret = s32g2_pmic_init(S32G2_I2C_CLK_FIRC_HZ); + if (ret != 0) { + wolfBoot_printf("s32g2: PMIC init failed (%d)\n", ret); + wolfBoot_panic(); + } + wolfBoot_printf("s32g2: VR5510 watchdog disabled\n"); + + /* Clock tree. The console moves with the LIN clock at step 8 + * and is reprogrammed in the trace callback at step 9. */ + wolfBoot_printf("s32g2: clocks...\n"); + linflex_drain(); + ret = s32g2_clk_init(s32g2_clk_trace); + if (ret != 0) { + wolfBoot_printf("s32g2: clock init failed at step %d\n", ret); + wolfBoot_panic(); + } + /* The counter ticks from FXOSC now; publish its rate and show it moving. */ + __asm__ volatile("msr cntfrq_el0, %0" :: "r"((uint64_t)S32G2_CNTFRQ_HZ)); + t0 = timer_get_count(); + hal_delay_us(10000); + t1 = timer_get_count(); + wolfBoot_printf("s32g2: A53 %u MHz, XBAR_2X %u MHz, LIN 125 MHz, timer +%u ticks/10ms\n", + (unsigned)(S32G2_A53_HZ / 1000000), (unsigned)(S32G2_XBAR_2X_HZ / 1000000), + (unsigned)(t1 - t0)); + + /* Caches on for everything from here, DDR training included. The + * coherent interconnect must be up before this core's caches. */ + ret = s32g2_ncore_init(); + if (ret != 0) { + wolfBoot_printf("s32g2: Ncore init failed (%d)\n", ret); + wolfBoot_panic(); + } + s32g2_el3_mmu_enable(); + wolfBoot_printf("s32g2: MMU on at EL3, caches enabled\n"); + + /* LPDDR4 training, then a read-back check through the uncached + * mapping so a failed training cannot pass on cached data. */ + wolfBoot_printf("s32g2: DDR init...\n"); + ret = s32g2_ddr_init(); + if (ret != 0) { + wolfBoot_printf("s32g2: DDR init failed (%d)\n", ret); + wolfBoot_panic(); + } + wolfBoot_printf("s32g2: DDR up, %u MB, erratum polling %d\n", + (unsigned)(S32G2_DRAM_SIZE >> 20), s32g2_ddr_polling_needed()); + s32g2_el3_mmu_map_dram(0); + if (s32g2_ddr_test((uintptr_t)S32G2_DRAM_BASE, 0x400000) != 0 || + s32g2_ddr_test((uintptr_t)WOLFBOOT_LOAD_ADDRESS, 0x400000) != 0 || + s32g2_ddr_test((uintptr_t)S32G2_DRAM_BANK1_BASE, 0x400000) != 0) { + wolfBoot_printf("s32g2: DDR read-back test FAILED\n"); + wolfBoot_panic(); + } + wolfBoot_printf("s32g2: DDR read-back test passed\n"); + s32g2_el3_mmu_map_dram(1); + + /* Interrupt routing, the GIC for this core, and the three + * secondary cores parked in the spin table. */ + s32g2_mscm_init(); + s32g2_gic_init_dist(); + s32g2_gic_init_cpu(); + ret = s32g2_smp_release((uintptr_t)WOLFBOOT_ORIGIN); + if (ret != 0) + wolfBoot_panic(); + s32g2_sd_pads(); +#ifdef S32G2_QSPI + for (v = 85; v <= 93; v++) + wr32(S32G2_SIUL2_0_MSCR_BASE + 4 * v, S32G2_QSPI_DATA_MSCR_VAL); + for (v = 96; v <= 99; v++) + wr32(S32G2_SIUL2_0_MSCR_BASE + 4 * v, S32G2_QSPI_CLK_MSCR_VAL); + wr32(S32G2_SIUL2_0_MSCR_BASE + 4 * 100, S32G2_QSPI_CS_MSCR_VAL); + wr32(S32G2_SIUL2_0_MSCR_BASE + 4 * 101, S32G2_QSPI_CS_MSCR_VAL); + for (v = 540; v <= 548; v++) + wr32(S32G2_SIUL2_0_IMCR_BASE + 4 * (v - 512), S32G2_QSPI_IMCR_VAL); + ret = s32g2_qspi_init(); + if (ret != 0) + wolfBoot_printf("s32g2: QuadSPI init failed (%d)\n", ret); +#ifdef S32G2_QSPI_PROGRAM_FILE + /* NOR programmer build: read an image file from the FAT partition of the + * SD card or eMMC into DDR, erase and program it at + * S32G2_QSPI_PROGRAM_OFFSET, read it back, then halt. */ + { + static struct fs_volume vol; + static struct fs_file f; + uint8_t* img = (uint8_t*)(uintptr_t)WOLFBOOT_LOAD_ADDRESS; + uint8_t* chk = img + S32G2_QSPI_PROGRAM_MAX; + uint32_t n = 0, off, len; + + ret = disk_init(0); + if (ret >= 0) + ret = disk_open(0); + if (ret >= 0) + ret = fs_mount(&vol, 0, BOOT_PART_A); + if (ret == 0) + ret = fs_open(&vol, &f, S32G2_QSPI_PROGRAM_FILE, + S32G2_QSPI_PROGRAM_MAX); + if (ret == 0) { + for (off = 0; off < (uint32_t)f.size; off += 0x10000) { + len = (uint32_t)f.size - off; + if (len > 0x10000) + len = 0x10000; + if (fs_read(&f, off, len, img + off) < 0) { + ret = -1; + break; + } + } + if (ret == 0) + n = (uint32_t)f.size; + } + wolfBoot_printf("s32g2: NOR image %s: %d, %u bytes -> 0x%08x\n", + S32G2_QSPI_PROGRAM_FILE, ret, (unsigned)n, + (uint32_t)S32G2_QSPI_PROGRAM_OFFSET); + if (n != 0) { + ret = s32g2_qspi_erase(S32G2_QSPI_PROGRAM_OFFSET, n); + if (ret == 0) + ret = s32g2_qspi_write(S32G2_QSPI_PROGRAM_OFFSET, img, n); + if (ret == 0) + ret = s32g2_qspi_read(S32G2_QSPI_PROGRAM_OFFSET, chk, n); + if (ret == 0 && memcmp(img, chk, n) != 0) + ret = -2; + wolfBoot_printf("s32g2: NOR program and read back: %s (%d)\n", + (ret == 0) ? "OK" : "FAILED", ret); + } + wolfBoot_printf("s32g2: NOR programmer done, halting\n"); + for (;;) + __asm__ volatile("wfi"); + } +#endif +#endif +} + +/* Public HAL entry points */ + +void hal_init(void) +{ +#if defined(DEBUG_UART) + unsigned long el; + + uart_init(); + __asm__ volatile("mrs %0, CurrentEL" : "=r"(el)); + wolfBoot_printf("wolfBoot S32G2 (EL%d, running from SRAM)\n", + (int)((el >> 2) & 0x3)); +#endif + s32g2_soc_init(); +} + +void hal_prepare_boot(void) +{ +#if defined(DISK_SDCARD) || defined(DISK_EMMC) + /* Quiesce the controller so the OS does not inherit a uSDHC this + * firmware left clocked and running. */ + sdhci_shutdown(); +#endif + /* SCTLR.I is IMPLEMENTATION DEFINED at entry, so invalidate the I-cache + * before branching. */ + __asm__ volatile( + "dsb sy \n" + "ic iallu \n" + "dsb sy \n" + "isb \n" + ::: "memory"); +} + +/* No internal flash: wolfBoot runs from SRAM. Provide the required no-op + * HAL surface. */ +int RAMFUNCTION hal_flash_write(uintptr_t address, const uint8_t *data, int len) +{ + (void)address; (void)data; (void)len; + return 0; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ +} + +void RAMFUNCTION hal_flash_lock(void) +{ +} + +int RAMFUNCTION hal_flash_erase(uintptr_t address, int len) +{ + (void)address; (void)len; + return 0; +} diff --git a/hal/s32g2.h b/hal/s32g2.h new file mode 100644 index 0000000000..05cab74528 --- /dev/null +++ b/hal/s32g2.h @@ -0,0 +1,252 @@ +/* s32g2.h + * + * 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 + */ + +/* NXP S32G2 (S32G274A) definitions for the S32G-VNP-GLDBOX / S32G274A-RDB2. + * boot_aarch64_start.S includes this, so keep assembly off the C-only parts. */ + +#ifndef _S32G2_H_ +#define _S32G2_H_ + +/* Simple AArch64 startup: wolfBoot is entered by the Boot ROM at EL3 in + * SRAM, with the SRAM initialisation added in front of the common path. */ +#define USE_BUILTIN_STARTUP +#define USE_SIMPLE_STARTUP + +/* System SRAM, and its two controllers (4 MB each). An ECC read-modify-write + * on SRAM the controller has not initialised faults, so the startup code runs + * the controller over everything it uses beyond what the Boot ROM copied. The + * initialisation address of a byte offset is ((off >> 9) << 2) | ((off >> 4) & 3). */ +#define S32G2_SRAM_BASE 0x34000000 +#define S32G2_SRAM_SIZE 0x00800000 +#define S32G2_SRAMC0_BASE 0x4019C000 /* 0x34000000-0x343FFFFF */ +#define S32G2_SRAMC1_BASE 0x401A0000 /* 0x34400000-0x347FFFFF */ +#define SRAMC_PRAMCR 0x00 +#define SRAMC_PRAMIAS 0x04 +#define SRAMC_PRAMIAE 0x08 +#define SRAMC_PRAMSR 0x0C +#define SRAMC_PRAMCR_INITREQ 1 +#define SRAMC_PRAMSR_IDONE 1 + +/* Reset Generation Module: destructive and functional reset-cause flags, + * write-1-to-clear. */ +#define S32G2_MC_RGM_BASE 0x40078000 +#define MC_RGM_DES 0x00 +#define MC_RGM_FES 0x08 + +/* SIUL2_0: chip identification and the pad muxing for UART0. */ +#define S32G2_SIUL2_0_BASE 0x4009C000 +#define SIUL2_MIDR1 0x04 +#define SIUL2_MIDR2 0x08 +#define S32G2_SIUL2_0_MSCR_BASE 0x4009C240 /* MSCR n at + 4 * n */ +#define S32G2_SIUL2_0_IMCR_BASE 0x4009CA40 /* IMCR n at + 4 * (n - 512) */ +#define S32G2_UART0_TX_MSCR 41 /* PC09 */ +#define S32G2_UART0_RX_MSCR 42 /* PC10 */ +#define S32G2_UART0_RX_IMCR 512 +/* ALT1 | OBE | SRE 133 MHz; ALT0 | IBE | SRE; IMCR source ALT2 */ +#define S32G2_UART0_TX_MSCR_VAL 0x00218001 +#define S32G2_UART0_RX_MSCR_VAL 0x00098000 +#define S32G2_UART0_RX_IMCR_VAL 0x2 +/* uSDHC0 pads 46-56 (MSCR) and input muxes 515-524 (IMCR): ALT1 with the + * 150 MHz slew; CLK pull-down, CMD/DATA/VSELECT pull-up, RST output only. */ +#define S32G2_SD_CLK_MSCR_VAL 0x00296001 +#define S32G2_SD_DATA_MSCR_VAL 0x00297001 +#define S32G2_SD_RST_MSCR_VAL 0x00216001 +#define S32G2_SD_IMCR_VAL 0x2 + +/* QuadSPI (S32G2_QSPI): controller, the 64 MB NOR on bus A chip select 0, + * pads 85-93 (data, DQS) 96-99 (clock) 100-101 (chip selects) with input + * muxes 540-548; ALT1 on every pad with the fastest slew, data pulled down + * and chip selects pulled up. QSPI_2X_CLK is MC_CGM0 mux 12 from PERIPH + * DFS port 0; the serial clock is half of it. */ +#define S32G2_QSPI_BASE 0x40134000 +#define S32G2_QSPI_AHB_BASE 0x00000000 +#define S32G2_QSPI_FLASH_SIZE 0x04000000 +#define S32G2_QSPI_DATA_MSCR_VAL 0x00282001 +#define S32G2_QSPI_CLK_MSCR_VAL 0x00200001 +#define S32G2_QSPI_CS_MSCR_VAL 0x00203001 +#define S32G2_QSPI_IMCR_VAL 0x2 +#define S32G2_MC_ME_QSPI_BLOCK 0 /* partition 0, COFB block 0 */ +#ifndef S32G2_QSPI_2X_DIV +#define S32G2_QSPI_2X_DIV 8 /* 800 MHz / 8 = 100 MHz QSPI_2X, 50 MHz SCK */ +#endif + +/* Out of reset the LIN clock is the 48 MHz FIRC, until the clock tree is + * programmed: 48e6 / (16 * 115200) = 26.04. */ +#define S32G2_FIRC_HZ 48000000 +#define S32G2_LINIBRR_115200_FIRC 26 +#define S32G2_LINFBRR_115200_FIRC 1 + +/* Clock generation: crystal oscillator, PLLs, DFS fractional dividers, the + * clock-generation modules and the mode-entry module's partition gating. */ +#define S32G2_FXOSC_BASE 0x40050000 +#define S32G2_ARM_PLL_BASE 0x40038000 +#define S32G2_PERIPH_PLL_BASE 0x4003C000 +#define S32G2_DDR_PLL_BASE 0x40044000 +#define S32G2_ARM_DFS_BASE 0x40054000 +#define S32G2_PERIPH_DFS_BASE 0x40058000 +#define S32G2_MC_CGM0_BASE 0x40030000 +#define S32G2_MC_CGM1_BASE 0x40034000 +#define S32G2_MC_CGM5_BASE 0x40068000 +#define S32G2_MC_ME_BASE 0x40088000 +#define S32G2_MC_ME_USDHC_BLOCK 0 /* partition 0, COFB block 0 */ +#define S32G2_MC_ME_DDR_BLOCK 1 /* partition 0, COFB block 1 */ +#define S32G2_FXOSC_HZ 40000000 +#define S32G2_A53_HZ 1000000000 +#define S32G2_XBAR_2X_HZ 800000000 +/* The system counter runs at FXOSC / 8 once the crystal is up. */ +#define S32G2_CNTFRQ_HZ 5000000 + +/* I2C4 and the VR5510 PMIC behind it. The controller clock is XBAR / 2 / 3. */ +#define S32G2_I2C4_BASE 0x402DC000 +/* XBAR_DIV3: XBAR_2X / 2 / 3, so 8 MHz on FIRC and 133 MHz once the tree is up */ +#define S32G2_I2C_CLK_FIRC_HZ 8000000 +#define S32G2_I2C_CLK_HZ 133333333 +#define S32G2_I2C_SPEED_HZ 100000 +#define S32G2_VR5510_MU_ADDR 0x20 +#define S32G2_VR5510_FSU_ADDR 0x21 +#define S32G2_SIUL2_1_IMCR_BASE 0x44010C1C /* IMCR n at + 4 * (n - 631) */ +#define S32G2_SIUL2_1_IMCR_START 631 +#define S32G2_I2C4_SDA_MSCR 33 /* PC01 */ +#define S32G2_I2C4_SCL_MSCR 34 /* PC02 */ +#define S32G2_I2C4_SDA_IMCR 724 +#define S32G2_I2C4_SCL_IMCR 723 +/* ALT1/ALT2 | OBE | ODE | IBE | SRE 133 MHz; IMCR source ALT3 */ +#define S32G2_I2C4_SDA_MSCR_VAL 0x00398001 +#define S32G2_I2C4_SCL_MSCR_VAL 0x00398002 +#define S32G2_I2C4_IMCR_VAL 0x3 + +/* Interconnect, interrupt router and GICv3. */ +#define S32G2_NCORE_BASE 0x50400000 +#define S32G2_NCORE_CAIUTC 0x50400000 /* CAIU0 transaction control */ +#define S32G2_NCORE_CAIUTC_ISOLEN (1U << 1) +#define S32G2_MSCM_BASE 0x40198000 +#define S32G2_MSCM_IRSPRC 0x880 /* 16-bit routing per SPI */ +#define S32G2_MSCM_IRSPRC_NUM 240 +#define S32G2_GICD_BASE 0x50800000 +#define S32G2_GICR_BASE 0x50880000 +#define S32G2_GICR_STRIDE 0x20000 +#ifndef __ASSEMBLER__ /* src/boot_aarch64_start.S has its own GICD names */ +#define GICD_CTLR 0x000 +#define GICD_TYPER 0x004 +#define GICD_IGROUPR 0x080 +#define GICD_IGROUPMODR 0xD00 +#define GICD_CTLR_ENGRP0 (1U << 0) +#define GICD_CTLR_ENGRP1NS (1U << 1) +#define GICD_CTLR_ENGRP1S (1U << 2) +#define GICD_CTLR_ARE_S (1U << 4) +#define GICD_CTLR_ARE_NS (1U << 5) +#define GICR_TYPER 0x008 +#define GICR_WAKER 0x014 +#define GICR_WAKER_PROCESSORSLEEP (1U << 1) +#define GICR_WAKER_CHILDRENASLEEP (1U << 2) +#define GICR_SGI_BASE 0x10000 +#define GICR_IGROUPR0 0x080 +#define GICR_ISENABLER0 0x100 +#define GICR_IGROUPMODR0 0xD00 +#endif + +/* Spin table for the secondary cores, in the SRAM page below the image, one + * 64-byte slot per linear core id (aff1 * 2 + aff0): entry at +0, status at + * +8. The kernel's cpu-release-addr points at the entry word. The secondary + * stacks sit above the table; the whole 32 KB is ECC-initialised at entry. */ +#define S32G2_NUM_CORES 4 +#define S32G2_SPIN_TABLE_BASE 0x34000000 +#define S32G2_SPIN_TABLE_STRIDE 0x40 +#define S32G2_SPIN_TABLE_STATUS 8 +#define S32G2_SECONDARY_STACK_TOP 0x34004000 /* 2 KB per core, downwards */ +#define S32G2_SECONDARY_STACK_SIZE 0x800 +#define S32G2_SRAM_LOW_INIT_END 0x34008000 + +#ifndef __ASSEMBLER__ +int s32g2_cgm_mux_set(uintptr_t cgm, uint32_t mux, uint32_t src); +uint32_t s32g2_cgm_mux_get(uintptr_t cgm, uint32_t mux); +int s32g2_ddr_init(void); +int s32g2_ddr_polling_needed(void); +void hal_delay_us(uint32_t us); +#endif + +/* DRAM bank 0. Bank 1 is a further 2 GB at 0x880000000. */ +#define S32G2_DRAM_BASE 0x80000000 +#define S32G2_DRAM_END 0x100000000 +#define S32G2_DRAM_BANK1_BASE 0x880000000ULL +#define S32G2_DRAM_BANK0_SIZE 0x80000000ULL +#define S32G2_DRAM_BANK1_SIZE 0x80000000ULL +#define S32G2_DRAM_SIZE (S32G2_DRAM_BANK0_SIZE + S32G2_DRAM_BANK1_SIZE) + +/* DRAM staging addresses for the address hooks of builds without fixed + * partitions. */ +#define S32G2_PAYLOAD_ADDR 0x90000000 +#define S32G2_UPDATE_ADDR 0x98000000 + +/* Generic timer fallback if firmware left CNTFRQ_EL0 unprogrammed; the S32G2 + * system counter runs at FXOSC/8 = 5 MHz. */ +#ifndef TIMER_CLK_FREQ +#define TIMER_CLK_FREQ 5000000 +#endif + +/* Console LINFlexD_0 (ttyLF0): stdout-path in s32g274a-rdb2.dts, pins PC09/PC10, + * 115200 8N1. LINFlexD_1 at 0x401CC000 is the second FTDI port. */ +#define S32G2_LINFLEX0_BASE 0x401C8000 +#define LINFLEX_LINCR1 0x00 +#define LINFLEX_LINSR 0x08 +#define LINFLEX_UARTCR 0x10 +#define LINFLEX_UARTSR 0x14 +#define LINFLEX_LINFBRR 0x24 +#define LINFLEX_LINIBRR 0x28 +#define LINFLEX_BDRL 0x38 +#define LINFLEX_BDRM 0x3C +#define LINFLEX_UARTPTO 0x50 + +#define LINFLEX_LINCR1_INIT (1 << 0) +#define LINFLEX_LINCR1_MME (1 << 4) +#define LINFLEX_LINSR_LINS_MASK (0xF << 12) +#define LINFLEX_LINSR_LINS_INIT (0x1 << 12) +#define LINFLEX_UARTCR_UART (1 << 0) +/* UART | WL0 | PC0 | TXEN | RXEN | PC1 | TFBM | RFBM: 8N1, FIFO mode on both + * directions, which is also how U-Boot and TF-A leave the port. */ +#define LINFLEX_UARTCR_8N1_FIFO 0x37B +#define LINFLEX_UARTSR_DTF (1 << 1) /* TX FIFO full (FIFO mode) */ +#define LINFLEX_UARTSR_RFE (1 << 2) /* RX FIFO empty (FIFO mode) */ + +/* LIN_BAUD clock as the earlier stage programs it (PERIPH_PLL_PHI3), and the + * 115200 divisor from it: 125e6 / (16 * 115200) = 67.8 -> 67 + 13/16. */ +#define S32G2_LIN_CLK_HZ 125000000 +#define S32G2_LINIBRR_115200 67 +#define S32G2_LINFBRR_115200 13 + +/* uSDHC0: the only SD/MMC host on S32G2. The GoldBox routes it to the microSD + * slot by default and to the on-board eMMC through a board mux. */ +#define S32G2_USDHC0_BASE 0x402F0000 + +/* uSDHC registers and the shim interface (hal/imx_usdhc_sdhci.c) */ +#include "imx_usdhc_sdhci.h" + +/* uSDHC per clock as the earlier stage programs it (PERIPH_DFS3 / 2). */ +#ifndef S32G2_USDHC_PERCLK_HZ +#define S32G2_USDHC_PERCLK_HZ 400000000 +#endif +#define IMX_USDHC_BASE S32G2_USDHC0_BASE +#define IMX_USDHC_PERCLK_HZ S32G2_USDHC_PERCLK_HZ +#ifdef S32G2_USDHC_MAX_CLK_KHZ +#define IMX_USDHC_MAX_CLK_KHZ S32G2_USDHC_MAX_CLK_KHZ +#endif + +#endif /* _S32G2_H_ */ diff --git a/hal/s32g2.its b/hal/s32g2.its new file mode 100644 index 0000000000..d09f276590 --- /dev/null +++ b/hal/s32g2.its @@ -0,0 +1,28 @@ +/dts-v1/; +/* + * hal/s32g2.its - FIT for the S32G2: kernel plus the + * device tree, both under the wolfBoot signature. No earlier loader patches a + * DTB here, so wolfBoot adds /memory and the spin-table release addresses. + * tools/scripts/s32g2/s32g2-mkfit.sh [version] [key] + * Copyright (C) 2026 wolfSSL Inc. GPLv3. + */ +/ { + description = "wolfBoot S32G2 (S32G274A) Linux + DTB"; + #address-cells = <1>; + images { + kernel-1 { + description = "Linux kernel (arm64, gzip)"; + data = /incbin/("../Image.gz"); + type = "kernel"; arch = "arm64"; os = "linux"; compression = "gzip"; + load = <0x84080000>; entry = <0x84080000>; + hash-1 { algo = "sha256"; }; + }; + fdt-1 { + description = "Flattened device tree"; + data = /incbin/("../s32g2.dtb"); + type = "flat_dt"; arch = "arm64"; compression = "none"; + hash-1 { algo = "sha256"; }; + }; + }; + configurations { default = "conf1"; conf1 { kernel = "kernel-1"; fdt = "fdt-1"; hash-1 { algo = "sha256"; }; }; }; +}; diff --git a/hal/s32g2.ld b/hal/s32g2.ld new file mode 100644 index 0000000000..7867866d60 --- /dev/null +++ b/hal/s32g2.ld @@ -0,0 +1,68 @@ +/* s32g2.ld - link map for the NXP S32G2: wolfBoot is the Boot ROM's + * application image, copied into the 8 MB system SRAM and entered at + * WOLFBOOT_ORIGIN (0x340A0000) at EL3. Everything stays below 0x343F0000: + * inside SRAM controller 0's range, which the startup code ECC-initialises, + * and below the Boot ROM's reservation at 0x343FF000. + */ +MEMORY +{ + SRAM_MEM(rwx): ORIGIN = @WOLFBOOT_ORIGIN@, LENGTH = 0x343F0000 - @WOLFBOOT_ORIGIN@ +} +ENTRY(_vector_table); + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.boot*)) + *(.text*) + *(.rodata*) + *(.note.*) + . = ALIGN(4); + _end_text = .; + } > SRAM_MEM + .ARM.exidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > SRAM_MEM + + .data : + { + PROVIDE(_stored_data = .); + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + /* The SRAM controller initialises 512-byte granules; the startup + * clears from here, so nothing loaded may share this granule. */ + . = ALIGN(512); + _end_data = .; + } > SRAM_MEM + + /* From here on the SRAM was not written by the Boot ROM copy, so the + * startup code must run the SRAM controller's initialisation over it + * before any store (an ECC read-modify-write on uninitialised SRAM + * faults). _end_data to END_STACK is that range. */ + .bss (NOLOAD) : + { + _start_bss = .; + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + __bss_end__ = .; + } > SRAM_MEM + + .stack (NOLOAD) : + { + . = ALIGN(16); + . += 0x40000; /* 256 KB: the ECC verify, FIT parse and inflate run here */ + . = ALIGN(16); + END_STACK = .; + _end = .; + } > SRAM_MEM + . = ALIGN(4); +} diff --git a/hal/s32g2_ddr.c b/hal/s32g2_ddr.c new file mode 100644 index 0000000000..94b77a762d --- /dev/null +++ b/hal/s32g2_ddr.c @@ -0,0 +1,485 @@ +/* s32g2_ddr.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 + */ + +/* LPDDR4 bring-up for the NXP S32G2: a Synopsys uMCTL2 controller with a + * DWC LPDDR4 multiPHY. The sequence is the controller's and PHY's standard + * one: program the controller, release it from reset, load and run the PHY + * training firmware (1D then 2D), load the PHY init engine, then hand the + * DFI over and enter normal operation. + * + * The board's register tables and the PHY training firmware are not part of + * wolfBoot. They come from a data blob built by + * tools/scripts/s32g2/s32g2-ddr-blob.py and linked in with S32G2_DDR_BLOB. + * Inline ECC is not supported: the blob is generated without it. */ + +#include +#include +#include "hal/s32g2.h" +#include "hal.h" + +#if defined(DEBUG_UART) + #define PRINTF_ENABLED +#endif +#include "printf.h" + +static inline uint32_t rd32(uintptr_t a) { return *(volatile uint32_t*)a; } +static inline void wr32(uintptr_t a, uint32_t v) { *(volatile uint32_t*)a = v; } + +/* uMCTL2 controller */ +#define DDRC 0x403C0000UL +#define DDRC_STAT (DDRC + 0x004) +#define DDRC_STAT_OPMODE 0x7U +#define DDRC_MRCTRL0 (DDRC + 0x010) +#define DDRC_MRCTRL0_READ (1U << 0) +#define DDRC_MRCTRL0_RANK0 (1U << 4) +#define DDRC_MRCTRL0_RANK_MASK (0xFU << 4) +#define DDRC_MRCTRL0_START (1U << 31) +#define DDRC_MRCTRL1 (DDRC + 0x014) +#define DDRC_MRSTAT (DDRC + 0x018) +#define DDRC_MRSTAT_BUSY (1U << 0) +#define DDRC_DERATEEN (DDRC + 0x020) +#define DDRC_DERATEEN_EN (1U << 0) +#define DDRC_PWRCTL (DDRC + 0x030) +#define DDRC_PWRCTL_SELFREF_EN (1U << 0) +#define DDRC_PWRCTL_PDN_EN (1U << 1) +#define DDRC_PWRCTL_CLKDIS_EN (1U << 3) +#define DDRC_PWRCTL_SELFREF_SW (1U << 5) +#define DDRC_RFSHCTL3 (DDRC + 0x060) +#define DDRC_RFSHCTL3_DIS_AUTO (1U << 0) +#define DDRC_RFSHCTL3_UPDATE (1U << 1) +#define DDRC_RFSHTMG (DDRC + 0x064) +#define DDRC_ECCCFG0 (DDRC + 0x070) +#define DDRC_DRAMTMG0 (DDRC + 0x100) +#define DDRC_DRAMTMG1 (DDRC + 0x104) +#define DDRC_DRAMTMG4 (DDRC + 0x110) +#define DDRC_DFIMISC (DDRC + 0x1B0) +#define DDRC_DFIMISC_COMPLETE_EN (1U << 0) +#define DDRC_DFIMISC_INIT_START (1U << 5) +#define DDRC_DFISTAT (DDRC + 0x1BC) +#define DDRC_DFISTAT_COMPLETE (1U << 0) +#define DDRC_DBG1 (DDRC + 0x304) +#define DDRC_SWCTL (DDRC + 0x320) +#define DDRC_SWSTAT (DDRC + 0x324) +#define DDRC_PCTRL(n) (DDRC + 0x490 + (n) * 0xB0) +#define DDRC_NUM_AXI_PORTS 3 + +/* Performance block: mode register read data, and the LPDDR4 select */ +#define DDR_PERF_MRR0 0x403E0040UL +#define DDR_PERF_MRR1 0x403E0044UL + +/* Subsystem glue: AXI parity and DFI1 enable; controller reset; DDR clock */ +#define DDR_SS_REG 0x403D0000UL +#define DDR_SS_AXI_PARITY_EN 0x00001FF0U +#define DDR_SS_AXI_PARITY_ODD 0x01FF0000U +#define DDR_SS_DFI1_EN 0x00000001U +#define DDR_RGM_PRST0 0x40078040UL +#define DDR_RGM_PRST0_DDR (1U << 3) +#define DDR_CGM_MUX_FIRC 0 +#define DDR_CGM_MUX_PLL_PHI0 36 + +/* PHY: APB-only block (firmware mailbox, reset), master block, memories */ +#define PHY_MUX_SEL 0x40380400UL /* 0 = CSRs writable */ +#define PHY_UCT_SHADOW 0x40380404UL +#define PHY_UCT_SHADOW_MSG (1U << 0) /* 0 = a message is waiting */ +#define PHY_DCT_WRITE_PROT 0x4038040CUL +#define PHY_UCT_MSG 0x40380410UL +#define PHY_MICRO_RESET 0x40380420UL +#define PHY_CAL_BUSY 0x4038165CUL +#define PHY_PLL_CTRL2 0x403816DCUL +#define PHY_PLL_CTRL1 0x403816F0UL +#define PHY_PLL_TESTMODE 0x40381708UL +#define PHY_PLL_CTRL4 0x4038171CUL +#define PHY_IMEM 0x403A0000UL +#define PHY_DMEM 0x403B0000UL +#define PHY_MSG_DONE 0x07U +#define PHY_MSG_FAIL 0xFFU + +#define DDR_POLL_US 1000000U +#define DDR_MAX_MESSAGES 10000U + +/* Blob written by tools/scripts/s32g2/s32g2-ddr-blob.py */ +#define BLOB_MAGIC 0x44323353U /* "S32D" */ +#define BLOB_VERSION 1U +#define BLOB_HDR_WORDS 4 +enum { + BLOB_DDRC, BLOB_DQ_SWAP, BLOB_PHY, BLOB_PIE, + BLOB_IMEM_1D, BLOB_DMEM_1D, BLOB_IMEM_2D, BLOB_DMEM_2D, + BLOB_SECTIONS +}; + +#ifdef S32G2_DDR_BLOB +__asm__( + ".section .rodata.s32g2_ddr_blob, \"a\"\n" + ".balign 8\n" + ".global s32g2_ddr_blob\n" + "s32g2_ddr_blob:\n" + ".incbin \"" S32G2_DDR_BLOB "\"\n" + ".global s32g2_ddr_blob_end\n" + "s32g2_ddr_blob_end:\n" + ".previous\n"); +extern const uint8_t s32g2_ddr_blob[]; +extern const uint8_t s32g2_ddr_blob_end[]; +#endif + +static const uint32_t* blob; +static int errata_polling = -1; + +static int wait_bits(uintptr_t reg, uint32_t mask, uint32_t want) +{ + uint32_t us; + + for (us = 0; us < DDR_POLL_US; us++) { + if ((rd32(reg) & mask) == want) + return 0; + hal_delay_us(1); + } + return -1; +} + +static void set_bits(uintptr_t reg, uint32_t bits) +{ + wr32(reg, rd32(reg) | bits); +} + +static void clr_bits(uintptr_t reg, uint32_t bits) +{ + wr32(reg, rd32(reg) & ~bits); +} + +static uint32_t crc32(const uint8_t* p, uint32_t len) +{ + uint32_t crc = 0xFFFFFFFFU, i; + int b; + + for (i = 0; i < len; i++) { + crc ^= p[i]; + for (b = 0; b < 8; b++) + crc = (crc >> 1) ^ (0xEDB88320U & (0U - (crc & 1U))); + } + return ~crc; +} + +static int blob_open(void) +{ +#ifdef S32G2_DDR_BLOB + const uint32_t* b = (const uint32_t*)(uintptr_t)s32g2_ddr_blob; + uint32_t avail = (uint32_t)(s32g2_ddr_blob_end - s32g2_ddr_blob); + uint32_t total, i, off, count, bytes; + + if (avail < (BLOB_HDR_WORDS + 2 * BLOB_SECTIONS) * 4 || + b[0] != BLOB_MAGIC || b[1] != BLOB_VERSION) + return -1; + total = b[2]; + if (total > avail || total < (BLOB_HDR_WORDS + 2 * BLOB_SECTIONS) * 4) + return -1; + if (crc32((const uint8_t*)b + 16, total - 16) != b[3]) + return -2; + for (i = 0; i < BLOB_SECTIONS; i++) { + off = b[BLOB_HDR_WORDS + 2 * i]; + count = b[BLOB_HDR_WORDS + 2 * i + 1]; + bytes = (i < BLOB_IMEM_1D) ? 8 : 2; + if ((off & 3) != 0 || off > total || count > (total - off) / bytes) + return -3; + } + blob = b; + return 0; +#else + return -4; +#endif +} + +static const void* blob_section(int id, uint32_t* count) +{ + *count = blob[BLOB_HDR_WORDS + 2 * id + 1]; + return (const uint8_t*)blob + blob[BLOB_HDR_WORDS + 2 * id]; +} + +/* A table of {register address, value} writes */ +static void write_table(int id) +{ + uint32_t count, i; + const uint32_t* t = (const uint32_t*)blob_section(id, &count); + + for (i = 0; i < count; i++) + wr32((uintptr_t)t[2 * i], t[2 * i + 1]); +} + +/* 16-bit words into consecutive 32-bit PHY memory cells */ +static void load_phy_mem(uintptr_t dst, int id) +{ + uint32_t count, i; + const uint16_t* w = (const uint16_t*)blob_section(id, &count); + + wr32(PHY_MUX_SEL, 0); + for (i = 0; i < count; i++) + wr32(dst + 4 * i, w[i]); + wr32(PHY_MUX_SEL, 1); +} + +/* Quasi-dynamic register group: open with sw_done = 0, close with 1 */ +static int swctl(uint32_t done) +{ + wr32(DDRC_SWCTL, done); + return wait_bits(DDRC_SWSTAT, 1, done); +} + +/* Bring the controller out of reset with the DDR clock parked on FIRC, as + * the reset release needs, then quiesce it for PHY training. */ +static int controller_release(void) +{ + int moved = 0; + + set_bits(DDR_SS_REG, DDR_SS_AXI_PARITY_EN); + set_bits(DDR_SS_REG, DDR_SS_AXI_PARITY_ODD); + set_bits(DDR_SS_REG, DDR_SS_DFI1_EN); + + if (s32g2_cgm_mux_get(S32G2_MC_CGM5_BASE, 0) != DDR_CGM_MUX_FIRC) { + if (s32g2_cgm_mux_set(S32G2_MC_CGM5_BASE, 0, DDR_CGM_MUX_FIRC) != 0) + return -10; + moved = 1; + } + clr_bits(DDR_RGM_PRST0, DDR_RGM_PRST0_DDR); + if (moved && + s32g2_cgm_mux_set(S32G2_MC_CGM5_BASE, 0, DDR_CGM_MUX_PLL_PHI0) != 0) + return -11; + + wr32(DDRC_DBG1, 0); + set_bits(DDRC_RFSHCTL3, DDRC_RFSHCTL3_DIS_AUTO); + clr_bits(DDRC_PWRCTL, DDRC_PWRCTL_PDN_EN); + clr_bits(DDRC_PWRCTL, DDRC_PWRCTL_SELFREF_EN); + clr_bits(DDRC_PWRCTL, DDRC_PWRCTL_CLKDIS_EN); + if (swctl(0) != 0) + return -12; + clr_bits(DDRC_DFIMISC, DDRC_DFIMISC_COMPLETE_EN); + if (swctl(1) != 0) + return -13; + return 0; +} + +/* Start the PHY microcontroller on the loaded firmware and follow its + * mailbox until it reports the training stage done or failed. */ +static int run_training(void) +{ + uint32_t n, msg; + + /* PHY PLL settings for this data rate, then reset and release */ + wr32(PHY_PLL_CTRL1, 0x21); + wr32(PHY_PLL_TESTMODE, 0x24); + wr32(PHY_PLL_CTRL4, 0x17F); + wr32(PHY_PLL_CTRL2, 0x19); + wr32(PHY_MUX_SEL, 1); + wr32(PHY_MICRO_RESET, 0x9); + wr32(PHY_MICRO_RESET, 0x1); + wr32(PHY_MICRO_RESET, 0x0); + + for (n = 0; n < DDR_MAX_MESSAGES; n++) { + if (wait_bits(PHY_UCT_SHADOW, PHY_UCT_SHADOW_MSG, 0) != 0) + return -20; + msg = rd32(PHY_UCT_MSG); + /* Acknowledge, wait for the firmware to take it, re-arm */ + wr32(PHY_DCT_WRITE_PROT, 0); + if (wait_bits(PHY_UCT_SHADOW, PHY_UCT_SHADOW_MSG, + PHY_UCT_SHADOW_MSG) != 0) + return -21; + wr32(PHY_DCT_WRITE_PROT, 1); + if (msg == PHY_MSG_DONE) + return 0; + if (msg == PHY_MSG_FAIL) + return -22; + } + return -23; +} + +static int phy_train(void) +{ + uint32_t count; + int ret; + + write_table(BLOB_DQ_SWAP); + write_table(BLOB_PHY); + + load_phy_mem(PHY_IMEM, BLOB_IMEM_1D); + load_phy_mem(PHY_DMEM, BLOB_DMEM_1D); + wolfBoot_printf("DDR: 1D training\n"); + ret = run_training(); + if (ret != 0) + return ret; + + (void)blob_section(BLOB_IMEM_2D, &count); + if (count != 0) { + load_phy_mem(PHY_IMEM, BLOB_IMEM_2D); + load_phy_mem(PHY_DMEM, BLOB_DMEM_2D); + wolfBoot_printf("DDR: 2D training\n"); + ret = run_training(); + if (ret != 0) + return ret - 10; + } + + /* PHY init engine sequences, with the CSRs writable */ + wr32(PHY_MUX_SEL, 0); + write_table(BLOB_PIE); + wr32(PHY_MUX_SEL, 1); + return 0; +} + +/* Read an LPDDR4 mode register of rank 0 through the controller */ +static int mode_reg_read(uint32_t mr, uint32_t* val) +{ + uint32_t v; + + set_bits(DDR_PERF_MRR0, 1); + if (wait_bits(DDRC_MRSTAT, DDRC_MRSTAT_BUSY, 0) != 0) + return -1; + v = rd32(DDRC_MRCTRL0) & ~DDRC_MRCTRL0_RANK_MASK; + wr32(DDRC_MRCTRL0, v | DDRC_MRCTRL0_READ | DDRC_MRCTRL0_RANK0); + wr32(DDRC_MRCTRL1, (rd32(DDRC_MRCTRL1) & ~0xFF00U) | (mr << 8)); + __asm__ volatile("dsb sy"); + set_bits(DDRC_MRCTRL0, DDRC_MRCTRL0_START); + if (wait_bits(DDRC_MRSTAT, DDRC_MRSTAT_BUSY, 0) != 0) + return -1; + *val = rd32(DDR_PERF_MRR1); + return 0; +} + +/* Add delta to a timing field, failing instead of overflowing it */ +static int field_add(uintptr_t reg, uint32_t shift, uint32_t mask, + uint32_t delta) +{ + uint32_t v = rd32(reg); + uint32_t f = ((v >> shift) & mask) + delta; + + if (f > mask) + return -1; + wr32(reg, (v & ~(mask << shift)) | (f << shift)); + return 0; +} + +/* NXP erratum ERR050543: the controller's temperature derating can use a + * stale MR4 refresh-rate code. Read MR4; when the device asks for derated + * timing (code above 3), switch automatic derating off and apply the + * derated refresh interval and AC timings by hand; otherwise automatic + * derating is safe to enable. An OS then has to keep polling MR4. */ +static int errata_050543(void) +{ + uint32_t mr4, code, v; + + if (mode_reg_read(4, &mr4) != 0) + return -30; + code = mr4 & 0x7; + if (((mr4 >> 16) & 0x7) > code) + code = (mr4 >> 16) & 0x7; + + if (code <= 3) { + set_bits(DDRC_DERATEEN, DDRC_DERATEEN_EN); + errata_polling = 0; + return 0; + } + clr_bits(DDRC_DERATEEN, DDRC_DERATEEN_EN); + /* t_rfc_nom_x1_x32 to a quarter, flagged through refresh_update_level */ + v = rd32(DDRC_RFSHTMG); + wr32(DDRC_RFSHTMG, (v & ~(0xFFFU << 16)) | + ((((v >> 16) & 0xFFFU) >> 2) << 16)); + wr32(DDRC_RFSHCTL3, rd32(DDRC_RFSHCTL3) ^ DDRC_RFSHCTL3_UPDATE); + if (swctl(0) != 0) + return -31; + if (field_add(DDRC_DRAMTMG4, 24, 0x1F, 2) != 0 || /* t_rcd */ + field_add(DDRC_DRAMTMG4, 8, 0xF, 2) != 0 || /* t_rrd */ + field_add(DDRC_DRAMTMG0, 0, 0x3F, 2) != 0 ||/* t_ras_min */ + field_add(DDRC_DRAMTMG4, 0, 0x1F, 2) != 0 ||/* t_rp */ + field_add(DDRC_DRAMTMG1, 0, 0x7F, 3) != 0) /* t_rc */ + return -32; + if (swctl(1) != 0) + return -33; + errata_polling = 1; + return 0; +} + +/* DFI initialisation, normal operating mode, low-power features, AXI ports */ +static int controller_start(void) +{ + uint32_t n; + int ret; + + if (wait_bits(PHY_CAL_BUSY, 1, 0) != 0) + return -40; + if (swctl(0) != 0) + return -41; + set_bits(DDRC_DFIMISC, DDRC_DFIMISC_INIT_START); + if (swctl(1) != 0) + return -42; + if (wait_bits(DDRC_DFISTAT, DDRC_DFISTAT_COMPLETE, + DDRC_DFISTAT_COMPLETE) != 0) + return -43; + if (swctl(0) != 0) + return -44; + clr_bits(DDRC_DFIMISC, DDRC_DFIMISC_INIT_START); + set_bits(DDRC_DFIMISC, DDRC_DFIMISC_COMPLETE_EN); + clr_bits(DDRC_PWRCTL, DDRC_PWRCTL_SELFREF_SW); + if (swctl(1) != 0) + return -45; + + for (n = 0; n < DDR_POLL_US; n++) { + if ((rd32(DDRC_STAT) & DDRC_STAT_OPMODE) != 0) + break; + hal_delay_us(1); + } + if (n == DDR_POLL_US) + return -46; + clr_bits(DDRC_RFSHCTL3, DDRC_RFSHCTL3_DIS_AUTO); + if ((rd32(DDRC_ECCCFG0) & 0x7) != 0) + return -47; /* inline ECC tables: not supported */ + set_bits(DDRC_PWRCTL, DDRC_PWRCTL_PDN_EN); + set_bits(DDRC_PWRCTL, DDRC_PWRCTL_SELFREF_EN); + set_bits(DDRC_PWRCTL, DDRC_PWRCTL_CLKDIS_EN); + + ret = errata_050543(); + if (ret != 0) + return ret; + for (n = 0; n < DDRC_NUM_AXI_PORTS; n++) + wr32(DDRC_PCTRL(n), 1); + return 0; +} + +int s32g2_ddr_init(void) +{ + int ret; + + ret = blob_open(); + if (ret != 0) { + wolfBoot_printf("DDR: no valid data blob (%d), build with " + "S32G2_DDR_BLOB\n", ret); + return ret; + } + write_table(BLOB_DDRC); + ret = controller_release(); + if (ret == 0) + ret = phy_train(); + if (ret == 0) + ret = controller_start(); + return ret; +} + +/* 1 when ERR050543 left derating to be handled by polling MR4, 0 when + * automatic derating is on, -1 before s32g2_ddr_init() succeeded. */ +int s32g2_ddr_polling_needed(void) +{ + return errata_polling; +} diff --git a/src/boot_aarch64.c b/src/boot_aarch64.c index 3407776ab8..307578bd09 100644 --- a/src/boot_aarch64.c +++ b/src/boot_aarch64.c @@ -263,6 +263,15 @@ void RAMFUNCTION do_boot(const uint32_t *app_offset) asm volatile("isb"); #endif +#if defined(TARGET_s32g2) + /* wolfBoot runs at EL3 on this target; the payload is entered at EL2. */ + { + extern void s32g2_el3_to_el2_boot(uintptr_t entry, uintptr_t dts) + __attribute__((noreturn)); + s32g2_el3_to_el2_boot((uintptr_t)app_offset, (uintptr_t)dts_offset); + } +#endif + /* Set application address via x4 */ asm volatile("mov x4, %0" : : "r"(app_offset)); diff --git a/src/boot_aarch64_start.S b/src/boot_aarch64_start.S index aaa335baf9..e5907a3ef4 100644 --- a/src/boot_aarch64_start.S +++ b/src/boot_aarch64_start.S @@ -62,6 +62,10 @@ #include "hal/imx8mm.h" #endif +#ifdef TARGET_s32g2 +#include "hal/s32g2.h" +#endif + /* VA region the TCR_EL{1,2,3} T0SZ values below cover. A high DDR window * must fit inside it; keep in step with those TCR literals. */ #ifndef WOLFBOOT_AARCH64_VA_LIMIT @@ -1322,6 +1326,39 @@ _cm4_entry: 9: #endif +#if defined(TARGET_s32g2) + /* Entered by the Boot ROM, which ECC-initialised only the bytes it copied. + * Run SRAM controller 0 over .bss and the stack (_end_data to END_STACK, + * widened to its 512-byte granules) before anything is stored there: + * registers only, and the controller is reached through MMIO. */ + mrs x3, mpidr_el1 + and x3, x3, #0xFFFF + cbnz x3, s32g2_secondary_entry /* secondaries: released by the primary */ + ldr x2, =_end_data + ldr x3, =END_STACK + bl s32g2_sramc0_clear + /* The spin table and the secondary stacks, below the image. */ + ldr x2, =S32G2_SPIN_TABLE_BASE + ldr x3, =S32G2_SRAM_LOW_INIT_END + bl s32g2_sramc0_clear + /* SMPEN first: the A53 wants it before any cache maintenance or cache + * enable. Then the instruction cache: with the MMU off, fetches are + * cacheable (write-through) while data stays Device; without it every + * instruction comes from SRAM over the interconnect and the stage runs + * at a few MIPS. */ + mrs x2, S3_1_C15_C2_1 + orr x2, x2, #(1 << 6) + msr S3_1_C15_C2_1, x2 + isb + ic iallu + dsb sy + isb + mrs x2, sctlr_el3 + orr x2, x2, #(1 << 12) + msr sctlr_el3, x2 + isb +#endif + 4: ldr x1, =_vector_table // get start of .text in x1 // Read current EL mrs x0, CurrentEL @@ -1335,6 +1372,13 @@ _cm4_entry: msr scr_el3, x2 msr cptr_el3, xzr // enable FP/SIMD +#if defined(TARGET_s32g2) + /* Entered by the Boot ROM with no usable VBAR_EL3: install vectors that + * report ESR/ELR/FAR over the console instead of hanging silently. */ + ldr x2, =s32g2_el3_vectors + msr VBAR_EL3, x2 + isb +#endif // EL == 1? 2: cmp x0, #4 @@ -1370,7 +1414,7 @@ _cm4_entry: 8: #if defined(TARGET_tegra234) || defined(TARGET_imx8qm) || \ - defined(TARGET_imx95_a55) || \ + defined(TARGET_imx95_a55) || defined(TARGET_s32g2) || \ (defined(TARGET_imx8mm) && defined(IMX8MM_BL33)) /* Reserved in-DRAM stack from the linker script: the generic path sets sp * to the image base, which here is the BL33 load address. */ @@ -1489,6 +1533,170 @@ simple_el2_fault_stack_top: .section ".boot" #endif #endif /* TARGET_cm4 || TARGET_imx8qm || (TARGET_imx8mm && IMX8MM_BL33) */ + +#if defined(TARGET_s32g2) +/* EL3 exception vectors: wolfBoot is entered by the Boot ROM at EL3 with no + * usable VBAR_EL3. */ +.macro EL3_VEC label, idx + .align 7 +\label: + mov x3, #\idx + b s32g2_el3_fault_common +.endm + +.align 11 +.global s32g2_el3_vectors +s32g2_el3_vectors: + EL3_VEC el3_v0, 0 + EL3_VEC el3_v1, 1 + EL3_VEC el3_v2, 2 + EL3_VEC el3_v3, 3 + EL3_VEC el3_v4, 4 + EL3_VEC el3_v5, 5 + EL3_VEC el3_v6, 6 + EL3_VEC el3_v7, 7 + EL3_VEC el3_v8, 8 + EL3_VEC el3_v9, 9 + EL3_VEC el3_v10, 10 + EL3_VEC el3_v11, 11 + EL3_VEC el3_v12, 12 + EL3_VEC el3_v13, 13 + EL3_VEC el3_v14, 14 + EL3_VEC el3_v15, 15 + +s32g2_el3_fault_common: +#if defined(DEBUG) && defined(DEBUG_UART) + ldr x4, =END_STACK + mov sp, x4 + mrs x0, esr_el3 + mrs x1, elr_el3 + mrs x2, far_el3 + bl s32g2_el3_fault_handler /* void (esr, elr, far, vector) */ +#endif +9: wfi + b 9b + +/* s32g2_sramc0_clear(x2 = start address, x3 = end address): run SRAM + * controller 0 over the 512-byte granules covering [x2, x3). The Boot ROM + * only ECC-initialised the bytes it copied; a store anywhere else faults. + * Registers only (x2-x8), reached through MMIO, no stack. */ +s32g2_sramc0_clear: + ldr x4, =S32G2_SRAM_BASE + sub x2, x2, x4 + sub x3, x3, x4 + and x2, x2, #~511 + add x3, x3, #511 + and x3, x3, #~511 + sub x3, x3, #1 + /* initialisation address = ((off >> 9) << 2) | ((off >> 4) & 3) */ + lsr x5, x2, #9 + lsl x5, x5, #2 + lsr x6, x2, #4 + and x6, x6, #3 + orr x5, x5, x6 + lsr x6, x3, #9 + lsl x6, x6, #2 + lsr x7, x3, #4 + and x7, x7, #3 + orr x6, x6, x7 + ldr x4, =S32G2_SRAMC0_BASE + str wzr, [x4, #SRAMC_PRAMCR] + str w5, [x4, #SRAMC_PRAMIAS] + str w6, [x4, #SRAMC_PRAMIAE] + mov w7, #SRAMC_PRAMCR_INITREQ + str w7, [x4, #SRAMC_PRAMCR] + mov x8, #0x1000000 /* bound on the wait for IDONE */ +6: ldr w7, [x4, #SRAMC_PRAMSR] + tbnz w7, #0, 7f + subs x8, x8, #1 + b.ne 6b +7: + mov w7, #SRAMC_PRAMSR_IDONE + str w7, [x4, #SRAMC_PRAMSR] + ret + +/* Secondary cores enter here at EL3 once the primary releases them: EL3 + * state, a 2 KB SRAM stack, the per-core setup in C, then an ERET to EL2 + * where they park on their spin-table slot until the OS writes an entry + * address and sends an event. x19 = linear core id, x20 = slot. */ +s32g2_secondary_entry: + mrs x0, mpidr_el1 + and x19, x0, #0x3 + ubfx x1, x0, #8, #8 + add x19, x19, x1, lsl #1 /* id = aff1 * 2 + aff0 */ + ldr x2, =s32g2_el3_vectors + msr VBAR_EL3, x2 + mrs x2, scr_el3 + orr x2, x2, #0x0F /* NS | IRQ | FIQ | EA */ + msr scr_el3, x2 + msr cptr_el3, xzr + isb + ldr x2, =S32G2_SECONDARY_STACK_TOP + mov x3, #S32G2_SECONDARY_STACK_SIZE + msub x2, x19, x3, x2 + mov sp, x2 + mov x0, x19 + bl s32g2_secondary_main + ldr x20, =S32G2_SPIN_TABLE_BASE + add x20, x20, x19, lsl #6 + /* NS | EA | HCE | RW, with IRQ and FIQ left to EL2: routing them to EL3 + * makes the GICv3 trap the OS's group register accesses. */ + mov x2, #0x509 + msr scr_el3, x2 + mov x2, #(1 << 31) /* HCR_EL2.RW */ + msr hcr_el2, x2 + mov x2, #0x3C9 /* EL2h, DAIF masked */ + msr spsr_el3, x2 + adr x2, s32g2_secondary_park + msr elr_el3, x2 + isb + eret +s32g2_secondary_park: + wfe + ldr x0, [x20] + cbz x0, s32g2_secondary_park + dsb sy + isb + mov x4, x0 + mov x0, xzr + mov x1, xzr + mov x2, xzr + mov x3, xzr + br x4 + +/* s32g2_el3_to_el2_boot(entry, dtb): enter the payload at EL2 from EL3 with + * the arm64 boot contract (MMU off, DAIF masked, x0 = DTB). */ +.global s32g2_el3_to_el2_boot +s32g2_el3_to_el2_boot: + msr daifset, #0xf + mov x20, x0 + mov x21, x1 + /* wolfBoot ran with its identity MMU and caches: write everything back + * and hand over with the MMU off. Registers only from here on. The shared + * routine cleans and invalidates the caches and clears SCTLR_EL2. */ + bl el2_flush_and_disable_mmu + mrs x2, sctlr_el3 + bic x2, x2, #(1 << 0) + bic x2, x2, #(1 << 2) + bic x2, x2, #(1 << 12) + msr sctlr_el3, x2 + dsb sy + isb + mov x2, #0x509 /* NS | EA | HCE | RW, IRQ/FIQ to EL2 */ + msr scr_el3, x2 + mov x2, #(1 << 31) + msr hcr_el2, x2 + mov x2, #0x3C9 + msr spsr_el3, x2 + msr elr_el3, x20 + dsb sy + isb + mov x0, x21 + mov x1, xzr + mov x2, xzr + mov x3, xzr + eret +#endif /* TARGET_s32g2 */ #if defined(TARGET_cm4) /* Firmware-provided DTB pointer, stored at _cm4_entry. Read by hal_get_boot_dts() * (hal/cm4.c) for the kernel-only FIT boot path. In .data so it is not subject to diff --git a/src/update_disk.c b/src/update_disk.c index 50e37f9359..644708db55 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -991,6 +991,9 @@ void RAMFUNCTION wolfBoot_start(void) (unsigned int)(DISK_BLOCK_SIZE / 1024U)); #endif max_ver = (pB_ver_u > pA_ver_u) ? pB_ver_u : pA_ver_u; +#ifdef ALLOW_DOWNGRADE + (void)max_ver; +#endif /* Choose partition with higher version */ selected = (pB_ver_u > pA_ver_u) ? 1 : 0; diff --git a/tools/scripts/s32g2/s32g2-ddr-blob.py b/tools/scripts/s32g2/s32g2-ddr-blob.py new file mode 100755 index 0000000000..d8445671a8 --- /dev/null +++ b/tools/scripts/s32g2/s32g2-ddr-blob.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# s32g2-ddr-blob.py - build the LPDDR4 data blob wolfBoot needs on the S32G2. +# +# s32g2-ddr-blob.py [--soc s32g274a] [--rev rev2] +# +# wolfBoot trains the LPDDR4 itself but does not carry the board's register +# tables or the Synopsys PHY training firmware. Both are published by NXP in +# their U-Boot tree (github.com/nxp-auto-linux/u-boot, BSP 28 to 30, +# board/freescale/s32-gen1). This script reads them from a checkout of that +# tree and packs them into the binary hal/s32g2_ddr.c consumes. The tables are +# taken with CONFIG_S32GEN1_DRAM_INLINE_ECC unset. +# +# Blob layout, little-endian 32-bit words: +# magic "S32D", version, total size, CRC-32 of everything after this word, +# then eight {offset, count} descriptors and the data they point at: +# 0 controller table {address, value} pairs +# 1 DQ swap table {address, value} pairs +# 2 PHY table {address, value} pairs +# 3 PHY init engine table {address, value} pairs +# 4 1D training code 16-bit words +# 5 1D training data 16-bit words +# 6 2D training code 16-bit words +# 7 2D training data 16-bit words +# +# Copyright (C) 2026 wolfSSL Inc. GPLv3. + +import os +import re +import struct +import sys +import zlib + +MAGIC = 0x44323353 # "S32D" +VERSION = 1 +NUM_SECTIONS = 8 +ECC_MACRO = "CONFIG_S32GEN1_DRAM_INLINE_ECC" + + +def strip_conditionals(text): + """Keep the #else side of every #ifdef ECC_MACRO block, drop comments.""" + text = re.sub(r"/\*.*?\*/", "", text, flags=re.S) + out = [] + skip = [] + for line in text.splitlines(): + s = line.strip() + if s.startswith("#ifdef") and ECC_MACRO in s: + skip.append(True) + elif s.startswith("#else") and skip: + skip[-1] = not skip[-1] + elif s.startswith("#endif") and skip: + skip.pop() + elif s.startswith("#"): + continue + elif not (skip and skip[-1]): + out.append(line) + return "\n".join(out) + + +def array_numbers(path, name): + with open(path, "r", errors="replace") as f: + text = strip_conditionals(f.read()) + m = re.search(r"\b%s\s*\[\s*\]\s*=\s*\{(.*?)\}\s*;" % re.escape(name), + text, flags=re.S) + if m is None: + sys.exit("%s: array %s not found" % (path, name)) + return [int(v, 0) for v in re.findall(r"0[xX][0-9a-fA-F]+|\b\d+\b", + m.group(1))] + + +def pairs(path, name): + n = array_numbers(path, name) + if len(n) == 0 or len(n) % 2 != 0: + sys.exit("%s: %s is not a list of {address, value}" % (path, name)) + return struct.pack("<%dI" % len(n), *n), len(n) // 2 + + +def words16(path, name): + n = array_numbers(path, name) + if len(n) == 0 or max(n) > 0xFFFF: + sys.exit("%s: %s is not a list of 16-bit words" % (path, name)) + data = struct.pack("<%dH" % len(n), *n) + return data + b"\0" * (-len(data) % 4), len(n) + + +def main(argv): + args = [a for a in argv[1:] if not a.startswith("--")] + if len(args) != 2: + sys.stderr.write("usage: s32g2-ddr-blob.py " + " [--soc s32g274a] [--rev rev2]\n") + return 2 + soc = "s32g274a" + rev = "rev2" + if "--soc" in argv: + soc = argv[argv.index("--soc") + 1] + if "--rev" in argv: + rev = argv[argv.index("--rev") + 1] + base = os.path.join(args[0], "board", "freescale", "s32-gen1") + socdir = os.path.join(base, soc) + revdir = os.path.join(socdir, rev) + sfx = "_" + rev + if not os.path.isdir(revdir): + sys.exit("%s: not found (is this the NXP BSP U-Boot tree?)" % revdir) + + sections = [ + pairs(os.path.join(revdir, "ddrc_cfg%s.c" % sfx), "ddrc_cfg" + sfx), + pairs(os.path.join(socdir, "dq_swap_cfg.c"), "dq_swap_cfg"), + pairs(os.path.join(revdir, "phy_cfg%s.c" % sfx), "phy_cfg" + sfx), + pairs(os.path.join(revdir, "pie_cfg%s.c" % sfx), "pie_cfg" + sfx), + words16(os.path.join(base, "imem_cfg.c"), "imem_1d"), + words16(os.path.join(revdir, "dmem_cfg%s.c" % sfx), "dmem_1d" + sfx), + words16(os.path.join(base, "imem_cfg.c"), "imem_2d"), + words16(os.path.join(revdir, "dmem_cfg%s.c" % sfx), "dmem_2d" + sfx), + ] + assert len(sections) == NUM_SECTIONS + + off = 16 + 8 * NUM_SECTIONS + table = b"" + body = b"" + for data, count in sections: + table += struct.pack(" +# +# Output: s32g2-fitImage_v_signed.bin in the wolfBoot root. Copy it +# to the FAT32 boot partition as /fitImage_a.bin or /fitImage_b.bin +# (config/examples/s32g2-goldbox.config), or program it into a NOR partition. +set -e +ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +KERNEL="${1:?usage: $0 [version] [key.der] }" +VERSION="${2:-1}" +KEY="${3:-$ROOT/wolfboot_signing_private_key.der}" +DTB="${4:?the device tree is required: $0 [version] [key.der] }" +ITS="$ROOT/hal/s32g2.its" +SIGN_OPTS="${SIGN_OPTS:---ecc384 --sha384}" + +[ -f "$KERNEL" ] || { echo "!! kernel not found: $KERNEL"; exit 1; } +[ -f "$KEY" ] || { echo "!! signing key not found: $KEY"; exit 1; } +command -v mkimage >/dev/null || { echo "!! mkimage not found (u-boot-tools)"; exit 1; } + +case "$KERNEL" in + *.gz) cp -L "$KERNEL" "$ROOT/Image.gz" ;; + *) gzip -9 -c "$KERNEL" > "$ROOT/Image.gz" ;; +esac +[ -f "$DTB" ] || { echo "!! dtb not found: $DTB"; exit 1; } +cp -L "$DTB" "$ROOT/s32g2.dtb" +echo "== building FIT ($ITS) ==" +mkimage -f "$ITS" "$ROOT/s32g2-fitImage" >/dev/null +echo "== signing the FIT (v$VERSION, $SIGN_OPTS) ==" +( cd "$ROOT" && ./tools/keytools/sign $SIGN_OPTS s32g2-fitImage "$KEY" "$VERSION" >/dev/null ) +OUT="$ROOT/s32g2-fitImage_v${VERSION}_signed.bin" +[ -f "$OUT" ] || { echo "!! signing failed"; exit 1; } +ls -l "$OUT" diff --git a/tools/scripts/s32g2/s32g2_mkimage.py b/tools/scripts/s32g2/s32g2_mkimage.py new file mode 100755 index 0000000000..45fcfe43b7 --- /dev/null +++ b/tools/scripts/s32g2/s32g2_mkimage.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +# s32g2_mkimage.py - wrap wolfboot.bin for the S32G2 Boot ROM. +# +# s32g2_mkimage.py [--entry 0x340A0000] +# [--qspi [--qspi-params sdr|octal]] +# +# The output is the SD/eMMC content from byte offset 0x1000 (dd it with +# seek=8): the Image Vector Table, a no-op DCD and the Application Boot Code +# Header, with the code 0x200-aligned behind it. The layout and header fields +# are the ones the stock BSP image uses (U-Boot's mkimage -T s32gen1image / +# s32ccimage), so the Boot ROM finds them where it always has. +# +# With --qspi the output is the QuadSPI NOR content from offset 0: the IVT at +# 0, the 0x200-byte QuadSPI parameter block the ROM programs the controller +# with at 0x200, then the DCD and the application. "sdr" (default) reads the +# flash in plain SPI mode, which leaves it in the mode wolfBoot's own +# driver expects; "octal" is the stock BSP block for the MX25UW51245G, which +# switches the flash to octal DTR at 200 MHz. +# +# The ROM copies code_length bytes from right after the 0x40-byte header to +# ram_start and jumps to ram_entry. Entry must land on a 0x200-aligned file +# offset, so ram_start is entry minus the pad between the header and the code. +# +# Copyright (C) 2026 wolfSSL Inc. GPLv3. + +import struct +import sys + +SD_IVT_OFFSET = 0x1000 # where this file lands on the card +IVT_OFF = 0x0000 # offsets below are relative to the IVT +QSPI_PARAMS_OFF = 0x0200 # QuadSPI boot only, fixed by the ROM +DCD_OFF = 0x0200 +QSPI_DCD_OFF = 0x0400 +APP_OFF = 0x2200 # application boot code header +CODE_ALIGN = 0x200 +APP_HDR_SIZE = 0x40 + +SRAM_BASE = 0x34000000 +SRAM_SIZE = 0x00800000 +# Boot ROM working areas while it loads from SD; the image must not be copied +# over them (U-Boot tools/s32gen1image.c). +SD_RESERVED = [(0x34008000, 0x34078000), (0x343FF000, 0x34400000)] + + +def ivt(dcd_ptr, app_ptr): + b = bytearray(0x100) + b[0] = 0xD1 # tag + b[1:3] = (0x100).to_bytes(2, "big") # length, big-endian + b[3] = 0x60 # version + struct.pack_into(" SRAM_BASE + SRAM_SIZE: + sys.stderr.write("image does not fit in SRAM\n") + return 1 + for lo, hi in SD_RESERVED: + if ram_start < hi and end > lo: + sys.stderr.write("image overlaps the Boot ROM reserved SRAM %#x-%#x\n" + % (lo, hi)) + return 1 + + out = bytearray(code_aligned + length - pad) + out[IVT_OFF:IVT_OFF + 0x100] = ivt(base + dcd_off, base + APP_OFF) + if qspi: + out[QSPI_PARAMS_OFF:QSPI_PARAMS_OFF + 0x200] = qspi_params(params) + out[dcd_off:dcd_off + 0x14] = dcd_nop() + out[APP_OFF:APP_OFF + APP_HDR_SIZE] = app_header(ram_start, entry, length) + out[code_aligned:code_aligned + len(code)] = code + with open(argv[2], "wb") as f: + f.write(out) + print("%s: %d bytes, ram_start %#x entry %#x code_length %#x (%s offset %#x)" + % (argv[2], len(out), ram_start, entry, length, + "QSPI %s" % params if qspi else "SD", base)) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv))