On 19/06/2020 13:16, Icenowy Zheng wrote: Hi Icenowy,
> Previously we have known that R40 has a configuration register for its > rank 1, which allows different configuration than rank 0. Reverse > engineering of newest libdram of A64 from Allwinner shows that A64 has > this register too. It's bit 0 (which enables dual rank in rank 0 > configuration register) means a dedicated rank size setup is used for > rank 1. Ah! That's a nice discovery, as it probably explains how my Eachlink H6 TV box works with its asymmetric 3GB DRAM config! > Now, Pine64 scheduled to use a 3GiB LPDDR3 DRAM chip (which has 2GiB > rank 0 and 1GiB rank 1) on PinePhone, that makes asymmetric dual rank > DRAM support necessary. > > Add this support. As we have gained knowledge of asymmetric dual rank, > we can now allow R40 dual rank memory setup to work too. > > Signed-off-by: Icenowy Zheng <icen...@aosc.io> > --- > Testing on R40 boards and A64 single rank boards (e.g. the original Pine > A64+) is welcomed. I have these boards, but I get too lazy to take them > out and test them. I briefly tested this on a Bananapi M2 Berry (R40, 1GB), Pine64 512MB and Pine64+ 2GB. All three boot happily into U-Boot, tested the Pine64+ to the Linux prompt as well. I will do a proper review shortly. Thanks for the patch! Cheers, Andre > > .../include/asm/arch-sunxi/dram_sunxi_dw.h | 11 +- > arch/arm/mach-sunxi/dram_sunxi_dw.c | 100 +++++++++++++----- > 2 files changed, 84 insertions(+), 27 deletions(-) > > diff --git a/arch/arm/include/asm/arch-sunxi/dram_sunxi_dw.h > b/arch/arm/include/asm/arch-sunxi/dram_sunxi_dw.h > index a5a7ebde44..e843c14202 100644 > --- a/arch/arm/include/asm/arch-sunxi/dram_sunxi_dw.h > +++ b/arch/arm/include/asm/arch-sunxi/dram_sunxi_dw.h > @@ -215,12 +215,17 @@ struct sunxi_mctl_ctl_reg { > #define NR_OF_BYTE_LANES (32 / BITS_PER_BYTE) > /* The eight data lines (DQn) plus DM, DQS and DQSN */ > #define LINES_PER_BYTE_LANE (BITS_PER_BYTE + 3) > -struct dram_para { > + > +struct rank_para { > u16 page_size; > - u8 bus_full_width; > - u8 dual_rank; > u8 row_bits; > u8 bank_bits; > +}; > + > +struct dram_para { > + u8 dual_rank; > + u8 bus_full_width; > + struct rank_para ranks[2]; > const u8 dx_read_delays[NR_OF_BYTE_LANES][LINES_PER_BYTE_LANE]; > const u8 dx_write_delays[NR_OF_BYTE_LANES][LINES_PER_BYTE_LANE]; > const u8 ac_delays[31]; > diff --git a/arch/arm/mach-sunxi/dram_sunxi_dw.c > b/arch/arm/mach-sunxi/dram_sunxi_dw.c > index a462538521..7a40d92349 100644 > --- a/arch/arm/mach-sunxi/dram_sunxi_dw.c > +++ b/arch/arm/mach-sunxi/dram_sunxi_dw.c > @@ -349,18 +349,24 @@ static void mctl_set_cr(uint16_t socid, struct > dram_para *para) > #else > #error Unsupported DRAM type! > #endif > - (para->bank_bits == 3 ? MCTL_CR_EIGHT_BANKS : > MCTL_CR_FOUR_BANKS) | > + (para->ranks[0].bank_bits == 3 ? MCTL_CR_EIGHT_BANKS : > MCTL_CR_FOUR_BANKS) | > MCTL_CR_BUS_FULL_WIDTH(para->bus_full_width) | > (para->dual_rank ? MCTL_CR_DUAL_RANK : MCTL_CR_SINGLE_RANK) | > - MCTL_CR_PAGE_SIZE(para->page_size) | > - MCTL_CR_ROW_BITS(para->row_bits), &mctl_com->cr); > + MCTL_CR_PAGE_SIZE(para->ranks[0].page_size) | > + MCTL_CR_ROW_BITS(para->ranks[0].row_bits), &mctl_com->cr); > > - if (socid == SOCID_R40) { > - if (para->dual_rank) > - panic("Dual rank memory not supported\n"); > + if (socid == SOCID_A64 || socid == SOCID_R40) { > + writel((para->ranks[1].bank_bits == 3 ? MCTL_CR_EIGHT_BANKS : > MCTL_CR_FOUR_BANKS) | > + MCTL_CR_BUS_FULL_WIDTH(para->bus_full_width) | > + (para->dual_rank ? MCTL_CR_DUAL_RANK : > MCTL_CR_SINGLE_RANK) | > + MCTL_CR_PAGE_SIZE(para->ranks[1].page_size) | > + MCTL_CR_ROW_BITS(para->ranks[1].row_bits), > &mctl_com->cr_r1); > + } > > + if (socid == SOCID_R40) { > /* Mux pin to A15 address line for single rank memory. */ > - setbits_le32(&mctl_com->cr_r1, MCTL_CR_R1_MUX_A15); > + if (!para->dual_rank) > + setbits_le32(&mctl_com->cr_r1, MCTL_CR_R1_MUX_A15); > } > } > > @@ -584,35 +590,63 @@ static int mctl_channel_init(uint16_t socid, struct > dram_para *para) > return 0; > } > > -static void mctl_auto_detect_dram_size(uint16_t socid, struct dram_para > *para) > +/* > + * Test if memory at offset offset matches memory at a certain base > + */ > +static bool mctl_mem_matches_base(u32 offset, ulong base) > +{ > + /* Try to write different values to RAM at two addresses */ > + writel(0, base); > + writel(0xaa55aa55, base + offset); > + dsb(); > + /* Check if the same value is actually observed when reading back */ > + return readl(base) == > + readl(base + offset); > +} > + > +static void mctl_auto_detect_dram_size_rank(uint16_t socid, struct dram_para > *para, ulong base, struct rank_para *rank) > { > /* detect row address bits */ > - para->page_size = 512; > - para->row_bits = 16; > - para->bank_bits = 2; > + rank->page_size = 512; > + rank->row_bits = 16; > + rank->bank_bits = 2; > mctl_set_cr(socid, para); > > - for (para->row_bits = 11; para->row_bits < 16; para->row_bits++) > - if (mctl_mem_matches((1 << (para->row_bits + para->bank_bits)) > * para->page_size)) > + for (rank->row_bits = 11; rank->row_bits < 16; rank->row_bits++) > + if (mctl_mem_matches_base((1 << (rank->row_bits + > rank->bank_bits)) * rank->page_size, base)) > break; > > /* detect bank address bits */ > - para->bank_bits = 3; > + rank->bank_bits = 3; > mctl_set_cr(socid, para); > > - for (para->bank_bits = 2; para->bank_bits < 3; para->bank_bits++) > - if (mctl_mem_matches((1 << para->bank_bits) * para->page_size)) > + for (rank->bank_bits = 2; rank->bank_bits < 3; rank->bank_bits++) > + if (mctl_mem_matches_base((1 << rank->bank_bits) * > rank->page_size, base)) > break; > > /* detect page size */ > - para->page_size = 8192; > + rank->page_size = 8192; > mctl_set_cr(socid, para); > > - for (para->page_size = 512; para->page_size < 8192; para->page_size *= > 2) > - if (mctl_mem_matches(para->page_size)) > + for (rank->page_size = 512; rank->page_size < 8192; rank->page_size *= > 2) > + if (mctl_mem_matches_base(rank->page_size, base)) > break; > } > > +static unsigned long mctl_calc_rank_size(struct rank_para *rank) > +{ > + return (1UL << (rank->row_bits + rank->bank_bits)) * rank->page_size; > +} > + > +static void mctl_auto_detect_dram_size(uint16_t socid, struct dram_para > *para) > +{ > + mctl_auto_detect_dram_size_rank(socid, para, > (ulong)CONFIG_SYS_SDRAM_BASE, ¶->ranks[0]); > + > + if ((socid == SOCID_A64 || socid == SOCID_R40) && para->dual_rank) { > + mctl_auto_detect_dram_size_rank(socid, para, > (ulong)CONFIG_SYS_SDRAM_BASE + mctl_calc_rank_size(¶->ranks[0]), > ¶->ranks[1]); > + } > +} > + > /* > * The actual values used here are taken from Allwinner provided boot0 > * binaries, though they are probably board specific, so would likely benefit > @@ -691,12 +725,23 @@ unsigned long sunxi_dram_init(void) > struct sunxi_mctl_ctl_reg * const mctl_ctl = > (struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE; > > + unsigned long size; > + > struct dram_para para = { > .dual_rank = 1, > .bus_full_width = 1, > - .row_bits = 15, > - .bank_bits = 3, > - .page_size = 4096, > + .ranks = { > + { > + .row_bits = 15, > + .bank_bits = 3, > + .page_size = 4096, > + }, > + { > + .row_bits = 15, > + .bank_bits = 3, > + .page_size = 4096, > + } > + }, > > #if defined(CONFIG_MACH_SUN8I_H3) > .dx_read_delays = SUN8I_H3_DX_READ_DELAYS, > @@ -765,6 +810,13 @@ unsigned long sunxi_dram_init(void) > mctl_auto_detect_dram_size(socid, ¶); > mctl_set_cr(socid, ¶); > > - return (1UL << (para.row_bits + para.bank_bits)) * para.page_size * > - (para.dual_rank ? 2 : 1); > + size = mctl_calc_rank_size(¶.ranks[0]); > + if (socid == SOCID_A64 || socid == SOCID_R40) { > + if (para.dual_rank) > + size += mctl_calc_rank_size(¶.ranks[1]); > + } else if (para.dual_rank) { > + size *= 2; > + } > + > + return size; > } >