在 2023-01-03星期二的 01:17 +0000,Andre Przywara写道: > 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.
The D1 DRAM controller should be similar to the R329 one, which I added (legacy) support at [1]. [1] https://patchwork.ozlabs.org/project/uboot/patch/20210722063015.421923-8-icen...@sipeed.com/ > > Signed-off-by: Andre Przywara <andre.przyw...@arm.com> > --- > drivers/Makefile | 1 + > drivers/ram/Makefile | 2 + > drivers/ram/sunxi/Kconfig | 55 ++ > drivers/ram/sunxi/Makefile | 4 + > drivers/ram/sunxi/dram_sun20i_d1.c | 1425 > ++++++++++++++++++++++++++++ > drivers/ram/sunxi/dram_sun20i_d1.h | 70 ++ > 6 files changed, 1557 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 6f1de58e003..6a0d1ec2ff0 100644 > --- a/drivers/Makefile > +++ b/drivers/Makefile > @@ -52,6 +52,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/ > 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 5a39611349d..82686046f1d 100644 > --- a/drivers/ram/Makefile > +++ b/drivers/ram/Makefile > @@ -21,4 +21,6 @@ obj-$(CONFIG_IMXRT_SDRAM) += imxrt_sdram.o > > obj-$(CONFIG_RAM_SIFIVE) += sifive/ > > +obj-$(CONFIG_DRAM_SUN8I_R528) += sunxi/ > + > obj-$(CONFIG_ARCH_OCTEON) += octeon/ > diff --git a/drivers/ram/sunxi/Kconfig b/drivers/ram/sunxi/Kconfig > index 97e261de542..36875ddf22d 100644 > --- a/drivers/ram/sunxi/Kconfig > +++ b/drivers/ram/sunxi/Kconfig > @@ -11,3 +11,58 @@ config DRAM_SUN8I_R528 > default y if MACH_SUN8I_R528 > help > Select this DRAM controller driver for the R528/T113s SoCs. > + > +config DRAM_SUNXI_ODT_EN > + 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 > + help > + TPR13 value from vendor DRAM settings. It tells which > features > + should be configured. > + > +choice > + prompt "DRAM chip type" > + default SUNXI_DRAM_DDR3 if DRAM_SUN8I_R528 || DRAM_SUN20I_D1 > + > +config SUNXI_DRAM_DDR2 > + bool "DDR2 chips" > + > +config SUNXI_DRAM_DDR3 > + bool "DDR3 chips" > + > +config SUNXI_DRAM_LPDDR2 > + bool "LPDDR2 chips" > + > +config SUNXI_DRAM_LPDDR3 > + bool "LPDDR3 chips" > +endchoice > + > +config SUNXI_DRAM_TYPE > + int > + default 2 if SUNXI_DRAM_DDR2 > + default 3 if SUNXI_DRAM_DDR3 > + default 6 if SUNXI_DRAM_LPDDR2 > + default 7 if SUNXI_DRAM_LPDDR3 > 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..06b4cd2c70b > --- /dev/null > +++ b/drivers/ram/sunxi/dram_sun20i_d1.c > @@ -0,0 +1,1425 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Allwinner D1/D1s/R528/T113-sx DRAM initialisation > + * > + * As usual there is no documentation for the memory controller or > PHY IP > + * used here. The baseline of this code was lifted from awboot[1], > which > + * seems to be based on some form of de-compilation of some original > Allwinner > + * code bits (with a GPL2 license tag from the very beginning). > + * This version here is a reworked version, to match the U-Boot > coding style > + * and style of the other Allwinner DRAM drivers. > + * > + * [1] https://github.com/szemzoa/awboot.git > + */ > + > +#include <asm/io.h> > +#include <common.h> > +#ifdef CONFIG_RAM > + #include <dm.h> > + #include <ram.h> > +#endif > +#include <linux/delay.h> > + > +#include "dram_sun20i_d1.h" > + > +#ifndef SUNXI_SID_BASE > +#define SUNXI_SID_BASE 0x3006200 > +#endif > + > +static void sid_read_ldoB_cal(dram_para_t *para) > +{ > + uint32_t reg; > + > + reg = (readl(SUNXI_SID_BASE + 0x1c) & 0xff00) >> 8; > + > + if (reg == 0) > + return; > + > + switch (para->dram_type) { > + case SUNXI_DRAM_TYPE_DDR2: > + break; > + case SUNXI_DRAM_TYPE_DDR3: > + if (reg > 0x20) > + reg -= 0x16; > + break; > + default: > + reg = 0; > + break; > + } > + > + clrsetbits_le32(0x3000150, 0xff00, reg << 8); > +} > + > +static void dram_voltage_set(dram_para_t *para) > +{ > + int vol; > + > + switch (para->dram_type) { > + case SUNXI_DRAM_TYPE_DDR2: > + vol = 47; > + break; > + case SUNXI_DRAM_TYPE_DDR3: > + vol = 25; > + break; > + default: > + vol = 0; > + break; > + } > + > + clrsetbits_le32(0x3000150, 0x20ff00, vol << 8); > + > + udelay(1); > + > + sid_read_ldoB_cal(para); > +} > + > +static void dram_enable_all_master(void) > +{ > + writel(~0, 0x3102020); > + writel(0xff, 0x3102024); > + writel(0xffff, 0x3102028); > + udelay(10); > +} > + > +static void dram_disable_all_master(void) > +{ > + writel(1, 0x3102020); > + writel(0, 0x3102024); > + writel(0, 0x3102028); > + udelay(10); > +} > + > +static void eye_delay_compensation(dram_para_t *para) // s1 > +{ > + uint32_t delay; > + unsigned long ptr; > + > + // DATn0IOCR, n = 0...7 > + delay = (para->dram_tpr11 & 0xf) << 9; > + delay |= (para->dram_tpr12 & 0xf) << 1; > + for (ptr = 0x3103310; ptr < 0x3103334; ptr += 4) > + setbits_le32(ptr, delay); > + > + // DATn1IOCR, n = 0...7 > + delay = (para->dram_tpr11 & 0xf0) << 5; > + delay |= (para->dram_tpr12 & 0xf0) >> 3; > + for (ptr = 0x3103390; ptr != 0x31033b4; ptr += 4) > + setbits_le32(ptr, delay); > + > + // PGCR0: assert AC loopback FIFO reset > + clrbits_le32(0x3103100, 0x04000000); > + > + // ?? > + > + delay = (para->dram_tpr11 & 0xf0000) >> 7; > + delay |= (para->dram_tpr12 & 0xf0000) >> 15; > + setbits_le32(0x3103334, delay); > + setbits_le32(0x3103338, delay); > + > + delay = (para->dram_tpr11 & 0xf00000) >> 11; > + delay |= (para->dram_tpr12 & 0xf00000) >> 19; > + setbits_le32(0x31033b4, delay); > + setbits_le32(0x31033b8, delay); > + > + setbits_le32(0x310333c, (para->dram_tpr11 & 0xf0000) << 9); > + setbits_le32(0x31033bc, (para->dram_tpr11 & 0xf00000) << 5); > + > + // PGCR0: release AC loopback FIFO reset > + setbits_le32(0x3103100, BIT(26)); > + > + udelay(1); > + > + delay = (para->dram_tpr10 & 0xf0) << 4; > + for (ptr = 0x3103240; ptr != 0x310327c; ptr += 4) > + setbits_le32(ptr, delay); > + for (ptr = 0x3103228; ptr != 0x3103240; ptr += 4) > + setbits_le32(ptr, delay); > + > + setbits_le32(0x3103218, (para->dram_tpr10 & 0x0f) << 8); > + setbits_le32(0x310321c, (para->dram_tpr10 & 0x0f) << 8); > + > + setbits_le32(0x3103280, (para->dram_tpr10 & 0xf00) >> 4); > +} > + > +/* > + * Main purpose of the auto_set_timing routine seems to be to > calculate all > + * timing settings for the specific type of sdram used. Read > together with > + * an sdram datasheet for context on the various variables. > + */ > +static void mctl_set_timing_params(dram_para_t *para) > +{ > + /* DRAM_TPR0 */ > + u8 tccd = 2; > + u8 tfaw; > + u8 trrd; > + u8 trcd; > + u8 trc; > + > + /* DRAM_TPR1 */ > + u8 txp; > + u8 twtr; > + u8 trtp = 4; > + u8 twr; > + u8 trp; > + u8 tras; > + > + /* DRAM_TPR2 */ > + u16 trefi; > + u16 trfc; > + > + u8 tcksrx; > + u8 tckesr; > + u8 trd2wr; > + u8 twr2rd; > + u8 trasmax; > + u8 twtp; > + u8 tcke; > + u8 tmod; > + u8 tmrd; > + u8 tmrw; > + > + u8 tcl; > + u8 tcwl; > + u8 t_rdata_en; > + u8 wr_latency; > + > + u32 mr0; > + u32 mr1; > + u32 mr2; > + u32 mr3; > + > + u32 tdinit0; > + u32 tdinit1; > + u32 tdinit2; > + u32 tdinit3; > + > + switch (CONFIG_SUNXI_DRAM_TYPE) { > + case SUNXI_DRAM_TYPE_DDR2: > + /* DRAM_TPR0 */ > + tfaw = ns_to_t(50); > + trrd = ns_to_t(10); > + trcd = ns_to_t(20); > + trc = ns_to_t(65); > + > + /* DRAM_TPR1 */ > + txp = 2; > + twtr = ns_to_t(8); > + twr = ns_to_t(15); > + trp = ns_to_t(15); > + tras = ns_to_t(45); > + > + /* DRAM_TRP2 */ > + trfc = ns_to_t(328); > + trefi = ns_to_t(7800) / 32; > + > + trasmax = CONFIG_DRAM_CLK / 30; > + if (CONFIG_DRAM_CLK < 409) { > + t_rdata_en = 1; > + tcl = 3; > + mr0 = 0x06a3; > + } else { > + t_rdata_en = 2; > + tcl = 4; > + mr0 = 0x0e73; > + } > + tmrd = 2; > + twtp = twr + 5; > + tcksrx = 5; > + tckesr = 4; > + trd2wr = 4; > + tcke = 3; > + tmod = 12; > + wr_latency = 1; > + tmrw = 0; > + twr2rd = twtr + 5; > + tcwl = 0; > + > + mr1 = para->dram_mr1; > + mr2 = 0; > + mr3 = 0; > + > + tdinit0 = 200 * CONFIG_DRAM_CLK + 1; > + tdinit1 = 100 * CONFIG_DRAM_CLK / 1000 + 1; > + tdinit2 = 200 * CONFIG_DRAM_CLK + 1; > + tdinit3 = 1 * CONFIG_DRAM_CLK + 1; > + > + break; > + case SUNXI_DRAM_TYPE_DDR3: > + trfc = ns_to_t(350); > + trefi = ns_to_t(7800) / 32 + 1; // > XXX > + > + twtr = ns_to_t(8) + 2; // + > 2 ? XXX > + /* Only used by trd2wr calculation, which gets > discard below */ > +// twr = max(ns_to_t(15), 2); > + trrd = max(ns_to_t(10), 2); > + txp = max(ns_to_t(10), 2); > + > + if (CONFIG_DRAM_CLK <= 800) { > + tfaw = ns_to_t(50); > + trcd = ns_to_t(15); > + trp = ns_to_t(15); > + trc = ns_to_t(53); > + tras = ns_to_t(38); > + > + mr0 = 0x1c70; > + mr2 = 0x18; > + tcl = 6; > + wr_latency = 2; > + tcwl = 4; > + t_rdata_en = 4; > + } else { > + tfaw = ns_to_t(35); > + trcd = ns_to_t(14); > + trp = ns_to_t(14); > + trc = ns_to_t(48); > + tras = ns_to_t(34); > + > + mr0 = 0x1e14; > + mr2 = 0x20; > + tcl = 7; > + wr_latency = 3; > + tcwl = 5; > + t_rdata_en = 5; > + } > + > + trasmax = CONFIG_DRAM_CLK / 30; > + twtp = tcwl + 2 + twtr; // > WL+BL/2+tWTR > + /* Gets overwritten below */ > +// trd2wr = tcwl + 2 + twr; // > WL+BL/2+tWR > + twr2rd = tcwl + twtr; // > WL+tWTR > + > + tdinit0 = 500 * CONFIG_DRAM_CLK + 1; // > 500 us > + tdinit1 = 360 * CONFIG_DRAM_CLK / 1000 + 1; > // 360 ns > + tdinit2 = 200 * CONFIG_DRAM_CLK + 1; // > 200 us > + tdinit3 = 1 * CONFIG_DRAM_CLK + 1; // > 1 us > + > + mr1 = para->dram_mr1; > + mr3 = 0; > + tcke = 3; > + tcksrx = 5; > + tckesr = 4; > + if (((para->dram_tpr13 & 0xc) == 0x04) || > CONFIG_DRAM_CLK < 912) > + trd2wr = 5; > + else > + trd2wr = 6; > + > + tmod = 12; > + tmrd = 4; > + tmrw = 0; > + > + break; > + case SUNXI_DRAM_TYPE_LPDDR2: > + tfaw = max(ns_to_t(50), 4); > + trrd = max(ns_to_t(10), 1); > + trcd = max(ns_to_t(24), 2); > + trc = ns_to_t(70); > + txp = ns_to_t(8); > + if (txp < 2) { > + txp++; > + twtr = 2; > + } else { > + twtr = txp; > + } > + twr = max(ns_to_t(15), 2); > + trp = ns_to_t(17); > + tras = ns_to_t(42); > + trefi = ns_to_t(3900) / 32; > + trfc = ns_to_t(210); > + > + trasmax = CONFIG_DRAM_CLK / 60; > + mr3 = para->dram_mr3; > + twtp = twr + 5; > + mr2 = 6; > + mr1 = 5; > + tcksrx = 5; > + tckesr = 5; > + trd2wr = 10; > + tcke = 2; > + tmod = 5; > + tmrd = 5; > + tmrw = 3; > + tcl = 4; > + wr_latency = 1; > + t_rdata_en = 1; > + > + tdinit0 = 200 * CONFIG_DRAM_CLK + 1; > + tdinit1 = 100 * CONFIG_DRAM_CLK / 1000 + 1; > + tdinit2 = 11 * CONFIG_DRAM_CLK + 1; > + tdinit3 = 1 * CONFIG_DRAM_CLK + 1; > + twr2rd = twtr + 5; > + tcwl = 2; > + mr1 = 195; > + mr0 = 0; > + > + break; > + case SUNXI_DRAM_TYPE_LPDDR3: > + tfaw = max(ns_to_t(50), 4); > + trrd = max(ns_to_t(10), 1); > + trcd = max(ns_to_t(24), 2); > + trc = ns_to_t(70); > + twtr = max(ns_to_t(8), 2); > + twr = max(ns_to_t(15), 2); > + trp = ns_to_t(17); > + tras = ns_to_t(42); > + trefi = ns_to_t(3900) / 32; > + trfc = ns_to_t(210); > + txp = twtr; > + > + trasmax = CONFIG_DRAM_CLK / 60; > + if (CONFIG_DRAM_CLK < 800) { > + tcwl = 4; > + wr_latency = 3; > + t_rdata_en = 6; > + mr2 = 12; > + } else { > + tcwl = 3; > + tcke = 6; > + wr_latency = 2; > + t_rdata_en = 5; > + mr2 = 10; > + } > + twtp = tcwl + 5; > + tcl = 7; > + mr3 = para->dram_mr3; > + tcksrx = 5; > + tckesr = 5; > + trd2wr = 13; > + tcke = 3; > + tmod = 12; > + tdinit0 = 400 * CONFIG_DRAM_CLK + 1; > + tdinit1 = 500 * CONFIG_DRAM_CLK / 1000 + 1; > + tdinit2 = 11 * CONFIG_DRAM_CLK + 1; > + tdinit3 = 1 * CONFIG_DRAM_CLK + 1; > + tmrd = 5; > + tmrw = 5; > + twr2rd = tcwl + twtr + 5; > + mr1 = 195; > + mr0 = 0; > + > + break; > + default: > + trfc = 128; > + trp = 6; > + trefi = 98; > + txp = 10; > + twr = 8; > + twtr = 3; > + tras = 14; > + tfaw = 16; > + trc = 20; > + trcd = 6; > + trrd = 3; > + > + twr2rd = 8; // 48(sp) > + tcksrx = 4; // t1 > + tckesr = 3; // t4 > + trd2wr = 4; // t6 > + trasmax = 27; // t3 > + twtp = 12; // s6 > + tcke = 2; // s8 > + tmod = 6; // t0 > + tmrd = 2; // t5 > + tmrw = 0; // a1 > + tcwl = 3; // a5 > + tcl = 3; // a0 > + wr_latency = 1; // a7 > + t_rdata_en = 1; // a4 > + mr3 = 0; // s0 > + mr2 = 0; // t2 > + mr1 = 0; // s1 > + mr0 = 0; // a3 > + tdinit3 = 0; // 40(sp) > + tdinit2 = 0; // 32(sp) > + tdinit1 = 0; // 24(sp) > + tdinit0 = 0; // 16(sp) > + > + break; > + } > + > + /* Set mode registers */ > + writel(mr0, 0x3103030); > + writel(mr1, 0x3103034); > + writel(mr2, 0x3103038); > + writel(mr3, 0x310303c); > + /* TODO: dram_odt_en is either 0x0 or 0x1, so right shift > looks weird */ > + writel((para->dram_odt_en >> 4) & 0x3, 0x310302c); > + > + /* Set dram timing DRAMTMG0 - DRAMTMG5 */ > + writel((twtp << 24) | (tfaw << 16) | (trasmax << 8) | (tras > << 0), > + 0x3103058); > + writel((txp << 16) | (trtp << 8) | (trc << 0), > + 0x310305c); > + writel((tcwl << 24) | (tcl << 16) | (trd2wr << 8) | (twr2rd > << 0), > + 0x3103060); > + writel((tmrw << 16) | (tmrd << 12) | (tmod << 0), > + 0x3103064); > + writel((trcd << 24) | (tccd << 16) | (trrd << 8) | (trp << > 0), > + 0x3103068); > + writel((tcksrx << 24) | (tcksrx << 16) | (tckesr << 8) | > (tcke << 0), > + 0x310306c); > + > + /* Set dual rank timing */ > + clrsetbits_le32(0x3103078, 0xf000ffff, > + (CONFIG_DRAM_CLK < 800) ? 0xf0006610 : > 0xf0007610); > + > + /* Set phy interface time PITMG0, PTR3, PTR4 */ > + writel((0x2 << 24) | (t_rdata_en << 16) | BIT(8) | > (wr_latency << 0), > + 0x3103080); > + writel(((tdinit0 << 0) | (tdinit1 << 20)), 0x3103050); > + writel(((tdinit2 << 0) | (tdinit3 << 20)), 0x3103054); > + > + /* Set refresh timing and mode */ > + writel((trefi << 16) | (trfc << 0), 0x3103090); > + writel((trefi << 15) & 0x0fff0000, 0x3103094); > +} > + > +// Purpose of this routine seems to be to initialize the PLL driving > +// the MBUS and sdram. > +// > +static int ccu_set_pll_ddr_clk(int index, dram_para_t *para) > +{ > + unsigned int val, clk, n; > + > + if (para->dram_tpr13 & BIT(6)) > + clk = para->dram_tpr9; > + else > + clk = para->dram_clk; > + > + // set VCO clock divider > + n = (clk * 2) / 24; > + > + val = readl(0x2001010); > + val &= 0xfff800fc; // clear dividers > + val |= (n - 1) << 8; // set PLL division > + val |= 0xc0000000; // enable PLL and LDO > + val &= 0xdfffffff; > + writel(val | 0x20000000, 0x2001010); > + > + // wait for PLL to lock > + while ((readl(0x2001010) & 0x10000000) == 0) { > + ; > + } > + > + udelay(20); > + > + // enable PLL output > + val = readl(0x2001000); > + val |= 0x08000000; > + writel(val, 0x2001000); > + > + // turn clock gate on > + val = readl(0x2001800); > + val &= 0xfcfffcfc; // select DDR clk source, n=1, m=1 > + val |= 0x80000000; // turn clock on > + writel(val, 0x2001800); > + > + return n * 24; > +} > + > +// Main purpose of sys_init seems to be to initalise the clocks for > +// the sdram controller. > +// > +static void mctl_sys_init(dram_para_t *para) > +{ > + // assert MBUS reset > + clrbits_le32(0x2001540, BIT(30)); > + > + // turn off sdram clock gate, assert sdram reset > + clrbits_le32(0x200180c, 0x10001); > + clrsetbits_le32(0x2001800, BIT(31) | BIT(30), BIT(27)); > + udelay(10); > + > + // set ddr pll clock > + para->dram_clk = ccu_set_pll_ddr_clk(0, para) / 2; > + udelay(100); > + dram_disable_all_master(); > + > + // release sdram reset > + setbits_le32(0x200180c, BIT(16)); > + > + // release MBUS reset > + setbits_le32(0x2001540, BIT(30)); > + setbits_le32(0x2001800, BIT(30)); > + > + udelay(5); > + > + // turn on sdram clock gate > + setbits_le32(0x200180c, BIT(0)); > + > + // turn dram clock gate on, trigger sdr clock update > + setbits_le32(0x2001800, BIT(31) | BIT(27)); > + udelay(5); > + > + // mCTL clock enable > + writel(0x8000, 0x310300c); > + udelay(10); > +} > + > +// The main purpose of this routine seems to be to copy an address > configuration > +// from the dram_para1 and dram_para2 fields to the PHY > configuration registers > +// (0x3102000, 0x3102004). > +// > +static void mctl_com_init(dram_para_t *para) > +{ > + uint32_t val, width; > + unsigned long ptr; > + int i; > + > + // purpose ?? > + clrsetbits_le32(0x3102008, 0x3f00, 0x2000); > + > + // set SDRAM type and word width > + val = readl(0x3102000) & ~0x00fff000; > + val |= (para->dram_type & 0x7) << 16; // DRAM type > + val |= (~para->dram_para2 & 0x1) << 12; // DQ width > + val |= BIT(22); // ?? > + if (para->dram_type == SUNXI_DRAM_TYPE_LPDDR2 || > + para->dram_type == SUNXI_DRAM_TYPE_LPDDR3) { > + val |= BIT(19); // type 6 and 7 must use 1T > + } else { > + if (para->dram_tpr13 & BIT(5)) > + val |= BIT(19); > + } > + writel(val, 0x3102000); > + > + // init rank / bank / row for single/dual or two different > ranks > + if ((para->dram_para2 & BIT(8)) && > + ((para->dram_para2 & 0xf000) != 0x1000)) > + width = 32; > + else > + width = 16; > + > + ptr = 0x3102000; > + for (i = 0; i < width; i += 16) { > + val = readl(ptr) & 0xfffff000; > + > + val |= (para->dram_para2 >> 12) & 0x3; // rank > + val |= ((para->dram_para1 >> (i + 12)) << 2) & 0x4; > // bank - 2 > + val |= (((para->dram_para1 >> (i + 4)) - 1) << 4) & > 0xff; // row - 1 > + > + // convert from page size to column addr width - 3 > + switch ((para->dram_para1 >> i) & 0xf) { > + case 8: val |= 0xa00; break; > + case 4: val |= 0x900; break; > + case 2: val |= 0x800; break; > + case 1: val |= 0x700; break; > + default: val |= 0x600; break; > + } > + writel(val, ptr); > + ptr += 4; > + } > + > + // set ODTMAP based on number of ranks in use > + val = (readl(0x3102000) & 0x1) ? 0x303 : 0x201; > + writel(val, 0x3103120); > + > + // set mctl reg 3c4 to zero when using half DQ > + if (para->dram_para2 & BIT(0)) > + writel(0, 0x31033c4); > + > + // purpose ?? > + if (para->dram_tpr4) { > + setbits_le32(0x3102000, (para->dram_tpr4 & 0x3) << > 25); > + setbits_le32(0x3102004, (para->dram_tpr4 & 0x7fc) << > 10); > + } > +} > + > +static const uint8_t ac_remapping_tables[][22] = { > + [0] = { 0 }, > + [1] = { 1, 9, 3, 7, 8, 18, 4, 13, 5, 6, 10, > + 2, 14, 12, 0, 0, 21, 17, 20, 19, 11, 22 }, > + [2] = { 4, 9, 3, 7, 8, 18, 1, 13, 2, 6, 10, > + 5, 14, 12, 0, 0, 21, 17, 20, 19, 11, 22 }, > + [3] = { 1, 7, 8, 12, 10, 18, 4, 13, 5, 6, 3, > + 2, 9, 0, 0, 0, 21, 17, 20, 19, 11, 22 }, > + [4] = { 4, 12, 10, 7, 8, 18, 1, 13, 2, 6, 3, > + 5, 9, 0, 0, 0, 21, 17, 20, 19, 11, 22 }, > + [5] = { 13, 2, 7, 9, 12, 19, 5, 1, 6, 3, 4, > + 8, 10, 0, 0, 0, 21, 22, 18, 17, 11, 20 }, > + [6] = { 3, 10, 7, 13, 9, 11, 1, 2, 4, 6, 8, > + 5, 12, 0, 0, 0, 20, 1, 0, 21, 22, 17 }, > + [7] = { 3, 2, 4, 7, 9, 1, 17, 12, 18, 14, 13, > + 8, 15, 6, 10, 5, 19, 22, 16, 21, 20, 11 }, > +}; > + > +/* > + * This routine chooses one of several remapping tables for 22 > lines. > + * It is unclear which lines are being remapped. It seems to pick > + * table cfg7 for the Nezha board. > + */ > +static void mctl_phy_ac_remapping(dram_para_t *para) > +{ > + const uint8_t *cfg; > + uint32_t fuse, val; > + > + /* > + * It is unclear whether the LPDDRx types don't need any > remapping, > + * or whether the original code just didn't provide tables. > + */ > + if (para->dram_type != SUNXI_DRAM_TYPE_DDR2 && > + para->dram_type != SUNXI_DRAM_TYPE_DDR3) > + return; > + > + fuse = (readl(SUNXI_SID_BASE + 0x28) & 0xf00) >> 8; > + debug("DDR efuse: 0x%x\n", fuse); > + > + if (para->dram_type == SUNXI_DRAM_TYPE_DDR2) { > + if (fuse == 15) > + return; > + cfg = ac_remapping_tables[6]; > + } else { > + if (para->dram_tpr13 & 0xc0000) { > + cfg = ac_remapping_tables[7]; > + } else { > + switch (fuse) { > + case 8: cfg = ac_remapping_tables[2]; break; > + case 9: cfg = ac_remapping_tables[3]; break; > + case 10: cfg = ac_remapping_tables[5]; break; > + case 11: cfg = ac_remapping_tables[4]; break; > + default: > + case 12: cfg = ac_remapping_tables[1]; break; > + case 13: > + case 14: cfg = ac_remapping_tables[0]; break; > + } > + } > + } > + > + val = (cfg[4] << 25) | (cfg[3] << 20) | (cfg[2] << 15) | > + (cfg[1] << 10) | (cfg[0] << 5); > + writel(val, 0x3102500); > + > + val = (cfg[10] << 25) | (cfg[9] << 20) | (cfg[8] << 15) | > + (cfg[ 7] << 10) | (cfg[6] << 5) | cfg[5]; > + writel(val, 0x3102504); > + > + val = (cfg[15] << 20) | (cfg[14] << 15) | (cfg[13] << 10) | > + (cfg[12] << 5) | cfg[11]; > + writel(val, 0x3102508); > + > + val = (cfg[21] << 25) | (cfg[20] << 20) | (cfg[19] << 15) | > + (cfg[18] << 10) | (cfg[17] << 5) | cfg[16]; > + writel(val, 0x310250c); > + > + val = (cfg[4] << 25) | (cfg[3] << 20) | (cfg[2] << 15) | > + (cfg[1] << 10) | (cfg[0] << 5) | 1; > + writel(val, 0x3102500); > +} > + > +// Init the controller channel. The key part is placing commands in > the main > +// command register (PIR, 0x3103000) and checking command status > (PGSR0, 0x3103010). > +// > +static unsigned int mctl_channel_init(unsigned int ch_index, > dram_para_t *para) > +{ > + unsigned int val, dqs_gating_mode; > + > + dqs_gating_mode = (para->dram_tpr13 & 0xc) >> 2; > + > + // set DDR clock to half of CPU clock > + clrsetbits_le32(0x310200c, 0xfff, (para->dram_clk / 2) - 1); > + > + // MRCTRL0 nibble 3 undocumented > + clrsetbits_le32(0x3103108, 0xf00, 0x300); > + > + if (para->dram_odt_en) > + val = 0; > + else > + val = BIT(5); > + > + // DX0GCR0 > + if (para->dram_clk > 672) > + clrsetbits_le32(0x3103344, 0xf63e, val); > + else > + clrsetbits_le32(0x3103344, 0xf03e, val); > + > + // DX1GCR0 > + if (para->dram_clk > 672) { > + setbits_le32(0x3103344, 0x400); > + clrsetbits_le32(0x31033c4, 0xf63e, val); > + } else { > + clrsetbits_le32(0x31033c4, 0xf03e, val); > + } > + > + // 0x3103208 undocumented > + setbits_le32(0x3103208, BIT(1)); > + > + eye_delay_compensation(para); > + > + // set PLL SSCG ? > + val = readl(0x3103108); > + if (dqs_gating_mode == 1) { > + clrsetbits_le32(0x3103108, 0xc0, 0); > + clrbits_le32(0x31030bc, 0x107); > + } else if (dqs_gating_mode == 2) { > + clrsetbits_le32(0x3103108, 0xc0, 0x80); > + > + clrsetbits_le32(0x31030bc, 0x107, > + (((para->dram_tpr13 >> 16) & 0x1f) - > 2) | 0x100); > + clrsetbits_le32(0x310311c, BIT(31), BIT(27)); > + } else { > + clrbits_le32(0x3103108, 0x40); > + udelay(10); > + setbits_le32(0x3103108, 0xc0); > + } > + > + if (para->dram_type == SUNXI_DRAM_TYPE_LPDDR2 || > + para->dram_type == SUNXI_DRAM_TYPE_LPDDR3) { > + if (dqs_gating_mode == 1) > + clrsetbits_le32(0x310311c, 0x080000c0, > 0x80000000); > + else > + clrsetbits_le32(0x310311c, 0x77000000, > 0x22000000); > + } > + > + clrsetbits_le32(0x31030c0, 0x0fffffff, > + (para->dram_para2 & BIT(12)) ? 0x03000001 : > 0x01000007); > + > + if (readl(0x70005d4) & (1 << 16)) { > + clrbits_le32(0x7010250, 0x2); > + udelay(10); > + } > + > + // Set ZQ config > + clrsetbits_le32(0x3103140, 0x3ffffff, > + (para->dram_zq & 0x00ffffff) | BIT(25)); > + > + // Initialise DRAM controller > + if (dqs_gating_mode == 1) { > + //writel(0x52, 0x3103000); // prep PHY reset + PLL > init + z-cal > + writel(0x53, 0x3103000); // Go > + > + while ((readl(0x3103010) & 0x1) == 0) { > + } // wait for IDONE > + udelay(10); > + > + // 0x520 = prep DQS gating + DRAM init + d-cal > + if (para->dram_type == SUNXI_DRAM_TYPE_DDR3) > + writel(0x5a0, 0x3103000); // + > DRAM reset > + else > + writel(0x520, 0x3103000); > + } else { > + if ((readl(0x70005d4) & (1 << 16)) == 0) { > + // prep DRAM init + PHY reset + d-cal + PLL > init + z-cal > + if (para->dram_type == SUNXI_DRAM_TYPE_DDR3) > + writel(0x1f2, 0x3103000); // + > DRAM reset > + else > + writel(0x172, 0x3103000); > + } else { > + // prep PHY reset + d-cal + z-cal > + writel(0x62, 0x3103000); > + } > + } > + > + setbits_le32(0x3103000, 0x1); // GO > + > + udelay(10); > + while ((readl(0x3103010) & 0x1) == 0) { > + } // wait for IDONE > + > + if (readl(0x70005d4) & (1 << 16)) { > + clrsetbits_le32(0x310310c, 0x06000000, 0x04000000); > + udelay(10); > + > + setbits_le32(0x3103004, 0x1); > + > + while ((readl(0x3103018) & 0x7) != 0x3) { > + } > + > + clrbits_le32(0x7010250, 0x1); > + udelay(10); > + > + clrbits_le32(0x3103004, 0x1); > + > + while ((readl(0x3103018) & 0x7) != 0x1) { > + } > + > + udelay(15); > + > + if (dqs_gating_mode == 1) { > + clrbits_le32(0x3103108, 0xc0); > + clrsetbits_le32(0x310310c, 0x06000000, > 0x02000000); > + udelay(1); > + writel(0x401, 0x3103000); > + > + while ((readl(0x3103010) & 0x1) == 0) { > + } > + } > + } > + > + // Check for training error > + if (readl(0x3103010) & BIT(20)) { > + printf("ZQ calibration error, check external 240 ohm > resistor\n"); > + return 0; > + } > + > + // STATR = Zynq STAT? Wait for status 'normal'? > + while ((readl(0x3103018) & 0x1) == 0) { > + } > + > + setbits_le32(0x310308c, BIT(31)); > + udelay(10); > + clrbits_le32(0x310308c, BIT(31)); > + udelay(10); > + setbits_le32(0x3102014, BIT(31)); > + udelay(10); > + > + clrbits_le32(0x310310c, 0x06000000); > + > + if (dqs_gating_mode == 1) > + clrsetbits_le32(0x310311c, 0xc0, 0x40); > + > + return 1; > +} > + > +static unsigned int calculate_rank_size(uint32_t regval) > +{ > + unsigned int bits; > + > + bits = (regval >> 8) & 0xf; /* page size - 3 */ > + bits += (regval >> 4) & 0xf; /* row width - 1 */ > + bits += (regval >> 2) & 0x3; /* bank count - 2 */ > + bits -= 14; /* 1MB = 20 bits, minus above > 6 = 14 */ > + > + return 1U << bits; > +} > + > +/* > + * The below routine reads the dram config registers and extracts > + * the number of address bits in each rank available. It then > calculates > + * total memory size in MB. > + */ > +static unsigned int DRAMC_get_dram_size(void) > +{ > + uint32_t val; > + unsigned int size; > + > + val = readl(0x3102000); /* MC_WORK_MODE0 */ > + size = calculate_rank_size(val); > + if ((val & 0x3) == 0) /* single rank? */ > + return size; > + > + val = readl(0x3102004); /* MC_WORK_MODE1 */ > + if ((val & 0x3) == 0) /* two identical ranks? */ > + return size * 2; > + > + /* add sizes of both ranks */ > + return size + calculate_rank_size(val); > +} > + > +/* > + * The below routine reads the command status register to extract > + * DQ width and rank count. This follows the DQS training command in > + * channel_init. If error bit 22 is reset, we have two ranks and > full DQ. > + * If there was an error, figure out whether it was half DQ, single > rank, > + * or both. Set bit 12 and 0 in dram_para2 with the results. > + */ > +static int dqs_gate_detect(dram_para_t *para) > +{ > + uint32_t dx0, dx1; > + > + if ((readl(0x3103010) & BIT(22)) == 0) { > + para->dram_para2 = (para->dram_para2 & ~0xf) | > BIT(12); > + debug("dual rank and full DQ\n"); > + > + return 1; > + } > + > + dx0 = (readl(0x03103348) & 0x3000000) >> 24; > + if (dx0 == 0) { > + para->dram_para2 = (para->dram_para2 & ~0xf) | > 0x1001; > + debug("dual rank and half DQ\n"); > + > + return 1; > + } > + > + if (dx0 == 2) { > + dx1 = (readl(0x031033c8) & 0x3000000) >> 24; > + if (dx1 == 2) { > + para->dram_para2 = para->dram_para2 & > ~0xf00f; > + debug("single rank and full DQ\n"); > + } else { > + para->dram_para2 = (para->dram_para2 & > ~0xf00f) | BIT(0); > + debug("single rank and half DQ\n"); > + } > + > + return 1; > + } > + > + if ((para->dram_tpr13 & BIT(29)) == 0) > + return 0; > + > + debug("DX0 state: %d\n", dx0); > + debug("DX1 state: %d\n", dx1); > + > + return 0; > +} > + > +static int dramc_simple_wr_test(unsigned int mem_mb, int len) > +{ > + unsigned int offs = (mem_mb / 2) << 18; // half of > memory size > + unsigned int patt1 = 0x01234567; > + unsigned int patt2 = 0xfedcba98; > + unsigned int *addr, v1, v2, i; > + > + addr = (unsigned int *)CONFIG_SYS_SDRAM_BASE; > + for (i = 0; i != len; i++, addr++) { > + writel(patt1 + i, (unsigned long)addr); > + writel(patt2 + i, (unsigned long)(addr + offs)); > + } > + > + addr = (unsigned int *)CONFIG_SYS_SDRAM_BASE; > + for (i = 0; i != len; i++) { > + v1 = readl((unsigned long)(addr + i)); > + v2 = patt1 + i; > + if (v1 != v2) { > + printf("DRAM: simple test FAIL\n"); > + printf("%x != %x at address %p\n", v1, v2, > addr + i); > + return 1; > + } > + v1 = readl((unsigned long)(addr + offs + i)); > + v2 = patt2 + i; > + if (v1 != v2) { > + printf("DRAM: simple test FAIL\n"); > + printf("%x != %x at address %p\n", v1, v2, > addr + offs + i); > + return 1; > + } > + } > + > + debug("DRAM: simple test OK\n"); > + return 0; > +} > + > +// Set the Vref mode for the controller > +// > +static void mctl_vrefzq_init(dram_para_t *para) > +{ > + if (para->dram_tpr13 & BIT(17)) > + return; > + > + clrsetbits_le32(0x3103110, 0x7f7f7f7f, para->dram_tpr5); > + > + // IOCVR1 > + if ((para->dram_tpr13 & BIT(16)) == 0) > + clrsetbits_le32(0x3103114, 0x7f, para->dram_tpr6 & > 0x7f); > +} > + > +// Perform an init of the controller. This is actually done 3 times. > The first > +// time to establish the number of ranks and DQ width. The second > time to > +// establish the actual ram size. The third time is final one, with > the final > +// settings. > +// > +static int mctl_core_init(dram_para_t *para) > +{ > + mctl_sys_init(para); > + > + mctl_vrefzq_init(para); > + > + mctl_com_init(para); > + > + mctl_phy_ac_remapping(para); > + > + mctl_set_timing_params(para); > + > + return mctl_channel_init(0, para); > +} > + > +/* > + * 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(dram_para_t *para) > +{ > + unsigned int rval, i, j, rank, maxrank, offs; > + unsigned int shft; > + unsigned long ptr, mc_work_mode, chk; > + > + if (mctl_core_init(para) == 0) { > + printf("DRAM initialisation error : 0\n"); > + return 0; > + } > + > + maxrank = (para->dram_para2 & 0xf000) ? 2 : 1; > + mc_work_mode = 0x3102000; > + offs = 0; > + > + /* write test pattern */ > + for (i = 0, ptr = CONFIG_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 = CONFIG_SYS_SDRAM_BASE + (1U << (i + > 11)); > + ptr = CONFIG_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 = para->dram_para1; > + rval &= ~(0xff << shft); > + rval |= i << shft; > + para->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 = CONFIG_SYS_SDRAM_BASE + (1U << 22); > + ptr = CONFIG_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 = para->dram_para1; > + rval &= ~(0xf << shft); > + rval |= j << shft; > + para->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 = CONFIG_SYS_SDRAM_BASE + (1U << i); > + ptr = CONFIG_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 = para->dram_para1; > + rval &= ~(0xf << shft); > + rval |= pgsize << shft; > + para->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) { > + para->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 { > + para->dram_para2 |= BIT(8); > + debug("rank1 config different from rank0\n"); > + } > + } > + > + return 1; > +} > + > +/* > + * This routine sets up parameters with dqs_gating_mode equal to 1 > and two > + * ranks enabled. It then configures the core and tests for 1 or 2 > ranks and > + * full or half DQ width. It then resets the parameters to the > original values. > + * dram_para2 is updated with the rank and width findings. > + */ > +static int auto_scan_dram_rank_width(dram_para_t *para) > +{ > + unsigned int s1 = para->dram_tpr13; > + unsigned int s2 = para->dram_para1; > + > + para->dram_para1 = 0x00b000b0; > + para->dram_para2 = (para->dram_para2 & ~0xf) | BIT(12); > + > + /* set DQS probe mode */ > + para->dram_tpr13 = (para->dram_tpr13 & ~0x8) | BIT(2) | > BIT(0); > + > + mctl_core_init(para); > + > + if (readl(0x3103010) & BIT(20)) > + return 0; > + > + if (dqs_gate_detect(para) == 0) > + return 0; > + > + para->dram_tpr13 = s1; > + para->dram_para1 = s2; > + > + return 1; > +} > + > +/* > + * This routine determines the SDRAM topology. It first establishes > the number > + * of ranks and the DQ width. Then it scans the SDRAM address lines > to establish > + * the size of each rank. It then updates dram_tpr13 to reflect that > the sizes > + * are now known: a re-init will not repeat the autoscan. > + */ > +static int auto_scan_dram_config(dram_para_t *para) > +{ > + if (((para->dram_tpr13 & BIT(14)) == 0) && > + (auto_scan_dram_rank_width(para) == 0)) { > + printf("ERROR: auto scan dram rank & width > failed\n"); > + return 0; > + } > + > + if (((para->dram_tpr13 & BIT(0)) == 0) && > + (auto_scan_dram_size(para) == 0)) { > + printf("ERROR: auto scan dram size failed\n"); > + return 0; > + } > + > + if ((para->dram_tpr13 & BIT(15)) == 0) > + para->dram_tpr13 |= BIT(14) | BIT(13) | BIT(1) | > BIT(0); > + > + return 1; > +} > + > +int init_DRAM(int type, dram_para_t *para) > +{ > + u32 rc, mem_size_mb; > + > + debug("DRAM BOOT DRIVE INFO: %s\n", "V0.24"); > + debug("DRAM CLK = %d MHz\n", para->dram_clk); > + debug("DRAM Type = %d (2:DDR2,3:DDR3)\n", para->dram_type); > + if ((para->dram_odt_en & 0x1) == 0) > + debug("DRAMC read ODT off\n"); > + else > + debug("DRAMC ZQ value: 0x%x\n", para->dram_zq); > + > + /* Test ZQ status */ > + if (para->dram_tpr13 & BIT(16)) { > + debug("DRAM only have internal ZQ\n"); > + setbits_le32(0x3000160, BIT(8)); > + writel(0, 0x3000168); > + udelay(10); > + } else { > + clrbits_le32(0x3000160, 0x3); > + writel(para->dram_tpr13 & BIT(16), 0x7010254); > + udelay(10); > + clrsetbits_le32(0x3000160, 0x108, BIT(1)); > + udelay(10); > + setbits_le32(0x3000160, BIT(0)); > + udelay(20); > + debug("ZQ value = 0x%x\n", readl(0x300016c)); > + } > + > + dram_voltage_set(para); > + > + /* Set SDRAM controller auto config */ > + if ((para->dram_tpr13 & BIT(0)) == 0) { > + if (auto_scan_dram_config(para) == 0) { > + printf("auto_scan_dram_config() FAILED\n"); > + return 0; > + } > + } > + > + /* report ODT */ > + rc = para->dram_mr1; > + if ((rc & 0x44) == 0) > + debug("DRAM ODT off\n"); > + else > + debug("DRAM ODT value: 0x%x\n", rc); > + > + /* Init core, final run */ > + if (mctl_core_init(para) == 0) { > + printf("DRAM initialisation error: 1\n"); > + return 0; > + } > + > + /* Get SDRAM size */ > + /* TODO: who ever puts a negative number in the top half? */ > + rc = para->dram_para2; > + if (rc & BIT(31)) { > + rc = (rc >> 16) & ~BIT(15); > + } else { > + rc = DRAMC_get_dram_size(); > + debug("DRAM: size = %dMB\n", rc); > + para->dram_para2 = (para->dram_para2 & 0xffffU) | rc > << 16; > + } > + mem_size_mb = rc; > + > + /* Purpose ?? */ > + if (para->dram_tpr13 & BIT(30)) { > + rc = para->dram_tpr8; > + if (rc == 0) > + rc = 0x10000200; > + writel(rc, 0x31030a0); > + writel(0x40a, 0x310309c); > + setbits_le32(0x3103004, BIT(0)); > + debug("Enable Auto SR\n"); > + } else { > + clrbits_le32(0x31030a0, 0xffff); > + clrbits_le32(0x3103004, 0x1); > + } > + > + /* Purpose ?? */ > + if (para->dram_tpr13 & BIT(9)) { > + clrsetbits_le32(0x3103100, 0xf000, 0x5000); > + } else { > + if (para->dram_type != SUNXI_DRAM_TYPE_LPDDR2) > + clrbits_le32(0x3103100, 0xf000); > + } > + > + setbits_le32(0x3103140, BIT(31)); > + > + /* CHECK: is that really writing to a different register? */ > + if (para->dram_tpr13 & BIT(8)) > + writel(readl(0x3103140) | 0x300, 0x31030b8); > + > + if (para->dram_tpr13 & BIT(16)) > + clrbits_le32(0x3103108, BIT(13)); > + else > + setbits_le32(0x3103108, BIT(13)); > + > + /* Purpose ?? */ > + if (para->dram_type == SUNXI_DRAM_TYPE_LPDDR3) > + clrsetbits_le32(0x310307c, 0xf0000, 0x1000); > + > + dram_enable_all_master(); > + if (para->dram_tpr13 & BIT(28)) { > + if ((readl(0x70005d4) & BIT(16)) || > + dramc_simple_wr_test(mem_size_mb, 4096)) > + return 0; > + } > + > + return mem_size_mb; > +} > + > +unsigned long sunxi_dram_init(void) > +{ > + dram_para_t para = { > + .dram_clk = CONFIG_DRAM_CLK, > + .dram_type = CONFIG_SUNXI_DRAM_TYPE, > + .dram_zq = CONFIG_DRAM_ZQ, > + .dram_odt_en = CONFIG_DRAM_SUNXI_ODT_EN, > + .dram_para1 = 0x000010d2, > + .dram_para2 = 0, > + .dram_mr0 = 0x1c70, > + .dram_mr1 = 0x42, > + .dram_mr2 = 0x18, > + .dram_mr3 = 0, > + .dram_tpr0 = 0x004a2195, > + .dram_tpr1 = 0x02423190, > + .dram_tpr2 = 0x0008b061, > + .dram_tpr3 = 0xb4787896, // unused > + .dram_tpr4 = 0, > + .dram_tpr5 = 0x48484848, > + .dram_tpr6 = 0x00000048, > + .dram_tpr7 = 0x1620121e, // unused > + .dram_tpr8 = 0, > + .dram_tpr9 = 0, // clock? > + .dram_tpr10 = 0, > + .dram_tpr11 = CONFIG_DRAM_SUNXI_TPR11, > + .dram_tpr12 = CONFIG_DRAM_SUNXI_TPR12, > + .dram_tpr13 = CONFIG_DRAM_SUNXI_TPR13, > + }; > + > + return init_DRAM(0, ¶) * 1024UL * 1024; > +}; > + > +#ifdef CONFIG_RAM /* using the driver model */ > +struct sunxi_ram_priv { > + size_t size; > +}; > + > +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); > + return -ENODEV; > + } > + > + priv->size = dram_size; > + > + return 0; > +} > + > +static int sunxi_ram_get_info(struct udevice *dev, struct ram_info > *info) > +{ > + struct sunxi_ram_priv *priv = dev_get_priv(dev); > + > + debug("%s: %s: getting info\n", __func__, dev->name); > + > + info->base = CONFIG_SYS_SDRAM_BASE; > + info->size = priv->size; > + > + return 0; > +} > + > +static struct ram_ops sunxi_ram_ops = { > + .get_info = sunxi_ram_get_info, > +}; > + > +static const struct udevice_id sunxi_ram_ids[] = { > + { .compatible = "allwinner,sun20i-d1-mbus" }, > + { } > +}; > + > +U_BOOT_DRIVER(sunxi_ram) = { > + .name = "sunxi_ram", > + .id = UCLASS_RAM, > + .of_match = sunxi_ram_ids, > + .ops = &sunxi_ram_ops, > + .probe = sunxi_ram_probe, > + .priv_auto = sizeof(struct sunxi_ram_priv), > +}; > +#endif /* CONFIG_RAM (using driver model) */ > diff --git a/drivers/ram/sunxi/dram_sun20i_d1.h > b/drivers/ram/sunxi/dram_sun20i_d1.h > new file mode 100644 > index 00000000000..89aac9dadef > --- /dev/null > +++ b/drivers/ram/sunxi/dram_sun20i_d1.h > @@ -0,0 +1,70 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * D1/R528/T113 DRAM controller register and constant defines > + * > + * (C) Copyright 2022 Arm Ltd. > + * Based on H6 and H616 header, which are: > + * (C) Copyright 2017 Icenowy Zheng <icen...@aosc.io> > + * (C) Copyright 2020 Jernej Skrabec <jernej.skra...@siol.net> > + * > + */ > + > +#ifndef _SUNXI_DRAM_SUN20I_D1_H > +#define _SUNXI_DRAM_SUN20I_D1_H > + > +enum sunxi_dram_type { > + SUNXI_DRAM_TYPE_DDR2 = 2, > + SUNXI_DRAM_TYPE_DDR3 = 3, > + SUNXI_DRAM_TYPE_LPDDR2 = 6, > + SUNXI_DRAM_TYPE_LPDDR3 = 7, > +}; > + > +/* > + * This structure contains a mixture of fixed configuration > settings, > + * variables that are used at runtime to communicate settings > between > + * different stages and functions, and unused values. > + * This is copied from Allwinner's boot0 data structure, which can > be > + * found at offset 0x38 in any boot0 binary. To allow matching up > some > + * board specific settings, this struct is kept compatible, even > though > + * we don't need all members in our code. > + */ > +typedef struct dram_para { > + /* normal configuration */ > + u32 dram_clk; > + u32 dram_type; > + u32 dram_zq; > + u32 dram_odt_en; > + > + /* control configuration */ > + u32 dram_para1; > + u32 dram_para2; > + > + /* timing configuration */ > + u32 dram_mr0; > + u32 dram_mr1; > + u32 dram_mr2; > + u32 dram_mr3; > + u32 dram_tpr0; //DRAMTMG0 > + u32 dram_tpr1; //DRAMTMG1 > + u32 dram_tpr2; //DRAMTMG2 > + u32 dram_tpr3; //DRAMTMG3 > + u32 dram_tpr4; //DRAMTMG4 > + u32 dram_tpr5; //DRAMTMG5 > + u32 dram_tpr6; //DRAMTMG8 > + u32 dram_tpr7; > + u32 dram_tpr8; > + u32 dram_tpr9; > + u32 dram_tpr10; > + u32 dram_tpr11; > + u32 dram_tpr12; > + u32 dram_tpr13; /* contains a bitfield of DRAM setup > settings */ > +} dram_para_t; > + > +static inline int ns_to_t(int nanoseconds) > +{ > + const unsigned int ctrl_freq = CONFIG_DRAM_CLK / 2; > + > + return DIV_ROUND_UP(ctrl_freq * nanoseconds, 1000); > +} > + > +#endif /* _SUNXI_DRAM_SUN20I_D1_H */