Skip to content

Commit 869ae18

Browse files
committed
imx95_a55: SCMI cold-init of uSDHC2 and M7 power domain for a cold BL33 start
1 parent 8781a13 commit 869ae18

6 files changed

Lines changed: 427 additions & 1 deletion

File tree

‎arch.mk‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,23 @@ ifeq ($(ARCH),AARCH64)
242242
ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),)
243243
DISK_DRIVER=usdhc
244244
OBJS+=hal/imx95_usdhc.o
245+
# SCMI cold-init of uSDHC2 (clock+pinmux) when no prior stage did it.
246+
ifeq ($(IMX95_SCMI_COLD_INIT),1)
247+
CFLAGS+=-DIMX95_SCMI_COLD_INIT
248+
endif
249+
endif
250+
# SCMI M7 power-on + TCM ECC scrub (U-Boot's power_on_m7 equivalent) for a
251+
# Cortex-M7 that Linux launches later. Independent of the disk driver.
252+
ifeq ($(IMX95_INIT_M7),1)
253+
CFLAGS+=-DIMX95_INIT_M7
254+
endif
255+
# imx95_scmi.o carries the SCMI-over-MU client used by both of the above.
256+
ifneq ($(filter 1,$(IMX95_SCMI_COLD_INIT) $(IMX95_INIT_M7)),)
257+
OBJS+=hal/imx95_scmi.o
258+
endif
259+
# Verbose per-step trace of the SCMI cold-init and SD bring-up.
260+
ifeq ($(IMX95_SCMI_DEBUG),1)
261+
CFLAGS+=-DIMX95_SCMI_DEBUG
245262
endif
246263
endif
247264

‎config/examples/imx95-a55.config‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ CROSS_COMPILE?=aarch64-none-elf-
6262
# Storage: carrier SD on uSDHC2 (hal/imx95_usdhc.c; i.MX uSDHC, not SDHCI).
6363
# Boot containers stay on eMMC.
6464
DISK_SDCARD?=1
65+
# Cold start: with no U-Boot ahead of wolfBoot the uSDHC2 clock and pinmux are
66+
# still owned by the System Manager and must be requested over SCMI.
67+
IMX95_SCMI_COLD_INIT?=1
68+
# Power the Cortex-M7 mix and scrub its TCM, as U-Boot's board init does, for
69+
# boards that launch an M7 image from Linux.
70+
IMX95_INIT_M7?=1
6571
# A/B slots by 0-based index into the parsed partition table.
6672
CFLAGS_EXTRA+=-DBOOT_PART_A=1 -DBOOT_PART_B=2
6773
CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x10000

‎docs/Targets.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9453,7 +9453,8 @@ Notes:
94539453
- The FIT DTB gets `/chosen` bootargs, initrd properties and a `/memory` node from `hal_dts_fixup()`; deployment DTBs commonly ship without `/memory` (the bootloader is expected to add it) and the kernel hangs silently without one.
94549454
- The EL2 exit is a fused asm routine (flush, DAIF mask, TLB invalidate, jump) that touches no memory after the D-cache goes off; set/way cleaning does not reach the A55 cluster's DSU system cache, so payload ranges are also cleaned by VA.
94559455
- Use plain `earlycon` (DTB-derived); an explicit `earlycon=lpuart32,mmio32,<addr>` uses the wrong register layout on i.MX and silences all console output.
9456-
- Storage requires the prior stage to have brought up uSDHC2 (clock/pinmux/card power are System Manager-owned via SCMI); a cold-boot SCMI client is follow-on work.
9456+
- uSDHC2's clock, pinmux and card power are owned by the M33 System Manager, and its pad registers data-abort on direct access from BL33. `IMX95_SCMI_COLD_INIT=1` brings them up over SCMI (`hal/imx95_scmi.c`), which is what lets wolfBoot boot from SD on a cold power-on rather than only after a stage that already initialized the controller.
9457+
- `IMX95_INIT_M7=1` powers the Cortex-M7 mix and scrubs its TCM for ECC, matching what U-Boot's board init does, for boards that launch an M7 image from Linux later.
94579458
94589459
## TI C2000 C28x (LAUNCHXL-F28P55X)
94599460

‎hal/imx95_a55.c‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040
#include "printf.h"
4141
#include "hal/imx95_a55.h"
4242

