[PATCH v2 16/22] sunxi: add R528/T113-s3/D1(s) DRAM initialisation code

Andre Przywara andre.przywara at arm.com
Mon Oct 23 00:40:18 CEST 2023


On Sat, 21 Oct 2023 22:52:06 -0500
Samuel Holland <samuel at sholland.org> wrote:

Hi Samuel,

> On 9/28/23 16:54, Andre Przywara wrote:
> > The Allwinner R528/T113-s/D1/D1s SoCs all share the same die, so use the
> > same DRAM initialisation code.
> > Make use of prior art here and lift some code from awboot[1], which
> > carried init code based on earlier decompilation efforts, but with a
> > GPL2 license tag.
> > This code has been heavily reworked and cleaned up, to match previous
> > DRAM routines for other SoCs, and also to be closer to U-Boot's coding
> > style and support routines.
> > The actual DRAM chip timing parameters are included in the main file,
> > since they cover all DRAM types, and are protected by a new Kconfig
> > CONFIG_SUNXI_DRAM_TYPE symbol, which allows the compiler to pick only
> > the relevant settings, at build time.
> > 
> > The relevant DRAM chips/board specific configuration parameters are
> > delivered via Kconfig, so this code here should work for all supported
> > SoCs and DRAM chips combinations.
> > 
> > Signed-off-by: Andre Przywara <andre.przywara at arm.com>
> > Tested-by: Sam Edwards <CFSworks at gmail.com>
> > ---
> >  drivers/Makefile                   |    1 +
> >  drivers/ram/Makefile               |    3 +
> >  drivers/ram/sunxi/Kconfig          |   59 ++
> >  drivers/ram/sunxi/Makefile         |    4 +
> >  drivers/ram/sunxi/dram_sun20i_d1.c | 1432 ++++++++++++++++++++++++++++
> >  drivers/ram/sunxi/dram_sun20i_d1.h |   73 ++
> >  6 files changed, 1572 insertions(+)
> >  create mode 100644 drivers/ram/sunxi/Makefile
> >  create mode 100644 drivers/ram/sunxi/dram_sun20i_d1.c
> >  create mode 100644 drivers/ram/sunxi/dram_sun20i_d1.h
> > 
> > diff --git a/drivers/Makefile b/drivers/Makefile
> > index efc2a4afb24..5a4bedf7305 100644
> > --- a/drivers/Makefile
> > +++ b/drivers/Makefile
> > @@ -56,6 +56,7 @@ obj-$(CONFIG_$(SPL_)ALTERA_SDRAM) += ddr/altera/
> >  obj-$(CONFIG_ARCH_IMX8M) += ddr/imx/imx8m/
> >  obj-$(CONFIG_IMX8ULP_DRAM) += ddr/imx/imx8ulp/
> >  obj-$(CONFIG_ARCH_IMX9) += ddr/imx/imx9/
> > +obj-$(CONFIG_DRAM_SUN8I_R528) += ram/  
> 
> This would need to be duplicated for DRAM_SUN20I_D1.
> 
> >  obj-$(CONFIG_SPL_DM_RESET) += reset/
> >  obj-$(CONFIG_SPL_MUSB_NEW) += usb/musb-new/
> >  obj-$(CONFIG_SPL_USB_GADGET) += usb/gadget/
> > diff --git a/drivers/ram/Makefile b/drivers/ram/Makefile
> > index 6eb1a241359..b4750ea11c4 100644
> > --- a/drivers/ram/Makefile
> > +++ b/drivers/ram/Makefile
> > @@ -23,6 +23,9 @@ obj-$(CONFIG_RAM_SIFIVE) += sifive/
> >  ifdef CONFIG_SPL_BUILD
> >  obj-$(CONFIG_SPL_STARFIVE_DDR) += starfive/
> >  endif
> > +
> > +obj-$(CONFIG_DRAM_SUN8I_R528) += sunxi/  
> 
> This would need to be duplicated for DRAM_SUN20I_D1.

Right, so I joined both symbols into one now. I think I wanted to keep
the DM_RAM and non-DM parts logically apart, but it works nevertheless.

My gut feeling is this needs more adjustments anyway once we need the
DM_RAM or want to extend it to more SoCs, so we can fix things when
they need fixing, later.

