diff --git a/.github/workflows/test-build-aarch64.yml b/.github/workflows/test-build-aarch64.yml index 2ace6bcada..4865295430 100644 --- a/.github/workflows/test-build-aarch64.yml +++ b/.github/workflows/test-build-aarch64.yml @@ -95,6 +95,9 @@ jobs: - name: Select config run: | cp ${{inputs.config-file}} .config + if [ -n "$EXTRA_CFLAGS" ]; then + printf 'CFLAGS_EXTRA+=%s\n' "$EXTRA_CFLAGS" >> .config + fi - name: Build tools run: | diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 411299218a..92a289f8ba 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -774,6 +774,13 @@ jobs: make-args: ZYNQMP_FSBL=1 ZYNQMP_PSU_INIT_DIR=hal/board/zynqmp DISK_SDCARD=1 psu-init-stub: true + zynqmp_phy_test: + uses: ./.github/workflows/test-build-aarch64.yml + with: + arch: aarch64 + config-file: ./config/examples/zynqmp.config + make-args: CFLAGS_EXTRA=-DWOLFBOOT_ZYNQMP_PHY_INIT + zynq7000_test: uses: ./.github/workflows/test-build.yml with: diff --git a/config/examples/zynqmp.config b/config/examples/zynqmp.config index a3ecd52024..920d1d0ef5 100644 --- a/config/examples/zynqmp.config +++ b/config/examples/zynqmp.config @@ -104,3 +104,23 @@ CFLAGS_EXTRA+=-DWOLFBOOT_SHA_BLOCK_SIZE=4096 # QSPI force IO mode (default is faster DMA mode) #CFLAGS_EXTRA+=-DGQSPI_MODE_IO + +# Optional: Ethernet PHY init over GEM MDIO. +# Replays a board-specific "mii"/"mw" register sequence (normally run from +# U-Boot) so the PHY is configured before the OS starts. wolfBoot has no +# network stack; this drives only the MDIO management plane. +#CFLAGS_EXTRA+=-DWOLFBOOT_ZYNQMP_PHY_INIT +# Defaults (hal/zynq.h) target the ZCU102 PHY (DP83867 at MDIO 0x0C on GEM3) +# and just read the PHY ID. For another board, keep its values in a small +# header and select it with one line (this keeps the efficient step array): +#CFLAGS_EXTRA+=-DZYNQMP_PHY_INIT_HEADER='"myboard_phy.h"' +# where myboard_phy.h #defines any of: +# ZYNQMP_GEM_BASE GEM register base (default 0xFF0E0000 = GEM3) +# ZYNQMP_PHY_ADDR PHY MDIO address (default 0x0C) +# ZYNQMP_PHY_GPIO_ADDR PL register poked by a GPIO step; 0 disables it +# ZYNQMP_GEM_MDC_DIV NWCFG MDC divisor selector (default 5 = pclk/96) +# ZYNQMP_PHY_INIT_STEPS the {op,arg0,arg1} sequence rows +# Simple scalars can also be set directly, e.g.: +#CFLAGS_EXTRA+=-DZYNQMP_GEM_BASE=0xFF0B0000UL +# A PHY behind the PL only responds once the FPGA bitstream is loaded, so the +# boot image (BOOT.BIN) must include it (bootgen [destination_device=pl]). diff --git a/config/examples/zynqmp_sdcard.config b/config/examples/zynqmp_sdcard.config index 3eb8beb32a..b13ad03cf7 100644 --- a/config/examples/zynqmp_sdcard.config +++ b/config/examples/zynqmp_sdcard.config @@ -159,3 +159,25 @@ WOLFBOOT_SECTOR_SIZE=0x1000 #CFLAGS_EXTRA+=-DDEBUG_GPT # Disk read/write test at boot #CFLAGS_EXTRA+=-DDISK_TEST + +# ============================================================================ +# Optional: Ethernet PHY init over GEM MDIO +# ============================================================================ +# Replays a board-specific "mii"/"mw" register sequence (normally run from +# U-Boot) so the PHY is configured before the OS starts. wolfBoot has no +# network stack; this drives only the MDIO management plane. +#CFLAGS_EXTRA+=-DWOLFBOOT_ZYNQMP_PHY_INIT +# Defaults (hal/zynq.h) target the ZCU102 PHY (DP83867 at MDIO 0x0C on GEM3) +# and just read the PHY ID. For another board, keep its values in a small +# header and select it with one line (this keeps the efficient step array): +#CFLAGS_EXTRA+=-DZYNQMP_PHY_INIT_HEADER='"myboard_phy.h"' +# where myboard_phy.h #defines any of: +# ZYNQMP_GEM_BASE GEM register base (default 0xFF0E0000 = GEM3) +# ZYNQMP_PHY_ADDR PHY MDIO address (default 0x0C) +# ZYNQMP_PHY_GPIO_ADDR PL register poked by a GPIO step; 0 disables it +# ZYNQMP_GEM_MDC_DIV NWCFG MDC divisor selector (default 5 = pclk/96) +# ZYNQMP_PHY_INIT_STEPS the {op,arg0,arg1} sequence rows +# Simple scalars can also be set directly, e.g.: +#CFLAGS_EXTRA+=-DZYNQMP_GEM_BASE=0xFF0B0000UL +# A PHY behind the PL only responds once the FPGA bitstream is loaded, so the +# boot image (BOOT.BIN) must include it (bootgen [destination_device=pl]). diff --git a/docs/Targets.md b/docs/Targets.md index 35324a7163..7b08a6118d 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -3795,6 +3795,10 @@ Key configuration options: - `HASH=SHA3` - SHA3-384 hashing - `ELF=1` - ELF loading support +### Ethernet PHY init (optional) + +Opt-in (off by default), wolfBoot can replay a board's U-Boot Ethernet PHY register sequence over the GEM MDIO management plane so the PHY is ready before the OS runs. Enable with `CFLAGS_EXTRA+=-DWOLFBOOT_ZYNQMP_PHY_INIT`. The default targets the ZCU102 on-board PHY (TI DP83867 at MDIO `0x0C` on GEM3, `0xFF0E0000`) and just reads the PHY ID as a diagnostic (printed with `DEBUG_UART=1`). A board supplies its own sequence by keeping its values in a small header selected with one line, `CFLAGS_EXTRA+=-DZYNQMP_PHY_INIT_HEADER='"myboard_phy.h"'`, where that header `#define`s any of `ZYNQMP_GEM_BASE`, `ZYNQMP_PHY_ADDR`, `ZYNQMP_PHY_GPIO_ADDR`, `ZYNQMP_GEM_MDC_DIV`, and the `{op, arg0, arg1}` step array `ZYNQMP_PHY_INIT_STEPS`; scalars can also be set directly with `-D`, and anything omitted falls back to the ZCU102 defaults (see `hal/zynq.h` and the commented example in `config/examples/zynqmp.config`). Where the PHY is behind the PL, the boot image must include the FPGA bitstream (bootgen `[destination_device=pl] system.bit`) or the transactions are no-ops. + ### Building with Xilinx tools (Vitis IDE) See [IDE/XilinxSDK/README.md](/IDE/XilinxSDK/README.md) for using Xilinx IDE diff --git a/hal/zynq.c b/hal/zynq.c index a7a0c5b3f0..7717e866d9 100644 --- a/hal/zynq.c +++ b/hal/zynq.c @@ -2076,6 +2076,106 @@ static void zynqmp_enable_cci(void) } #endif /* ZYNQMP_ENABLE_CCI */ +#ifdef WOLFBOOT_ZYNQMP_PHY_INIT +/* Minimal GEM MDIO management engine, used only to run a board-specific PHY + * init sequence before boot. See hal/zynq.h for the register/config macros. */ + +static int gem_mdio_wait_idle(void) +{ + uint32_t spin; + for (spin = 0; spin < 100000; spin++) { + if (GEM_NWSR & GEM_NWSR_PHY_IDLE) { + return 0; + } + } + return -1; +} + +static int gem_mdio_write(uint8_t phy, uint8_t reg, uint16_t val) +{ + if (gem_mdio_wait_idle() != 0) { + return -1; + } + GEM_PHYMNTNC = GEM_PHYMNTNC_CLAUSE22 | GEM_PHYMNTNC_OP_W + | (((uint32_t)phy & 0x1FU) << 23) + | (((uint32_t)reg & 0x1FU) << 18) + | (uint32_t)val; + if (gem_mdio_wait_idle() != 0) { + return -2; + } + return 0; +} + +static int gem_mdio_read(uint8_t phy, uint8_t reg, uint16_t *out) +{ + if (gem_mdio_wait_idle() != 0) { + return -1; + } + GEM_PHYMNTNC = GEM_PHYMNTNC_CLAUSE22 | GEM_PHYMNTNC_OP_R + | (((uint32_t)phy & 0x1FU) << 23) + | (((uint32_t)reg & 0x1FU) << 18); + if (gem_mdio_wait_idle() != 0) { + return -2; + } + *out = (uint16_t)(GEM_PHYMNTNC & 0xFFFFU); + return 0; +} + +/* Run the configurable PHY init sequence (default: the board's U-Boot flow). + * Best-effort: MDIO errors are reported but never block boot. */ +static void zynq_phy_init(void) +{ + static const struct { + uint8_t op; + uint8_t arg0; + uint16_t arg1; + } steps[] = { ZYNQMP_PHY_INIT_STEPS }; + uint32_t i; + uint16_t rval; + int ret; + + wolfBoot_printf("ZynqMP PHY init (addr 0x%02x)\n", (int)ZYNQMP_PHY_ADDR); + + /* Enable the MDIO management port with a safe MDC divisor. */ + GEM_NWCFG = (GEM_NWCFG & ~(0x7UL << GEM_NWCFG_MDCDIV_SHIFT)) + | (((uint32_t)ZYNQMP_GEM_MDC_DIV & 0x7U) << GEM_NWCFG_MDCDIV_SHIFT); + GEM_NWCTRL |= GEM_NWCTRL_MDEN; + + for (i = 0; i < (sizeof(steps) / sizeof(steps[0])); i++) { + switch (steps[i].op) { + case ZYNQMP_PHY_OP_GPIO: + if (ZYNQMP_PHY_GPIO_ADDR != 0) { + *((volatile uint32_t*)ZYNQMP_PHY_GPIO_ADDR) = + (uint32_t)steps[i].arg1; + hal_delay_ms(1); + } + break; + case ZYNQMP_PHY_OP_WR: + ret = gem_mdio_write(ZYNQMP_PHY_ADDR, steps[i].arg0, steps[i].arg1); + if (ret != 0) { + wolfBoot_printf("PHY write reg 0x%02x failed (%d)\n", + (int)steps[i].arg0, ret); + } + break; + case ZYNQMP_PHY_OP_RD: + rval = 0; + ret = gem_mdio_read(ZYNQMP_PHY_ADDR, steps[i].arg0, &rval); + if (ret != 0) { + wolfBoot_printf("PHY read reg 0x%02x failed (%d)\n", + (int)steps[i].arg0, ret); + } + else { + wolfBoot_printf("PHY reg 0x%02x = 0x%04x\n", + (int)steps[i].arg0, (int)rval); + } + break; + default: + break; + } + } +} +#endif /* WOLFBOOT_ZYNQMP_PHY_INIT */ + /* public HAL functions */ void hal_init(void) { @@ -2110,6 +2210,10 @@ void hal_init(void) wolfBoot_printf("Build: %s %s\n", __DATE__, __TIME__); #endif +#ifdef WOLFBOOT_ZYNQMP_PHY_INIT + zynq_phy_init(); +#endif + #if defined(EXT_FLASH) && (EXT_FLASH == 1) qspi_init(); #endif diff --git a/hal/zynq.h b/hal/zynq.h index ed8809a304..9e6661e965 100644 --- a/hal/zynq.h +++ b/hal/zynq.h @@ -436,6 +436,85 @@ #define SD_CONFIG_REG2_SD0_SLOTTYPE_MASK 0x00003000UL +#ifdef WOLFBOOT_ZYNQMP_PHY_INIT +/* Ethernet PHY init over the GEM MDIO management interface. + * + * wolfBoot has no Ethernet stack; this brings up only the MDIO management + * plane so a board-specific register sequence (normally run from U-Boot's + * "mii"/"mw" commands) can configure the PHY before the OS starts. Every + * magic value below is behind an #ifndef so a board can override it from + * CFLAGS_EXTRA. Register layout mirrors the Cadence GEM driver in the + * separate wolfIP project (src/port/amd/common/gem_regs.h there). + * + * The defaults target the ZCU102 on-board PHY (TI DP83867 at MDIO address + * 0x0C on GEM3) and just read the PHY ID as a diagnostic. + * + * A board keeps its own values (GEM base, PHY address, PL poke, and the + * ZYNQMP_PHY_INIT_STEPS sequence) in a small header and selects it with one + * config line, which keeps the efficient step array without editing this file: + * CFLAGS_EXTRA+=-DZYNQMP_PHY_INIT_HEADER='"myboard_phy.h"' + * That header just #defines the macros it wants to change; anything it omits + * falls back to the ZCU102 defaults below. Individual scalars can also be set + * directly with -D. Where the PHY sits behind the PL, the boot image must + * include the FPGA bitstream so the FSBL configures it before this runs, + * otherwise the transactions are no-ops. */ + +#ifdef ZYNQMP_PHY_INIT_HEADER +#include ZYNQMP_PHY_INIT_HEADER +#endif + +/* GEM instance used for MDIO. Default is GEM3 (ZCU102 on-board PHY). */ +#ifndef ZYNQMP_GEM_BASE +#define ZYNQMP_GEM_BASE 0xFF0E0000UL /* GEM3 */ +#endif +#define GEM_NWCTRL (*((volatile uint32_t*)(ZYNQMP_GEM_BASE + 0x000))) +#define GEM_NWCFG (*((volatile uint32_t*)(ZYNQMP_GEM_BASE + 0x004))) +#define GEM_NWSR (*((volatile uint32_t*)(ZYNQMP_GEM_BASE + 0x008))) +#define GEM_PHYMNTNC (*((volatile uint32_t*)(ZYNQMP_GEM_BASE + 0x034))) + +#define GEM_NWCTRL_MDEN (1UL << 4) /* enable MDIO management port */ +#define GEM_NWSR_PHY_IDLE (1UL << 2) /* MDIO logic idle */ +#define GEM_NWCFG_MDCDIV_SHIFT 18 /* NWCFG[20:18] MDC clock divisor */ +#define GEM_PHYMNTNC_CLAUSE22 0x40020000UL +#define GEM_PHYMNTNC_OP_R (2UL << 28) +#define GEM_PHYMNTNC_OP_W (1UL << 28) + +/* MDC divisor selector. 5 => pclk/96, keeping MDC below the 2.5 MHz max. */ +#ifndef ZYNQMP_GEM_MDC_DIV +#define ZYNQMP_GEM_MDC_DIV 5 +#endif + +/* PHY MDIO address (clause-22). Default 0x0C = ZCU102 on-board DP83867. */ +#ifndef ZYNQMP_PHY_ADDR +#define ZYNQMP_PHY_ADDR 0x0C +#endif + +/* Optional PL register address poked by a ZYNQMP_PHY_OP_GPIO step. Default 0 + * disables the poke (ZCU102 has no such register). A board can set this to its + * own PL address; note a PL address only responds once the FPGA bitstream is + * loaded. */ +#ifndef ZYNQMP_PHY_GPIO_ADDR +#define ZYNQMP_PHY_GPIO_ADDR 0 +#endif + +/* Op codes for the configurable init sequence. */ +#define ZYNQMP_PHY_OP_GPIO 0 /* write arg1 to ZYNQMP_PHY_GPIO_ADDR */ +#define ZYNQMP_PHY_OP_WR 1 /* MDIO write: reg=arg0, val=arg1 */ +#define ZYNQMP_PHY_OP_RD 2 /* MDIO read: reg=arg0 (arg1 ignored) */ + +/* Default sequence: read the PHY ID registers (2, 3) as a diagnostic. With + * DEBUG_UART=1 this prints the ID and confirms MDIO reached the PHY. Override + * ZYNQMP_PHY_INIT_STEPS with {op, arg0, arg1} rows to replay a board's own + * "mii"/"mw" sequence (ZYNQMP_PHY_OP_WR writes, then an optional + * ZYNQMP_PHY_OP_GPIO poke of ZYNQMP_PHY_GPIO_ADDR). */ +#ifndef ZYNQMP_PHY_INIT_STEPS +#define ZYNQMP_PHY_INIT_STEPS \ + { ZYNQMP_PHY_OP_RD, 0x02, 0 }, \ + { ZYNQMP_PHY_OP_RD, 0x03, 0 } +#endif +#endif /* WOLFBOOT_ZYNQMP_PHY_INIT */ + + /* Configuration Security Unit (CSU) */ /* Triple-Dedundant MicroBlaze processor */ /* 128 KB CSU ROM (immutable) */