[U-Boot] [PATCH 0/7] sunxi: Add H6 DDR3 DRAM support

So far the SPL DRAM driver for the Allwinner H6 SoC only supports LPDDR3 DRAM chips, which are used on most single board computers with this SoC. There are some TV boxes with the H6 out now, but most of them are using DDR3 DRAM instead of LPDDR3.
This series extends the existing H6 DRAM driver to cover DDR3 DRAMs as well. The information used in these patches is from: - register dumps after Allwinner's boot0 (libdram) has initialised the DRAM - some disassembly of the libdram library - timing parameters as found in the boot0 binary - comparison with Xilinx ZynqMP DRAM controller documentation
The box I played with (Eachlink H6 Mini) has 3GB of DDR3-1600 chips and runs at 840 MHz, however I couldn't get it to work with these parameters. Instead Jernej suggested to use a lower clock and adjust some timing parameters, which made it work for me as well.
Many thanks to Jernej Skrabec for his help, also to others who helped with testing and experiments.
The first two patches contain some fixes for the existing driver. Patch 3 moves the existing LPDDR3 timing parameters into a separate file, patch 5 introduces the respective DDR3 timings, patch 6 adds some generic delay lines values. Patch 4 enhances the DRAM controller driver to program DDR3 specific registers as well and use different settings on other registers. The final patch introduces support for the Eachlink H6 Mini TV box, with the usual device tree and defconfig file.
Please have a look and comment!
Cheers, Andre.
Andre Przywara (6): sunxi: H6: DRAM: avoid memcpy() on MMIO registers sunxi: H6: DRAM: follow recommended PHY init algorithm sunxi: H6: move LPDDR3 timing definition into separate file sunxi: H6: Add DDR3 support to DRAM controller driver sunxi: H6: Add DDR3-1333 timings sunxi: H6: Add basic Eachlink H6 Mini support
Jernej Skrabec (1): sunxi: H6: Add DDR3 DRAM delay values
arch/arm/dts/Makefile | 1 + arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts | 116 +++++++++++ arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h | 35 ++++ arch/arm/mach-sunxi/Kconfig | 18 +- arch/arm/mach-sunxi/Makefile | 1 + arch/arm/mach-sunxi/dram_sun50i_h6.c | 240 +++++++--------------- arch/arm/mach-sunxi/dram_timings/Makefile | 2 + arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c | 144 +++++++++++++ arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c | 132 ++++++++++++ configs/eachlink_h6_mini_defconfig | 17 ++ configs/orangepi_one_plus_defconfig | 1 + configs/pine_h64_defconfig | 1 + 12 files changed, 537 insertions(+), 171 deletions(-) create mode 100644 arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts create mode 100644 arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c create mode 100644 arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c create mode 100644 configs/eachlink_h6_mini_defconfig