> 
> > +
> >  obj-$(CONFIG_ARCH_OCTEON) += octeon/
> >  
> >  obj-$(CONFIG_ARCH_RMOBILE) += renesas/
> > diff --git a/drivers/ram/sunxi/Kconfig b/drivers/ram/sunxi/Kconfig
> > index 261d7f57409..35eeda58efa 100644
> > --- a/drivers/ram/sunxi/Kconfig
> > +++ b/drivers/ram/sunxi/Kconfig
> > @@ -12,3 +12,62 @@ config DRAM_SUN8I_R528
> >  	default y if MACH_SUN8I_R528
> >  	help
> >  	  Select this DRAM controller driver for the R528/T113s SoCs.
> > +
> > +if DRAM_SUN20I_D1 || DRAM_SUN8I_R528
> > +
> > +config DRAM_SUNXI_ODT_EN  
> 
> You have a mixture of "DRAM_SUNXI" and "SUNXI_DRAM" for the tunables
> here. I would recommend being consistent.

This was chosen to stay consistent with the existing DRAM drivers,
which use DRAM_SUNxx_ for parameters, but SUNXI_DRAM_TYPE_ for the type
selection. I was looking into moving the other Allwinner DRAM drivers
into here as well (at least for every future SoC), and tested this with
the H616: keeping the symbols compatible was a requirement.

> 
> > +	hex "DRAM ODT EN parameter"
> > +	default 0x1
> > +	help
> > +	  ODT EN value from vendor DRAM settings.
> > +
> > +config DRAM_SUNXI_TPR0
> > +	hex "DRAM TPR0 parameter"
> > +	default 0x0
> > +	help
> > +	  TPR0 value from vendor DRAM settings.
> > +
> > +config DRAM_SUNXI_TPR11
> > +	hex "DRAM TPR11 parameter"
> > +	default 0x0
> > +	help
> > +	  TPR11 value from vendor DRAM settings.
> > +
> > +config DRAM_SUNXI_TPR12
> > +	hex "DRAM TPR12 parameter"
> > +	default 0x0
> > +	help
> > +	  TPR12 value from vendor DRAM settings.
> > +
> > +config DRAM_SUNXI_TPR13
> > +	hex "DRAM TPR13 parameter"
> > +	default 0x0  
> 
> I would suggest dropping the defaults for these tunables. It was
> non-obvious that I was missing some configuration when I switched to
> your driver. I think it's reasonable to require the defconfig/user to
> provide these.

Yeah, that's a good point. No actual value is 0, so this doesn't even
simplify defconfigs.

