sunxi: support asymmetric dual rank DRAM on A64/R40
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.
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. The code could support both A64 and R40, but because
dual rank detection is broken on R40 now, we cannot really use it on R40
currently.
Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
Reviewed-by: Andre Przywara <andre.przywara@arm.com>
Tested-by: Peter Robinson <pbrobinson@gmail.com>
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
diff --git a/arch/arm/mach-sunxi/dram_sunxi_dw.c b/arch/arm/mach-sunxi/dram_sunxi_dw.c
index d060001..2b9d631 100644
--- a/arch/arm/mach-sunxi/dram_sunxi_dw.c
+++ b/arch/arm/mach-sunxi/dram_sunxi_dw.c
@@ -399,11 +399,19 @@
#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 (para->dual_rank && (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) |
+ MCTL_CR_DUAL_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) {
if (para->dual_rank)
@@ -646,35 +654,63 @@
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
@@ -769,12 +805,23 @@
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,
@@ -846,6 +893,13 @@
mctl_auto_detect_dram_size(socid, ¶);
mctl_set_cr(socid, ¶);
+ 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 (1UL << (para.row_bits + para.bank_bits)) * para.page_size *
- (para.dual_rank ? 2 : 1);
+ return size;
}