[PATCH 1/1] sunxi: h616: add GPIO-selected DRAM profiles

James Hilliard james.hilliard1 at gmail.com
Sun Mar 15 02:26:51 CET 2026


On Sat, Mar 14, 2026 at 5:55 PM Andre Przywara <andre.przywara at arm.com> wrote:
>
> On Thu, 12 Mar 2026 14:49:06 -0600
> James Hilliard <james.hilliard1 at gmail.com> wrote:
>
> Hi James,
>
> many thanks for doing this, although ....
> This patch is pretty massive and intrusive, as it not only provides
> support for your use case, but changes quite a lot in the H616 DRAM
> code, which is of course used by many more boards.
> The DRAM controller initialisation code, being part of the size
> constrained SPL, has been carefully tuned to compile to as small a
> binary as possible, without sacrificing code quality. We lean on
> toolchain garbage collection (removal of unused code) and constant
> propagation to have readable and portable code, but still very size
> optimised assembly. Your change might void some of these optimisations.
> I am not against those changes, but some things would need to happen to
> make it upstream:
>
> 1. Please split this patch up. This changes all at once, making
> it very hard to review, and to pinpoint any issues to the real
> root cause, when this commit is found in bisecting. I would expect
> between 5 and 10 patches out of this series, maybe even more. Some
> hints below, if you are unsure about the split, come back and ask.

I sent a v3 with just some of the initial refactoring prep changes that
should have no functional changes to make it easier to review:
https://lore.kernel.org/u-boot/20260315005422.471159-1-james.hilliard1@gmail.com/

> 2. Try to keep the changes to boards using the hardcoded parameters as
> small as possible. In fact *every* Allwinner board uses this scheme so
> far, and this works quite nicely, so that's not a niche use case, and I
> don't want to jeopardise that for just one board (yours), which is not
> even upstream (hint!).

Yeah, I'm thinking it's probably easier to do a few rounds of refactoring
with just the hardcoded parameters in order to make the code suitable
for adding these features without having to touch the hardcoded param
paths too much.

> 3. Avoid #ifdef code sections: they are confusing to read, especially
> longer sections, and defy compile time testing, especially in your
> situation. If for instance any of the external functions you call in
> your #ifdef'ed code change, we wouldn't notice. If you rely on
> functions not available normally (DT in SPL), move the whole
> new code into a separate file, and put the condition in the Makefile.
> Also "if (IS_ENABLED(CONFIG_FOO))" can be used if you need to
> differentiate based on config options.

Yeah, the current code relies quite heavily on the #ifdef style, in
my v2 I had done more extensive refactoring to try and make it
easier to work with the multiple dram backends cleanly:
https://lore.kernel.org/u-boot/20260314060256.381642-2-james.hilliard1@gmail.com/

My minimal refactoring only v3 also tries to address this issue with
too many #ifdef's but without introducing the new features at all yet.

> 4. This is hard to test, and even compile testing by CI wouldn't happen
> at the moment, since there is no user. Is there any chance you would
> upstream your board, starting with a DT submission to the kernel repo?
> That would make a much better case for merging intrusive code that
> just increases the code size for many boards.

Maybe, although there's quite a bit of unrelated stuff broken that's
still in the process of being upstreamed. I think there's some other
publicly available boards that use the runtime dram selection with
BSP based firmware, although not sure if any of those are upstream
yet at all.

> > Add an H616 profile-source choice so SPL can either use the existing
> > fixed Kconfig profile or load a DRAM profile selected by GPIO straps
> > from the U-Boot device tree.
>
> Those are only the last at least two changes of the whole series: DT
> profile parsing, and GPIO strap selection (so at least two patches).
>
> > The GPIO-selected path decodes up to four
> > allwinner,dram-coding-gpios bits and loads the matching profile from
> > /dram-profiles.
>
> This describes just the last step of the whole change set.

I removed all these in my v3 to make it easier to review.

> > Refactor the H616 DRAM code so the PHY init tables and timing backend are
> > selected at runtime from para->type, and switch the H616 timing helpers
> > to use para->clk instead of CONFIG_DRAM_CLK.
>
> Those changes seems to be step 2 and step 1.
>
> > This allows a single SPL
> > build to support multiple H616 DRAM types selected at boot while keeping
> > fixed-profile mode as the default.
>
> But even the latter comes at a cost, as it blows up code size, right? A
> quick test reveals it's not by much, but we maybe can reduce this
> overhead.

I think I had fixed the size blow up in my v2 by restoring the conditional
backend compilation(my v3 also retains the conditional backend
compilation):
https://lore.kernel.org/u-boot/20260314060256.381642-2-james.hilliard1@gmail.com/

> I refrain from reviewing this mammoth of a patch, but some quick hints
> after a quick glance:
> - Avoid prefixing generic functions with h616_, h616_ns_to_t() and
>   h616_fdt_read_u32() don't seem H616 specific at all.

I removed most of these prefixes in my v3.

> - If you move h616.c into a header and make mctl_set_timing_params() a
>   static inline function, the generated code might be better, and we
>   probably talk the compiler into optimising away the whole choice when
>   SUNXI_DRAM_H616_FIXED_PROFILE is used, maybe with the help of some #ifdef's.

I got rid of h616.c and moved these into dram_sun50i_h616.h in my v3.

> - h616_get_strap_gpio() seems to be overly complicated? Isn't it just
>   bank * 32 + pin?

Hmm, probably, I'll take a look at that after the initial refactoring.

