[PATCH RFC 5/9] arm: meson: initial u-boot SPL support for GX SoCs
Ferass El Hafidi
funderscore at postmarketos.org
Mon Sep 8 18:53:57 CEST 2025
On Mon Sep 8, 2025 at 8:24 AM UTC, Neil Armstrong via groups.io wrote:
> On 07/09/2025 16:36, Ferass El Hafidi wrote:
>> Add initial boilerplate for U-Boot SPL support on Amlogic.
>>
>> Signed-off-by: Ferass El Hafidi <funderscore at postmarketos.org>
>> ---
>> arch/arm/include/asm/arch-meson/clock-gx.h | 1 +
>> arch/arm/include/asm/arch-meson/gx.h | 36 ++++
>> arch/arm/mach-meson/Kconfig | 42 ++++-
>> arch/arm/mach-meson/Makefile | 5 +
>> arch/arm/mach-meson/board-common.c | 11 ++
>> arch/arm/mach-meson/spl-gx.c | 278 +++++++++++++++++++++++++++++
>> arch/arm/mach-meson/spl.c | 123 +++++++++++++
>> 7 files changed, 495 insertions(+), 1 deletion(-)
>>
> <...>
>> diff --git a/arch/arm/mach-meson/spl-gx.c b/arch/arm/mach-meson/spl-gx.c
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..65b301bc9ed89b7ca2156b7fa672ed4ef3f8633b
>> --- /dev/null
>> +++ b/arch/arm/mach-meson/spl-gx.c
>> @@ -0,0 +1,278 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Portions Copyright (C) 2015, Amlogic, Inc. All rights reserved.
>> + * Copyright (C) 2023, Ferass El Hafidi <funderscore at postmarketos.org>
>> + */
>> +#include <hang.h>
>> +#include <image.h>
>> +#include <spl.h>
>> +#include <vsprintf.h>
>> +#include <asm/io.h>
>> +#include <asm/arch/boot.h>
>> +#include <asm/arch/clock-gx.h>
>> +#include <asm/arch/gx.h>
>> +#include <linux/delay.h>
>> +
>> +/* Meson GX SPL code */
>> +
>> +#if CONFIG_IS_ENABLED(FIT_IMAGE_POST_PROCESS)
>> +#if defined(CONFIG_MESON_GXBB)
>
> I think you should indicate why it's only needed for GXBB
>
Sure! I will also explain here to clarify.
Basically this is sending the SCP firmware (aka. bl30/bl301) to the SCP.
Historically (in GXBB) this was done by bl2.bin, but (to save space?) it
was moved to BL31 in GXL:
- https://git.trustedfirmware.org/plugins/gitiles/TF-A/trusted-firmware-a/+/refs/heads/master/plat/amlogic/gxl/gxl_bl31_setup.c#129
and BL2 would give image info for bl30/bl301 to BL31. I patched this
upstream so we don't have to do that in SPL:
- https://git.trustedfirmware.org/plugins/gitiles/TF-A/trusted-firmware-a/+/refs/heads/master/plat/amlogic/gxl/gxl_bl31_setup.c#105
Whether we can move SCP fw loading to BL31 on GXBB is .. an interesting
thing that could be considered. That way we could possibly also save
some space in U-Boot SPL.
>> +inline void send_scp(void *addr, size_t size, const uint8_t *sha2,
>> + uint32_t sha2_length)
>> +{
>> + int i;
>> +
>> + puts("Trying to send the SCP firmware\n");
>> + writel(size, GX_MB_SRAM_BASE);
>> +
>> + udelay(500);
>> +
>> + writel(GX_MB_CMD_DATA_LEN, GX_SEC_HIU_MAILBOX_SET_0 + 3 * 3 * 4);
>> + while (readl(GX_SEC_HIU_MAILBOX_SET_0 + 3 * 3 * 4))
>> + ;
>> + udelay(500);
>> +
>> + memcpy((void *)GX_MB_SRAM_BASE, (const void *)sha2, sha2_length);
>> + writel(GX_MB_CMD_SHA, GX_SEC_HIU_MAILBOX_SET_0 + 3 * 3 * 4);
>> + while (readl(GX_SEC_HIU_MAILBOX_STAT_0 + 3 * 3 * 4))
>> + ;
>> + udelay(500);
>> +
>> + for (i = 0; i < size; i += 1024) {
>> + if (size >= i + 1024)
>> + memcpy((void *)GX_MB_SRAM_BASE,
>> + (const void *)(unsigned long)(addr + i), 1024);
>> + else if (size > i)
>> + memcpy((void *)GX_MB_SRAM_BASE,
>> + (const void *)(unsigned long)(addr + i), (size - i));
>> +
>> + writel(GX_MB_CMD_DATA, GX_SEC_HIU_MAILBOX_SET_0 + 3 * 3 * 4);
>> + while (readl(GX_SEC_HIU_MAILBOX_STAT_0 + 3 * 3 * 4))
>> + ;
>> + }
>> + writel(GX_MB_CMD_OP_SHA, GX_SEC_HIU_MAILBOX_SET_0 + 3 * 3 * 4);
>> +
>> + while (readl(GX_SEC_HIU_MAILBOX_STAT_0 + 3 * 3 * 4))
>> + ;
>> + udelay(500);
>> +
>> + /* We transferred all of the SCP firmware. Running it */
>> + writel(GX_MB_CMD_END, GX_SEC_HIU_MAILBOX_SET_0 + 3 * 3 * 4);
>> +}
>> +#endif
>> +
>> +void board_fit_image_post_process(const void *fit, int node, void **p_image, size_t *p_size)
>> +{
>> +#if defined(CONFIG_MESON_GXBB)
>> + const char *name = fit_get_name(fit, node, NULL);
>> + int noffset = 0, value_len;
>> + u8 *value;
>> +
>> + if (strcmp("scp", name) && strcmp("bl301", name))
>> + return;
>> +
>> + fdt_for_each_subnode(noffset, fit, node) {
>> + if (strncmp(fit_get_name(fit, noffset, NULL),
>> + FIT_HASH_NODENAME, strlen(FIT_HASH_NODENAME)))
>> + continue;
>> +
>> + if (fit_image_hash_get_value(fit, noffset, &value, &value_len))
>> + continue;
>> +
>> + /* Send the SCP firmware to the SCP */
>> + send_scp(*p_image, *p_size, value, value_len);
>> + break;
>> + }
>> +#endif
>> +}
>> +#endif
>> +
>> +inline void cpu_pll_switch_to(int mode)
>> +{
>> + u32 reg;
>> +
>> + reg = readl(GX_HIU_BASE + HHI_SYS_CPU_CLK_CNTL0);
>> +
>> + while (reg & (1 << 28))
>> + reg = readl(GX_HIU_BASE + HHI_SYS_CPU_CLK_CNTL0);
>> +
>> + reg |= (1 << 26);
>> +
>> + if (mode == 1) {
>> + /* Switch to System PLL */
>> + reg |= (1 << 11);
>> + } else {
>> + if (reg & (1 << 10)) {
>> + reg = (reg & ~((1 << 10) | (0x3f << 4) | (1 << 2) |
>> + (0x3 << 0)));
>> + } else {
>> + reg = (reg & ~((1 << 10) | (0x3f << 20) |
>> + (1 << 18) | (0x3 << 16))) | (1 << 10);
>> + }
>> + /* Select dynamic mux */
>> + reg = reg & ~(1 << 11);
>> + }
>> + writel(reg, GX_HIU_BASE + HHI_SYS_CPU_CLK_CNTL0);
>
> I know it's a lot, but it would nbe mush better if you had defines
> for all those PLL register bits.
>
> Or perhaps you can use the `struct parm` & PARM_GET/PARM_SET from
> drivers/clk/meson/clk_meson.h to define and set all the PLL parameters
>
ACK.
>> +}
>> +
>> +int meson_pll_init(void)
>> +{
>> + clrbits_32(GX_HIU_BASE + HHI_MPEG_CLK_CNTL, 1 << 8);
>> + cpu_pll_switch_to(0);
>> +
>> + setbits_32(GX_HIU_BASE + HHI_MPLL_CNTL6, 1 << 26);
>> + udelay(100);
>> +
>> + while (!((readl(GX_HIU_BASE + HHI_SYS_PLL_CNTL) >> 31) & 1)) {
>> + if (IS_ENABLED(CONFIG_MESON_GXBB)) {
>> + setbits_32(GX_HIU_BASE + HHI_SYS_PLL_CNTL, 1 << 29);
>> + writel(0x5ac80000, GX_HIU_BASE + HHI_SYS_PLL_CNTL2);
>> + writel(0x8e452015, GX_HIU_BASE + HHI_SYS_PLL_CNTL3);
>> + writel(0x401d40c, GX_HIU_BASE + HHI_SYS_PLL_CNTL4);
>> + writel(0x870, GX_HIU_BASE + HHI_SYS_PLL_CNTL5);
>> + writel((1 << 30) | (1 << 29) |
>> + ((0 << 16) | (1 << 9) |
>> + (1536 / 24)), /* 1.5 GHz */
>> + GX_HIU_BASE + HHI_SYS_PLL_CNTL);
>> + clrbits_32(GX_HIU_BASE + HHI_SYS_PLL_CNTL, 1 << 29);
>> + } else if (IS_ENABLED(CONFIG_MESON_GXL)) {
>> + writel(0xc4258100, GX_HIU_BASE + HHI_SYS_PLL_CNTL1);
>> + writel(0xb7400000, GX_HIU_BASE + HHI_SYS_PLL_CNTL2);
>> + writel(0xa59a288, GX_HIU_BASE + HHI_SYS_PLL_CNTL3);
>> + writel(0x40002d, GX_HIU_BASE + HHI_SYS_PLL_CNTL4);
>> + writel(0x7c700007, GX_HIU_BASE + HHI_SYS_PLL_CNTL5);
>> + writel((1 << 30) | ((1 << 9) |
>> + (1200 / 24)), /* 1.2 GHz */
>> + GX_HIU_BASE + HHI_SYS_PLL_CNTL);
>> + }
>> + udelay(20);
>> + }
>> + cpu_pll_switch_to(1); /* Hook the CPU to the PLL divider output */
>> +
>> + if (IS_ENABLED(CONFIG_MESON_GXBB))
>> + writel(0x10007, GX_HIU_BASE + HHI_MPLL_CNTL4);
>> + else if (IS_ENABLED(CONFIG_MESON_GXL))
>> + writel(0x10006, GX_HIU_BASE + HHI_MPLL_CNTL4);
>> +
>> + setbits_32(GX_HIU_BASE + HHI_MPLL_CNTL, 1 << 29);
>> + udelay(200);
>> +
>> + writel(0x59C80000, GX_HIU_BASE + HHI_MPLL_CNTL2);
>> + writel(0xCA45B822, GX_HIU_BASE + HHI_MPLL_CNTL3);
>> +
>> + if (IS_ENABLED(CONFIG_MESON_GXBB))
>> + writel(0xB5500E1A, GX_HIU_BASE + HHI_MPLL_CNTL5);
>> + else if (IS_ENABLED(CONFIG_MESON_GXL))
>> + writel(0x95520E1A, GX_HIU_BASE + HHI_MPLL_CNTL5);
>> +
>> + writel(0xFC454545, GX_HIU_BASE + HHI_MPLL_CNTL6);
>> +
>> + if (IS_ENABLED(CONFIG_MESON_GXBB)) {
>> + writel((1 << 30) | (1 << 29) | (3 << 9) | (250 << 0), GX_HIU_BASE + HHI_MPLL_CNTL);
>> + clrbits_32(GX_HIU_BASE + HHI_MPLL_CNTL, 1 << 29);
>> + } else if (IS_ENABLED(CONFIG_MESON_GXL)) {
>> + writel((1 << 30) | (3 << 9) | (250 << 0), GX_HIU_BASE + HHI_MPLL_CNTL);
>> + }
>> + udelay(800);
>> +
>> + setbits_32(GX_HIU_BASE + HHI_MPLL_CNTL4, 1 << 14);
>> +
>> + while (!((readl(GX_HIU_BASE + HHI_MPLL_CNTL) >> 31) & 1)) {
>> + if ((readl(GX_HIU_BASE + HHI_MPLL_CNTL) & (1 << 31)) != 0)
>> + break;
>> + setbits_32(GX_HIU_BASE + HHI_MPLL_CNTL, 1 << 29);
>> + udelay(1000);
>> + clrbits_32(GX_HIU_BASE + HHI_MPLL_CNTL, 1 << 29);
>> + udelay(1000);
>> + }
>> +
>> + if (IS_ENABLED(CONFIG_MESON_GXBB)) {
>> + writel(0xFFF << 16, GX_HIU_BASE + HHI_MPLL_CNTL10);
>> + writel(((7 << 16) | (1 << 15) | (1 << 14) | (4681 << 0)),
>> + GX_HIU_BASE + HHI_MPLL_CNTL7);
>> + writel(((readl(GX_HIU_BASE + HHI_MPEG_CLK_CNTL) & (~((0x7 << 12) | (1 << 7) |
>> + (0x7F << 0)))) | ((5 << 12) | (1 << 7) | (2 << 0))),
>> + GX_HIU_BASE + HHI_MPEG_CLK_CNTL);
>> + setbits_32(GX_HIU_BASE + HHI_MPEG_CLK_CNTL, 1 << 8);
>> + writel(((5 << 16) | (1 << 15) | (1 << 14) | (12524 << 0)),
>> + GX_HIU_BASE + HHI_MPLL_CNTL8);
>> + } else if (IS_ENABLED(CONFIG_MESON_GXL)) {
>> + writel((1 << 12) | 3, GX_HIU_BASE + HHI_MPLL_CNTL10);
>> + writel(0x5edb7, GX_HIU_BASE + HHI_MPLL_CNTL7);
>> + clrbits_32(GX_HIU_BASE + HHI_MPEG_CLK_CNTL,
>> + (3 << 13) | (1 << 12) | (15 << 4) | 15);
>> + setbits_32(GX_HIU_BASE + HHI_MPEG_CLK_CNTL,
>> + (1 << 14) | (1 << 12) | (1 << 8) | (2 << 6) | (1 << 1));
>> + writel((4 << 16) | (7 << 13) | (1 << 8) | (5 << 4) | 10,
>> + GX_HIU_BASE + HHI_MPLL_CNTL8);
>> + }
>> +
>> + udelay(200);
>> +
>> + /* TODO: Some error handling and timeouts... */
>> + return 0;
>> +}
>> +
> <...>
>> +void meson_power_init(void)
>> +{
>> + /* TODO: Support more voltages */
>> +
>> + /* Init PWM B */
>> + clrsetbits_32(GX_PWM_MISC_REG_AB, 0x7f << 16, (1 << 23) | (1 << 1));
>> +
>> + /* Set voltage */
>> + if (CONFIG_IS_ENABLED(MESON_GX_VCCK_1120MV))
>> + writel(0x02001a, GX_PWM_PWM_B);
>> + else if (CONFIG_IS_ENABLED(MESON_GX_VCCK_1100MV))
>> + writel(0x040018, GX_PWM_PWM_B);
>> + else if (CONFIG_IS_ENABLED(MESON_GX_VCCK_1000MV))
>> + writel(0x0e000e, GX_PWM_PWM_B);
>> +
>> + if (IS_ENABLED(CONFIG_MESON_GXBB)) {
>> + clrbits_32(GX_PIN_MUX_REG7, 1 << 22);
>> + clrsetbits_32(GX_PIN_MUX_REG3, 1 << 22, 1 << 21);
>> + } else {
>> + clrbits_32(GX_PIN_MUX_REG1, 1 << 10);
>> + clrsetbits_32(GX_PIN_MUX_REG2, 1 << 5, 1 << 11);
>> + }
>> +
>> + /* Init PWM D */
>> + clrsetbits_32(GX_PWM_MISC_REG_CD, 0x7f << 16, (1 << 23) | (1 << 1));
>> +
>> + /* Set voltage */
>> + if (CONFIG_IS_ENABLED(MESON_GX_VDDEE_1100MV))
>> + writel(0x040018, GX_PWM_PWM_B);
>> + else if (CONFIG_IS_ENABLED(MESON_GX_VDDEE_1000MV))
>> + writel(0x0e000e, GX_PWM_PWM_B);
>> +
>> + if (IS_ENABLED(CONFIG_MESON_GXBB)) {
>> + clrbits_32(GX_PIN_MUX_REG7, 1 << 23);
>> + setbits_32(GX_PIN_MUX_REG3, 1 << 20);
>> + } else {
>> + clrbits_32(GX_PIN_MUX_REG1, (1 << 9) | (1 << 11));
>> + setbits_32(GX_PIN_MUX_REG2, 1 << 12);
>> + }
>> +}
>
> Seems much of the code is duplicated between GXBB and GXL, so I wonder if it would'nt
> be cleaner to have spl-gxbb an spl-gxl ?
>
You mean separate power init functions (meson_power_init_gxbb/gxl)? Should be doable.
> <...>
>
> Anyway it's really clean !
>
> Thanks!
> Neil
>
Thank you!
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Groups.io Links: You receive all messages sent to this group.
> View/Reply Online (#2757): https://groups.io/g/u-boot-amlogic/message/2757
> Mute This Topic: https://groups.io/mt/115113902/8399868
> Group Owner: u-boot-amlogic+owner at groups.io
> Unsubscribe: https://groups.io/g/u-boot-amlogic/unsub [funderscore at postmarketos.org]
> -=-=-=-=-=-=-=-=-=-=-=-
More information about the U-Boot
mailing list