43+
#ifdef IMX95_INIT_M7
44+
/* SCMI M7 power-domain on + TCM ECC scrub (hal/imx95_scmi.c). */
45+
extern int imx95_m7_tcm_init(void);
46+
#endif
47+
4348
/* x0 at entry (captured by the .boot stub); .data so BSS clear spares it. */
4449
volatile uint64_t boot_handoff_x0 = 0xFFFFFFFFFFFFFFFFULL;
4550

@@ -275,6 +280,12 @@ void hal_init(void)
275280
wolfBoot_printf("imx95: refusing to boot with an unsafe load window\n");
276281
wolfBoot_panic();
277282
}
283+
#ifdef IMX95_INIT_M7
284+
/* U-Boot powers up the M7 mix and scrubs its TCM (ECC) at board init; as
285+
* its BL33 replacement wolfBoot must too. Non-fatal: the A55 boot proceeds
286+
* even if the M7 bring-up fails. */
287+
(void)imx95_m7_tcm_init();
288+
#endif
278289
}
279290

280291
/* src/boot_aarch64_start.S: clean+invalidate by VA. */
@@ -329,6 +340,14 @@ void* hal_get_update_address(void)
329340

330341
#if defined(__WOLFBOOT) && defined(LINUX_BOOTARGS)
331342
#include "fdt.h"
343+
344+
/* Primary ethernet (end0) MAC written into the DTB. Default is the Toradex
345+
* SMARC iMX95 module's address (OUI 00:14:2d; low bytes are the module serial),
346+
* read from its config block. Override with -DIMX95_ETH0_MAC={...} per board. */
347+
#ifndef IMX95_ETH0_MAC
348+
#define IMX95_ETH0_MAC { 0x00, 0x14, 0x2d, 0x88, 0x8d, 0x20 }
349+
#endif
350+
332351
/* Fix up /chosen and /memory on the (already verified) FIT DTB. */
333352
int hal_dts_fixup(void* dts_addr, uint32_t capacity)
334353
{
@@ -385,6 +404,25 @@ int hal_dts_fixup(void* dts_addr, uint32_t capacity)
385404
return off;
386405
}
387406

407+
/* Ethernet MAC: the ENETC ports are PCIe-enumerated and carry no
408+
* local-mac-address in the OS DTB (U-Boot normally programs it from the
409+
* Toradex config block), so Linux would pick a random MAC each boot and the
410+
* DHCP address would float. Set the primary port (ethernet0 = end0) to the
411+
* module's address. Non-fatal: networking is not boot-critical. */
412+
{
413+
static const uint8_t eth0_mac[6] = IMX95_ETH0_MAC;
414+
off = fdt_path_offset(&ctx, "/soc/pcie@4ca00000/ethernet@0,0");
415+
if (off >= 0) {
416+
ret = fdt_setprop(&ctx, off, "local-mac-address", eth0_mac,
417+
sizeof(eth0_mac));
418+
if (ret != 0)
419+
wolfBoot_printf("FDT: eth0 MAC fixup failed (%d)\n", ret);
420+
}
421+
else {
422+
wolfBoot_printf("FDT: eth0 node not found (%d)\n", off);
423+
}
424+
}
425+
388426
/* Last DTB write before the jump, after hal_prepare_boot()'s flush;
389427
* set/way cleaning in do_boot misses the DSU system cache, so clean
390428
* the blob by VA here or the kernel can read stale /chosen edits. */

