|
| 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