Using memcpy() is, however tempting, not a good idea: It depends on the specific implementation of memcpy, also lacks barriers. In this particular case the first registers were written using 64-bit writes, and the last register using four separate single-byte writes.
Replace the memcpy with a proper loop using the writel() accessor.
Signed-off-by: Andre Przywara andre.przywara@arm.com --- arch/arm/mach-sunxi/dram_sun50i_h6.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c index 5da90a2835..e2f141eb9b 100644 --- a/arch/arm/mach-sunxi/dram_sun50i_h6.c +++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c @@ -182,6 +182,7 @@ static void mctl_set_timing_lpddr3(struct dram_para *para) (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE; struct sunxi_mctl_phy_reg * const mctl_phy = (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY0_BASE; + int i;
u8 tccd = 2; u8 tfaw = max(ns_to_t(50), 4); @@ -237,8 +238,9 @@ static void mctl_set_timing_lpddr3(struct dram_para *para) u8 twr2rd = tcwl + 4 + 1 + twtr; u8 trd2wr = tcl + 4 + (tcksrea >> 1) - tcwl + 1;
- /* set mode register */ - memcpy(mctl_phy->mr, mr_lpddr3, sizeof(mr_lpddr3)); + /* set mode registers */ + for (i = 0; i < ARRAY_SIZE(mr_lpddr3); i++) + writel(mr_lpddr3[i], &mctl_phy->mr[i]);
/* set DRAM timing */ writel((twtp << 24) | (tfaw << 16) | (trasmax << 8) | tras,

The DRAM controller manual suggests to first program the PHY initialisation parameters to the PHY_PIR register, and then set bit 0 to trigger the initialisation. This is also used in boot0.
Follow this recommendation by setting bit 0 in a separate step.
Signed-off-by: Andre Przywara andre.przywara@arm.com --- arch/arm/mach-sunxi/dram_sun50i_h6.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c index e2f141eb9b..7a8b724f08 100644 --- a/arch/arm/mach-sunxi/dram_sun50i_h6.c +++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c @@ -75,12 +75,14 @@ static void mctl_core_init(struct dram_para *para) mctl_channel_init(para); }
+/* PHY initialisation */ static void mctl_phy_pir_init(u32 val) { struct sunxi_mctl_phy_reg * const mctl_phy = (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY0_BASE;
- writel(val | BIT(0), &mctl_phy->pir); + writel(val, &mctl_phy->pir); + writel(val | BIT(0), &mctl_phy->pir); /* Start initialisation. */ mctl_await_completion(&mctl_phy->pgsr[0], BIT(0), BIT(0)); }

Currently the H6 DRAM driver only supports one kind of LPDDR3 DRAM. Split the timing parameters for LPDDR3-1333 into a separate file, to allow selecting an alternative later at compile time (as the sunxi-dw driver does).
Signed-off-by: Andre Przywara andre.przywara@arm.com --- arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h | 28 ++++ arch/arm/mach-sunxi/Kconfig | 10 +- arch/arm/mach-sunxi/Makefile | 1 + arch/arm/mach-sunxi/dram_sun50i_h6.c | 150 +--------------------- arch/arm/mach-sunxi/dram_timings/Makefile | 1 + arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c | 132 +++++++++++++++++++ configs/orangepi_one_plus_defconfig | 1 + configs/pine_h64_defconfig | 1 + 8 files changed, 176 insertions(+), 148 deletions(-) create mode 100644 arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c
diff --git a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h index eeb4da5c3f..b28ae583c9 100644 --- a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h +++ b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h @@ -287,6 +287,32 @@ check_member(sunxi_mctl_phy_reg, dx[3].reserved_0xf0, 0xaf0); #define DCR_DDR4 (4 << 0) #define DCR_DDR8BANK BIT(3)
+ +/* + * The delay parameters below allow to allegedly specify delay times of some + * unknown unit for each individual bit trace in each of the four data bytes + * the 32-bit wide access consists of. Also three control signals can be + * adjusted individually. + */ +#define NR_OF_BYTE_LANES (32 / BITS_PER_BYTE) +/* The eight data lines (DQn) plus DM, DQS, DQS/DM/DQ Output Enable and DQSN */ +#define WR_LINES_PER_BYTE_LANE (BITS_PER_BYTE + 4) +/* + * The eight data lines (DQn) plus DM, DQS, DQS/DM/DQ Output Enable, DQSN, + * Termination and Power down + */ +#define RD_LINES_PER_BYTE_LANE (BITS_PER_BYTE + 6) +struct dram_para { + u32 clk; + enum sunxi_dram_type type; + u8 cols; + u8 rows; + u8 ranks; + const u8 dx_read_delays[NR_OF_BYTE_LANES][RD_LINES_PER_BYTE_LANE]; + const u8 dx_write_delays[NR_OF_BYTE_LANES][WR_LINES_PER_BYTE_LANE]; +}; + + static inline int ns_to_t(int nanoseconds) { const unsigned int ctrl_freq = CONFIG_DRAM_CLK / 2; @@ -294,4 +320,6 @@ static inline int ns_to_t(int nanoseconds) return DIV_ROUND_UP(ctrl_freq * nanoseconds, 1000); }
+void mctl_set_timing_params(struct dram_para *para); + #endif /* _SUNXI_DRAM_SUN50I_H6_H */ diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig index 1669e62a6d..e01cb6a09a 100644 --- a/arch/arm/mach-sunxi/Kconfig +++ b/arch/arm/mach-sunxi/Kconfig @@ -340,7 +340,7 @@ config ARM_BOOT_HOOK_RMR This allows both the SPL and the U-Boot proper to be entered in either mode and switch to AArch64 if needed.
-if SUNXI_DRAM_DW +if SUNXI_DRAM_DW || DRAM_SUN50I_H6 config SUNXI_DRAM_DDR3 bool
@@ -370,6 +370,14 @@ config SUNXI_DRAM_LPDDR3_STOCK This option is the LPDDR3 timing used by the stock boot0 by Allwinner.
+config SUNXI_DRAM_H6_LPDDR3 + bool "LPDDR3-1333 on the H6 DRAM controller" + select SUNXI_DRAM_LPDDR3 + depends on DRAM_SUN50I_H6 + ---help--- + This option is the LPDDR3 timing used by the stock boot0 by + Allwinner. + config SUNXI_DRAM_DDR2_V3S bool "DDR2 found in V3s chip" select SUNXI_DRAM_DDR2 diff --git a/arch/arm/mach-sunxi/Makefile b/arch/arm/mach-sunxi/Makefile index 43a93e3085..d129f33479 100644 --- a/arch/arm/mach-sunxi/Makefile +++ b/arch/arm/mach-sunxi/Makefile @@ -39,4 +39,5 @@ obj-$(CONFIG_SPL_SPI_SUNXI) += spl_spi_sunxi.o obj-$(CONFIG_SUNXI_DRAM_DW) += dram_sunxi_dw.o obj-$(CONFIG_SUNXI_DRAM_DW) += dram_timings/ obj-$(CONFIG_DRAM_SUN50I_H6) += dram_sun50i_h6.o +obj-$(CONFIG_DRAM_SUN50I_H6) += dram_timings/ endif diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c index 7a8b724f08..697b8af4ce 100644 --- a/arch/arm/mach-sunxi/dram_sun50i_h6.c +++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c @@ -32,33 +32,8 @@ * similar PHY is ZynqMP. */
-/* - * The delay parameters below allow to allegedly specify delay times of some - * unknown unit for each individual bit trace in each of the four data bytes - * the 32-bit wide access consists of. Also three control signals can be - * adjusted individually. - */ -#define NR_OF_BYTE_LANES (32 / BITS_PER_BYTE) -/* The eight data lines (DQn) plus DM, DQS, DQS/DM/DQ Output Enable and DQSN */ -#define WR_LINES_PER_BYTE_LANE (BITS_PER_BYTE + 4) -/* - * The eight data lines (DQn) plus DM, DQS, DQS/DM/DQ Output Enable, DQSN, - * Termination and Power down - */ -#define RD_LINES_PER_BYTE_LANE (BITS_PER_BYTE + 6) -struct dram_para { - u32 clk; - enum sunxi_dram_type type; - u8 cols; - u8 rows; - u8 ranks; - const u8 dx_read_delays[NR_OF_BYTE_LANES][RD_LINES_PER_BYTE_LANE]; - const u8 dx_write_delays[NR_OF_BYTE_LANES][WR_LINES_PER_BYTE_LANE]; -}; - static void mctl_sys_init(struct dram_para *para); static void mctl_com_init(struct dram_para *para); -static void mctl_set_timing_lpddr3(struct dram_para *para); static void mctl_channel_init(struct dram_para *para);
static void mctl_core_init(struct dram_para *para) @@ -67,7 +42,7 @@ static void mctl_core_init(struct dram_para *para) mctl_com_init(para); switch (para->type) { case SUNXI_DRAM_TYPE_LPDDR3: - mctl_set_timing_lpddr3(para); + mctl_set_timing_params(para); break; default: panic("Unsupported DRAM type!"); @@ -171,127 +146,6 @@ static void mctl_set_master_priority(void) MBUS_CONF(HDCP2, true, HIGH, 2, 100, 64, 32); }
-static u32 mr_lpddr3[12] = { - 0x00000000, 0x00000043, 0x0000001a, 0x00000001, - 0x00000000, 0x00000000, 0x00000048, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000003, -}; - -/* TODO: flexible timing */ -static void mctl_set_timing_lpddr3(struct dram_para *para) -{ - struct sunxi_mctl_ctl_reg * const mctl_ctl = - (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE; - struct sunxi_mctl_phy_reg * const mctl_phy = - (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY0_BASE; - int i; - - u8 tccd = 2; - u8 tfaw = max(ns_to_t(50), 4); - u8 trrd = max(ns_to_t(10), 2); - u8 trcd = max(ns_to_t(24), 2); - u8 trc = ns_to_t(70); - u8 txp = max(ns_to_t(8), 2); - u8 twtr = max(ns_to_t(8), 2); - u8 trtp = max(ns_to_t(8), 2); - u8 twr = max(ns_to_t(15), 2); - u8 trp = ns_to_t(18); - u8 tras = ns_to_t(42); - u8 twtr_sa = ns_to_t(5); - u8 tcksrea = ns_to_t(11); - u16 trefi = ns_to_t(3900) / 32; - u16 trfc = ns_to_t(210); - u16 txsr = ns_to_t(220); - - if (CONFIG_DRAM_CLK % 400 == 0) { - /* Round up these parameters */ - twtr_sa++; - tcksrea++; - } - - u8 tmrw = 5; - u8 tmrd = 5; - u8 tmod = 12; - u8 tcke = 3; - u8 tcksrx = 5; - u8 tcksre = 5; - u8 tckesr = 5; - u8 trasmax = CONFIG_DRAM_CLK / 60; - u8 txs = 4; - u8 txsdll = 4; - u8 txsabort = 4; - u8 txsfast = 4; - - u8 tcl = 5; /* CL 10 */ - u8 tcwl = 3; /* CWL 6 */ - u8 t_rdata_en = twtr_sa + 8; - - u32 tdinit0 = (200 * CONFIG_DRAM_CLK) + 1; /* 200us */ - u32 tdinit1 = (100 * CONFIG_DRAM_CLK) / 1000 + 1; /* 100ns */ - u32 tdinit2 = (11 * CONFIG_DRAM_CLK) + 1; /* 11us */ - u32 tdinit3 = (1 * CONFIG_DRAM_CLK) + 1; /* 1us */ - - u8 twtp = tcwl + 4 + twr + 1; - /* - * The code below for twr2rd and trd2wr follows the IP core's - * document from ZynqMP and i.MX7. The BSP has both number - * substracted by 2. - */ - u8 twr2rd = tcwl + 4 + 1 + twtr; - u8 trd2wr = tcl + 4 + (tcksrea >> 1) - tcwl + 1; - - /* set mode registers */ - for (i = 0; i < ARRAY_SIZE(mr_lpddr3); i++) - writel(mr_lpddr3[i], &mctl_phy->mr[i]); - - /* set DRAM timing */ - writel((twtp << 24) | (tfaw << 16) | (trasmax << 8) | tras, - &mctl_ctl->dramtmg[0]); - writel((txp << 16) | (trtp << 8) | trc, &mctl_ctl->dramtmg[1]); - writel((tcwl << 24) | (tcl << 16) | (trd2wr << 8) | twr2rd, - &mctl_ctl->dramtmg[2]); - writel((tmrw << 20) | (tmrd << 12) | tmod, &mctl_ctl->dramtmg[3]); - writel((trcd << 24) | (tccd << 16) | (trrd << 8) | trp, - &mctl_ctl->dramtmg[4]); - writel((tcksrx << 24) | (tcksre << 16) | (tckesr << 8) | tcke, - &mctl_ctl->dramtmg[5]); - /* Value suggested by ZynqMP manual and used by libdram */ - writel((txp + 2) | 0x02020000, &mctl_ctl->dramtmg[6]); - writel((txsfast << 24) | (txsabort << 16) | (txsdll << 8) | txs, - &mctl_ctl->dramtmg[8]); - writel(txsr, &mctl_ctl->dramtmg[14]); - - clrsetbits_le32(&mctl_ctl->init[0], (3 << 30), (1 << 30)); - writel(0, &mctl_ctl->dfimisc); - clrsetbits_le32(&mctl_ctl->rankctl, 0xff0, 0x660); - - /* - * Set timing registers of the PHY. - * Note: the PHY is clocked 2x from the DRAM frequency. - */ - writel((trrd << 25) | (tras << 17) | (trp << 9) | (trtp << 1), - &mctl_phy->dtpr[0]); - writel((tfaw << 17) | 0x28000400 | (tmrd << 1), &mctl_phy->dtpr[1]); - writel(((txs << 6) - 1) | (tcke << 17), &mctl_phy->dtpr[2]); - writel(((txsdll << 22) - (0x1 << 16)) | twtr_sa | (tcksrea << 8), - &mctl_phy->dtpr[3]); - writel((txp << 1) | (trfc << 17) | 0x800, &mctl_phy->dtpr[4]); - writel((trc << 17) | (trcd << 9) | (twtr << 1), &mctl_phy->dtpr[5]); - writel(0x0505, &mctl_phy->dtpr[6]); - - /* Configure DFI timing */ - writel(tcl | 0x2000200 | (t_rdata_en << 16) | 0x808000, - &mctl_ctl->dfitmg0); - writel(0x040201, &mctl_ctl->dfitmg1); - - /* Configure PHY timing */ - writel(tdinit0 | (tdinit1 << 20), &mctl_phy->ptr[3]); - writel(tdinit2 | (tdinit3 << 18), &mctl_phy->ptr[4]); - - /* set refresh timing */ - writel((trefi << 16) | trfc, &mctl_ctl->rfshtmg); -} - static void mctl_sys_init(struct dram_para *para) { struct sunxi_ccm_reg * const ccm = @@ -735,12 +589,14 @@ unsigned long sunxi_dram_init(void) (struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE; struct dram_para para = { .clk = CONFIG_DRAM_CLK, +#ifdef CONFIG_SUNXI_DRAM_H6_LPDDR3 .type = SUNXI_DRAM_TYPE_LPDDR3, .ranks = 2, .cols = 11, .rows = 14, .dx_read_delays = SUN50I_H6_DX_READ_DELAYS, .dx_write_delays = SUN50I_H6_DX_WRITE_DELAYS, +#endif };
unsigned long size; diff --git a/arch/arm/mach-sunxi/dram_timings/Makefile b/arch/arm/mach-sunxi/dram_timings/Makefile index 278a8a14cc..c3e74362eb 100644 --- a/arch/arm/mach-sunxi/dram_timings/Makefile +++ b/arch/arm/mach-sunxi/dram_timings/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_SUNXI_DRAM_DDR3_1333) += ddr3_1333.o 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_1333.o diff --git a/arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c b/arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c new file mode 100644 index 0000000000..1000860113 --- /dev/null +++ b/arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c @@ -0,0 +1,132 @@ +/* + * sun50i H6 LPDDR3 timings + * + * (C) Copyright 2017 Icenowy Zheng icenowy@aosc.io + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <asm/arch/dram.h> +#include <asm/arch/cpu.h> + +static u32 mr_lpddr3[12] = { + 0x00000000, 0x00000043, 0x0000001a, 0x00000001, + 0x00000000, 0x00000000, 0x00000048, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000003, +}; + +/* TODO: flexible timing */ +void mctl_set_timing_params(struct dram_para *para) +{ + struct sunxi_mctl_ctl_reg * const mctl_ctl = + (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE; + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY0_BASE; + int i; + + u8 tccd = 2; + u8 tfaw = max(ns_to_t(50), 4); + u8 trrd = max(ns_to_t(10), 2); + u8 trcd = max(ns_to_t(24), 2); + u8 trc = ns_to_t(70); + u8 txp = max(ns_to_t(8), 2); + u8 twtr = max(ns_to_t(8), 2); + u8 trtp = max(ns_to_t(8), 2); + u8 twr = max(ns_to_t(15), 2); + u8 trp = ns_to_t(18); + u8 tras = ns_to_t(42); + u8 twtr_sa = ns_to_t(5); + u8 tcksrea = ns_to_t(11); + u16 trefi = ns_to_t(3900) / 32; + u16 trfc = ns_to_t(210); + u16 txsr = ns_to_t(220); + + if (CONFIG_DRAM_CLK % 400 == 0) { + /* Round up these parameters */ + twtr_sa++; + tcksrea++; + } + + u8 tmrw = 5; + u8 tmrd = 5; + u8 tmod = 12; + u8 tcke = 3; + u8 tcksrx = 5; + u8 tcksre = 5; + u8 tckesr = 5; + u8 trasmax = CONFIG_DRAM_CLK / 60; + u8 txs = 4; + u8 txsdll = 4; + u8 txsabort = 4; + u8 txsfast = 4; + + u8 tcl = 5; /* CL 10 */ + u8 tcwl = 3; /* CWL 6 */ + u8 t_rdata_en = twtr_sa + 8; + + u32 tdinit0 = (200 * CONFIG_DRAM_CLK) + 1; /* 200us */ + u32 tdinit1 = (100 * CONFIG_DRAM_CLK) / 1000 + 1; /* 100ns */ + u32 tdinit2 = (11 * CONFIG_DRAM_CLK) + 1; /* 11us */ + u32 tdinit3 = (1 * CONFIG_DRAM_CLK) + 1; /* 1us */ + + u8 twtp = tcwl + 4 + twr + 1; + /* + * The code below for twr2rd and trd2wr follows the IP core's + * document from ZynqMP and i.MX7. The BSP has both number + * substracted by 2. + */ + u8 twr2rd = tcwl + 4 + 1 + twtr; + u8 trd2wr = tcl + 4 + (tcksrea >> 1) - tcwl + 1; + + /* set mode registers */ + for (i = 0; i < ARRAY_SIZE(mr_lpddr3); i++) + writel(mr_lpddr3[i], &mctl_phy->mr[i]); + + /* set DRAM timing */ + writel((twtp << 24) | (tfaw << 16) | (trasmax << 8) | tras, + &mctl_ctl->dramtmg[0]); + writel((txp << 16) | (trtp << 8) | trc, &mctl_ctl->dramtmg[1]); + writel((tcwl << 24) | (tcl << 16) | (trd2wr << 8) | twr2rd, + &mctl_ctl->dramtmg[2]); + writel((tmrw << 20) | (tmrd << 12) | tmod, &mctl_ctl->dramtmg[3]); + writel((trcd << 24) | (tccd << 16) | (trrd << 8) | trp, + &mctl_ctl->dramtmg[4]); + writel((tcksrx << 24) | (tcksre << 16) | (tckesr << 8) | tcke, + &mctl_ctl->dramtmg[5]); + /* Value suggested by ZynqMP manual and used by libdram */ + writel((txp + 2) | 0x02020000, &mctl_ctl->dramtmg[6]); + writel((txsfast << 24) | (txsabort << 16) | (txsdll << 8) | txs, + &mctl_ctl->dramtmg[8]); + writel(txsr, &mctl_ctl->dramtmg[14]); + + clrsetbits_le32(&mctl_ctl->init[0], (3 << 30), (1 << 30)); + writel(0, &mctl_ctl->dfimisc); + clrsetbits_le32(&mctl_ctl->rankctl, 0xff0, 0x660); + + /* + * Set timing registers of the PHY. + * Note: the PHY is clocked 2x from the DRAM frequency. + */ + writel((trrd << 25) | (tras << 17) | (trp << 9) | (trtp << 1), + &mctl_phy->dtpr[0]); + writel((tfaw << 17) | 0x28000400 | (tmrd << 1), &mctl_phy->dtpr[1]); + writel(((txs << 6) - 1) | (tcke << 17), &mctl_phy->dtpr[2]); + writel(((txsdll << 22) - (0x1 << 16)) | twtr_sa | (tcksrea << 8), + &mctl_phy->dtpr[3]); + writel((txp << 1) | (trfc << 17) | 0x800, &mctl_phy->dtpr[4]); + writel((trc << 17) | (trcd << 9) | (twtr << 1), &mctl_phy->dtpr[5]); + writel(0x0505, &mctl_phy->dtpr[6]); + + /* Configure DFI timing */ + writel(tcl | 0x2000200 | (t_rdata_en << 16) | 0x808000, + &mctl_ctl->dfitmg0); + writel(0x040201, &mctl_ctl->dfitmg1); + + /* Configure PHY timing */ + writel(tdinit0 | (tdinit1 << 20), &mctl_phy->ptr[3]); + writel(tdinit2 | (tdinit3 << 18), &mctl_phy->ptr[4]); + + /* set refresh timing */ + writel((trefi << 16) | trfc, &mctl_ctl->rfshtmg); +} diff --git a/configs/orangepi_one_plus_defconfig b/configs/orangepi_one_plus_defconfig index 65537c422f..e4b9f3a1c8 100644 --- a/configs/orangepi_one_plus_defconfig +++ b/configs/orangepi_one_plus_defconfig @@ -3,6 +3,7 @@ CONFIG_ARCH_SUNXI=y CONFIG_NR_DRAM_BANKS=1 CONFIG_SPL=y CONFIG_MACH_SUN50I_H6=y +CONFIG_SUNXI_DRAM_H6_LPDDR3=y CONFIG_MMC0_CD_PIN="PF6" # CONFIG_PSCI_RESET is not set # CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set diff --git a/configs/pine_h64_defconfig b/configs/pine_h64_defconfig index 5ac89b462c..d88a6b1fd8 100644 --- a/configs/pine_h64_defconfig +++ b/configs/pine_h64_defconfig @@ -3,6 +3,7 @@ CONFIG_ARCH_SUNXI=y CONFIG_NR_DRAM_BANKS=1 CONFIG_SPL=y CONFIG_MACH_SUN50I_H6=y +CONFIG_SUNXI_DRAM_H6_LPDDR3=y CONFIG_MMC0_CD_PIN="PF6" CONFIG_MMC_SUNXI_SLOT_EXTRA=2 # CONFIG_PSCI_RESET is not set

At the moment the H6 DRAM driver only supports LPDDR3 DRAM.
Extend the driver to cover DDR3 DRAM as well.
The changes are partly motivated by looking at the ZynqMP register documentation, partly by looking at register dumps after boot0/libdram has initialised the controller.
Many thanks to Jernej for contributing some fixes!
Signed-off-by: Andre Przywara andre.przywara@arm.com --- arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h | 7 +++ arch/arm/mach-sunxi/dram_sun50i_h6.c | 71 +++++++++++++++++------- 2 files changed, 57 insertions(+), 21 deletions(-)
diff --git a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h index b28ae583c9..8b8085611f 100644 --- a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h +++ b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h @@ -9,6 +9,8 @@ #ifndef _SUNXI_DRAM_SUN50I_H6_H #define _SUNXI_DRAM_SUN50I_H6_H
+#include <stdbool.h> + enum sunxi_dram_type { SUNXI_DRAM_TYPE_DDR3 = 3, SUNXI_DRAM_TYPE_DDR4, @@ -16,6 +18,11 @@ enum sunxi_dram_type { SUNXI_DRAM_TYPE_LPDDR3, };
+static inline bool sunxi_dram_is_lpddr(int type) +{ + return type >= SUNXI_DRAM_TYPE_LPDDR2; +} + /* * The following information is mainly retrieved by disassembly and some FPGA * test code of sun50iw3 platform. diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c index 697b8af4ce..0436265bdb 100644 --- a/arch/arm/mach-sunxi/dram_sun50i_h6.c +++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c @@ -42,6 +42,7 @@ static void mctl_core_init(struct dram_para *para) mctl_com_init(para); switch (para->type) { case SUNXI_DRAM_TYPE_LPDDR3: + case SUNXI_DRAM_TYPE_DDR3: mctl_set_timing_params(para); break; default: @@ -302,22 +303,37 @@ static void mctl_com_init(struct dram_para *para) reg_val = 0x3f00; clrsetbits_le32(&mctl_com->unk_0x008, 0x3f00, reg_val);
- /* TODO: half DQ, non-LPDDR3 types */ - writel(MSTR_DEVICETYPE_LPDDR3 | MSTR_BUSWIDTH_FULL | - MSTR_BURST_LENGTH(8) | MSTR_ACTIVE_RANKS(para->ranks) | - 0x80000000, &mctl_ctl->mstr); - writel(DCR_LPDDR3 | DCR_DDR8BANK | 0x400, &mctl_phy->dcr); + /* TODO: half DQ, DDR4 */ + reg_val = MSTR_BUSWIDTH_FULL | MSTR_BURST_LENGTH(8) | + MSTR_ACTIVE_RANKS(para->ranks); + if (para->type == SUNXI_DRAM_TYPE_LPDDR3) + reg_val |= MSTR_DEVICETYPE_LPDDR3; + if (para->type == SUNXI_DRAM_TYPE_DDR3) + reg_val |= MSTR_DEVICETYPE_DDR3 | MSTR_2TMODE; + writel(reg_val | BIT(31), &mctl_ctl->mstr); + + if (para->type == SUNXI_DRAM_TYPE_LPDDR3) + reg_val = DCR_LPDDR3 | DCR_DDR8BANK; + if (para->type == SUNXI_DRAM_TYPE_DDR3) + reg_val = DCR_DDR3 | DCR_DDR8BANK | BIT(28); /* 2T mode */ + writel(reg_val | 0x400, &mctl_phy->dcr);
if (para->ranks == 2) writel(0x0303, &mctl_ctl->odtmap); else writel(0x0201, &mctl_ctl->odtmap);
- /* TODO: non-LPDDR3 types */ - tmp = para->clk * 7 / 2000; - reg_val = 0x0400; - reg_val |= (tmp + 7) << 24; - reg_val |= (((para->clk < 400) ? 3 : 4) - tmp) << 16; + /* TODO: DDR4 */ + if (para->type == SUNXI_DRAM_TYPE_LPDDR3) { + tmp = para->clk * 7 / 2000; + reg_val = 0x0400; + reg_val |= (tmp + 7) << 24; + reg_val |= (((para->clk < 400) ? 3 : 4) - tmp) << 16; + } else if (para->type == SUNXI_DRAM_TYPE_DDR3) { + reg_val = 0x06000400; /* TODO?: Use CL - CWL value in [7:0] */ + } else if (para->type == SUNXI_DRAM_TYPE_DDR4) { + panic("DDR4 not yet supported\n"); + } writel(reg_val, &mctl_ctl->odtcfg);
/* TODO: half DQ */ @@ -372,6 +388,9 @@ static void mctl_bit_delay_set(struct dram_para *para) setbits_le32(&mctl_phy->pgcr[0], BIT(26)); udelay(1);
+ if (para->type != SUNXI_DRAM_TYPE_LPDDR3) + return; + for (i = 1; i < 14; i++) { val = readl(&mctl_phy->acbdlr[i]); val += 0x0a0a0a0a; @@ -419,7 +438,8 @@ static void mctl_channel_init(struct dram_para *para) else clrsetbits_le32(&mctl_phy->dtcr[1], 0x30000, 0x10000);
- clrbits_le32(&mctl_phy->dtcr[1], BIT(1)); + if (sunxi_dram_is_lpddr(para->type)) + clrbits_le32(&mctl_phy->dtcr[1], BIT(1)); if (para->ranks == 2) { writel(0x00010001, &mctl_phy->rankidr); writel(0x20000, &mctl_phy->odtcr); @@ -428,8 +448,11 @@ static void mctl_channel_init(struct dram_para *para) writel(0x10000, &mctl_phy->odtcr); }
- /* TODO: non-LPDDR3 types */ - clrsetbits_le32(&mctl_phy->dtcr[0], 0xF0000000, 0x10000040); + /* set bits [3:0] to 1? 0 not valid in ZynqMP d/s */ + if (para->type == SUNXI_DRAM_TYPE_LPDDR3) + clrsetbits_le32(&mctl_phy->dtcr[0], 0xF0000000, 0x10000040); + else + clrsetbits_le32(&mctl_phy->dtcr[0], 0xF0000000, 0x10000000); if (para->clk <= 792) { if (para->clk <= 672) { if (para->clk <= 600) @@ -459,12 +482,13 @@ static void mctl_channel_init(struct dram_para *para) writel(0x06060606, &mctl_phy->acbdlr[i]); }
- /* TODO: non-LPDDR3 types */ - mctl_phy_pir_init(PIR_ZCAL | PIR_DCAL | PIR_PHYRST | PIR_DRAMINIT | - PIR_QSGATE | PIR_RDDSKW | PIR_WRDSKW | PIR_RDEYE | - PIR_WREYE); + val = PIR_ZCAL | PIR_DCAL | PIR_PHYRST | PIR_DRAMINIT | PIR_QSGATE | + PIR_RDDSKW | PIR_WRDSKW | PIR_RDEYE | PIR_WREYE; + if (para->type == SUNXI_DRAM_TYPE_DDR3) + val |= PIR_DRAMRST | PIR_WL; + mctl_phy_pir_init(val);
- /* TODO: non-LPDDR3 types */ + /* TODO: DDR4 types ? */ for (i = 0; i < 4; i++) writel(0x00000909, &mctl_phy->dx[i].gcr[5]);
@@ -520,7 +544,8 @@ static void mctl_channel_init(struct dram_para *para) panic("Error while initializing DRAM PHY!\n"); }
- clrsetbits_le32(&mctl_phy->dsgcr, 0xc0, 0x40); + if (sunxi_dram_is_lpddr(para->type)) + clrsetbits_le32(&mctl_phy->dsgcr, 0xc0, 0x40); clrbits_le32(&mctl_phy->pgcr[1], 0x40); clrbits_le32(&mctl_ctl->dfimisc, BIT(0)); writel(1, &mctl_ctl->swctl); @@ -589,11 +614,15 @@ unsigned long sunxi_dram_init(void) (struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE; struct dram_para para = { .clk = CONFIG_DRAM_CLK, -#ifdef CONFIG_SUNXI_DRAM_H6_LPDDR3 - .type = SUNXI_DRAM_TYPE_LPDDR3, .ranks = 2, .cols = 11, .rows = 14, +#ifdef CONFIG_SUNXI_DRAM_H6_LPDDR3 + .type = SUNXI_DRAM_TYPE_LPDDR3, + .dx_read_delays = SUN50I_H6_DX_READ_DELAYS, + .dx_write_delays = SUN50I_H6_DX_WRITE_DELAYS, +#elif defined(CONFIG_SUNXI_DRAM_H6_DDR3_1333) + .type = SUNXI_DRAM_TYPE_DDR3, .dx_read_delays = SUN50I_H6_DX_READ_DELAYS, .dx_write_delays = SUN50I_H6_DX_WRITE_DELAYS, #endif

Hi!
Dne sreda, 19. junij 2019 ob 03:11:06 CEST je Andre Przywara napisal(a):
At the moment the H6 DRAM driver only supports LPDDR3 DRAM.
Extend the driver to cover DDR3 DRAM as well.
The changes are partly motivated by looking at the ZynqMP register documentation, partly by looking at register dumps after boot0/libdram has initialised the controller.
Many thanks to Jernej for contributing some fixes!
Signed-off-by: Andre Przywara andre.przywara@arm.com
arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h | 7 +++ arch/arm/mach-sunxi/dram_sun50i_h6.c | 71 +++++++++++++++++------- 2 files changed, 57 insertions(+), 21 deletions(-)
diff --git a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h index b28ae583c9..8b8085611f 100644 --- a/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h +++ b/arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h @@ -9,6 +9,8 @@ #ifndef _SUNXI_DRAM_SUN50I_H6_H #define _SUNXI_DRAM_SUN50I_H6_H
+#include <stdbool.h>
enum sunxi_dram_type { SUNXI_DRAM_TYPE_DDR3 = 3, SUNXI_DRAM_TYPE_DDR4, @@ -16,6 +18,11 @@ enum sunxi_dram_type { SUNXI_DRAM_TYPE_LPDDR3, };
+static inline bool sunxi_dram_is_lpddr(int type) +{
- return type >= SUNXI_DRAM_TYPE_LPDDR2;
+}
/*
- The following information is mainly retrieved by disassembly and some
FPGA * test code of sun50iw3 platform. diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c index 697b8af4ce..0436265bdb 100644 --- a/arch/arm/mach-sunxi/dram_sun50i_h6.c +++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c @@ -42,6 +42,7 @@ static void mctl_core_init(struct dram_para *para) mctl_com_init(para); switch (para->type) { case SUNXI_DRAM_TYPE_LPDDR3:
- case SUNXI_DRAM_TYPE_DDR3: mctl_set_timing_params(para); break; default:
@@ -302,22 +303,37 @@ static void mctl_com_init(struct dram_para *para) reg_val = 0x3f00; clrsetbits_le32(&mctl_com->unk_0x008, 0x3f00, reg_val);
- /* TODO: half DQ, non-LPDDR3 types */
- writel(MSTR_DEVICETYPE_LPDDR3 | MSTR_BUSWIDTH_FULL |
MSTR_BURST_LENGTH(8) | MSTR_ACTIVE_RANKS(para->ranks) |
0x80000000, &mctl_ctl->mstr);
- writel(DCR_LPDDR3 | DCR_DDR8BANK | 0x400, &mctl_phy->dcr);
- /* TODO: half DQ, DDR4 */
- reg_val = MSTR_BUSWIDTH_FULL | MSTR_BURST_LENGTH(8) |
MSTR_ACTIVE_RANKS(para->ranks);
- if (para->type == SUNXI_DRAM_TYPE_LPDDR3)
reg_val |= MSTR_DEVICETYPE_LPDDR3;
- if (para->type == SUNXI_DRAM_TYPE_DDR3)
reg_val |= MSTR_DEVICETYPE_DDR3 | MSTR_2TMODE;
- writel(reg_val | BIT(31), &mctl_ctl->mstr);
- if (para->type == SUNXI_DRAM_TYPE_LPDDR3)
reg_val = DCR_LPDDR3 | DCR_DDR8BANK;
- if (para->type == SUNXI_DRAM_TYPE_DDR3)
reg_val = DCR_DDR3 | DCR_DDR8BANK | BIT(28); /* 2T mode
*/
You should make a define for BIT(28).
writel(reg_val | 0x400, &mctl_phy->dcr);
if (para->ranks == 2) writel(0x0303, &mctl_ctl->odtmap); else writel(0x0201, &mctl_ctl->odtmap);
- /* TODO: non-LPDDR3 types */
- tmp = para->clk * 7 / 2000;
- reg_val = 0x0400;
- reg_val |= (tmp + 7) << 24;
- reg_val |= (((para->clk < 400) ? 3 : 4) - tmp) << 16;
- /* TODO: DDR4 */
- if (para->type == SUNXI_DRAM_TYPE_LPDDR3) {
tmp = para->clk * 7 / 2000;
reg_val = 0x0400;
reg_val |= (tmp + 7) << 24;
reg_val |= (((para->clk < 400) ? 3 : 4) - tmp) << 16;
- } else if (para->type == SUNXI_DRAM_TYPE_DDR3) {
reg_val = 0x06000400; /* TODO?: Use CL - CWL value in
[7:0] */
- } else if (para->type == SUNXI_DRAM_TYPE_DDR4) {
panic("DDR4 not yet supported\n");
If we go this route, you should also add LPDDR2 or just generalize this message and remove "if".
Best regards, Jernej
} writel(reg_val, &mctl_ctl->odtcfg);
/* TODO: half DQ */
@@ -372,6 +388,9 @@ static void mctl_bit_delay_set(struct dram_para *para) setbits_le32(&mctl_phy->pgcr[0], BIT(26)); udelay(1);
- if (para->type != SUNXI_DRAM_TYPE_LPDDR3)
return;
- for (i = 1; i < 14; i++) { val = readl(&mctl_phy->acbdlr[i]); val += 0x0a0a0a0a;
@@ -419,7 +438,8 @@ static void mctl_channel_init(struct dram_para *para) else clrsetbits_le32(&mctl_phy->dtcr[1], 0x30000, 0x10000);
- clrbits_le32(&mctl_phy->dtcr[1], BIT(1));
- if (sunxi_dram_is_lpddr(para->type))
if (para->ranks == 2) { writel(0x00010001, &mctl_phy->rankidr); writel(0x20000, &mctl_phy->odtcr);clrbits_le32(&mctl_phy->dtcr[1], BIT(1));
@@ -428,8 +448,11 @@ static void mctl_channel_init(struct dram_para *para) writel(0x10000, &mctl_phy->odtcr); }
- /* TODO: non-LPDDR3 types */
- clrsetbits_le32(&mctl_phy->dtcr[0], 0xF0000000, 0x10000040);
- /* set bits [3:0] to 1? 0 not valid in ZynqMP d/s */
- if (para->type == SUNXI_DRAM_TYPE_LPDDR3)
clrsetbits_le32(&mctl_phy->dtcr[0], 0xF0000000,
0x10000040);
- else
clrsetbits_le32(&mctl_phy->dtcr[0], 0xF0000000,
0x10000000);
if (para->clk <= 792) { if (para->clk <= 672) { if (para->clk <= 600) @@ -459,12 +482,13 @@ static void mctl_channel_init(struct dram_para *para) writel(0x06060606, &mctl_phy->acbdlr[i]); }
- /* TODO: non-LPDDR3 types */
- mctl_phy_pir_init(PIR_ZCAL | PIR_DCAL | PIR_PHYRST | PIR_DRAMINIT
|
PIR_QSGATE | PIR_RDDSKW | PIR_WRDSKW |
PIR_RDEYE |
PIR_WREYE);
- val = PIR_ZCAL | PIR_DCAL | PIR_PHYRST | PIR_DRAMINIT | PIR_QSGATE
|
PIR_RDDSKW | PIR_WRDSKW | PIR_RDEYE | PIR_WREYE;
- if (para->type == SUNXI_DRAM_TYPE_DDR3)
val |= PIR_DRAMRST | PIR_WL;
- mctl_phy_pir_init(val);
- /* TODO: non-LPDDR3 types */
- /* TODO: DDR4 types ? */ for (i = 0; i < 4; i++) writel(0x00000909, &mctl_phy->dx[i].gcr[5]);
@@ -520,7 +544,8 @@ static void mctl_channel_init(struct dram_para *para) panic("Error while initializing DRAM PHY!\n"); }
- clrsetbits_le32(&mctl_phy->dsgcr, 0xc0, 0x40);
- if (sunxi_dram_is_lpddr(para->type))
clrbits_le32(&mctl_phy->pgcr[1], 0x40); clrbits_le32(&mctl_ctl->dfimisc, BIT(0)); writel(1, &mctl_ctl->swctl);clrsetbits_le32(&mctl_phy->dsgcr, 0xc0, 0x40);
@@ -589,11 +614,15 @@ unsigned long sunxi_dram_init(void) (struct sunxi_mctl_com_reg
*)SUNXI_DRAM_COM_BASE;
struct dram_para para = { .clk = CONFIG_DRAM_CLK, -#ifdef CONFIG_SUNXI_DRAM_H6_LPDDR3
.ranks = 2, .cols = 11, .rows = 14,.type = SUNXI_DRAM_TYPE_LPDDR3,
+#ifdef CONFIG_SUNXI_DRAM_H6_LPDDR3
.type = SUNXI_DRAM_TYPE_LPDDR3,
.dx_read_delays = SUN50I_H6_DX_READ_DELAYS,
.dx_write_delays = SUN50I_H6_DX_WRITE_DELAYS,
+#elif defined(CONFIG_SUNXI_DRAM_H6_DDR3_1333)
.dx_read_delays = SUN50I_H6_DX_READ_DELAYS, .dx_write_delays = SUN50I_H6_DX_WRITE_DELAYS,.type = SUNXI_DRAM_TYPE_DDR3,
#endif

Add a routine to program the timing parameters for DDR3-1333 DRAM chips connected to the H6 DRAM controller.
The values were gathered from doing back-calculations from a register dump, trying to match them up with the official JEDEC DDDR3 spec. If in doubt, the register dump values were taken for now, but the JEDEC recommendation were added as a comment.
Many thanks to Jernej for contributing fixes!
Signed-off-by: Andre Przywara andre.przywara@arm.com --- arch/arm/mach-sunxi/Kconfig | 8 ++ arch/arm/mach-sunxi/dram_timings/Makefile | 1 + arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c | 144 ++++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c
diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig index e01cb6a09a..12fa3ad811 100644 --- a/arch/arm/mach-sunxi/Kconfig +++ b/arch/arm/mach-sunxi/Kconfig @@ -378,6 +378,14 @@ config SUNXI_DRAM_H6_LPDDR3 This option is the LPDDR3 timing used by the stock boot0 by Allwinner.
+config SUNXI_DRAM_H6_DDR3_1333 + bool "DDR3-1333 boot0 timings on the H6 DRAM controller" + select SUNXI_DRAM_DDR3 + depends on DRAM_SUN50I_H6 + ---help--- + This option is the DDR3 timing used by the boot0 on H6 TV boxes + which use a DDR3-1333 timing. + config SUNXI_DRAM_DDR2_V3S bool "DDR2 found in V3s chip" select SUNXI_DRAM_DDR2 diff --git a/arch/arm/mach-sunxi/dram_timings/Makefile b/arch/arm/mach-sunxi/dram_timings/Makefile index c3e74362eb..45f2e5a6b9 100644 --- a/arch/arm/mach-sunxi/dram_timings/Makefile +++ b/arch/arm/mach-sunxi/dram_timings/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_SUNXI_DRAM_DDR3_1333) += ddr3_1333.o 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_1333.o +obj-$(CONFIG_SUNXI_DRAM_H6_DDR3_1333) += h6_ddr3_1333.o diff --git a/arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c b/arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c new file mode 100644 index 0000000000..12de4db310 --- /dev/null +++ b/arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c @@ -0,0 +1,144 @@ +/* + * sun50i H6 DDR3-1333 timings, as programmed by Allwinner's boot0 + * for some TV boxes with the H6 and DDR3 memory. + * + * The chips are probably able to be driven by a faster clock, but boot0 + * uses a more conservative timing (as usual). + * + * (C) Copyright 2018,2019 Arm Ltd. + * based on previous work by: + * (C) Copyright 2017 Icenowy Zheng icenowy@aosc.io + * + * References used: + * - JEDEC DDR3 SDRAM standard: JESD79-3F.pdf + * - Samsung K4B2G0446D datasheet + * - ZynqMP UG1087 register DDRC/PHY documentation + * + * Many thanks to Jernej Skrabec for contributing some fixes! + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <asm/arch/dram.h> +#include <asm/arch/cpu.h> + +/* + * Only the first four are used for DDR3(?) + * MR0: BL8, seq. read burst, no test, fast exit (DLL on), no DLL reset, + * CAS latency (CL): 11, write recovery (WR): 12 + * MR1: DLL enabled, output strength RZQ/6, Rtt_norm RZQ/2, + * write levelling disabled, TDQS disabled, output buffer enabled + * MR2: manual full array self refresh, dynamic ODT off, + * CAS write latency (CWL): 8 + */ +static u32 mr_ddr3[7] = { + 0x00001c70, 0x00000040, 0x00000018, 0x00000000, + 0x00000000, 0x00000400, 0x00000848, +}; + +/* TODO: flexible timing */ +void mctl_set_timing_params(struct dram_para *para) +{ + struct sunxi_mctl_ctl_reg * const mctl_ctl = + (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE; + struct sunxi_mctl_phy_reg * const mctl_phy = + (struct sunxi_mctl_phy_reg *)SUNXI_DRAM_PHY0_BASE; + int i; + + u8 tccd = 2; /* JEDEC: 4nCK */ + u8 tfaw = ns_to_t(50); /* JEDEC: 40 ns */ + u8 trrd = max(ns_to_t(10), 2); /* JEDEC: max(7.5 ns, 4nCK) */ + u8 trcd = ns_to_t(15); /* JEDEC: 13.75 ns */ + u8 trc = ns_to_t(53); /* JEDEC: 48.75 ns */ + u8 txp = max(ns_to_t(8), 2); /* JEDEC: max(6 ns, 3nCK) */ + u8 twtr = max(ns_to_t(8), 2); /* JEDEC: max(7.5 ns, 4nCK) */ + u8 trtp = max(ns_to_t(8), 2); /* JEDEC: max(7.5 ns, 4nCK) */ + u8 twr = max(ns_to_t(15), 2); /* ? */ + u8 trp = ns_to_t(15); /* JEDEC: >= 13.75 ns */ + u8 tras = ns_to_t(38); /* JEDEC >= 35 ns, <= 9*trefi */ + u8 twtr_sa = 2; /* ? */ + u8 tcksrea = 4; /* ? */ + u16 trefi = ns_to_t(7800) / 32; /* JEDEC: 7.8us@Tcase <= 85C */ + u16 trfc = ns_to_t(350); /* JEDEC: 160 ns for 2Gb */ + u16 txsr = 4; /* ? */ + + u8 tmrw = 0; /* ? */ + u8 tmrd = 4; + u8 tmod = 12; + u8 tcke = 3; + u8 tcksrx = 5; + u8 tcksre = 5; + u8 tckesr = tcke + 1; + u8 trasmax = 24; /* JEDEC: tREFI * 9 */ + u8 txs = ns_to_t(360) / 32; /* JEDEC: max(5nCK,tRFC+10ns) */ + u8 txsdll = 4; /* JEDEC: 512 nCK */ + u8 txsabort = 4; /* ? */ + u8 txsfast = 4; /* ? */ + u8 tcl = 6; /* JEDEC: 11 / 2 => 6 */ + u8 tcwl = 4; /* JEDEC: 8 */ + u8 t_rdata_en = 7; /* ? */ + + u32 tdinit0 = (500 * CONFIG_DRAM_CLK) + 1; /* 500us */ + u32 tdinit1 = (360 * CONFIG_DRAM_CLK) / 1000 + 1; + u32 tdinit2 = (200 * CONFIG_DRAM_CLK) + 1; + u32 tdinit3 = (1 * CONFIG_DRAM_CLK) + 1; /* 1us */ + + u8 twtp = tcwl + 2 + twr; /* (WL + BL / 2 + tWR) / 2 */ + u8 twr2rd = tcwl + 2 + twtr; /* (WL + BL / 2 + tWTR) / 2 */ + u8 trd2wr = 5; /* (RL + BL / 2 + 2 - WL) / 2 */ + + if (tcl + 1 >= trtp + trp) + trtp = tcl + 2 - trp; + + /* set mode registers */ + for (i = 0; i < ARRAY_SIZE(mr_ddr3); i++) + writel(mr_ddr3[i], &mctl_phy->mr[i]); + + /* set DRAM timing */ + writel((twtp << 24) | (tfaw << 16) | (trasmax << 8) | tras, + &mctl_ctl->dramtmg[0]); + writel((txp << 16) | (trtp << 8) | trc, &mctl_ctl->dramtmg[1]); + writel((tcwl << 24) | (tcl << 16) | (trd2wr << 8) | twr2rd, + &mctl_ctl->dramtmg[2]); + writel((tmrw << 20) | (tmrd << 12) | tmod, &mctl_ctl->dramtmg[3]); + writel((trcd << 24) | (tccd << 16) | (trrd << 8) | trp, + &mctl_ctl->dramtmg[4]); + writel((tcksrx << 24) | (tcksre << 16) | (tckesr << 8) | tcke, + &mctl_ctl->dramtmg[5]); + /* Value suggested by ZynqMP manual and used by libdram */ + writel((txp + 2) | 0x02020000, &mctl_ctl->dramtmg[6]); + writel((txsfast << 24) | (txsabort << 16) | (txsdll << 8) | txs, + &mctl_ctl->dramtmg[8]); + writel(txsr, &mctl_ctl->dramtmg[14]); + + clrsetbits_le32(&mctl_ctl->init[0], (3 << 30), (1 << 30)); + writel(0, &mctl_ctl->dfimisc); + clrsetbits_le32(&mctl_ctl->rankctl, 0xff0, 0x660); + + /* + * Set timing registers of the PHY. + * Note: the PHY is clocked 2x from the DRAM frequency. + */ + writel((trrd << 25) | (tras << 17) | (trp << 9) | (trtp << 1), + &mctl_phy->dtpr[0]); + writel((tfaw << 17) | 0x28000400 | (tmrd << 1), &mctl_phy->dtpr[1]); + writel(((txs << 6) - 1) | (tcke << 17), &mctl_phy->dtpr[2]); + writel(((txsdll << 22) - (0x1 << 16)) | twtr_sa | (tcksrea << 8), + &mctl_phy->dtpr[3]); + writel((txp << 1) | (trfc << 17) | 0x800, &mctl_phy->dtpr[4]); + writel((trc << 17) | (trcd << 9) | (twtr << 1), &mctl_phy->dtpr[5]); + writel(0x0505, &mctl_phy->dtpr[6]); + + /* Configure DFI timing */ + writel(tcl | 0x2000200 | (t_rdata_en << 16) | 0x808000, + &mctl_ctl->dfitmg0); + writel(0x040201, &mctl_ctl->dfitmg1); + + /* Configure PHY timing. Zynq uses different registers. */ + writel(tdinit0 | (tdinit1 << 20), &mctl_phy->ptr[3]); + writel(tdinit2 | (tdinit3 << 18), &mctl_phy->ptr[4]); + + /* set refresh timing */ + writel((trefi << 16) | trfc, &mctl_ctl->rfshtmg); +}

Hi!
Dne sreda, 19. junij 2019 ob 03:11:07 CEST je Andre Przywara napisal(a):
Add a routine to program the timing parameters for DDR3-1333 DRAM chips connected to the H6 DRAM controller.
The values were gathered from doing back-calculations from a register dump, trying to match them up with the official JEDEC DDDR3 spec. If in doubt, the register dump values were taken for now, but the JEDEC recommendation were added as a comment.
Many thanks to Jernej for contributing fixes!
Signed-off-by: Andre Przywara andre.przywara@arm.com
arch/arm/mach-sunxi/Kconfig | 8 ++ arch/arm/mach-sunxi/dram_timings/Makefile | 1 + arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c | 144 ++++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c
diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig index e01cb6a09a..12fa3ad811 100644 --- a/arch/arm/mach-sunxi/Kconfig +++ b/arch/arm/mach-sunxi/Kconfig @@ -378,6 +378,14 @@ config SUNXI_DRAM_H6_LPDDR3 This option is the LPDDR3 timing used by the stock boot0 by Allwinner.
+config SUNXI_DRAM_H6_DDR3_1333
- bool "DDR3-1333 boot0 timings on the H6 DRAM controller"
- select SUNXI_DRAM_DDR3
- depends on DRAM_SUN50I_H6
- ---help---
- This option is the DDR3 timing used by the boot0 on H6 TV boxes
- which use a DDR3-1333 timing.
config SUNXI_DRAM_DDR2_V3S bool "DDR2 found in V3s chip" select SUNXI_DRAM_DDR2 diff --git a/arch/arm/mach-sunxi/dram_timings/Makefile b/arch/arm/mach-sunxi/dram_timings/Makefile index c3e74362eb..45f2e5a6b9 100644 --- a/arch/arm/mach-sunxi/dram_timings/Makefile +++ b/arch/arm/mach-sunxi/dram_timings/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_SUNXI_DRAM_DDR3_1333) += ddr3_1333.o 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_1333.o +obj-$(CONFIG_SUNXI_DRAM_H6_DDR3_1333) += h6_ddr3_1333.o diff --git a/arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c b/arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c new file mode 100644 index 0000000000..12de4db310 --- /dev/null +++ b/arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c @@ -0,0 +1,144 @@ +/*
- sun50i H6 DDR3-1333 timings, as programmed by Allwinner's boot0
- for some TV boxes with the H6 and DDR3 memory.
- The chips are probably able to be driven by a faster clock, but boot0
- uses a more conservative timing (as usual).
- (C) Copyright 2018,2019 Arm Ltd.
- based on previous work by:
- (C) Copyright 2017 Icenowy Zheng icenowy@aosc.io
- References used:
- JEDEC DDR3 SDRAM standard: JESD79-3F.pdf
- Samsung K4B2G0446D datasheet
- ZynqMP UG1087 register DDRC/PHY documentation
- Many thanks to Jernej Skrabec for contributing some fixes!
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <common.h> +#include <asm/arch/dram.h> +#include <asm/arch/cpu.h>
+/*
- Only the first four are used for DDR3(?)
- MR0: BL8, seq. read burst, no test, fast exit (DLL on), no DLL reset,
- CAS latency (CL): 11, write recovery (WR): 12
- MR1: DLL enabled, output strength RZQ/6, Rtt_norm RZQ/2,
- write levelling disabled, TDQS disabled, output buffer enabled
- MR2: manual full array self refresh, dynamic ODT off,
- CAS write latency (CWL): 8
- */
+static u32 mr_ddr3[7] = {
- 0x00001c70, 0x00000040, 0x00000018, 0x00000000,
- 0x00000000, 0x00000400, 0x00000848,
+};
+/* TODO: flexible timing */ +void mctl_set_timing_params(struct dram_para *para) +{
- struct sunxi_mctl_ctl_reg * const mctl_ctl =
(struct sunxi_mctl_ctl_reg
*)SUNXI_DRAM_CTL0_BASE;
- struct sunxi_mctl_phy_reg * const mctl_phy =
(struct sunxi_mctl_phy_reg
*)SUNXI_DRAM_PHY0_BASE;
- int i;
- u8 tccd = 2; /* JEDEC:
4nCK */
- u8 tfaw = ns_to_t(50); /*
JEDEC: 40 ns */
- u8 trrd = max(ns_to_t(10), 2); /* JEDEC: max(7.5
ns, 4nCK) */
- u8 trcd = ns_to_t(15); /*
JEDEC: 13.75 ns */
- u8 trc = ns_to_t(53); /*
JEDEC: 48.75 ns */
- u8 txp = max(ns_to_t(8), 2); /* JEDEC: max(6
ns, 3nCK) */
- u8 twtr = max(ns_to_t(8), 2); /* JEDEC: max(7.5
ns, 4nCK) */
- u8 trtp = max(ns_to_t(8), 2); /* JEDEC: max(7.5
ns, 4nCK) */
- u8 twr = max(ns_to_t(15), 2); /* ? */
- u8 trp = ns_to_t(15); /*
JEDEC: >= 13.75 ns */
- u8 tras = ns_to_t(38); /* JEDEC
= 35 ns, <= 9*trefi */
- u8 twtr_sa = 2; /* ? */
- u8 tcksrea = 4; /* ? */
- u16 trefi = ns_to_t(7800) / 32; /* JEDEC: 7.8us@Tcase <= 85C
*/
- u16 trfc = ns_to_t(350); /* JEDEC: 160 ns
for 2Gb */
- u16 txsr = 4; /* ? */
- u8 tmrw = 0; /* ? */
- u8 tmrd = 4;
- u8 tmod = 12;
- u8 tcke = 3;
- u8 tcksrx = 5;
- u8 tcksre = 5;
- u8 tckesr = tcke + 1;
- u8 trasmax = 24; /* JEDEC: tREFI * 9
*/
- u8 txs = ns_to_t(360) / 32; /* JEDEC:
max(5nCK,tRFC+10ns) */
- u8 txsdll = 4; /* JEDEC: 512 nCK */
- u8 txsabort = 4; /* ? */
- u8 txsfast = 4; /* ? */
- u8 tcl = 6; /* JEDEC:
11 / 2 => 6 */
- u8 tcwl = 4; /* JEDEC:
8 */
- u8 t_rdata_en = 7; /* ? */
- u32 tdinit0 = (500 * CONFIG_DRAM_CLK) + 1; /* 500us */
- u32 tdinit1 = (360 * CONFIG_DRAM_CLK) / 1000 + 1;
- u32 tdinit2 = (200 * CONFIG_DRAM_CLK) + 1;
- u32 tdinit3 = (1 * CONFIG_DRAM_CLK) + 1; /* 1us */
- u8 twtp = tcwl + 2 + twr; /* (WL + BL / 2 +
tWR) / 2 */
- u8 twr2rd = tcwl + 2 + twtr; /* (WL + BL / 2 + tWTR) / 2
*/
- u8 trd2wr = 5; /* (RL + BL / 2 + 2 -
WL) / 2 */
Your original formula from WIP branch (tcl + 2 + 1 - tcwl) gives correct value here, so I would use that instead of a fixed value. However, it's not clear how you derived that from the formula in the comment. Please add better explanation for all three calculations.
In general I noticed that there is just slight difference between value specified in JEDEC standard and that used by libdram for some parameters. I think it would be better to use JEDEC value if that doesn't break anything.
Best regards, Jernej
- if (tcl + 1 >= trtp + trp)
trtp = tcl + 2 - trp;
- /* set mode registers */
- for (i = 0; i < ARRAY_SIZE(mr_ddr3); i++)
writel(mr_ddr3[i], &mctl_phy->mr[i]);
- /* set DRAM timing */
- writel((twtp << 24) | (tfaw << 16) | (trasmax << 8) | tras,
&mctl_ctl->dramtmg[0]);
- writel((txp << 16) | (trtp << 8) | trc, &mctl_ctl->dramtmg[1]);
- writel((tcwl << 24) | (tcl << 16) | (trd2wr << 8) | twr2rd,
&mctl_ctl->dramtmg[2]);
- writel((tmrw << 20) | (tmrd << 12) | tmod, &mctl_ctl->dramtmg[3]);
- writel((trcd << 24) | (tccd << 16) | (trrd << 8) | trp,
&mctl_ctl->dramtmg[4]);
- writel((tcksrx << 24) | (tcksre << 16) | (tckesr << 8) | tcke,
&mctl_ctl->dramtmg[5]);
- /* Value suggested by ZynqMP manual and used by libdram */
- writel((txp + 2) | 0x02020000, &mctl_ctl->dramtmg[6]);
- writel((txsfast << 24) | (txsabort << 16) | (txsdll << 8) | txs,
&mctl_ctl->dramtmg[8]);
- writel(txsr, &mctl_ctl->dramtmg[14]);
- clrsetbits_le32(&mctl_ctl->init[0], (3 << 30), (1 << 30));
- writel(0, &mctl_ctl->dfimisc);
- clrsetbits_le32(&mctl_ctl->rankctl, 0xff0, 0x660);
- /*
* Set timing registers of the PHY.
* Note: the PHY is clocked 2x from the DRAM frequency.
*/
- writel((trrd << 25) | (tras << 17) | (trp << 9) | (trtp << 1),
&mctl_phy->dtpr[0]);
- writel((tfaw << 17) | 0x28000400 | (tmrd << 1), &mctl_phy-
dtpr[1]);
- writel(((txs << 6) - 1) | (tcke << 17), &mctl_phy->dtpr[2]);
- writel(((txsdll << 22) - (0x1 << 16)) | twtr_sa | (tcksrea << 8),
&mctl_phy->dtpr[3]);
- writel((txp << 1) | (trfc << 17) | 0x800, &mctl_phy->dtpr[4]);
- writel((trc << 17) | (trcd << 9) | (twtr << 1), &mctl_phy-
dtpr[5]);
- writel(0x0505, &mctl_phy->dtpr[6]);
- /* Configure DFI timing */
- writel(tcl | 0x2000200 | (t_rdata_en << 16) | 0x808000,
&mctl_ctl->dfitmg0);
- writel(0x040201, &mctl_ctl->dfitmg1);
- /* Configure PHY timing. Zynq uses different registers. */
- writel(tdinit0 | (tdinit1 << 20), &mctl_phy->ptr[3]);
- writel(tdinit2 | (tdinit3 << 18), &mctl_phy->ptr[4]);
- /* set refresh timing */
- writel((trefi << 16) | trfc, &mctl_ctl->rfshtmg);
+}

From: Jernej Skrabec jernej.skrabec@siol.net
Add some basic line delay values to be used with DDR3 DRAM chips on some H6 TV boxes. Taken from a register dump after boot0 initialised the DRAM. Put them as the default delay values for DDR3 DRAM until we know better.
Signed-off-by: Jernej Skrabec jernej.skrabec@siol.net Signed-off-by: Andre Przywara andre.przywara@arm.com --- arch/arm/mach-sunxi/dram_sun50i_h6.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-)
diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c index 0436265bdb..5fe53bf463 100644 --- a/arch/arm/mach-sunxi/dram_sun50i_h6.c +++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c @@ -597,17 +597,28 @@ unsigned long mctl_calc_size(struct dram_para *para) return (1ULL << (para->cols + para->rows + 3)) * 4 * para->ranks; }
-#define SUN50I_H6_DX_WRITE_DELAYS \ +#define SUN50I_H6_LPDDR3_DX_WRITE_DELAYS \ {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0 }, \ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }} -#define SUN50I_H6_DX_READ_DELAYS \ +#define SUN50I_H6_LPDDR3_DX_READ_DELAYS \ {{ 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0 }, \ { 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0 }, \ { 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0 }, \ { 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0 }}
+#define SUN50I_H6_DDR3_DX_WRITE_DELAYS \ + {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }} +#define SUN50I_H6_DDR3_DX_READ_DELAYS \ + {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0 }, \ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }} + unsigned long sunxi_dram_init(void) { struct sunxi_mctl_com_reg * const mctl_com = @@ -619,12 +630,12 @@ unsigned long sunxi_dram_init(void) .rows = 14, #ifdef CONFIG_SUNXI_DRAM_H6_LPDDR3 .type = SUNXI_DRAM_TYPE_LPDDR3, - .dx_read_delays = SUN50I_H6_DX_READ_DELAYS, - .dx_write_delays = SUN50I_H6_DX_WRITE_DELAYS, + .dx_read_delays = SUN50I_H6_LPDDR3_DX_READ_DELAYS, + .dx_write_delays = SUN50I_H6_LPDDR3_DX_WRITE_DELAYS, #elif defined(CONFIG_SUNXI_DRAM_H6_DDR3_1333) .type = SUNXI_DRAM_TYPE_DDR3, - .dx_read_delays = SUN50I_H6_DX_READ_DELAYS, - .dx_write_delays = SUN50I_H6_DX_WRITE_DELAYS, + .dx_read_delays = SUN50I_H6_DDR3_DX_READ_DELAYS, + .dx_write_delays = SUN50I_H6_DDR3_DX_WRITE_DELAYS, #endif };

The Eachlink H6 Mini is a modestly priced TV box, using the Allwinner H6 SoC. It comes with 4GB of DRAM (3GB usable) and 32GB of eMMC in the typical TV box enclosure. This adds a basic device tree and defconfig for it.
It contrast to the other supported H6 boards the H6 Mini uses DDR3 DRAM chips (not LPDDR3), which require a different DRAM controller setup.
Signed-off-by: Andre Przywara andre.przywara@arm.com --- arch/arm/dts/Makefile | 1 + arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts | 116 ++++++++++++++++++++++++++++ configs/eachlink_h6_mini_defconfig | 17 ++++ 3 files changed, 134 insertions(+) create mode 100644 arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts create mode 100644 configs/eachlink_h6_mini_defconfig
diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile index 528fb909d5..c463aca190 100644 --- a/arch/arm/dts/Makefile +++ b/arch/arm/dts/Makefile @@ -507,6 +507,7 @@ dtb-$(CONFIG_MACH_SUN50I_H5) += \ sun50i-h5-orangepi-zero-plus2.dtb dtb-$(CONFIG_MACH_SUN50I_H6) += \ sun50i-h6-beelink-gs1.dtb \ + sun50i-h6-eachlink-h6-mini.dtb \ sun50i-h6-orangepi-lite2.dtb \ sun50i-h6-orangepi-one-plus.dtb \ sun50i-h6-pine-h64.dtb diff --git a/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts b/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts new file mode 100644 index 0000000000..5956b5ccd7 --- /dev/null +++ b/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: (GPL-2.0+ or MIT) +/* + * Copyright (c) 2018 Arm Ltd. + * based on work by: + * Copyright (c) 2017 Icenowy Zheng icenowy@aosc.io + */ + +/dts-v1/; + +#include "sun50i-h6.dtsi" + +#include <dt-bindings/gpio/gpio.h> + +/ { + model = "Eachlink H6 Mini"; + compatible = "eachlink,h6-mini", "allwinner,sun50i-h6"; + + aliases { + serial0 = &uart0; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + connector { + compatible = "hdmi-connector"; + type = "a"; + + port { + hdmi_con_in: endpoint { + remote-endpoint = <&hdmi_out_con>; + }; + }; + }; + + reg_vcc3v3: vcc3v3 { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + + reg_vcc5v: vcc5v { + /* supply from the micro-USB DC jack */ + compatible = "regulator-fixed"; + regulator-name = "vcc-5v"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + }; +}; + +&de { + status = "okay"; +}; + +&hdmi { + status = "okay"; +}; + +&hdmi_out { + hdmi_out_con: endpoint { + remote-endpoint = <&hdmi_con_in>; + }; +}; + +&ehci0 { + phys = <&usb2phy 0>; + status = "okay"; +}; + +&ehci3 { + status = "okay"; +}; + +&mmc0 { + vmmc-supply = <®_vcc3v3>; + cd-gpios = <&pio 5 6 GPIO_ACTIVE_LOW>; + bus-width = <4>; + status = "okay"; +}; + +&mmc2 { + vmmc-supply = <®_vcc3v3>; + vqmmc-supply = <®_vcc3v3>; + non-removable; + cap-mmc-hw-reset; + bus-width = <8>; + status = "okay"; +}; + +&ohci0 { + phys = <&usb2phy 0>; + status = "okay"; +}; + +&ohci3 { + status = "okay"; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_ph_pins>; + status = "okay"; +}; + +&usb2otg { + dr_mode = "host"; + status = "okay"; +}; + +&usb2phy { + usb0_vbus-supply = <®_vcc5v>; + status = "okay"; +}; diff --git a/configs/eachlink_h6_mini_defconfig b/configs/eachlink_h6_mini_defconfig new file mode 100644 index 0000000000..d471a24dd5 --- /dev/null +++ b/configs/eachlink_h6_mini_defconfig @@ -0,0 +1,17 @@ +CONFIG_ARM=y +CONFIG_ARCH_SUNXI=y +CONFIG_NR_DRAM_BANKS=1 +CONFIG_SPL=y +CONFIG_MACH_SUN50I_H6=y +CONFIG_DRAM_CLK=660 +CONFIG_DRAM_ZQ=3881979 +CONFIG_SUNXI_DRAM_H6_DDR3_1333=y +CONFIG_MMC0_CD_PIN="PF6" +CONFIG_MMC_SUNXI_SLOT_EXTRA=2 +# CONFIG_PSCI_RESET is not set +# CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set +CONFIG_SPL_TEXT_BASE=0x20060 +# CONFIG_CMD_FLASH is not set +# CONFIG_SPL_DOS_PARTITION is not set +# CONFIG_SPL_EFI_PARTITION is not set +CONFIG_DEFAULT_DEVICE_TREE="sun50i-h6-eachlink-h6-mini"

Hi Andre,
On Wed, 19 Jun 2019 at 03:12, Andre Przywara andre.przywara@arm.com wrote:
The Eachlink H6 Mini is a modestly priced TV box, using the Allwinner H6 SoC. It comes with 4GB of DRAM (3GB usable) and 32GB of eMMC in the typical TV box enclosure. This adds a basic device tree and defconfig for it.
It contrast to the other supported H6 boards the H6 Mini uses DDR3 DRAM chips (not LPDDR3), which require a different DRAM controller setup.
Signed-off-by: Andre Przywara andre.przywara@arm.com
arch/arm/dts/Makefile | 1 + arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts | 116 ++++++++++++++++++++++++++++ configs/eachlink_h6_mini_defconfig | 17 ++++ 3 files changed, 134 insertions(+) create mode 100644 arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts create mode 100644 configs/eachlink_h6_mini_defconfig
diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile index 528fb909d5..c463aca190 100644 --- a/arch/arm/dts/Makefile +++ b/arch/arm/dts/Makefile @@ -507,6 +507,7 @@ dtb-$(CONFIG_MACH_SUN50I_H5) += \ sun50i-h5-orangepi-zero-plus2.dtb dtb-$(CONFIG_MACH_SUN50I_H6) += \ sun50i-h6-beelink-gs1.dtb \
sun50i-h6-eachlink-h6-mini.dtb \ sun50i-h6-orangepi-lite2.dtb \ sun50i-h6-orangepi-one-plus.dtb \ sun50i-h6-pine-h64.dtb
diff --git a/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts b/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts new file mode 100644 index 0000000000..5956b5ccd7 --- /dev/null +++ b/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: (GPL-2.0+ or MIT) +/*
- Copyright (c) 2018 Arm Ltd.
- based on work by:
- Copyright (c) 2017 Icenowy Zheng icenowy@aosc.io
- */
+/dts-v1/;
+#include "sun50i-h6.dtsi"
+#include <dt-bindings/gpio/gpio.h>
+/ {
model = "Eachlink H6 Mini";
compatible = "eachlink,h6-mini", "allwinner,sun50i-h6";
aliases {
serial0 = &uart0;
};
chosen {
stdout-path = "serial0:115200n8";
};
connector {
compatible = "hdmi-connector";
type = "a";
port {
hdmi_con_in: endpoint {
remote-endpoint = <&hdmi_out_con>;
};
};
};
reg_vcc3v3: vcc3v3 {
compatible = "regulator-fixed";
regulator-name = "vcc3v3";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
};
reg_vcc5v: vcc5v {
/* supply from the micro-USB DC jack */
compatible = "regulator-fixed";
regulator-name = "vcc-5v";
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
regulator-always-on;
};
+};
+&de {
status = "okay";
+};
+&hdmi {
status = "okay";
+};
+&hdmi_out {
hdmi_out_con: endpoint {
remote-endpoint = <&hdmi_con_in>;
};
+};
+&ehci0 {
phys = <&usb2phy 0>;
status = "okay";
+};
Same as Pine H64. If you plan to mainline this board in Linux maybe you should try to stuck with the linux one to avoid any differences.
+&ehci3 {
status = "okay";
+};
+&mmc0 {
vmmc-supply = <®_vcc3v3>;
cd-gpios = <&pio 5 6 GPIO_ACTIVE_LOW>;
bus-width = <4>;
status = "okay";
+};
+&mmc2 {
vmmc-supply = <®_vcc3v3>;
vqmmc-supply = <®_vcc3v3>;
non-removable;
cap-mmc-hw-reset;
bus-width = <8>;
status = "okay";
+};
+&ohci0 {
phys = <&usb2phy 0>;
status = "okay";
+};
+&ohci3 {
status = "okay";
+};
+&uart0 {
pinctrl-names = "default";
pinctrl-0 = <&uart0_ph_pins>;
status = "okay";
+};
+&usb2otg {
dr_mode = "host";
status = "okay";
+};
+&usb2phy {
usb0_vbus-supply = <®_vcc5v>;
status = "okay";
+}; diff --git a/configs/eachlink_h6_mini_defconfig b/configs/eachlink_h6_mini_defconfig new file mode 100644 index 0000000000..d471a24dd5 --- /dev/null +++ b/configs/eachlink_h6_mini_defconfig @@ -0,0 +1,17 @@ +CONFIG_ARM=y +CONFIG_ARCH_SUNXI=y +CONFIG_NR_DRAM_BANKS=1 +CONFIG_SPL=y +CONFIG_MACH_SUN50I_H6=y +CONFIG_DRAM_CLK=660 +CONFIG_DRAM_ZQ=3881979 +CONFIG_SUNXI_DRAM_H6_DDR3_1333=y +CONFIG_MMC0_CD_PIN="PF6" +CONFIG_MMC_SUNXI_SLOT_EXTRA=2 +# CONFIG_PSCI_RESET is not set +# CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set +CONFIG_SPL_TEXT_BASE=0x20060
This is now the default and it's no more required.
+# CONFIG_CMD_FLASH is not set +# CONFIG_SPL_DOS_PARTITION is not set +# CONFIG_SPL_EFI_PARTITION is not set +CONFIG_DEFAULT_DEVICE_TREE="sun50i-h6-eachlink-h6-mini"
You should also add yourself in the MAINTAINERS file for the dts and config file of the board.
Regards, Clément
-- 2.14.5
-- You received this message because you are subscribed to the Google Groups "linux-sunxi" group. To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe@googlegroups.com. To view this discussion on the web, visit https://groups.google.com/d/msgid/linux-sunxi/20190619011109.2080-8-andre.pr.... For more options, visit https://groups.google.com/d/optout.

On Wed, 19 Jun 2019 11:03:25 +0200 Clément Péron peron.clem@gmail.com wrote:
Hi Clément,
On Wed, 19 Jun 2019 at 03:12, Andre Przywara andre.przywara@arm.com wrote:
The Eachlink H6 Mini is a modestly priced TV box, using the Allwinner H6 SoC. It comes with 4GB of DRAM (3GB usable) and 32GB of eMMC in the typical TV box enclosure. This adds a basic device tree and defconfig for it.
It contrast to the other supported H6 boards the H6 Mini uses DDR3 DRAM chips (not LPDDR3), which require a different DRAM controller setup.
Signed-off-by: Andre Przywara andre.przywara@arm.com
arch/arm/dts/Makefile | 1 + arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts | 116 ++++++++++++++++++++++++++++ configs/eachlink_h6_mini_defconfig | 17 ++++ 3 files changed, 134 insertions(+) create mode 100644 arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts create mode 100644 configs/eachlink_h6_mini_defconfig
diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile index 528fb909d5..c463aca190 100644 --- a/arch/arm/dts/Makefile +++ b/arch/arm/dts/Makefile @@ -507,6 +507,7 @@ dtb-$(CONFIG_MACH_SUN50I_H5) += \ sun50i-h5-orangepi-zero-plus2.dtb dtb-$(CONFIG_MACH_SUN50I_H6) += \ sun50i-h6-beelink-gs1.dtb \
sun50i-h6-eachlink-h6-mini.dtb \ sun50i-h6-orangepi-lite2.dtb \ sun50i-h6-orangepi-one-plus.dtb \ sun50i-h6-pine-h64.dtb
diff --git a/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts b/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts new file mode 100644 index 0000000000..5956b5ccd7 --- /dev/null +++ b/arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: (GPL-2.0+ or MIT) +/*
- Copyright (c) 2018 Arm Ltd.
- based on work by:
- Copyright (c) 2017 Icenowy Zheng icenowy@aosc.io
- */
+/dts-v1/;
+#include "sun50i-h6.dtsi"
+#include <dt-bindings/gpio/gpio.h>
+/ {
model = "Eachlink H6 Mini";
compatible = "eachlink,h6-mini", "allwinner,sun50i-h6";
aliases {
serial0 = &uart0;
};
chosen {
stdout-path = "serial0:115200n8";
};
connector {
compatible = "hdmi-connector";
type = "a";
port {
hdmi_con_in: endpoint {
remote-endpoint = <&hdmi_out_con>;
};
};
};
reg_vcc3v3: vcc3v3 {
compatible = "regulator-fixed";
regulator-name = "vcc3v3";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
};
reg_vcc5v: vcc5v {
/* supply from the micro-USB DC jack */
compatible = "regulator-fixed";
regulator-name = "vcc-5v";
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
regulator-always-on;
};
+};
+&de {
status = "okay";
+};
+&hdmi {
status = "okay";
+};
+&hdmi_out {
hdmi_out_con: endpoint {
remote-endpoint = <&hdmi_con_in>;
};
+};
+&ehci0 {
phys = <&usb2phy 0>;
status = "okay";
+};
Same as Pine H64. If you plan to mainline this board in Linux maybe you should try to stuck with the linux one to avoid any differences.
Well, there is no DT in Linux yet, so how could I deviate? In fact I will send an ideally identical .dts to Linux later this week, so we have this bit in from the beginning.
I think it's a bit of a chicken and egg problem: I can't really post Linux support patches without having U-Boot running, so have to start somewhere.
+&ehci3 {
status = "okay";
+};
+&mmc0 {
vmmc-supply = <®_vcc3v3>;
cd-gpios = <&pio 5 6 GPIO_ACTIVE_LOW>;
bus-width = <4>;
status = "okay";
+};
+&mmc2 {
vmmc-supply = <®_vcc3v3>;
vqmmc-supply = <®_vcc3v3>;
non-removable;
cap-mmc-hw-reset;
bus-width = <8>;
status = "okay";
+};
+&ohci0 {
phys = <&usb2phy 0>;
status = "okay";
+};
+&ohci3 {
status = "okay";
+};
+&uart0 {
pinctrl-names = "default";
pinctrl-0 = <&uart0_ph_pins>;
status = "okay";
+};
+&usb2otg {
dr_mode = "host";
status = "okay";
+};
+&usb2phy {
usb0_vbus-supply = <®_vcc5v>;
status = "okay";
+}; diff --git a/configs/eachlink_h6_mini_defconfig b/configs/eachlink_h6_mini_defconfig new file mode 100644 index 0000000000..d471a24dd5 --- /dev/null +++ b/configs/eachlink_h6_mini_defconfig @@ -0,0 +1,17 @@ +CONFIG_ARM=y +CONFIG_ARCH_SUNXI=y +CONFIG_NR_DRAM_BANKS=1 +CONFIG_SPL=y +CONFIG_MACH_SUN50I_H6=y +CONFIG_DRAM_CLK=660 +CONFIG_DRAM_ZQ=3881979 +CONFIG_SUNXI_DRAM_H6_DDR3_1333=y +CONFIG_MMC0_CD_PIN="PF6" +CONFIG_MMC_SUNXI_SLOT_EXTRA=2 +# CONFIG_PSCI_RESET is not set +# CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set +CONFIG_SPL_TEXT_BASE=0x20060
This is now the default and it's no more required.
I know, but it's not in current mainline, which this patch is against.
+# CONFIG_CMD_FLASH is not set +# CONFIG_SPL_DOS_PARTITION is not set +# CONFIG_SPL_EFI_PARTITION is not set +CONFIG_DEFAULT_DEVICE_TREE="sun50i-h6-eachlink-h6-mini"
You should also add yourself in the MAINTAINERS file for the dts and config file of the board.
Sure.
Thanks for having a look!
Cheers, Andre.
-- 2.14.5
-- You received this message because you are subscribed to the Google Groups "linux-sunxi" group. To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe@googlegroups.com. To view this discussion on the web, visit https://groups.google.com/d/msgid/linux-sunxi/20190619011109.2080-8-andre.pr.... For more options, visit https://groups.google.com/d/optout.

On Fri, Jun 21, 2019 at 3:54 PM Andre Przywara andre.przywara@arm.com wrote:
So far the SPL DRAM driver for the Allwinner H6 SoC only supports LPDDR3 DRAM chips, which are used on most single board computers with this SoC. There are some TV boxes with the H6 out now, but most of them are using DDR3 DRAM instead of LPDDR3.
This series extends the existing H6 DRAM driver to cover DDR3 DRAMs as well. The information used in these patches is from:
- register dumps after Allwinner's boot0 (libdram) has initialised the DRAM
- some disassembly of the libdram library
- timing parameters as found in the boot0 binary
- comparison with Xilinx ZynqMP DRAM controller documentation
The box I played with (Eachlink H6 Mini) has 3GB of DDR3-1600 chips and runs at 840 MHz, however I couldn't get it to work with these parameters. Instead Jernej suggested to use a lower clock and adjust some timing parameters, which made it work for me as well.
Many thanks to Jernej Skrabec for his help, also to others who helped with testing and experiments.
The first two patches contain some fixes for the existing driver. Patch 3 moves the existing LPDDR3 timing parameters into a separate file, patch 5 introduces the respective DDR3 timings, patch 6 adds some generic delay lines values. Patch 4 enhances the DRAM controller driver to program DDR3 specific registers as well and use different settings on other registers. The final patch introduces support for the Eachlink H6 Mini TV box, with the usual device tree and defconfig file.
Please have a look and comment!
Cheers, Andre.
Andre Przywara (6): sunxi: H6: DRAM: avoid memcpy() on MMIO registers sunxi: H6: DRAM: follow recommended PHY init algorithm sunxi: H6: move LPDDR3 timing definition into separate file sunxi: H6: Add DDR3 support to DRAM controller driver sunxi: H6: Add DDR3-1333 timings sunxi: H6: Add basic Eachlink H6 Mini support
Jernej Skrabec (1): sunxi: H6: Add DDR3 DRAM delay values
arch/arm/dts/Makefile | 1 + arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts | 116 +++++++++++ arch/arm/include/asm/arch-sunxi/dram_sun50i_h6.h | 35 ++++ arch/arm/mach-sunxi/Kconfig | 18 +- arch/arm/mach-sunxi/Makefile | 1 + arch/arm/mach-sunxi/dram_sun50i_h6.c | 240 +++++++--------------- arch/arm/mach-sunxi/dram_timings/Makefile | 2 + arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c | 144 +++++++++++++ arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c | 132 ++++++++++++ configs/eachlink_h6_mini_defconfig | 17 ++ configs/orangepi_one_plus_defconfig | 1 + configs/pine_h64_defconfig | 1 + 12 files changed, 537 insertions(+), 171 deletions(-) create mode 100644 arch/arm/dts/sun50i-h6-eachlink-h6-mini.dts create mode 100644 arch/arm/mach-sunxi/dram_timings/h6_ddr3_1333.c create mode 100644 arch/arm/mach-sunxi/dram_timings/h6_lpddr3_1333.c create mode 100644 configs/eachlink_h6_mini_defconfig
-- 2.14.5
Tested-by: hexdump hexdump0815@googlemail.com
BEFORE (no patches from this series applied): my qplus (Q+) h6 tv box (seems to be the same hardware as the eachlink h6 mini of andre) did hang early on in the spl during the memory setup with mainline uboot
AFTER (the patch applied): my qplus (Q+) h6 tv box boots perfectly fine with mainline uboot - no more problems with the memory setup - thanks a lot for this patrch series
best wishes - hexdump
participants (4)
-
Andre Przywara
-
Clément Péron
-
hex dump
-
Jernej Škrabec