‎hal/imx95_scmi.c‎

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/* imx95_scmi.c
2+
*
3+
* Minimal SCMI-over-MU client for the NXP i.MX95 Cortex-A55 (BL33).
4+
*
5+
* Copyright (C) 2026 wolfSSL Inc.
6+
*
7+
* This file is part of wolfBoot.
8+
*
9+
* wolfBoot is free software; you can redistribute it and/or modify
10+
* it under the terms of the GNU General Public License as published by
11+
* the Free Software Foundation; either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* wolfBoot is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program; if not, write to the Free Software
21+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
22+
*/
23+
24+
/* With no U-Boot ahead of wolfBoot, clocks, pinmux and power domains are still
25+
* owned by the M33 System Manager and must be requested over SCMI; the pad
26+
* registers data-abort on direct access from BL33. This is the smallest client
27+
* that does it: SMT transport over MU2, then clock, pinctrl and power calls.
28+
* Cross-checked against U-Boot drivers/firmware/scmi, clk/clk_scmi.c and
29+
* pinctrl/pinctrl-scmi.c. */
30+
31+
#include <stdint.h>
32+
#include "printf.h"
33+
#include "hal/imx95_a55.h"
34+
35+
#if defined(IMX95_SCMI_COLD_INIT) || defined(IMX95_INIT_M7)
36+
37+
#define MU2_BASE 0x445B0000UL
38+
#define MU2_GCR (MU2_BASE + 0x114) /* set BIT0: ring A2P doorbell */
39+
#define MU2_GSR (MU2_BASE + 0x118) /* BIT0: GIR0 ack (write 1 to clr) */
40+
#define SCMI_SHMEM 0x445B1000UL /* scmi_buf0, 1 KiB */
41+
42+
/* SMT header offsets within the shared buffer */
43+
#define SMT_CHAN_STATUS 0x04
44+
#define SMT_FLAGS 0x10
45+
#define SMT_LENGTH 0x14
46+
#define SMT_MSG_HEADER 0x18
47+
#define SMT_PAYLOAD 0x1C
48+
#define CHAN_FREE 0x1UL
49+
50+
#define SCMI_PROTO_CLOCK 0x14
51+
#define SCMI_PROTO_PINCTRL 0x19
52+
#define SCMI_PROTO_POWER 0x11
53+
#define CLOCK_CONFIG_SET 0x7
54+
#define PINCTRL_CONFIG_SET 0x6
55+
#define PWD_STATE_SET 0x4
56+
#define PINCTRL_TYPE_MUX 192
57+
#define PINCTRL_TYPE_CONFIG 193
58+
59+
/* Power domain id for the M7 mix (dt-bindings/power/fsl,imx95-power.h). */
60+
#define IMX95_PD_M7 17
61+
62+
/* Pad ALT mode passed as the SCMI MUX value: 0 = uSDHC2 function, 5 = GPIO3. */
63+
#define PAD_ALT_USDHC2 0
64+
#define PAD_ALT_GPIO 5
65+
66+
#define IMX95_CLK_USDHC2 159 /* IMX95_CCM_NUM_CLK_SRC(41) + 118 */
67+
68+
#define SCMI_TIMEOUT 2000000
69+
70+
/* Build with -DIMX95_SCMI_DEBUG for a per-step trace of the cold-init. */
71+
#ifdef IMX95_SCMI_DEBUG
72+
#define SCMI_DBG(...) wolfBoot_printf(__VA_ARGS__)
73+
#else
74+
#define SCMI_DBG(...) do { } while (0)
75+
#endif
76+
77+
static inline uint32_t rd(uintptr_t a) { return *(volatile uint32_t*)a; }
78+
static inline void wr(uintptr_t a, uint32_t v) { *(volatile uint32_t*)a = v; }
79+
80+
/* One synchronous SCMI command. payload[] holds nwords input words; on return
81+
* it holds the response words (word 0 = SCMI status). Returns 0 on success. */
82+
static int scmi_cmd(uint32_t proto, uint32_t msg_id,
83+
uint32_t* payload, uint32_t nwords)
84+
{
85+
uintptr_t sh = SCMI_SHMEM;
86+
uint32_t n, i;
87+
88+
/* Wait for the channel to be free. */
89+
for (n = 0; n < SCMI_TIMEOUT; n++) {
90+
if (rd(sh + SMT_CHAN_STATUS) & CHAN_FREE)
91+
break;
92+
}
93+
if (n == SCMI_TIMEOUT)
94+
return -1;
95+
96+
wr(sh + SMT_FLAGS, 0); /* poll, no interrupt */
97+
wr(sh + SMT_LENGTH, 4 + nwords * 4); /* header word + payload */
98+
wr(sh + SMT_MSG_HEADER, (proto << 10) | (msg_id & 0xFF));
99+
for (i = 0; i < nwords; i++)
100+
wr(sh + SMT_PAYLOAD + i * 4, payload[i]);
101+
102+
/* Hand the channel to the SM and ring the doorbell. */
103+
wr(sh + SMT_CHAN_STATUS, rd(sh + SMT_CHAN_STATUS) & ~CHAN_FREE);
104+
wr(MU2_GCR, rd(MU2_GCR) | 0x1);
105+
106+
/* The SM sets FREE again when the response is in place. */
107+
for (n = 0; n < SCMI_TIMEOUT; n++) {
108+
if (rd(sh + SMT_CHAN_STATUS) & CHAN_FREE)
109+
break;
110+
}
111+
if (n == SCMI_TIMEOUT)
112+
return -2;
113+
wr(MU2_GSR, 0x1); /* clear GIR0 ack */
114+
115+
for (i = 0; i < nwords; i++)
116+
payload[i] = rd(sh + SMT_PAYLOAD + i * 4);
117+
return (int)payload[0]; /* SCMI status, 0 = OK */
118+
}
119+
120+
#if defined(DISK_SDCARD) && defined(IMX95_SCMI_COLD_INIT)
121+
static int scmi_clock_enable(uint32_t clock_id)
122+
{
123+
uint32_t p[3];
124+
p[0] = clock_id;
125+
p[1] = 1; /* attributes: enable */
126+
p[2] = 0; /* oem_config_val */
127+
return scmi_cmd(SCMI_PROTO_CLOCK, CLOCK_CONFIG_SET, p, 3);
128+
}
129+
130+
/* One pad: MUX(=mux) + CONFIG(=conf). identifier is the mux register offset/4
131+
* (the SM's pin numbering); function_id 0xFFFFFFFF, attributes = ncfgs<<2. */
132+
static int scmi_pin_config(uint32_t mux_ofs, uint32_t mux, uint32_t conf)
133+
{
134+
uint32_t p[7];
135+
p[0] = mux_ofs / 4; /* identifier */
136+
p[1] = 0xFFFFFFFF; /* function_id */
137+
p[2] = (uint32_t)(2 << 2); /* attributes: 2 configs */
138+
p[3] = PINCTRL_TYPE_MUX; p[4] = mux;
139+
p[5] = PINCTRL_TYPE_CONFIG; p[6] = conf;
140+
return scmi_cmd(SCMI_PROTO_PINCTRL, PINCTRL_CONFIG_SET, p, 7);
141+
}
142+
143+
/* Three uSDHC2 signals are carrier GPIOs on the SMARC, not controller pins:
144+
* their pads must first be SCMI-muxed to GPIO3 (ALT5), then driven by direct
145+
* RGPIO3 MMIO (which is permitted from BL33). Bits from the SMARC DTS:
146+
* GPIO3.0 SD2_CD_B card detect (input, active low)
147+
* GPIO3.7 SD2_RESET_B SDIO_PWR_EN (output high = card power on)
148+
* GPIO3.19 SD2_VSELECT PMIC_SD2_VSEL (output low = 3.3V, high = 1.8V) */
149+
#define RGPIO3_BASE 0x43820000UL
150+
#define RGPIO3_PDOR (RGPIO3_BASE + 0x40)
151+
#define RGPIO3_PDDR (RGPIO3_BASE + 0x54)
152+
#define GPIO3_CD 0U
153+
#define GPIO3_PWR 7U
154+
#define GPIO3_VSEL 19U
155+
156+
static void delay_loops(uint32_t loops)
157+
{
158+
volatile uint32_t d = loops;
159+
while (d-- > 0U) { }
160+
}
161+
162+
int imx95_usdhc2_cold_init(void)
163+
{
164+
/* All nine uSDHC2 pads: {mux register offset, ALT mode, pad conf}. The six
165+
* data/clk/cmd pins take the uSDHC2 function (ALT0); the three carrier GPIO
166+
* signals take GPIO3 (ALT5). Values are the SMARC usdhc2/cd/pwr-en/vsel
167+
* pinctrl groups. */
168+
static const uint32_t pads[9][3] = {
169+
{ 0x1A4, PAD_ALT_USDHC2, 0x158e }, /* SD2_CLK */
170+
{ 0x1A8, PAD_ALT_USDHC2, 0x138e }, /* SD2_CMD */
171+
{ 0x1AC, PAD_ALT_USDHC2, 0x138e }, /* SD2_DATA0 */
172+
{ 0x1B0, PAD_ALT_USDHC2, 0x138e }, /* SD2_DATA1 */
173+
{ 0x1B4, PAD_ALT_USDHC2, 0x138e }, /* SD2_DATA2 */
174+
{ 0x1B8, PAD_ALT_USDHC2, 0x138e }, /* SD2_DATA3 */
175+
{ 0x1A0, PAD_ALT_GPIO, 0x1100 }, /* SD2_CD_B -> GPIO3.0 */
176+
{ 0x1BC, PAD_ALT_GPIO, 0x011e }, /* SD2_RESET_B -> GPIO3.7 */
177+
{ 0x154, PAD_ALT_GPIO, 0x0004 }, /* SD2_VSELECT -> GPIO3.19 */
178+
};
179+
int i, ret;
180+
uint32_t pddr, pdor;
181+
182+
ret = scmi_clock_enable(IMX95_CLK_USDHC2);
183+
SCMI_DBG("scmi: clock enable(%d) -> %d\n", IMX95_CLK_USDHC2, ret);
184+
if (ret != 0) {
185+
wolfBoot_printf("scmi: uSDHC2 clock enable failed (%d)\n", ret);
186+
return ret;
187+
}
188+
189+
for (i = 0; i < 9; i++) {
190+
ret = scmi_pin_config(pads[i][0], pads[i][1], pads[i][2]);
191+
SCMI_DBG("scmi: pad 0x%x mux %d -> %d\n",
192+
(unsigned)pads[i][0], (int)pads[i][1], ret);
193+
if (ret != 0) {
194+
wolfBoot_printf("scmi: pad 0x%x config failed (%d)\n",
195+
(unsigned)pads[i][0], ret);
196+
return ret;
197+
}
198+
}
199+
200+
/* Card detect as input; voltage-select and power-enable as outputs. */
201+
pddr = rd(RGPIO3_PDDR);
202+
pddr &= ~(1U << GPIO3_CD);
203+
pddr |= (1U << GPIO3_VSEL) | (1U << GPIO3_PWR);
204+
wr(RGPIO3_PDDR, pddr);
205+
206+
/* Select 3.3V I/O (VSEL low) before powering the card. */
207+
pdor = rd(RGPIO3_PDOR);
208+
pdor &= ~(1U << GPIO3_VSEL);
209+
wr(RGPIO3_PDOR, pdor);
210+
211+
/* Power on, then wait past the regulator startup delay (DTS
212+
* startup-delay-us 20000). Caches are off here, so the loop count is
213+
* deliberately generous. */
214+
pdor |= (1U << GPIO3_PWR);
215+
wr(RGPIO3_PDOR, pdor);
216+
delay_loops(40000000U);
217+
218+
SCMI_DBG("scmi: GPIO3 PDDR=0x%x PDOR=0x%x\n",
219+
(unsigned)rd(RGPIO3_PDDR), (unsigned)rd(RGPIO3_PDOR));
220+
wolfBoot_printf("scmi: uSDHC2 clock+pinmux+power up\n");
221+
return 0;
222+
}
223+
#endif /* DISK_SDCARD && IMX95_SCMI_COLD_INIT */
224+
225+
#ifdef IMX95_INIT_M7
226+
/* Cortex-M7 TCM system-view bases and size (256 KiB each at TCM_SIZE=000b). */
227+
#define IMX95_M7_ITCM_SYS 0x203C0000UL
228+
#define IMX95_M7_DTCM_SYS 0x20400000UL
229+
#define IMX95_M7_TCM_BYTES 0x40000UL
230+
231+
/* Match U-Boot's power_on_m7(): power up the M7 mix over SCMI, then scrub its
232+
* TCM so that never-written words carry valid ECC. U-Boot does this at board
233+
* init, so wolfBoot must too when it replaces U-Boot as BL33 on a board whose
234+
* Cortex-M7 is launched later by Linux remoteproc. */
235+
int imx95_m7_tcm_init(void)
236+
{
237+
uint32_t p[3];
238+
volatile uint32_t *w;
239+
uint32_t i, words;
240+
int ret;
241+
242+
/* SCMI power domain: STATE_SET {flags=0, domain_id=M7, pstate=0=on}. */
243+
p[0] = 0;
244+
p[1] = IMX95_PD_M7;
245+
p[2] = 0;
246+
ret = scmi_cmd(SCMI_PROTO_POWER, PWD_STATE_SET, p, 3);
247+
SCMI_DBG("scmi: M7 power-on -> %d\n", ret);
248+
if (ret != 0) {
249+
wolfBoot_printf("scmi: M7 power domain on failed (%d)\n", ret);
250+
return ret;
251+
}
252+
253+
/* Scrub ITCM + DTCM to initialize ECC (word writes; MMU is off). */
254+
words = (uint32_t)(IMX95_M7_TCM_BYTES / 4U);
255+
w = (volatile uint32_t *)IMX95_M7_ITCM_SYS;
256+
for (i = 0; i < words; i++)
257+
w[i] = 0U;
258+
w = (volatile uint32_t *)IMX95_M7_DTCM_SYS;
259+
for (i = 0; i < words; i++)
260+
w[i] = 0U;
261+
262+
wolfBoot_printf("scmi: M7 powered, TCM ECC initialized\n");
263+
return 0;
264+
}
265+
#endif /* IMX95_INIT_M7 */
266+
267+
#endif /* IMX95_SCMI_COLD_INIT || IMX95_INIT_M7 */

0 commit comments

Comments
 (0)