diff mbox series

[RFC] sunxi: support asymmetric dual rank DRAM on A64/R40

Message ID 20200619121657.180850-1-icenowy@aosc.io
State RFC
Delegated to: Jagannadha Sutradharudu Teki
Headers show
Series [RFC] sunxi: support asymmetric dual rank DRAM on A64/R40 | expand

Commit Message

Icenowy Zheng June 19, 2020, 12:16 p.m. UTC
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. 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 <icenowy@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.

 .../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(-)

Comments

Andre Przywara June 24, 2020, 12:28 a.m. UTC | #1
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 <icenowy@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, &para->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(&para->ranks[0]), &para->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, &para);
>  	mctl_set_cr(socid, &para);
>  
> -	return (1UL << (para.row_bits + para.bank_bits)) * para.page_size *
> -	       (para.dual_rank ? 2 : 1);
> +	size = mctl_calc_rank_size(&para.ranks[0]);
> +	if (socid == SOCID_A64 || socid == SOCID_R40) {
> +		if (para.dual_rank)
> +			size += mctl_calc_rank_size(&para.ranks[1]);
> +	} else if (para.dual_rank) {
> +		size *= 2;
> +	}
> +
> +	return size;
>  }
>
Icenowy Zheng June 24, 2020, 12:34 a.m. UTC | #2
于 2020年6月24日 GMT+08:00 上午8:28:39, "André Przywara" <andre.przywara@arm.com> 写到:
>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!

Wait. The configuration of H6 DRAM controller is totally different. it uses
ADDRMAP registers in DRAM controller, instead of specifying parameters
in MCTL_COM part.

>
>> 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 <icenowy@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, &para->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(&para->ranks[0]),
>&para->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, &para);
>>  	mctl_set_cr(socid, &para);
>>  
>> -	return (1UL << (para.row_bits + para.bank_bits)) * para.page_size *
>> -	       (para.dual_rank ? 2 : 1);
>> +	size = mctl_calc_rank_size(&para.ranks[0]);
>> +	if (socid == SOCID_A64 || socid == SOCID_R40) {
>> +		if (para.dual_rank)
>> +			size += mctl_calc_rank_size(&para.ranks[1]);
>> +	} else if (para.dual_rank) {
>> +		size *= 2;
>> +	}
>> +
>> +	return size;
>>  }
>>
Andre Przywara June 24, 2020, 12:53 a.m. UTC | #3
On 24/06/2020 01:34, Icenowy Zheng wrote:

Hi,

> 于 2020年6月24日 GMT+08:00 上午8:28:39, "André Przywara" <andre.przywara@arm.com> 写到:
>> 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!
> 
> Wait. The configuration of H6 DRAM controller is totally different. it uses
> ADDRMAP registers in DRAM controller, instead of specifying parameters
> in MCTL_COM part.

I see, but at least it confirms that configuring the two ranks
differently is possible. I was always scratching my head how the box
works with two different set of chips (four 4GBit chips and four 2Gbit
chips). Do we support this asymmetric setup for the H6 controller
already, possibly due to the different way of setting this up?
If not I can check out if boot0 does some magic there.

Cheers,
Andre


> 
>>
>>> 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 <icenowy@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, &para->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(&para->ranks[0]),
>> &para->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, &para);
>>>  	mctl_set_cr(socid, &para);
>>>  
>>> -	return (1UL << (para.row_bits + para.bank_bits)) * para.page_size *
>>> -	       (para.dual_rank ? 2 : 1);
>>> +	size = mctl_calc_rank_size(&para.ranks[0]);
>>> +	if (socid == SOCID_A64 || socid == SOCID_R40) {
>>> +		if (para.dual_rank)
>>> +			size += mctl_calc_rank_size(&para.ranks[1]);
>>> +	} else if (para.dual_rank) {
>>> +		size *= 2;
>>> +	}
>>> +
>>> +	return size;
>>>  }
>>>
diff mbox series

Patch

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, &para->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(&para->ranks[0]), &para->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, &para);
 	mctl_set_cr(socid, &para);
 
-	return (1UL << (para.row_bits + para.bank_bits)) * para.page_size *
-	       (para.dual_rank ? 2 : 1);
+	size = mctl_calc_rank_size(&para.ranks[0]);
+	if (socid == SOCID_A64 || socid == SOCID_R40) {
+		if (para.dual_rank)
+			size += mctl_calc_rank_size(&para.ranks[1]);
+	} else if (para.dual_rank) {
+		size *= 2;
+	}
+
+	return size;
 }