> > +	help
> > +	  TPR13 value from vendor DRAM settings. It tells which features
> > +	  should be configured.
> > +
> > +choice
> > +	prompt "DRAM chip type"
> > +	default SUNXI_DRAM_TYPE_DDR3 if DRAM_SUN8I_R528 || DRAM_SUN20I_D1
> > +
> > +config SUNXI_DRAM_TYPE_DDR2
> > +        bool "DDR2 chips"
> > +
> > +config SUNXI_DRAM_TYPE_DDR3
> > +        bool "DDR3 chips"
> > +
> > +config SUNXI_DRAM_TYPE_LPDDR2
> > +        bool "LPDDR2 chips"
> > +
> > +config SUNXI_DRAM_TYPE_LPDDR3
> > +        bool "LPDDR3 chips"
> > +endchoice
> > +
> > +config SUNXI_DRAM_TYPE
> > +	int
> > +	default 2 if SUNXI_DRAM_TYPE_DDR2
> > +	default 3 if SUNXI_DRAM_TYPE_DDR3
> > +	default 6 if SUNXI_DRAM_TYPE_LPDDR2
> > +	default 7 if SUNXI_DRAM_TYPE_LPDDR3
> > +
> > +endif
> > diff --git a/drivers/ram/sunxi/Makefile b/drivers/ram/sunxi/Makefile
> > new file mode 100644
> > index 00000000000..d6fb2cf0b65
> > --- /dev/null
> > +++ b/drivers/ram/sunxi/Makefile
> > @@ -0,0 +1,4 @@
> > +# SPDX-License-Identifier: GPL-2.0+
> > +
> > +obj-$(CONFIG_DRAM_SUN20I_D1) += dram_sun20i_d1.o
> > +obj-$(CONFIG_DRAM_SUN8I_R528) += dram_sun20i_d1.o
> > diff --git a/drivers/ram/sunxi/dram_sun20i_d1.c b/drivers/ram/sunxi/dram_sun20i_d1.c
> > new file mode 100644
> > index 00000000000..c766fc24065
> > --- /dev/null
> > +++ b/drivers/ram/sunxi/dram_sun20i_d1.c
> > @@ -0,0 +1,1432 @@
> > [...]
> > +/*
> > + * This routine sizes a DRAM device by cycling through address lines and
> > + * figuring out if they are connected to a real address line, or if the
> > + * address is a mirror.
> > + * First the column and bank bit allocations are set to low values (2 and 9
> > + * address lines). Then a maximum allocation (16 lines) is set for rows and
> > + * this is tested.
> > + * Next the BA2 line is checked. This seems to be placed above the column,
> > + * BA0-1 and row addresses. Finally, the column address is allocated 13 lines
> > + * and these are tested. The results are placed in dram_para1 and dram_para2.
> > + */
> > +static int auto_scan_dram_size(const dram_para_t *para, dram_config_t *config)
> > +{
> > +	unsigned int rval, i, j, rank, maxrank, offs;
> > +	unsigned int shft;
> > +	unsigned long ptr, mc_work_mode, chk;  
> 
> This breaks on 64-bit architectures, because readl(chk) can never equal
> ~ptr. Please change ptr and chk to unsigned int.

I think they were originally ints (at least I see it like this is an
earlier version), but I changed it to make "ptr" a pointer compatible
type.
Definitely "chk" is only used as a pointer, so should be long (or
uintptr_t).
The actual problem is that "ptr" is used both as a pointer *and* a
payload, which is odd. I will keep ptr long, but make sure to
write only a true 32 bit payload into the address it points to.

> 
> > +
> > +	if (mctl_core_init(para, config) == 0) {
> > +		printf("DRAM initialisation error : 0\n");
> > +		return 0;
> > +	}
> > +
> > +	maxrank	= (config->dram_para2 & 0xf000) ? 2 : 1;
> > +	mc_work_mode = 0x3102000;
> > +	offs = 0;
> > +
> > +	/* write test pattern */
> > +	for (i = 0, ptr = CFG_SYS_SDRAM_BASE; i < 64; i++, ptr += 4)
> > +		writel((i & 0x1) ? ptr : ~ptr, ptr);
> > +
> > +	for (rank = 0; rank < maxrank;) {
> > +		/* set row mode */
> > +		clrsetbits_le32(mc_work_mode, 0xf0c, 0x6f0);
> > +		udelay(1);
> > +
> > +		// Scan per address line, until address wraps (i.e. see shadow)
> > +		for (i = 11; i < 17; i++) {
> > +			chk = CFG_SYS_SDRAM_BASE + (1U << (i + 11));
> > +			ptr = CFG_SYS_SDRAM_BASE;
> > +			for (j = 0; j < 64; j++) {
> > +				if (readl(chk) != ((j & 1) ? ptr : ~ptr))
> > +					break;
> > +				ptr += 4;
> > +				chk += 4;
> > +			}
> > +			if (j == 64)
> > +				break;
> > +		}
> > +		if (i > 16)
> > +			i = 16;
> > +		debug("rank %d row = %d\n", rank, i);
> > +
> > +		/* Store rows in para 1 */
> > +		shft = offs + 4;
> > +		rval = config->dram_para1;
> > +		rval &= ~(0xff << shft);
> > +		rval |= i << shft;
> > +		config->dram_para1 = rval;
> > +
> > +		if (rank == 1)		/* Set bank mode for rank0 */
> > +			clrsetbits_le32(0x3102000, 0xffc, 0x6a4);
> > +
> > +		/* Set bank mode for current rank */
> > +		clrsetbits_le32(mc_work_mode, 0xffc, 0x6a4);
> > +		udelay(1);
> > +
> > +		// Test if bit A23 is BA2 or mirror XXX A22?
> > +		chk = CFG_SYS_SDRAM_BASE + (1U << 22);
> > +		ptr = CFG_SYS_SDRAM_BASE;
> > +		for (i = 0, j = 0; i < 64; i++) {
> > +			if (readl(chk) != ((i & 1) ? ptr : ~ptr)) {
> > +				j = 1;
> > +				break;
> > +			}
> > +			ptr += 4;
> > +			chk += 4;
> > +		}
> > +
> > +		debug("rank %d bank = %d\n", rank, (j + 1) << 2); /* 4 or 8 */
> > +
> > +		/* Store banks in para 1 */
> > +		shft = 12 + offs;
> > +		rval = config->dram_para1;
> > +		rval &= ~(0xf << shft);
> > +		rval |= j << shft;
> > +		config->dram_para1 = rval;
> > +
> > +		if (rank == 1)		/* Set page mode for rank0 */
> > +			clrsetbits_le32(0x3102000, 0xffc, 0xaa0);
> > +
> > +		/* Set page mode for current rank */
> > +		clrsetbits_le32(mc_work_mode, 0xffc, 0xaa0);
> > +		udelay(1);
> > +
> > +		// Scan per address line, until address wraps (i.e. see shadow)
> > +		for (i = 9; i < 14; i++) {
> > +			chk = CFG_SYS_SDRAM_BASE + (1U << i);
> > +			ptr = CFG_SYS_SDRAM_BASE;
> > +			for (j = 0; j < 64; j++) {
> > +				if (readl(chk) != ((j & 1) ? ptr : ~ptr))
> > +					break;
> > +				ptr += 4;
> > +				chk += 4;
> > +			}
> > +			if (j == 64)
> > +				break;
> > +		}
> > +		if (i > 13)
> > +			i = 13;
> > +
> > +		unsigned int pgsize = (i == 9) ? 0 : (1 << (i - 10));
> > +		debug("rank %d page size = %d KB\n", rank, pgsize);
> > +
> > +		/* Store page size */
> > +		shft = offs;
> > +		rval = config->dram_para1;
> > +		rval &= ~(0xf << shft);
> > +		rval |= pgsize << shft;
> > +		config->dram_para1 = rval;
> > +
> > +		// Move to next rank
> > +		rank++;
> > +		if (rank != maxrank) {
> > +			if (rank == 1) {
> > +				/* MC_WORK_MODE */
> > +				clrsetbits_le32(0x3202000, 0xffc, 0x6f0);
> > +
> > +				/* MC_WORK_MODE2 */
> > +				clrsetbits_le32(0x3202004, 0xffc, 0x6f0);
> > +			}
> > +			/* store rank1 config in upper half of para1 */
> > +			offs += 16;
> > +			mc_work_mode += 4;	/* move to MC_WORK_MODE2 */
> > +		}
> > +	}
> > +	if (maxrank == 2) {
> > +		config->dram_para2 &= 0xfffff0ff;
> > +		/* note: rval is equal to para->dram_para1 here */
> > +		if ((rval & 0xffff) == (rval >> 16)) {
> > +			debug("rank1 config same as rank0\n");
> > +		} else {
> > +			config->dram_para2 |= BIT(8);
> > +			debug("rank1 config different from rank0\n");
> > +		}
> > +	}
> > +
> > +	return 1;
> > +}
> > [...]
> > +static int sunxi_ram_probe(struct udevice *dev)
> > +{
> > +	struct sunxi_ram_priv *priv = dev_get_priv(dev);
> > +	unsigned long dram_size;
> > +
> > +	debug("%s: %s: probing\n", __func__, dev->name);
> > +
> > +	dram_size = sunxi_dram_init();
> > +	if (!dram_size) {
> > +		printf("DRAM init failed: %d\n", ret);  
> 
> There is no ret variable anymore, so this fails to compile. With this
> line and the variable size issue fixed, this driver works on my Nezha
> board, so with those fixes:

Ah, right, thanks for the heads up. I think I had some trick to compile
test this code once, but didn't use it since last year.

> Tested-by: Samuel Holland <samuel at sholland.org>

Thanks!
Andre

P.S. This driver has a lot of rough edges anyway (the whole
clock/"system" setup side, for a start), but we should get things
moving now, before the RFC version celebrates its first birthday. So I
will merge it, but am happy to take fixes: either cleanups, or fixes
for the D1.

> > +		return -ENODEV;
> > +	}
> > +
> > +	priv->size = dram_size;
> > +
> > +	return 0;
> > +}  
> 



More information about the U-Boot mailing list