>
> Cheers,
> Andre
>
> > Hide the fixed H616 Kconfig timing and parameter prompts when GPIO-based
> > selection is enabled, since those settings are not used in that mode.
> > The selector name stays H616-specific because this only models the BSP
> > GPIO strap flow, not the GPADC-based variants.
> >
> > Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
> > ---
> >  .../include/asm/arch-sunxi/dram_sun50i_h616.h |   7 +-
> >  arch/arm/mach-sunxi/Kconfig                   |  47 ++-
> >  arch/arm/mach-sunxi/dram_sun50i_h616.c        | 367 ++++++++++++++++--
> >  arch/arm/mach-sunxi/dram_timings/Makefile     |   5 +-
> >  arch/arm/mach-sunxi/dram_timings/h616.c       |  27 ++
> >  .../mach-sunxi/dram_timings/h616_ddr3_1333.c  |  32 +-
> >  .../arm/mach-sunxi/dram_timings/h616_lpddr3.c |  32 +-
> >  .../dram_timings/h616_lpddr4_2133.c           |  32 +-
> >  8 files changed, 463 insertions(+), 86 deletions(-)
> >  create mode 100644 arch/arm/mach-sunxi/dram_timings/h616.c
> >
> > diff --git a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h616.h b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h616.h
> > index a8fdda124a0..ea64f997fbf 100644
> > --- a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h616.h
> > +++ b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h616.h
> > @@ -168,13 +168,16 @@ struct dram_config {
> >       u8 bus_full_width;
> >  };
> >
> > -static inline int ns_to_t(int nanoseconds)
> > +static inline int h616_ns_to_t(const struct dram_para *para, int nanoseconds)
> >  {
> > -     const unsigned int ctrl_freq = CONFIG_DRAM_CLK / 2;
> > +     const unsigned int ctrl_freq = para->clk / 2;
> >
> >       return DIV_ROUND_UP(ctrl_freq * nanoseconds, 1000);
> >  }
> >
> > +void h616_ddr3_set_timing_params(const struct dram_para *para);
> > +void h616_lpddr3_set_timing_params(const struct dram_para *para);
> > +void h616_lpddr4_set_timing_params(const struct dram_para *para);
> >  void mctl_set_timing_params(const struct dram_para *para);
> >
> >  #endif /* _SUNXI_DRAM_SUN50I_H616_H */
> > diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig
> > index e979ee4a2cc..f8cb0a96182 100644
> > --- a/arch/arm/mach-sunxi/Kconfig
> > +++ b/arch/arm/mach-sunxi/Kconfig
> > @@ -62,7 +62,41 @@ config DRAM_SUN55I_A523
> >       help
> >         Select this DRAM controller driver for A523/T527 SoCs.
> >
> > -if DRAM_SUN50I_H616 || DRAM_SUN50I_A133 || DRAM_SUN55I_A523
> > +if DRAM_SUN50I_H616
> > +choice
> > +     prompt "H616 DRAM profile source"
> > +     default SUNXI_DRAM_H616_FIXED_PROFILE
> > +     help
> > +       Select whether SPL uses the fixed H616 Kconfig DRAM settings or
> > +       loads an H616 DRAM profile from the device tree at boot.
> > +
> > +config SUNXI_DRAM_H616_FIXED_PROFILE
> > +     bool "Fixed build-time DRAM profile"
> > +     help
> > +       Use a single H616 DRAM profile selected at build time.
> > +       This keeps the existing Kconfig-based timing selection flow.
> > +
> > +config SUNXI_DRAM_H616_GPIO_SELECT
> > +     bool "GPIO-selected DRAM profile"
> > +     select GPIO
> > +     select OF_CONTROL
> > +     select SPL_OF_CONTROL
> > +     select SPL_GPIO
> > +     help
> > +       Enable runtime H616 DRAM profile selection using GPIO strap bits
> > +       described in the U-Boot device tree.
> > +
> > +       SPL reads the allwinner,dram-coding-gpios property from the
> > +       /dram-profiles node, decodes up to four strap bits into a profile
> > +       ID, and loads the matching profile from /dram-profiles instead of
> > +       using a fixed build-time H616 DRAM profile.
> > +
> > +       Each profile must provide the H616 DRAM parameters used by the
> > +       driver. Supported dram-type values are DDR3, LPDDR3, and LPDDR4.
> > +endchoice
> > +endif
> > +
> > +if (DRAM_SUN50I_H616 && SUNXI_DRAM_H616_FIXED_PROFILE) || DRAM_SUN50I_A133 || DRAM_SUN55I_A523
> >  config DRAM_SUNXI_DX_ODT
> >       hex "DRAM DX ODT parameter"
> >       help
> > @@ -608,6 +642,7 @@ config SUNXI_DRAM_DDR4
> >
> >  choice
> >       prompt "DRAM Type and Timing"
> > +     depends on !DRAM_SUN50I_H616 || SUNXI_DRAM_H616_FIXED_PROFILE
> >       default SUNXI_DRAM_A523_LPDDR4 if MACH_SUN55I_A523
> >       default SUNXI_DRAM_DDR3_1333 if !MACH_SUN8I_V3S
> >       default SUNXI_DRAM_DDR2_V3S if MACH_SUN8I_V3S
> > @@ -647,7 +682,8 @@ config SUNXI_DRAM_H6_DDR3_1333
> >  config SUNXI_DRAM_H616_LPDDR3
> >       bool "LPDDR3 DRAM chips on the H616 DRAM controller"
> >       select SUNXI_DRAM_LPDDR3
> > -     depends on DRAM_SUN50I_H616 || DRAM_SUN50I_A133
> > +     depends on (DRAM_SUN50I_H616 && SUNXI_DRAM_H616_FIXED_PROFILE) || \
> > +                DRAM_SUN50I_A133
> >       help
> >         This option is the LPDDR3 timing used by the stock boot0 by
> >         Allwinner.
> > @@ -655,7 +691,8 @@ config SUNXI_DRAM_H616_LPDDR3
> >  config SUNXI_DRAM_H616_LPDDR4
> >       bool "LPDDR4 DRAM chips on the H616 DRAM controller"
> >       select SUNXI_DRAM_LPDDR4
> > -     depends on DRAM_SUN50I_H616 || DRAM_SUN50I_A133
> > +     depends on (DRAM_SUN50I_H616 && SUNXI_DRAM_H616_FIXED_PROFILE) || \
> > +                DRAM_SUN50I_A133
> >       help
> >         This option is the LPDDR4 timing used by the stock boot0 by
> >         Allwinner.
> > @@ -663,7 +700,8 @@ config SUNXI_DRAM_H616_LPDDR4
> >  config SUNXI_DRAM_H616_DDR3_1333
> >       bool "DDR3-1333 boot0 timings on the H616 DRAM controller"
> >       select SUNXI_DRAM_DDR3
> > -     depends on DRAM_SUN50I_H616 || DRAM_SUN50I_A133
> > +     depends on (DRAM_SUN50I_H616 && SUNXI_DRAM_H616_FIXED_PROFILE) || \
> > +                DRAM_SUN50I_A133
> >       help
> >         This option is the DDR3 timing used by the boot0 on H616 TV boxes
> >         which use a DDR3-1333 timing.
> > @@ -719,6 +757,7 @@ config DRAM_TYPE
> >
> >  config DRAM_CLK
> >       int "sunxi dram clock speed"
> > +     depends on !DRAM_SUN50I_H616 || SUNXI_DRAM_H616_FIXED_PROFILE
> >       default 792 if MACH_SUN9I
> >       default 648 if MACH_SUN8I_R40
> >       default 360 if MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || \
> > diff --git a/arch/arm/mach-sunxi/dram_sun50i_h616.c b/arch/arm/mach-sunxi/dram_sun50i_h616.c
> > index 3345c9b8e82..f72c1e0f38c 100644
> > --- a/arch/arm/mach-sunxi/dram_sun50i_h616.c
> > +++ b/arch/arm/mach-sunxi/dram_sun50i_h616.c
> > @@ -15,6 +15,17 @@
> >  #include <init.h>
> >  #include <log.h>
> >  #include <asm/io.h>
> > +
> > +#define H616_GPIO_SELECT_ENABLED     IS_ENABLED(CONFIG_SUNXI_DRAM_H616_GPIO_SELECT)
> > +
> > +#if H616_GPIO_SELECT_ENABLED
> > +#include <asm-generic/gpio.h>
> > +#include <asm/global_data.h>
> > +#include <errno.h>
> > +#include <sunxi_gpio.h>
> > +#include <linux/libfdt.h>
> > +#include <linux/string.h>
> > +#endif
> >  #include <asm/arch/clock.h>
> >  #include <asm/arch/dram.h>
> >  #include <asm/arch/dram_dw_helpers.h>
> > @@ -23,6 +34,12 @@
> >  #include <linux/bitops.h>
> >  #include <linux/delay.h>
> >
> > +#define H616_DRAM_STRAP_GPIO_COUNT   4
> > +
> > +#if H616_GPIO_SELECT_ENABLED
> > +DECLARE_GLOBAL_DATA_PTR;
> > +#endif
> > +
> >  enum {
> >       MBUS_QOS_LOWEST = 0,
> >       MBUS_QOS_LOW,
> > @@ -227,45 +244,67 @@ static void mctl_set_addrmap(const struct dram_config *config)
> >       mctl_ctl->addrmap[8] = 0x3F3F;
> >  }
> >
> > +#define H616_PHY_INIT_LEN    27
> > +
> >  #ifdef CONFIG_DRAM_SUNXI_PHY_ADDR_MAP_1
> > -static const u8 phy_init[] = {
> > -#ifdef CONFIG_SUNXI_DRAM_H616_DDR3_1333
> > +static const u8 phy_init_ddr3[H616_PHY_INIT_LEN] = {
> >       0x08, 0x02, 0x12, 0x05, 0x15, 0x17, 0x18, 0x0b,
> >       0x14, 0x07, 0x04, 0x13, 0x0c, 0x00, 0x16, 0x1a,
> >       0x0a, 0x11, 0x03, 0x10, 0x0e, 0x01, 0x0d, 0x19,
> >       0x06, 0x09, 0x0f
> > -#elif defined(CONFIG_SUNXI_DRAM_H616_LPDDR3)
> > +};
> > +
> > +static const u8 phy_init_lpddr3[H616_PHY_INIT_LEN] = {
> >       0x18, 0x00, 0x04, 0x09, 0x06, 0x05, 0x02, 0x19,
> >       0x17, 0x03, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
> >       0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x07,
> >       0x08, 0x01, 0x1a
> > -#elif defined(CONFIG_SUNXI_DRAM_H616_LPDDR4)
> > +};
> > +
> > +static const u8 phy_init_lpddr4[H616_PHY_INIT_LEN] = {
> >       0x03, 0x00, 0x17, 0x05, 0x02, 0x19, 0x06, 0x07,
> >       0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
> >       0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x01,
> >       0x18, 0x04, 0x1a
> > -#endif
> >  };
> > -#else /* CONFIG_DRAM_SUNXI_PHY_ADDR_MAP_0 */
> > -static const u8 phy_init[] = {
> > -#ifdef CONFIG_SUNXI_DRAM_H616_DDR3_1333
> > +#else
> > +static const u8 phy_init_ddr3[H616_PHY_INIT_LEN] = {
> >       0x07, 0x0b, 0x02, 0x16, 0x0d, 0x0e, 0x14, 0x19,
> >       0x0a, 0x15, 0x03, 0x13, 0x04, 0x0c, 0x10, 0x06,
> >       0x0f, 0x11, 0x1a, 0x01, 0x12, 0x17, 0x00, 0x08,
> >       0x09, 0x05, 0x18
> > -#elif defined(CONFIG_SUNXI_DRAM_H616_LPDDR3)
> > +};
> > +
> > +static const u8 phy_init_lpddr3[H616_PHY_INIT_LEN] = {
> >       0x18, 0x06, 0x00, 0x05, 0x04, 0x03, 0x09, 0x02,
> >       0x08, 0x01, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
> >       0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x07,
> >       0x17, 0x19, 0x1a
> > -#elif defined(CONFIG_SUNXI_DRAM_H616_LPDDR4)
> > +};
> > +
> > +static const u8 phy_init_lpddr4[H616_PHY_INIT_LEN] = {
> >       0x02, 0x00, 0x17, 0x05, 0x04, 0x19, 0x06, 0x07,
> >       0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
> >       0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x01,
> >       0x18, 0x03, 0x1a
> > -#endif
> >  };
> > -#endif /* CONFIG_DRAM_SUNXI_PHY_ADDR_MAP_0 */
> > +#endif
> > +
> > +static const u8 *h616_get_phy_init(const struct dram_para *para)
> > +{
> > +     switch (para->type) {
> > +     case SUNXI_DRAM_TYPE_DDR3:
> > +             return phy_init_ddr3;
> > +     case SUNXI_DRAM_TYPE_LPDDR3:
> > +             return phy_init_lpddr3;
> > +     case SUNXI_DRAM_TYPE_LPDDR4:
> > +             return phy_init_lpddr4;
> > +     case SUNXI_DRAM_TYPE_DDR4:
> > +     default:
> > +             panic("Unsupported H616 DRAM type: %u\n", para->type);
> > +     }
> > +}
> > +
> >  #define MASK_BYTE(reg, nr) (((reg) >> ((nr) * 8)) & 0x1f)
> >  static void mctl_phy_configure_odt(const struct dram_para *para)
> >  {
> > @@ -908,6 +947,7 @@ static bool mctl_phy_init(const struct dram_para *para,
> >                       (struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE;
> >       struct sunxi_mctl_ctl_reg * const mctl_ctl =
> >                       (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> > +     const u8 *phy_init = h616_get_phy_init(para);
> >       u32 val, val2, *ptr, mr0, mr2;
> >       int i;
> >
> > @@ -964,7 +1004,7 @@ static bool mctl_phy_init(const struct dram_para *para,
> >       writel(val2, SUNXI_DRAM_PHY0_BASE + 0x37c);
> >
> >       ptr = (u32 *)(SUNXI_DRAM_PHY0_BASE + 0xc0);
> > -     for (i = 0; i < ARRAY_SIZE(phy_init); i++)
> > +     for (i = 0; i < H616_PHY_INIT_LEN; i++)
> >               writel(phy_init[i], &ptr[i]);
> >
> >       if (para->tpr10 & TPR10_CA_BIT_DELAY)
> > @@ -1319,33 +1359,302 @@ bool mctl_core_init(const struct dram_para *para,
> >       return mctl_ctrl_init(para, config);
> >  }
> >
> > -static const struct dram_para para = {
> > -     .clk = CONFIG_DRAM_CLK,
> > +#if H616_GPIO_SELECT_ENABLED
> > +static int h616_fdt_read_u32(const void *blob, int node, const char *prop_name,
> > +                          u32 *val)
> > +{
> > +     const fdt32_t *prop;
> > +     int len;
> > +
> > +     prop = fdt_getprop(blob, node, prop_name, &len);
> > +     if (!prop || len != sizeof(*prop))
> > +             return -EINVAL;
> > +
> > +     *val = fdt32_to_cpu(*prop);
> > +
> > +     return 0;
> > +}
> > +
> > +static int h616_fdt_read_dram_type(const void *blob, int node, u32 *val)
> > +{
> > +     const char *prop;
> > +     int len;
> > +
> > +     prop = fdt_getprop(blob, node, "allwinner,dram-type", &len);
> > +     if (!prop)
> > +             return -EINVAL;
> > +
> > +     if (len == sizeof(fdt32_t)) {
> > +             *val = fdt32_to_cpu(*(const fdt32_t *)prop);
> > +             return 0;
> > +     }
> > +
> > +     if (len <= 0 || prop[len - 1] != '\0')
> > +             return -EINVAL;
> > +
> > +     if (!strcmp(prop, "ddr3")) {
> > +             *val = SUNXI_DRAM_TYPE_DDR3;
> > +             return 0;
> > +     }
> > +     if (!strcmp(prop, "ddr4")) {
> > +             *val = SUNXI_DRAM_TYPE_DDR4;
> > +             return 0;
> > +     }
> > +     if (!strcmp(prop, "lpddr3")) {
> > +             *val = SUNXI_DRAM_TYPE_LPDDR3;
> > +             return 0;
> > +     }
> > +     if (!strcmp(prop, "lpddr4")) {
> > +             *val = SUNXI_DRAM_TYPE_LPDDR4;
> > +             return 0;
> > +     }
> > +
> > +     return -EINVAL;
> > +}
> > +
> > +static int h616_get_strap_gpio(u32 bank, u32 pin)
> > +{
> > +     if (pin >= SUNXI_GPIOS_PER_BANK)
> > +             return -EINVAL;
> > +
> > +     switch (bank) {
> > +     case SUNXI_GPIO_A:
> > +             return SUNXI_GPA(pin);
> > +     case SUNXI_GPIO_B:
> > +             return SUNXI_GPB(pin);
> > +     case SUNXI_GPIO_C:
> > +             return SUNXI_GPC(pin);
> > +     case SUNXI_GPIO_D:
> > +             return SUNXI_GPD(pin);
> > +     case SUNXI_GPIO_E:
> > +             return SUNXI_GPE(pin);
> > +     case SUNXI_GPIO_F:
> > +             return SUNXI_GPF(pin);
> > +     case SUNXI_GPIO_G:
> > +             return SUNXI_GPG(pin);
> > +     case SUNXI_GPIO_H:
> > +             return SUNXI_GPH(pin);
> > +     case SUNXI_GPIO_I:
> > +             return SUNXI_GPI(pin);
> > +     case SUNXI_GPIO_L:
> > +             return SUNXI_GPL(pin);
> > +     case SUNXI_GPIO_M:
> > +             return SUNXI_GPM(pin);
> > +     case SUNXI_GPIO_N:
> > +             return SUNXI_GPN(pin);
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +}
> > +
> > +static int h616_fdt_get_gpio_spec(const void *blob, int node,
> > +                               const char *prop_name, int index,
> > +                               u32 *bank, u32 *pin)
> > +{
> > +     const fdt32_t *prop;
> > +     int entries, gpio_node, i, len, pos;
> > +     u32 cells, phandle;
> > +
> > +     prop = fdt_getprop(blob, node, prop_name, &len);
> > +     if (!prop)
> > +             return -ENOENT;
> > +     if (len % sizeof(*prop))
> > +             return -EINVAL;
> > +
> > +     entries = len / sizeof(*prop);
> > +     for (i = 0, pos = 0; pos < entries; i++) {
> > +             phandle = fdt32_to_cpu(prop[pos++]);
> > +             if (!phandle)
> > +                     break;
> > +
> > +             gpio_node = fdt_node_offset_by_phandle(blob, phandle);
> > +             if (gpio_node < 0)
> > +                     return gpio_node;
> > +             if (h616_fdt_read_u32(blob, gpio_node, "#gpio-cells", &cells))
> > +                     return -EINVAL;
> > +             if (cells < 2 || pos + cells > entries)
> > +                     return -EINVAL;
> > +
> > +             if (i == index) {
> > +                     *bank = fdt32_to_cpu(prop[pos]);
> > +                     *pin = fdt32_to_cpu(prop[pos + 1]);
> > +                     return 0;
> > +             }
> > +
> > +             pos += cells;
> > +     }
> > +
> > +     return -ENOENT;
> > +}
> > +
> > +static int h616_parse_dram_para(const void *blob, int node,
> > +                             struct dram_para *para)
> > +{
> > +     u32 val;
> > +
> > +     if (h616_fdt_read_u32(blob, node, "allwinner,dram-clk", &para->clk))
> > +             return -EINVAL;
> > +     if (h616_fdt_read_dram_type(blob, node, &val))
> > +             return -EINVAL;
> > +
> > +     switch (val) {
> > +     case SUNXI_DRAM_TYPE_DDR3:
> > +     case SUNXI_DRAM_TYPE_LPDDR3:
> > +     case SUNXI_DRAM_TYPE_LPDDR4:
> > +             para->type = val;
> > +             break;
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +
> > +     if (h616_fdt_read_u32(blob, node, "allwinner,dx-odt", &para->dx_odt))
> > +             return -EINVAL;
> > +     if (h616_fdt_read_u32(blob, node, "allwinner,dx-dri", &para->dx_dri))
> > +             return -EINVAL;
> > +     if (h616_fdt_read_u32(blob, node, "allwinner,ca-dri", &para->ca_dri))
> > +             return -EINVAL;
> > +     if (h616_fdt_read_u32(blob, node, "allwinner,odt-en", &para->odt_en))
> > +             return -EINVAL;
> > +     if (h616_fdt_read_u32(blob, node, "allwinner,tpr0", &para->tpr0))
> > +             return -EINVAL;
> > +     if (h616_fdt_read_u32(blob, node, "allwinner,tpr2", &para->tpr2))
> > +             return -EINVAL;
> > +     if (h616_fdt_read_u32(blob, node, "allwinner,tpr6", &para->tpr6))
> > +             return -EINVAL;
> > +     if (h616_fdt_read_u32(blob, node, "allwinner,tpr10", &para->tpr10))
> > +             return -EINVAL;
> > +     if (h616_fdt_read_u32(blob, node, "allwinner,tpr11", &para->tpr11))
> > +             return -EINVAL;
> > +     if (h616_fdt_read_u32(blob, node, "allwinner,tpr12", &para->tpr12))
> > +             return -EINVAL;
> > +
> > +     return 0;
> > +}
> > +
> > +static int h616_load_dram_profile(u32 profile_id, struct dram_para *para)
> > +{
> > +     const void *blob = gd->fdt_blob;
> > +     int node, profiles;
> > +     u32 reg;
> > +
> > +     profiles = fdt_path_offset(blob, "/dram-profiles");
> > +     if (profiles < 0)
> > +             return profiles;
> > +
> > +     for (node = fdt_first_subnode(blob, profiles);
> > +          node >= 0;
> > +          node = fdt_next_subnode(blob, node)) {
> > +             if (h616_fdt_read_u32(blob, node, "reg", &reg))
> > +                     continue;
> > +             if (reg != profile_id)
> > +                     continue;
> > +
> > +             return h616_parse_dram_para(blob, node, para);
> > +     }
> > +
> > +     return -ENOENT;
> > +}
> > +
> > +static int h616_get_dram_profile_id(u32 *profile_id)
> > +{
> > +     const void *blob = gd->fdt_blob;
> > +     int gpio, i, profiles, ret, value;
> > +     u32 bank, pin;
> > +
> > +     profiles = fdt_path_offset(blob, "/dram-profiles");
> > +     if (profiles < 0)
> > +             return profiles;
> > +
> > +     *profile_id = 0;
> > +     for (i = 0; i < H616_DRAM_STRAP_GPIO_COUNT; i++) {
> > +             ret = h616_fdt_get_gpio_spec(blob, profiles,
> > +                                          "allwinner,dram-coding-gpios",
> > +                                          i, &bank, &pin);
> > +             if (ret == -ENOENT)
> > +                     return i ? 0 : -ENOENT;
> > +             if (ret)
> > +                     return ret;
> > +
> > +             gpio = h616_get_strap_gpio(bank, pin);
> > +             if (gpio < 0)
> > +                     return gpio;
> > +
> > +             ret = gpio_request(gpio, "h616_dram_sel");
> > +             if (ret)
> > +                     return ret;
> > +
> > +             ret = gpio_direction_input(gpio);
> > +             if (ret) {
> > +                     gpio_free(gpio);
> > +                     return ret;
> > +             }
> > +
> > +             value = gpio_get_value(gpio);
> > +             gpio_free(gpio);
> > +             if (value < 0)
> > +                     return value;
> > +
> > +             *profile_id |= !!value << i;
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static void h616_get_dram_para(struct dram_para *para)
> > +{
> > +     u32 profile_id;
> > +     int ret;
> > +
> > +     ret = h616_get_dram_profile_id(&profile_id);
> > +     if (ret)
> > +             panic("H616 GPIO DRAM profile selection could not determine a profile\n");
> > +
> > +     ret = h616_load_dram_profile(profile_id, para);
> > +     if (ret)
> > +             panic("H616 GPIO DRAM profile selection failed to load profile %u\n",
> > +                   profile_id);
> > +}
> > +#else
> > +static enum sunxi_dram_type h616_get_fixed_dram_type(void)
> > +{
> >  #ifdef CONFIG_SUNXI_DRAM_H616_DDR3_1333
> > -     .type = SUNXI_DRAM_TYPE_DDR3,
> > +     return SUNXI_DRAM_TYPE_DDR3;
> >  #elif defined(CONFIG_SUNXI_DRAM_H616_LPDDR3)
> > -     .type = SUNXI_DRAM_TYPE_LPDDR3,
> > +     return SUNXI_DRAM_TYPE_LPDDR3;
> >  #elif defined(CONFIG_SUNXI_DRAM_H616_LPDDR4)
> > -     .type = SUNXI_DRAM_TYPE_LPDDR4,
> > +     return SUNXI_DRAM_TYPE_LPDDR4;
> > +#endif
> > +     panic("No fixed H616 DRAM type selected\n");
> > +}
> > +
> > +static void h616_get_dram_para(struct dram_para *para)
> > +{
> > +     *para = (struct dram_para) {
> > +             .clk = CONFIG_DRAM_CLK,
> > +             .type = h616_get_fixed_dram_type(),
> > +             .dx_odt = CONFIG_DRAM_SUNXI_DX_ODT,
> > +             .dx_dri = CONFIG_DRAM_SUNXI_DX_DRI,
> > +             .ca_dri = CONFIG_DRAM_SUNXI_CA_DRI,
> > +             .odt_en = CONFIG_DRAM_SUNXI_ODT_EN,
> > +             .tpr0 = CONFIG_DRAM_SUNXI_TPR0,
> > +             .tpr2 = CONFIG_DRAM_SUNXI_TPR2,
> > +             .tpr6 = CONFIG_DRAM_SUNXI_TPR6,
> > +             .tpr10 = CONFIG_DRAM_SUNXI_TPR10,
> > +             .tpr11 = CONFIG_DRAM_SUNXI_TPR11,
> > +             .tpr12 = CONFIG_DRAM_SUNXI_TPR12,
> > +     };
> > +}
> >  #endif
> > -     .dx_odt = CONFIG_DRAM_SUNXI_DX_ODT,
> > -     .dx_dri = CONFIG_DRAM_SUNXI_DX_DRI,
> > -     .ca_dri = CONFIG_DRAM_SUNXI_CA_DRI,
> > -     .odt_en = CONFIG_DRAM_SUNXI_ODT_EN,
> > -     .tpr0 = CONFIG_DRAM_SUNXI_TPR0,
> > -     .tpr2 = CONFIG_DRAM_SUNXI_TPR2,
> > -     .tpr6 = CONFIG_DRAM_SUNXI_TPR6,
> > -     .tpr10 = CONFIG_DRAM_SUNXI_TPR10,
> > -     .tpr11 = CONFIG_DRAM_SUNXI_TPR11,
> > -     .tpr12 = CONFIG_DRAM_SUNXI_TPR12,
> > -};
> >
> >  unsigned long sunxi_dram_init(void)
> >  {
> >       void *const prcm = (void *)SUNXI_PRCM_BASE;
> > +     struct dram_para para;
> >       struct dram_config config;
> >       unsigned long size;
> >
> > +     h616_get_dram_para(&para);
> > +
> >       setbits_le32(prcm + CCU_PRCM_RES_CAL_CTRL, BIT(8));
> >       clrbits_le32(prcm + CCU_PRCM_OHMS240, 0x3f);
> >
> > diff --git a/arch/arm/mach-sunxi/dram_timings/Makefile b/arch/arm/mach-sunxi/dram_timings/Makefile
> > index 5de9fd5aab4..34d46f2864a 100644
> > --- a/arch/arm/mach-sunxi/dram_timings/Makefile
> > +++ b/arch/arm/mach-sunxi/dram_timings/Makefile
> > @@ -3,9 +3,8 @@ obj-$(CONFIG_SUNXI_DRAM_LPDDR3_STOCK) += lpddr3_stock.o
> >  obj-$(CONFIG_SUNXI_DRAM_DDR2_V3S)    += ddr2_v3s.o
> >  obj-$(CONFIG_SUNXI_DRAM_H6_LPDDR3)   += h6_lpddr3.o
> >  obj-$(CONFIG_SUNXI_DRAM_H6_DDR3_1333)        += h6_ddr3_1333.o
> > -obj-$(CONFIG_SUNXI_DRAM_H616_DDR3_1333)      += h616_ddr3_1333.o
> > -obj-$(CONFIG_SUNXI_DRAM_H616_LPDDR3) += h616_lpddr3.o
> > -obj-$(CONFIG_SUNXI_DRAM_H616_LPDDR4) += h616_lpddr4_2133.o
> > +obj-$(CONFIG_DRAM_SUN50I_H616)               += h616.o h616_ddr3_1333.o
> > +obj-$(CONFIG_DRAM_SUN50I_H616)               += h616_lpddr3.o h616_lpddr4_2133.o
> >  obj-$(CONFIG_SUNXI_DRAM_A133_DDR4)   += a133_ddr4.o
> >  obj-$(CONFIG_SUNXI_DRAM_A133_LPDDR4) += a133_lpddr4.o
> >  obj-$(CONFIG_SUNXI_DRAM_A523_DDR3)   += a523_ddr3.o
> > diff --git a/arch/arm/mach-sunxi/dram_timings/h616.c b/arch/arm/mach-sunxi/dram_timings/h616.c
> > new file mode 100644
> > index 00000000000..d05d528cc57
> > --- /dev/null
> > +++ b/arch/arm/mach-sunxi/dram_timings/h616.c
> > @@ -0,0 +1,27 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * sun50i H616 DRAM timing dispatcher
> > + *
> > + * Build all H616 timing backends and select the appropriate one at runtime.
> > + */
> > +
> > +#include <asm/arch/dram.h>
> > +#include <vsprintf.h>
> > +
> > +void mctl_set_timing_params(const struct dram_para *para)
> > +{
> > +     switch (para->type) {
> > +     case SUNXI_DRAM_TYPE_DDR3:
> > +             h616_ddr3_set_timing_params(para);
> > +             break;
> > +     case SUNXI_DRAM_TYPE_LPDDR3:
> > +             h616_lpddr3_set_timing_params(para);
> > +             break;
> > +     case SUNXI_DRAM_TYPE_LPDDR4:
> > +             h616_lpddr4_set_timing_params(para);
> > +             break;
> > +     case SUNXI_DRAM_TYPE_DDR4:
> > +     default:
> > +             panic("Unsupported H616 DRAM type: %u\n", para->type);
> > +     }
> > +}
> > diff --git a/arch/arm/mach-sunxi/dram_timings/h616_ddr3_1333.c b/arch/arm/mach-sunxi/dram_timings/h616_ddr3_1333.c
> > index 3faf8d5bd97..4fde0a81a0a 100644
> > --- a/arch/arm/mach-sunxi/dram_timings/h616_ddr3_1333.c
> > +++ b/arch/arm/mach-sunxi/dram_timings/h616_ddr3_1333.c
> > @@ -14,33 +14,33 @@
> >  #include <asm/arch/dram.h>
> >  #include <asm/arch/cpu.h>
> >
> > -void mctl_set_timing_params(const struct dram_para *para)
> > +void h616_ddr3_set_timing_params(const struct dram_para *para)
> >  {
> >       struct sunxi_mctl_ctl_reg * const mctl_ctl =
> >                       (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> >
> >       u8 tccd         = 2;                    /* JEDEC: 4nCK */
> > -     u8 tfaw         = ns_to_t(50);          /* JEDEC: 30 ns w/ 1K pages */
> > -     u8 trrd         = max(ns_to_t(6), 4);   /* JEDEC: max(6 ns, 4nCK) */
> > -     u8 trcd         = ns_to_t(15);          /* JEDEC: 13.5 ns */
> > -     u8 trc          = ns_to_t(53);          /* JEDEC: 49.5 ns */
> > -     u8 txp          = max(ns_to_t(6), 3);   /* JEDEC: max(6 ns, 3nCK) */
> > -     u8 trtp         = max(ns_to_t(8), 2);   /* JEDEC: max(7.5 ns, 4nCK) */
> > -     u8 trp          = ns_to_t(15);          /* JEDEC: >= 13.75 ns */
> > -     u8 tras         = ns_to_t(38);          /* JEDEC >= 36 ns, <= 9*trefi */
> > -     u16 trefi       = ns_to_t(7800) / 32;   /* JEDEC: 7.8us at Tcase <= 85C */
> > -     u16 trfc        = ns_to_t(350);         /* JEDEC: 160 ns for 2Gb */
> > +     u8 tfaw         = h616_ns_to_t(para, 50);    /* JEDEC: 30 ns w/ 1K pages */
> > +     u8 trrd         = max(h616_ns_to_t(para, 6), 4); /* JEDEC: max(6 ns, 4nCK) */
> > +     u8 trcd         = h616_ns_to_t(para, 15);    /* JEDEC: 13.5 ns */
> > +     u8 trc          = h616_ns_to_t(para, 53);    /* JEDEC: 49.5 ns */
> > +     u8 txp          = max(h616_ns_to_t(para, 6), 3); /* JEDEC: max(6 ns, 3nCK) */
> > +     u8 trtp         = max(h616_ns_to_t(para, 8), 2); /* JEDEC: max(7.5 ns, 4nCK) */
> > +     u8 trp          = h616_ns_to_t(para, 15);    /* JEDEC: >= 13.75 ns */
> > +     u8 tras         = h616_ns_to_t(para, 38);    /* JEDEC >= 36 ns, <= 9*trefi */
> > +     u16 trefi       = h616_ns_to_t(para, 7800) / 32; /* JEDEC: 7.8us at Tcase <= 85C */
> > +     u16 trfc        = h616_ns_to_t(para, 350);   /* JEDEC: 160 ns for 2Gb */
> >       u16 txsr        = 4;                    /* ? */
> >
> >       u8 tmrw         = 0;                    /* ? */
> >       u8 tmrd         = 4;                    /* JEDEC: 4nCK */
> > -     u8 tmod         = max(ns_to_t(15), 12); /* JEDEC: max(15 ns, 12nCK) */
> > -     u8 tcke         = max(ns_to_t(6), 3);   /* JEDEC: max(5.625 ns, 3nCK) */
> > -     u8 tcksrx       = max(ns_to_t(10), 4);  /* JEDEC: max(10 ns, 5nCK) */
> > -     u8 tcksre       = max(ns_to_t(10), 4);  /* JEDEC: max(10 ns, 5nCK) */
> > +     u8 tmod         = max(h616_ns_to_t(para, 15), 12); /* JEDEC: max(15 ns, 12nCK) */
> > +     u8 tcke         = max(h616_ns_to_t(para, 6), 3);   /* JEDEC: max(5.625 ns, 3nCK) */
> > +     u8 tcksrx       = max(h616_ns_to_t(para, 10), 4);  /* JEDEC: max(10 ns, 5nCK) */
> > +     u8 tcksre       = max(h616_ns_to_t(para, 10), 4);  /* JEDEC: max(10 ns, 5nCK) */
> >       u8 tckesr       = tcke + 1;             /* JEDEC: tCKE(min) + 1nCK */
> >       u8 trasmax      = (para->clk / 2) / 15; /* JEDEC: tREFI * 9 */
> > -     u8 txs          = ns_to_t(360) / 32;    /* JEDEC: max(5nCK,tRFC+10ns) */
> > +     u8 txs          = h616_ns_to_t(para, 360) / 32; /* JEDEC: max(5nCK,tRFC+10ns) */
> >       u8 txsdll       = 16;                   /* JEDEC: 512 nCK */
> >       u8 txsabort     = 4;                    /* ? */
> >       u8 txsfast      = 4;                    /* ? */
> > diff --git a/arch/arm/mach-sunxi/dram_timings/h616_lpddr3.c b/arch/arm/mach-sunxi/dram_timings/h616_lpddr3.c
> > index ce2ffa7a020..d5106765b05 100644
> > --- a/arch/arm/mach-sunxi/dram_timings/h616_lpddr3.c
> > +++ b/arch/arm/mach-sunxi/dram_timings/h616_lpddr3.c
> > @@ -14,33 +14,33 @@
> >  #include <asm/arch/dram.h>
> >  #include <asm/arch/cpu.h>
> >
> > -void mctl_set_timing_params(const struct dram_para *para)
> > +void h616_lpddr3_set_timing_params(const struct dram_para *para)
> >  {
> >       struct sunxi_mctl_ctl_reg * const mctl_ctl =
> >                       (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> >
> >       u8 tccd         = 2;
> > -     u8 tfaw         = ns_to_t(50);
> > -     u8 trrd         = max(ns_to_t(6), 4);
> > -     u8 trcd         = ns_to_t(24);
> > -     u8 trc          = ns_to_t(70);
> > -     u8 txp          = max(ns_to_t(8), 3);
> > -     u8 trtp         = max(ns_to_t(8), 2);
> > -     u8 trp          = ns_to_t(27);
> > -     u8 tras         = ns_to_t(41);
> > -     u16 trefi       = ns_to_t(7800) / 64;
> > -     u16 trfc        = ns_to_t(210);
> > +     u8 tfaw         = h616_ns_to_t(para, 50);
> > +     u8 trrd         = max(h616_ns_to_t(para, 6), 4);
> > +     u8 trcd         = h616_ns_to_t(para, 24);
> > +     u8 trc          = h616_ns_to_t(para, 70);
> > +     u8 txp          = max(h616_ns_to_t(para, 8), 3);
> > +     u8 trtp         = max(h616_ns_to_t(para, 8), 2);
> > +     u8 trp          = h616_ns_to_t(para, 27);
> > +     u8 tras         = h616_ns_to_t(para, 41);
> > +     u16 trefi       = h616_ns_to_t(para, 7800) / 64;
> > +     u16 trfc        = h616_ns_to_t(para, 210);
> >       u16 txsr        = 88;
> >
> >       u8 tmrw         = 5;
> >       u8 tmrd         = 5;
> > -     u8 tmod         = max(ns_to_t(15), 12);
> > -     u8 tcke         = max(ns_to_t(6), 3);
> > -     u8 tcksrx       = max(ns_to_t(12), 4);
> > -     u8 tcksre       = max(ns_to_t(12), 4);
> > +     u8 tmod         = max(h616_ns_to_t(para, 15), 12);
> > +     u8 tcke         = max(h616_ns_to_t(para, 6), 3);
> > +     u8 tcksrx       = max(h616_ns_to_t(para, 12), 4);
> > +     u8 tcksre       = max(h616_ns_to_t(para, 12), 4);
> >       u8 tckesr       = tcke + 2;
> >       u8 trasmax      = (para->clk / 2) / 16;
> > -     u8 txs          = ns_to_t(360) / 32;
> > +     u8 txs          = h616_ns_to_t(para, 360) / 32;
> >       u8 txsdll       = 16;
> >       u8 txsabort     = 4;
> >       u8 txsfast      = 4;
> > diff --git a/arch/arm/mach-sunxi/dram_timings/h616_lpddr4_2133.c b/arch/arm/mach-sunxi/dram_timings/h616_lpddr4_2133.c
> > index 6f5c4acbd62..e1ea41f7a3e 100644
> > --- a/arch/arm/mach-sunxi/dram_timings/h616_lpddr4_2133.c
> > +++ b/arch/arm/mach-sunxi/dram_timings/h616_lpddr4_2133.c
> > @@ -12,30 +12,30 @@
> >  #include <asm/arch/dram.h>
> >  #include <asm/arch/cpu.h>
> >
> > -void mctl_set_timing_params(const struct dram_para *para)
> > +void h616_lpddr4_set_timing_params(const struct dram_para *para)
> >  {
> >       struct sunxi_mctl_ctl_reg * const mctl_ctl =
> >                       (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> >
> >       u8 tccd         = 4;
> > -     u8 tfaw         = ns_to_t(40);
> > -     u8 trrd         = max(ns_to_t(10), 2);
> > -     u8 trcd         = max(ns_to_t(18), 2);
> > -     u8 trc          = ns_to_t(65);
> > -     u8 txp          = max(ns_to_t(8), 2);
> > +     u8 tfaw         = h616_ns_to_t(para, 40);
> > +     u8 trrd         = max(h616_ns_to_t(para, 10), 2);
> > +     u8 trcd         = max(h616_ns_to_t(para, 18), 2);
> > +     u8 trc          = h616_ns_to_t(para, 65);
> > +     u8 txp          = max(h616_ns_to_t(para, 8), 2);
> >       u8 trtp         = 4;
> > -     u8 trp          = ns_to_t(21);
> > -     u8 tras         = ns_to_t(42);
> > -     u16 trefi       = ns_to_t(3904) / 32;
> > -     u16 trfc        = ns_to_t(280);
> > -     u16 txsr        = ns_to_t(190);
> > +     u8 trp          = h616_ns_to_t(para, 21);
> > +     u8 tras         = h616_ns_to_t(para, 42);
> > +     u16 trefi       = h616_ns_to_t(para, 3904) / 32;
> > +     u16 trfc        = h616_ns_to_t(para, 280);
> > +     u16 txsr        = h616_ns_to_t(para, 190);
> >
> > -     u8 tmrw         = max(ns_to_t(14), 5);
> > +     u8 tmrw         = max(h616_ns_to_t(para, 14), 5);
> >       u8 tmrd         = tmrw;
> >       u8 tmod         = 12;
> > -     u8 tcke         = max(ns_to_t(15), 2);
> > -     u8 tcksrx       = max(ns_to_t(2), 2);
> > -     u8 tcksre       = max(ns_to_t(5), 2);
> > +     u8 tcke         = max(h616_ns_to_t(para, 15), 2);
> > +     u8 tcksrx       = max(h616_ns_to_t(para, 2), 2);
> > +     u8 tcksre       = max(h616_ns_to_t(para, 5), 2);
> >       u8 tckesr       = tcke;
> >       u8 trasmax      = (trefi * 9) / 32;
> >       u8 txs          = 4;
> > @@ -49,7 +49,7 @@ void mctl_set_timing_params(const struct dram_para *para)
> >
> >       u8 twtp         = 24;
> >       u8 twr2rd       = max(trrd, (u8)4) + 14;
> > -     u8 trd2wr       = (ns_to_t(4) + 17) - ns_to_t(1);
> > +     u8 trd2wr       = (h616_ns_to_t(para, 4) + 17) - h616_ns_to_t(para, 1);
> >
> >       /* set DRAM timing */
> >       writel((twtp << 24) | (tfaw << 16) | (trasmax << 8) | tras,
>


More information about the U-Boot mailing list