ARM Sunxi Platform Development
 help / color / mirror / Atom feed
* [PATCH v2 0/6] sunxi: Allwinner A133 SoC support
@ 2025-05-11  1:09 Andre Przywara
  2025-05-11  1:09 ` [PATCH v2 1/6] sunxi: A133: add DRAM init code Andre Przywara
                   ` (5 more replies)
  0 siblings, 6 replies; 15+ messages in thread
From: Andre Przywara @ 2025-05-11  1:09 UTC (permalink / raw)
  To: u-boot
  Cc: Jagan Teki, Cody Eksal, Philippe Simons, Sumit Garg, linux-sunxi,
	Tom Rini, Jernej Skrabec

This is an update to that early post of the Allwinner A133 SoC support,
now much more serious, and also adding support for a development board.
The DRAM init code was heavily updated, also rebased to apply on latest
mainline. Some uncontroversial and simple patches from v1 have been
merged, so this series mostly is about the DRAM code and the Kconfig
bits.
To make this usable, this contains patches to add support for one
development board using this SoC. The DT patch for that board is
pending review on the Linux mailing list, I include it here for the sake
of completeness, it will eventually find its way via the DT rebasing
repo.
===========================

The Allwinner A100 SoC has been around for a while, mostly on cheap
tablets, but didn't generate much interest in the community so far.
There were some efforts by two Allwinner employees in 2020, which led
to basic upstream Linux support for that SoC, although this momentum
dried up pretty quickly, leaving a lot of peripherals unsupported.
    
The A100 was silently replaced with the seemingly identical Allwinner
A133, which is reportedly a better bin of the A100. So far we assume
that both are compatible from a software perspective. There are some
more devices with the A133 out there now, so people are working on
filling the gaps, and adding U-Boot and TF-A support.

This series aims to support boards with the A133 SoC in U-Boot.

Patch 1 is the technically most advanced and challenging patch: to add
support for the DRAM controller. Huge thanks and kudos go to Cody, for
reverse engineering, debugging and testing this. The code works for
me (TM), but I guess there be some improvements and refactoring coming
up.

Patch 2 then puts the right values for various existing Allwinner
specific configuration options in Kconfig, and adds the remaining bits
required to enable configuration and build for boards with the A133 SoC.

Patch 3 and 4 update the DT files from the latest kernel repo, anticipating
the update via the DT rebasing repo, to get a matching base for patch 5,
which adds the board .dts file for the Liontron H-A133L development
board. The DT has been proposed on the kernel ML, but has not been
reviewed or approved yet. Since a .dts file is essential for building
the image for a board, I include it here.

Please have a look, comment and review on the patches, so that we can
finish upstream support for this SoC.

Cheers,
Andre

Changelog v1 .. v2:
- update to latest mainline
- drop already applied patches
- heavy update of the DRAM driver (see commit msg for more details)
- add some (yet) missing DT patches
- add Liontron H-A133L .dts and defconfig file

Andre Przywara (5):
  sunxi: add support for the Allwinner A100/A133 SoC
  arm64: dts: allwinner: a100: Add CPU Operating Performance Points table
  arm64: dts: allwinner: a100: set maximum MMC frequency
  arm64: dts: allwinner: a100: add Liontron H-A133L board support
  sunxi: add support for Liontron H-A133L board

Cody Eksal (1):
  sunxi: A133: add DRAM init code

 arch/arm/cpu/armv8/fel_utils.S                |    2 +-
 .../include/asm/arch-sunxi/clock_sun50i_h6.h  |    7 +
 .../include/asm/arch-sunxi/cpu_sun50i_h6.h    |    4 +
 arch/arm/include/asm/arch-sunxi/dram.h        |    2 +
 .../include/asm/arch-sunxi/dram_sun50i_a133.h |  230 ++++
 arch/arm/mach-sunxi/Kconfig                   |  114 +-
 arch/arm/mach-sunxi/Makefile                  |    2 +
 arch/arm/mach-sunxi/board.c                   |    4 +
 arch/arm/mach-sunxi/clock_sun50i_h6.c         |    3 +-
 arch/arm/mach-sunxi/cpu_info.c                |    2 +
 arch/arm/mach-sunxi/dram_sun50i_a133.c        | 1204 +++++++++++++++++
 arch/arm/mach-sunxi/dram_timings/Makefile     |    2 +
 arch/arm/mach-sunxi/dram_timings/a133_ddr4.c  |   80 ++
 .../arm/mach-sunxi/dram_timings/a133_lpddr4.c |  102 ++
 board/sunxi/board.c                           |    4 +-
 common/spl/Kconfig                            |    4 +-
 configs/liontron-h-a133l_defconfig            |   37 +
 .../allwinner/sun50i-a100-allwinner-perf1.dts |    5 +
 .../arm64/allwinner/sun50i-a100-cpu-opp.dtsi  |   90 ++
 .../src/arm64/allwinner/sun50i-a100.dtsi      |   11 +
 .../sun50i-a133-liontron-h-a133l.dts          |  211 +++
 21 files changed, 2104 insertions(+), 16 deletions(-)
 create mode 100644 arch/arm/include/asm/arch-sunxi/dram_sun50i_a133.h
 create mode 100644 arch/arm/mach-sunxi/dram_sun50i_a133.c
 create mode 100644 arch/arm/mach-sunxi/dram_timings/a133_ddr4.c
 create mode 100644 arch/arm/mach-sunxi/dram_timings/a133_lpddr4.c
 create mode 100644 configs/liontron-h-a133l_defconfig
 create mode 100644 dts/upstream/src/arm64/allwinner/sun50i-a100-cpu-opp.dtsi
 create mode 100644 dts/upstream/src/arm64/allwinner/sun50i-a133-liontron-h-a133l.dts

-- 
2.46.3


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v2 1/6] sunxi: A133: add DRAM init code
  2025-05-11  1:09 [PATCH v2 0/6] sunxi: Allwinner A133 SoC support Andre Przywara
@ 2025-05-11  1:09 ` Andre Przywara
  2025-05-11  9:23   ` Andre Przywara
                     ` (2 more replies)
  2025-05-11  1:09 ` [PATCH v2 2/6] sunxi: add support for the Allwinner A100/A133 SoC Andre Przywara
                   ` (4 subsequent siblings)
  5 siblings, 3 replies; 15+ messages in thread
From: Andre Przywara @ 2025-05-11  1:09 UTC (permalink / raw)
  To: u-boot
  Cc: Jagan Teki, Cody Eksal, Philippe Simons, Sumit Garg, linux-sunxi,
	Tom Rini, Jernej Skrabec

From: Cody Eksal <masterr3c0rd@epochal.quest>

This adds preliminary support for the DRAM controller in the Allwinner
A100/A133 SoCs.
This is work in progress, and has rough edges, but works on at least
three different boards. It contains support for DDR4 and LPDDR4.

[Andre: formatting fixes, adapt to mainline, drop unused parameters,
	remove struct struct sunxi_mctl_com_reg, hardcode MR registers,
	switch to mctl_check_pattern(), remove simple DRAM check]
---
 .../include/asm/arch-sunxi/cpu_sun50i_h6.h    |    4 +
 arch/arm/include/asm/arch-sunxi/dram.h        |    2 +
 .../include/asm/arch-sunxi/dram_sun50i_a133.h |  230 ++++
 arch/arm/mach-sunxi/Kconfig                   |  104 +-
 arch/arm/mach-sunxi/Makefile                  |    2 +
 arch/arm/mach-sunxi/dram_sun50i_a133.c        | 1204 +++++++++++++++++
 arch/arm/mach-sunxi/dram_timings/Makefile     |    2 +
 arch/arm/mach-sunxi/dram_timings/a133_ddr4.c  |   80 ++
 .../arm/mach-sunxi/dram_timings/a133_lpddr4.c |  102 ++
 9 files changed, 1722 insertions(+), 8 deletions(-)
 create mode 100644 arch/arm/include/asm/arch-sunxi/dram_sun50i_a133.h
 create mode 100644 arch/arm/mach-sunxi/dram_sun50i_a133.c
 create mode 100644 arch/arm/mach-sunxi/dram_timings/a133_ddr4.c
 create mode 100644 arch/arm/mach-sunxi/dram_timings/a133_lpddr4.c

diff --git a/arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h
index 8a3f465545a..2a9b086991c 100644
--- a/arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h
+++ b/arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h
@@ -29,6 +29,10 @@
 #define SUNXI_DRAM_COM_BASE		0x047FA000
 #define SUNXI_DRAM_CTL0_BASE		0x047FB000
 #define SUNXI_DRAM_PHY0_BASE		0x04800000
+#elif CONFIG_MACH_SUN50I_A133
+#define SUNXI_DRAM_COM_BASE		0x04810000
+#define SUNXI_DRAM_CTL0_BASE		0x04820000
+#define SUNXI_DRAM_PHY0_BASE		0x04830000
 #endif
 
 #define SUNXI_TWI0_BASE			0x05002000
diff --git a/arch/arm/include/asm/arch-sunxi/dram.h b/arch/arm/include/asm/arch-sunxi/dram.h
index 9d21b492418..0708ae3ee3b 100644
--- a/arch/arm/include/asm/arch-sunxi/dram.h
+++ b/arch/arm/include/asm/arch-sunxi/dram.h
@@ -31,6 +31,8 @@
 #include <asm/arch/dram_sun50i_h6.h>
 #elif defined(CONFIG_MACH_SUN50I_H616)
 #include <asm/arch/dram_sun50i_h616.h>
+#elif defined(CONFIG_DRAM_SUN50I_A133)
+#include <asm/arch/dram_sun50i_a133.h>
 #elif defined(CONFIG_MACH_SUNIV)
 #include <asm/arch/dram_suniv.h>
 #else
diff --git a/arch/arm/include/asm/arch-sunxi/dram_sun50i_a133.h b/arch/arm/include/asm/arch-sunxi/dram_sun50i_a133.h
new file mode 100644
index 00000000000..a5fc6ad3656
--- /dev/null
+++ b/arch/arm/include/asm/arch-sunxi/dram_sun50i_a133.h
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier:	GPL-2.0+
+/*
+ * A133 dram controller register and constant defines
+ *
+ * (C) Copyright 2024  MasterR3C0RD <masterr3c0rd@epochal.quest>
+ */
+
+#ifndef _SUNXI_DRAM_SUN50I_A133_H
+#define _SUNXI_DRAM_SUN50I_A133_H
+
+#include <linux/bitops.h>
+
+enum sunxi_dram_type {
+	SUNXI_DRAM_TYPE_DDR3 = 3,
+	SUNXI_DRAM_TYPE_DDR4,
+	SUNXI_DRAM_TYPE_LPDDR3 = 7,
+	SUNXI_DRAM_TYPE_LPDDR4
+};
+
+static inline int ns_to_t(int nanoseconds)
+{
+	const unsigned int ctrl_freq = CONFIG_DRAM_CLK / 2;
+
+	return DIV_ROUND_UP(ctrl_freq * nanoseconds, 1000);
+}
+
+/* MBUS part is largely the same as in H6, except for one special register */
+#define MCTL_COM_UNK_008	0x008
+/* NOTE: This register has the same importance as mctl_ctl->clken in H616 */
+#define MCTL_COM_MAER0		0x020
+
+/*
+ * Controller registers seems to be the same or at least very similar
+ * to those in H6.
+ */
+struct sunxi_mctl_ctl_reg {
+	u32 mstr; 			/* 0x000 */
+	u32 statr; 			/* 0x004 unused */
+	u32 mstr1; 			/* 0x008 unused */
+	u32 clken; 			/* 0x00c */
+	u32 mrctrl0; 			/* 0x010 unused */
+	u32 mrctrl1; 			/* 0x014 unused */
+	u32 mrstatr; 			/* 0x018 unused */
+	u32 mrctrl2; 			/* 0x01c unused */
+	u32 derateen; 			/* 0x020 unused */
+	u32 derateint; 			/* 0x024 unused */
+	u8 reserved_0x028[8]; 		/* 0x028 */
+	u32 pwrctl; 			/* 0x030 unused */
+	u32 pwrtmg; 			/* 0x034 unused */
+	u32 hwlpctl; 			/* 0x038 unused */
+	u8 reserved_0x03c[20];		/* 0x03c */
+	u32 rfshctl0;			/* 0x050 unused */
+	u32 rfshctl1;			/* 0x054 unused */
+	u8 reserved_0x058[8];		/* 0x05c */
+	u32 rfshctl3;			/* 0x060 */
+	u32 rfshtmg;			/* 0x064 */
+	u8 reserved_0x068[104];		/* 0x068 */
+	u32 init[8];			/* 0x0d0 */
+	u32 dimmctl;			/* 0x0f0 unused */
+	u32 rankctl;			/* 0x0f4 */
+	u8 reserved_0x0f8[8];		/* 0x0f8 */
+	u32 dramtmg[17];		/* 0x100 */
+	u8 reserved_0x144[60];		/* 0x144 */
+	u32 zqctl[3];			/* 0x180 */
+	u32 zqstat;			/* 0x18c unused */
+	u32 dfitmg0;			/* 0x190 */
+	u32 dfitmg1;			/* 0x194 */
+	u32 dfilpcfg[2];		/* 0x198 unused */
+	u32 dfiupd[3];			/* 0x1a0 */
+	u32 reserved_0x1ac;		/* 0x1ac */
+	u32 dfimisc;			/* 0x1b0 */
+	u32 dfitmg2;			/* 0x1b4 unused */
+	u32 dfitmg3;			/* 0x1b8 unused */
+	u32 dfistat;			/* 0x1bc */
+	u32 dbictl;			/* 0x1c0 */
+	u8 reserved_0x1c4[60];		/* 0x1c4 */
+	u32 addrmap[12];		/* 0x200 */
+	u8 reserved_0x230[16];		/* 0x230 */
+	u32 odtcfg;			/* 0x240 */
+	u32 odtmap;			/* 0x244 */
+	u8 reserved_0x248[8];		/* 0x248 */
+	u32 sched[2];			/* 0x250 */
+	u8 reserved_0x258[180]; 	/* 0x258 */
+	u32 dbgcmd;			/* 0x30c unused */
+	u32 dbgstat;			/* 0x310 unused */
+	u8 reserved_0x314[12];		/* 0x314 */
+	u32 swctl;			/* 0x320 */
+	u32 swstat;			/* 0x324 */
+	u8 reserved_0x328[7768];	/* 0x328 */
+	u32 unk_0x2180;			/* 0x2180 */
+	u8 reserved_0x2184[188];	/* 0x2184 */
+	u32 unk_0x2240;			/* 0x2240 */
+	u8 reserved_0x2244[3900];	/* 0x2244 */
+	u32 unk_0x3180;			/* 0x3180 */
+	u8 reserved_0x3184[188];	/* 0x3184 */
+	u32 unk_0x3240;			/* 0x3240 */
+	u8 reserved_0x3244[3900];	/* 0x3244 */
+	u32 unk_0x4180;			/* 0x4180 */
+	u8 reserved_0x4184[188];	/* 0x4184 */
+	u32 unk_0x4240;			/* 0x4240 */
+};
+
+check_member(sunxi_mctl_ctl_reg, swstat, 0x324);
+check_member(sunxi_mctl_ctl_reg, unk_0x4240, 0x4240);
+
+#define MSTR_DEVICETYPE_DDR3	BIT(0)
+#define MSTR_DEVICETYPE_LPDDR2	BIT(2)
+#define MSTR_DEVICETYPE_LPDDR3	BIT(3)
+#define MSTR_DEVICETYPE_DDR4	BIT(4)
+#define MSTR_DEVICETYPE_LPDDR4	BIT(5)
+#define MSTR_DEVICETYPE_MASK	GENMASK(5, 0)
+#define MSTR_GEARDOWNMODE	BIT(0)		/* Same as MSTR_DEVICETYPE_DDR3, only used for DDR4 */
+#define MSTR_2TMODE		BIT(10)
+#define MSTR_BUSWIDTH_FULL	(0 << 12)
+#define MSTR_BUSWIDTH_HALF	(1 << 12)
+#define MSTR_ACTIVE_RANKS(x)	(((x == 1) ? 3 : 1) << 24)
+#define MSTR_BURST_LENGTH(x)	(((x) >> 1) << 16)
+#define MSTR_DEVICECONFIG_X32	(3 << 30)
+
+#define TPR10_CA_BIT_DELAY	BIT(16)
+#define TPR10_DX_BIT_DELAY0	BIT(17)
+#define TPR10_DX_BIT_DELAY1	BIT(18)
+#define TPR10_WRITE_LEVELING	BIT(20)
+#define TPR10_READ_CALIBRATION	BIT(21)
+#define TPR10_READ_TRAINING	BIT(22)
+#define TPR10_WRITE_TRAINING	BIT(23)
+
+/* MRCTRL constants */
+#define MRCTRL0_MR_RANKS_ALL	(3 << 4)
+#define MRCTRL0_MR_ADDR(x)	(x << 12)
+#define MRCTRL0_MR_WR		BIT(31)
+
+#define MRCTRL1_MR_ADDR(x)	(x << 8)
+#define MRCTRL1_MR_DATA(x)	(x)
+
+/* ADDRMAP constants */
+#define ADDRMAP_DISABLED_3F_B(b)	(0x3f + b)
+#define ADDRMAP_DISABLED_1F_B(b)	(0x1f + b)
+#define ADDRMAP_DISABLED_0F_B(b)	(0x0f + b)
+
+#define _ADDRMAP_VALUE(a,x,b)		(((a) - b) << (x * 8))
+
+/*
+ * Bx = internal base
+ * The selected HIF address bit for each address bit is determined
+ * by adding the internal base to the value of each field
+ * */
+
+#define ADDRMAP0_CS0_B6(v)	_ADDRMAP_VALUE(v, 0, 6)
+
+#define ADDRMAP1_BANK0_B2(v) 	_ADDRMAP_VALUE(v, 0, 2)
+#define ADDRMAP1_BANK1_B3(v)	_ADDRMAP_VALUE(v, 1, 3)
+#define ADDRMAP1_BANK2_B4(v)	_ADDRMAP_VALUE(v, 2, 4)
+
+#define ADDRMAP2_COL2_B2(v)	_ADDRMAP_VALUE(v, 0, 2)
+#define ADDRMAP2_COL3_B3(v)	_ADDRMAP_VALUE(v, 1, 3)
+#define ADDRMAP2_COL4_B4(v)	_ADDRMAP_VALUE(v, 2, 4)
+#define ADDRMAP2_COL5_B5(v)	_ADDRMAP_VALUE(v, 3, 5)
+
+#define ADDRMAP3_COL6_B6(v)	_ADDRMAP_VALUE(v, 0, 6)
+#define ADDRMAP3_COL7_B7(v)	_ADDRMAP_VALUE(v, 1, 7)
+#define ADDRMAP3_COL8_B8(v)	_ADDRMAP_VALUE(v, 2, 8)
+#define ADDRMAP3_COL9_B9(v)	_ADDRMAP_VALUE(v, 3, 9)
+
+#define ADDRMAP4_COL10_B10(v)	_ADDRMAP_VALUE(v, 0, 10)
+#define ADDRMAP4_COL11_B11(v)	_ADDRMAP_VALUE(v, 1, 11)
+
+#define ADDRMAP5_ROW0_B6(v)	_ADDRMAP_VALUE(v, 0, 6)
+#define ADDRMAP5_ROW1_B7(v)	_ADDRMAP_VALUE(v, 1, 7)
+#define ADDRMAP5_ROW2_10_B8(v)	_ADDRMAP_VALUE(v, 2, 8)
+#define ADDRMAP5_ROW11_B17(v)	_ADDRMAP_VALUE(v, 3, 17)
+
+#define ADDRMAP6_ROW12_B18(v)	_ADDRMAP_VALUE(v, 0, 18)
+#define ADDRMAP6_ROW13_B19(v)	_ADDRMAP_VALUE(v, 1, 19)
+#define ADDRMAP6_ROW14_B20(v)	_ADDRMAP_VALUE(v, 2, 20)
+#define ADDRMAP6_ROW15_B21(v)	_ADDRMAP_VALUE(v, 3, 21)
+
+#define ADDRMAP7_ROW16_B22(v)	_ADDRMAP_VALUE(v, 0, 22)
+#define ADDRMAP7_ROW17_B23(v)	_ADDRMAP_VALUE(v, 1, 23)
+
+#define ADDRMAP8_BG0_B2(v)	_ADDRMAP_VALUE(v, 0, 2)
+#define ADDRMAP8_BG1_B3(v)	_ADDRMAP_VALUE(v, 1, 3)
+
+/* These are only used if ADDRMAP5_ROW_BITS_2_10 = ADDRMAP_DISABLED_0F */
+#define ADDRMAP9_ROW2_B8(v)	_ADDRMAP_VALUE(v, 0, 8)
+#define ADDRMAP9_ROW3_B9(v)	_ADDRMAP_VALUE(v, 1, 9)
+#define ADDRMAP9_ROW4_B10(v)	_ADDRMAP_VALUE(v, 2, 10)
+#define ADDRMAP9_ROW5_B11(v)	_ADDRMAP_VALUE(v, 3, 11)
+
+#define ADDRMAP10_ROW6_B12(v)	_ADDRMAP_VALUE(v, 0, 12)
+#define ADDRMAP10_ROW7_B13(v)	_ADDRMAP_VALUE(v, 1, 13)
+#define ADDRMAP10_ROW8_B14(v)	_ADDRMAP_VALUE(v, 2, 14)
+#define ADDRMAP10_ROW9_B15(v)	_ADDRMAP_VALUE(v, 3, 15)
+
+#define ADDRMAP11_ROW10_B16(v)	_ADDRMAP_VALUE(v, 0, 16)
+
+struct dram_para {
+	uint32_t clk;
+	enum sunxi_dram_type type;
+	uint32_t dx_odt;
+	uint32_t dx_dri;
+	uint32_t ca_dri;
+	uint32_t para0;
+	uint32_t mr11;
+	uint32_t mr12;
+	uint32_t mr13;
+	uint32_t mr14;
+	uint32_t tpr1;
+	uint32_t tpr2;
+	uint32_t tpr3;
+	uint32_t tpr6;
+	uint32_t tpr10;
+	uint32_t tpr11;
+	uint32_t tpr12;
+	uint32_t tpr13;
+	uint32_t tpr14;
+};
+
+void mctl_set_timing_params(const struct dram_para *para);
+
+struct dram_config {
+	u8 cols;		/* Column bits */
+	u8 rows;		/* Row bits */
+	u8 ranks;		/* Rank bits (different from H616!) */
+	u8 banks;		/* Bank bits */
+	u8 bankgrps;		/* Bank group bits */
+	u8 bus_full_width;	/* 1 = x32, 0 = x16 */
+};
+
+#endif /* _SUNXI_DRAM_SUN50I_A133_H */
diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig
index c3718126cec..98b947f6e33 100644
--- a/arch/arm/mach-sunxi/Kconfig
+++ b/arch/arm/mach-sunxi/Kconfig
@@ -51,7 +51,13 @@ config DRAM_SUN50I_H616
 	  Select this dram controller driver for some sun50i platforms,
 	  like H616.
 
-if DRAM_SUN50I_H616
+config DRAM_SUN50I_A133
+	bool
+	help
+	  Select this dram controller driver for some sun50i platforms,
+	  like A133.
+
+if DRAM_SUN50I_H616 || DRAM_SUN50I_A133
 config DRAM_SUNXI_DX_ODT
 	hex "DRAM DX ODT parameter"
 	help
@@ -73,18 +79,64 @@ config DRAM_SUNXI_ODT_EN
 	help
 	  ODT EN value from vendor DRAM settings.
 
+config DRAM_SUNXI_PARA0
+	hex "DRAM PARA0 parameter"
+	depends on DRAM_SUN50I_A133
+	help
+	  PARA0 value from vendor DRAM settings.
+
+config DRAM_SUNXI_MR11
+	hex "DRAM MR11 parameter"
+	depends on DRAM_SUN50I_A133
+	default 0x0
+	help
+	  MR11 value from vendor DRAM settings.
+
+config DRAM_SUNXI_MR12
+	hex "DRAM MR12 parameter"
+	depends on DRAM_SUN50I_A133
+	default 0x0
+	help
+	  MR12 value from vendor DRAM settings.
+
+config DRAM_SUNXI_MR13
+	hex "DRAM MR13 parameter"
+	depends on DRAM_SUN50I_A133
+	default 0x0
+	help
+	  MR13 value from vendor DRAM settings.
+
+config DRAM_SUNXI_MR14
+	hex "DRAM MR14 parameter"
+	depends on DRAM_SUN50I_A133
+	default 0x0
+	help
+	  MR14 value from vendor DRAM settings.
+
 config DRAM_SUNXI_TPR0
 	hex "DRAM TPR0 parameter"
 	default 0x0
 	help
 	  TPR0 value from vendor DRAM settings.
 
+config DRAM_SUNXI_TPR1
+	hex "DRAM TPR1 parameter"
+	default 0x0
+	help
+	  TPR1 value from vendor DRAM settings.
+
 config DRAM_SUNXI_TPR2
 	hex "DRAM TPR2 parameter"
 	default 0x0
 	help
 	  TPR2 value from vendor DRAM settings.
 
+config DRAM_SUNXI_TPR3
+	hex "DRAM TPR3 parameter"
+	default 0x0
+	help
+	  TPR3 value from vendor DRAM settings.
+
 config DRAM_SUNXI_TPR6
 	hex "DRAM TPR6 parameter"
 	default 0x3300c080
@@ -109,6 +161,20 @@ config DRAM_SUNXI_TPR12
 	help
 	  TPR12 value from vendor DRAM settings.
 
+config DRAM_SUNXI_TPR13
+	hex "DRAM TPR13 parameter"
+	depends on DRAM_SUN50I_A133
+	default 0x0
+	help
+	  TPR13 value from vendor DRAM settings.
+
+config DRAM_SUNXI_TPR14
+	hex "DRAM TPR14 parameter"
+	depends on DRAM_SUN50I_A133
+	default 0x0
+	help
+	  TPR14 value from vendor DRAM settings.
+
 choice
 	prompt "DRAM PHY pin mapping selection"
 	default DRAM_SUNXI_PHY_ADDR_MAP_0
@@ -116,7 +182,8 @@ choice
 config DRAM_SUNXI_PHY_ADDR_MAP_0
 	bool "DRAM PHY address map 0"
 	help
-	  This pin mapping selection should be used by the H313, H616, H618.
+	  This pin mapping selection should be used by the H313, H616, H618,
+	  and A133, R818 SoCs.
 
 config DRAM_SUNXI_PHY_ADDR_MAP_1
 	bool "DRAM PHY address map 1"
@@ -500,7 +567,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 || DRAM_SUN50I_H6 || DRAM_SUN50I_H616
+if SUNXI_DRAM_DW || DRAM_SUN50I_H6 || DRAM_SUN50I_H616 || DRAM_SUN50I_A133
 config SUNXI_DRAM_DDR3
 	bool
 
@@ -513,6 +580,9 @@ config SUNXI_DRAM_LPDDR3
 config SUNXI_DRAM_LPDDR4
 	bool
 
+config SUNXI_DRAM_DDR4
+	bool
+
 choice
 	prompt "DRAM Type and Timing"
 	default SUNXI_DRAM_DDR3_1333 if !MACH_SUN8I_V3S
@@ -521,6 +591,7 @@ choice
 config SUNXI_DRAM_DDR3_1333
 	bool "DDR3 1333"
 	select SUNXI_DRAM_DDR3
+	depends on !DRAM_SUN50I_A133
 	---help---
 	This option is the original only supported memory type, which suits
 	many H3/H5/A64 boards available now.
@@ -528,6 +599,7 @@ config SUNXI_DRAM_DDR3_1333
 config SUNXI_DRAM_LPDDR3_STOCK
 	bool "LPDDR3 with Allwinner stock configuration"
 	select SUNXI_DRAM_LPDDR3
+	depends on !DRAM_SUN50I_A133
 	---help---
 	This option is the LPDDR3 timing used by the stock boot0 by
 	Allwinner.
@@ -551,7 +623,7 @@ config SUNXI_DRAM_H6_DDR3_1333
 config SUNXI_DRAM_H616_LPDDR3
 	bool "LPDDR3 DRAM chips on the H616 DRAM controller"
 	select SUNXI_DRAM_LPDDR3
-	depends on DRAM_SUN50I_H616
+	depends on DRAM_SUN50I_H616 || DRAM_SUN50I_A133
 	help
 	  This option is the LPDDR3 timing used by the stock boot0 by
 	  Allwinner.
@@ -559,7 +631,7 @@ config SUNXI_DRAM_H616_LPDDR3
 config SUNXI_DRAM_H616_LPDDR4
 	bool "LPDDR4 DRAM chips on the H616 DRAM controller"
 	select SUNXI_DRAM_LPDDR4
-	depends on DRAM_SUN50I_H616
+	depends on DRAM_SUN50I_H616 || DRAM_SUN50I_A133
 	help
 	  This option is the LPDDR4 timing used by the stock boot0 by
 	  Allwinner.
@@ -567,11 +639,27 @@ config SUNXI_DRAM_H616_LPDDR4
 config SUNXI_DRAM_H616_DDR3_1333
 	bool "DDR3-1333 boot0 timings on the H616 DRAM controller"
 	select SUNXI_DRAM_DDR3
-	depends on DRAM_SUN50I_H616
+	depends on DRAM_SUN50I_H616 || DRAM_SUN50I_A133
 	help
 	  This option is the DDR3 timing used by the boot0 on H616 TV boxes
 	  which use a DDR3-1333 timing.
 
+config SUNXI_DRAM_A133_DDR4
+	bool "DDR4 boot0 timings on the A133 DRAM controller"
+	select SUNXI_DRAM_DDR4
+	depends on DRAM_SUN50I_A133
+	help
+	  This option is the DDR4 timing used by the boot0 on A133 devices
+	  which use a DDR4 timing.
+
+config SUNXI_DRAM_A133_LPDDR4
+	bool "LPDDR4 boot0 timings on the A133 DRAM controller"
+	select SUNXI_DRAM_LPDDR4
+	depends on DRAM_SUN50I_A133
+	help
+	  This option is the LPDDR4 timing used by the boot0 on A133 devices
+	  which use an LPDDR4 timing.
+
 config SUNXI_DRAM_DDR2_V3S
 	bool "DDR2 found in V3s chip"
 	select SUNXI_DRAM_DDR2
@@ -599,7 +687,7 @@ config DRAM_CLK
 		       MACH_SUN8I_V3S
 	default 672 if MACH_SUN50I
 	default 744 if MACH_SUN50I_H6
-	default 720 if MACH_SUN50I_H616
+	default 720 if MACH_SUN50I_H616 || MACH_SUN50I_A133
 	---help---
 	Set the dram clock speed, valid range 240 - 480 (prior to sun9i),
 	must be a multiple of 24. For the sun9i (A80), the tested values
@@ -616,7 +704,7 @@ endif
 
 config DRAM_ZQ
 	int "sunxi dram zq value"
-	depends on !MACH_SUN50I_H616
+	depends on !MACH_SUN50I_H616 && !MACH_SUN50I_A133
 	default 123 if MACH_SUN4I || MACH_SUN5I || MACH_SUN6I || \
 		       MACH_SUN8I_A23 || MACH_SUN8I_A33 || MACH_SUN8I_A83T
 	default 127 if MACH_SUN7I
diff --git a/arch/arm/mach-sunxi/Makefile b/arch/arm/mach-sunxi/Makefile
index a33cd5b0f07..8eff20b77bf 100644
--- a/arch/arm/mach-sunxi/Makefile
+++ b/arch/arm/mach-sunxi/Makefile
@@ -45,4 +45,6 @@ obj-$(CONFIG_DRAM_SUN50I_H6)	+= dram_sun50i_h6.o dram_dw_helpers.o
 obj-$(CONFIG_DRAM_SUN50I_H6)	+= dram_timings/
 obj-$(CONFIG_DRAM_SUN50I_H616)	+= dram_sun50i_h616.o dram_dw_helpers.o
 obj-$(CONFIG_DRAM_SUN50I_H616)	+= dram_timings/
+obj-$(CONFIG_DRAM_SUN50I_A133)	+= dram_sun50i_a133.o
+obj-$(CONFIG_DRAM_SUN50I_A133)	+= dram_timings/
 endif
diff --git a/arch/arm/mach-sunxi/dram_sun50i_a133.c b/arch/arm/mach-sunxi/dram_sun50i_a133.c
new file mode 100644
index 00000000000..a0fca3738f4
--- /dev/null
+++ b/arch/arm/mach-sunxi/dram_sun50i_a133.c
@@ -0,0 +1,1204 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * sun50i A133 platform dram controller driver
+ *
+ * Controller and PHY appear to be quite similar to that of the H616;
+ * however certain offsets, timings, and other details are different enough that
+ * the original code does not work as expected. Some device flags and
+ * calibrations are not yet implemented, and configuration aside from DDR4
+ * have not been tested.
+ *
+ * (C) Copyright 2024 MasterR3C0RD <masterr3c0rd@epochal.quest>
+ *
+ * Uses code from H616 driver, which is
+ * (C) Copyright 2020 Jernej Skrabec <jernej.skrabec@siol.net>
+ *
+ */
+
+//#define DEBUG
+
+#include <asm/arch/clock.h>
+#include <asm/arch/cpu.h>
+#include <asm/arch/dram.h>
+#include <asm/arch/prcm.h>
+#include <asm/io.h>
+#include <init.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <log.h>
+
+#ifdef CONFIG_DRAM_SUNXI_PHY_ADDR_MAP_1
+static const u8 phy_init[] = {
+#ifdef CONFIG_SUNXI_DRAM_DDR3
+	0x0c, 0x08, 0x19, 0x18, 0x10, 0x06, 0x0a, 0x03, 0x0e,
+	0x00, 0x0b, 0x05, 0x09, 0x1a, 0x04, 0x13, 0x16, 0x11,
+	0x01, 0x15, 0x0d, 0x07, 0x12, 0x17, 0x14, 0x02, 0x0f
+#elif CONFIG_SUNXI_DRAM_DDR4
+	0x19, 0x1a, 0x04, 0x12, 0x09, 0x06, 0x08, 0x0a, 0x16,
+	0x17, 0x18, 0x0f, 0x0c, 0x13, 0x02, 0x05, 0x01, 0x11,
+	0x0e, 0x00, 0x0b, 0x07, 0x03, 0x14, 0x15, 0x0d, 0x10
+#elif CONFIG_SUNXI_DRAM_LPDDR3
+	0x08, 0x03, 0x02, 0x00, 0x18, 0x19, 0x09, 0x01, 0x06,
+	0x17, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+	0x12, 0x13, 0x14, 0x15, 0x16, 0x04, 0x05, 0x07, 0x1a
+#elif CONFIG_SUNXI_DRAM_LPDDR4
+	0x01, 0x05, 0x02, 0x00, 0x19, 0x03, 0x06, 0x07, 0x08,
+	0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+	0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x04, 0x1a
+#endif
+};
+#else
+static const u8 phy_init[] = {
+#ifdef CONFIG_SUNXI_DRAM_DDR3
+	0x03, 0x19, 0x18, 0x02, 0x10, 0x15, 0x16, 0x07, 0x06,
+	0x0e, 0x05, 0x08, 0x0d, 0x04, 0x17, 0x1a, 0x13, 0x11,
+	0x12, 0x14, 0x00, 0x01, 0x0c, 0x0a, 0x09, 0x0b, 0x0f
+#elif CONFIG_SUNXI_DRAM_DDR4
+	0x13, 0x17, 0x0e, 0x01, 0x06, 0x12, 0x14, 0x07, 0x09,
+	0x02, 0x0f, 0x00, 0x0d, 0x05, 0x16, 0x0c, 0x0a, 0x11,
+	0x04, 0x03, 0x18, 0x15, 0x08, 0x10, 0x0b, 0x19, 0x1a
+#elif CONFIG_SUNXI_DRAM_LPDDR3
+	0x05, 0x06, 0x17, 0x02, 0x19, 0x18, 0x04, 0x07, 0x03,
+	0x01, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+	0x12, 0x13, 0x14, 0x15, 0x16, 0x08, 0x09, 0x00, 0x1a
+#elif CONFIG_SUNXI_DRAM_LPDDR4
+	0x01, 0x03, 0x02, 0x19, 0x17, 0x00, 0x06, 0x07, 0x08,
+	0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+	0x12, 0x13, 0x14, 0x15, 0x16, 0x04, 0x18, 0x05, 0x1a
+#endif
+};
+#endif
+
+static void mctl_clk_init(u32 clk)
+{
+	void * const ccm = (void *)SUNXI_CCM_BASE;
+
+	/* Place all DRAM blocks into reset */
+	clrbits_le32(ccm + CCU_H6_MBUS_CFG, MBUS_ENABLE);
+	clrbits_le32(ccm + CCU_H6_MBUS_CFG, MBUS_RESET);
+	clrbits_le32(ccm + CCU_H6_DRAM_GATE_RESET, BIT(GATE_SHIFT));
+	clrbits_le32(ccm + CCU_H6_DRAM_GATE_RESET, BIT(RESET_SHIFT));
+	clrbits_le32(ccm + CCU_H6_PLL5_CFG, CCM_PLL5_CTRL_EN);
+	clrbits_le32(ccm + CCU_H6_DRAM_CLK_CFG, DRAM_MOD_RESET);
+	udelay(5);
+
+	/* Set up PLL5 clock, used for DRAM */
+	clrsetbits_le32(ccm + CCU_H6_PLL5_CFG, 0xff03,
+			CCM_PLL5_CTRL_N((clk * 2) / 24) | CCM_PLL5_CTRL_EN);
+	setbits_le32(ccm + CCU_H6_PLL5_CFG, BIT(24));
+	clrsetbits_le32(ccm + CCU_H6_PLL5_CFG, 0x3,
+			CCM_PLL5_LOCK_EN | CCM_PLL5_CTRL_EN | BIT(30));
+	clrbits_le32(ccm + CCU_H6_PLL5_CFG, 0x3 | BIT(30));
+	mctl_await_completion(ccm + CCU_H6_PLL5_CFG,
+			      CCM_PLL5_LOCK, CCM_PLL5_LOCK);
+
+	/* Enable DRAM clock and gate*/
+	clrbits_le32(ccm + CCU_H6_DRAM_CLK_CFG, BIT(24) | BIT(25));
+	clrsetbits_le32(ccm + CCU_H6_DRAM_CLK_CFG, 0x1f, BIT(1) | BIT(0));
+	setbits_le32(ccm + CCU_H6_DRAM_CLK_CFG, DRAM_CLK_UPDATE);
+	setbits_le32(ccm + CCU_H6_DRAM_GATE_RESET, BIT(RESET_SHIFT));
+	setbits_le32(ccm + CCU_H6_DRAM_GATE_RESET, BIT(GATE_SHIFT));
+
+	/* Re-enable MBUS and reset the DRAM module */
+	setbits_le32(ccm + CCU_H6_MBUS_CFG, MBUS_RESET);
+	setbits_le32(ccm + CCU_H6_MBUS_CFG, MBUS_ENABLE);
+	setbits_le32(ccm + CCU_H6_DRAM_CLK_CFG, DRAM_MOD_RESET);
+	udelay(5);
+}
+
+static void mctl_set_odtmap(const struct dram_para *para,
+			    const struct dram_config *config)
+{
+	struct sunxi_mctl_ctl_reg *mctl_ctl =
+		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
+
+	u32 val, temp1, temp2;
+
+	/* Set ODT/rank mappings*/
+	if (config->bus_full_width)
+		writel_relaxed(0x0201, &mctl_ctl->odtmap);
+	else
+		writel_relaxed(0x0303, &mctl_ctl->odtmap);
+
+	switch (para->type) {
+	case SUNXI_DRAM_TYPE_DDR3:
+		val = 0x06000400;
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR3:
+		/* TODO: What's the purpose of these values? */
+		temp1 = para->clk * 7 / 2000;
+		if (para->clk < 400)
+			temp2 = 0x3;
+		else
+			temp2 = 0x4;
+
+		val = 0x400 | (temp2 - temp1) << 16 | temp1 << 24;
+		break;
+	case SUNXI_DRAM_TYPE_DDR4:
+		/* MR4: CS to CMD / ADDR Latency   and  write preamble */
+		val = 0x400 | (0x000 << 10 & 0x70000) |
+		      (((0x0000 >> 12) & 1) + 6) << 24;
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR4:
+		val = 0x4000400;
+		break;
+	}
+
+	writel_relaxed(val, &mctl_ctl->odtcfg);
+	/* Documented as ODTCFG_SHADOW */
+	writel_relaxed(val, &mctl_ctl->unk_0x2240);
+	/* Offset's interesting; additional undocumented shadows? */
+	writel_relaxed(val, &mctl_ctl->unk_0x3240);
+	writel_relaxed(val, &mctl_ctl->unk_0x4240);
+}
+
+/*
+ * This function produces address mapping parameters, used internally by the
+ * controller to map address lines to HIF addresses. HIF addresses are word
+ * addresses, not byte addresses;
+ * In other words, DDR address 0x400 maps to HIF address 0x100.
+ *
+ * This implementation sets up a reasonable mapping where HIF address
+ * ordering (LSB->MSB) is as such:
+ * - Bank Groups
+ * - Columns
+ * - Banks
+ * - Rows
+ * - Ranks
+ *
+ * TODO: Handle 1.5GB + 3GB configurations. Info about these is stored in
+ * upper bits of TPR13 after autoscan in boot0, and then some extra logic
+ * happens in the address mapping
+ */
+#define INITIAL_HIF_OFFSET 3
+
+static void mctl_set_addrmap(const struct dram_config *config)
+{
+	struct sunxi_mctl_ctl_reg *mctl_ctl =
+		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
+
+	u8 bankgrp_bits = config->bankgrps;
+	u8 col_bits = config->cols;
+	u8 bank_bits = config->banks;
+	u8 row_bits = config->rows;
+	u8 rank_bits = config->ranks;
+
+	unsigned int i, hif_offset, hif_bits[6];
+
+	/*
+	 * When the bus is half width, we need to adjust address mapping,
+	 * as COL[0] will be reallocated as part of the byte address,
+	 * offsetting the column address mapping values by 1
+	 */
+	if (!config->bus_full_width)
+		col_bits--;
+
+	/* Match boot0's DRAM requirements */
+	if (bankgrp_bits > 2)
+		panic("invalid dram configuration (bankgrps_bits = %d)",
+		      bankgrp_bits);
+	if (col_bits < 8 || col_bits > 12)
+		panic("invalid dram configuration (col_bits = %d)", col_bits);
+
+	if (bank_bits < 2 || bank_bits > 3)
+		panic("invalid dram configuration (bank_bits = %d)", bank_bits);
+
+	if (row_bits < 14 || row_bits > 18)
+		panic("invalid dram configuration (row_bits = %d)", row_bits);
+
+	if (rank_bits > 1)
+		panic("invalid dram configuration (rank_bits = %d)", rank_bits);
+
+	/*
+	 * Col[0:1] + HIF[0:1] (hardwired), Col[2] = HIF[2] (required)
+	 * Thus, we start allocating from HIF[3] onwards
+	 */
+	hif_offset = INITIAL_HIF_OFFSET;
+
+	/* BG[bankgrp_bits:0] = HIF[3 + bankgrp_bits:3]*/
+	switch (bankgrp_bits) {
+	case 0:
+		writel_relaxed(ADDRMAP8_BG0_B2(ADDRMAP_DISABLED_1F_B(2)) |
+			       ADDRMAP8_BG1_B3(ADDRMAP_DISABLED_1F_B(3)),
+			&mctl_ctl->addrmap[8]);
+		break;
+	case 1:
+		writel_relaxed(ADDRMAP8_BG0_B2(hif_offset) |
+			       ADDRMAP8_BG1_B3(ADDRMAP_DISABLED_1F_B(3)),
+			&mctl_ctl->addrmap[8]);
+		break;
+	case 2:
+		writel_relaxed(ADDRMAP8_BG0_B2(hif_offset) |
+			       ADDRMAP8_BG1_B3(hif_offset + 1),
+			       &mctl_ctl->addrmap[8]);
+		break;
+	default:
+		panic("invalid dram configuration (bankgrp_bits = %d)",
+		      bankgrp_bits);
+	}
+
+	hif_offset += bankgrp_bits;
+
+	/* Col[2] = HIF[2], Col[5:3] = HIF[offset + 2:offset] */
+	writel_relaxed(ADDRMAP2_COL2_B2(2) | ADDRMAP2_COL3_B3(hif_offset) |
+		       ADDRMAP2_COL4_B4(hif_offset + 1) |
+		       ADDRMAP2_COL5_B5(hif_offset + 2),
+		       &mctl_ctl->addrmap[2]);
+
+	/* Col[col_bits:6] = HIF[col_bits + offset - 3:offset - 3] */
+	for (i = 6; i < 12; i++) {
+		if (i < col_bits)
+			hif_bits[i - 6] = hif_offset + (i - INITIAL_HIF_OFFSET);
+		else
+			hif_bits[i - 6] = ADDRMAP_DISABLED_1F_B(i);
+	}
+
+	writel_relaxed(ADDRMAP3_COL6_B6(hif_bits[0]) |
+		       ADDRMAP3_COL7_B7(hif_bits[1]) |
+		       ADDRMAP3_COL8_B8(hif_bits[2]) |
+		       ADDRMAP3_COL9_B9(hif_bits[3]),
+		       &mctl_ctl->addrmap[3]);
+
+	writel_relaxed(ADDRMAP4_COL10_B10(hif_bits[4]) |
+		       ADDRMAP4_COL11_B11(hif_bits[5]),
+		       &mctl_ctl->addrmap[4]);
+
+	hif_offset = bankgrp_bits + col_bits;
+
+	/* Bank[bank_bits:0] = HIF[bank_bits + offset:offset] */
+	if (bank_bits == 3)
+		writel_relaxed(ADDRMAP1_BANK0_B2(hif_offset) |
+			       ADDRMAP1_BANK1_B3(hif_offset + 1) |
+			       ADDRMAP1_BANK2_B4(hif_offset + 2),
+			       &mctl_ctl->addrmap[1]);
+	else
+		writel_relaxed(ADDRMAP1_BANK0_B2(hif_offset) |
+			       ADDRMAP1_BANK1_B3(hif_offset + 1) |
+			       ADDRMAP1_BANK2_B4(ADDRMAP_DISABLED_1F_B(4)),
+			       &mctl_ctl->addrmap[1]);
+
+	hif_offset += bank_bits;
+
+	/* Row[11:0] = HIF[11 + offset:offset] */
+	writel_relaxed(ADDRMAP5_ROW0_B6(hif_offset) |
+		       ADDRMAP5_ROW1_B7(hif_offset + 1) |
+		       ADDRMAP5_ROW2_10_B8(hif_offset + 2) |
+		       ADDRMAP5_ROW11_B17(hif_offset + 11),
+		       &mctl_ctl->addrmap[5]);
+
+	/*
+	 * There's some complexity here because of a special case
+	 * in boot0 code that appears to work around a hardware bug.
+	 * For (col_bits, row_bits, rank_bits) = (10, 16, 1), we have to
+	 * place CS[0] in the position we would normally place ROW[14],
+	 * and shift ROW[14] and ROW[15] over by one. Using the bit following
+	 * ROW[15], as would be standard here, seems to cause nonsensical
+	 * aliasing patterns.
+	 *
+	 * Aside from this case, mapping is simple:
+	 * Row[row_bits:12] = HIF[offset + row_bits:offset + 12]
+	 */
+	for (i = 12; i < 18; i++) {
+		if (i >= row_bits)
+			hif_bits[i - 12] = ADDRMAP_DISABLED_0F_B(6 + i);
+		else if (rank_bits != 1 || col_bits != 10 || row_bits != 16 ||
+			 i < 14)
+			hif_bits[i - 12] = hif_offset + i;
+		else
+			hif_bits[i - 12] = hif_offset + i + 1;
+	}
+
+	writel_relaxed(ADDRMAP6_ROW12_B18(hif_bits[0]) |
+		       ADDRMAP6_ROW13_B19(hif_bits[1]) |
+		       ADDRMAP6_ROW14_B20(hif_bits[2]) |
+		       ADDRMAP6_ROW15_B21(hif_bits[3]),
+		       &mctl_ctl->addrmap[6]);
+
+	writel_relaxed(ADDRMAP7_ROW16_B22(hif_bits[4]) |
+		       ADDRMAP7_ROW17_B23(hif_bits[5]),
+		       &mctl_ctl->addrmap[7]);
+
+	hif_offset += row_bits;
+
+	/*
+	 * Ranks
+	 * Most cases: CS[0] = HIF[offset]
+	 * Special case (see above): CS[0] = HIF[offset - 2]
+	 */
+	if (rank_bits == 0)
+		writel_relaxed(ADDRMAP0_CS0_B6(ADDRMAP_DISABLED_1F_B(6)),
+			       &mctl_ctl->addrmap[0]);
+	else if (col_bits == 10 && row_bits == 16)
+		writel_relaxed(ADDRMAP0_CS0_B6(hif_offset - 2),
+			       &mctl_ctl->addrmap[0]);
+	else
+		writel_relaxed(ADDRMAP0_CS0_B6(hif_offset),
+			       &mctl_ctl->addrmap[0]);
+}
+
+static void mctl_com_init(const struct dram_para *para,
+			  const struct dram_config *config)
+{
+	void *const mctl_com = (void *)SUNXI_DRAM_COM_BASE;
+	struct sunxi_mctl_ctl_reg *mctl_ctl =
+		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
+
+	/* Might control power/reset of DDR-related blocks */
+	clrsetbits_le32(mctl_com + MCTL_COM_UNK_008, BIT(24), BIT(25) | BIT(9));
+
+	/* Unlock mctl_ctl registers */
+	setbits_le32(mctl_com + MCTL_COM_MAER0, BIT(15));
+
+	if (para->type == SUNXI_DRAM_TYPE_LPDDR4)
+		setbits_le32(0x03102ea8, BIT(0));
+
+	clrsetbits_le32(&mctl_ctl->sched[0], 0xff << 8, 0x30 << 8);
+	if (!(para->tpr13 & BIT(28)))
+		clrsetbits_le32(&mctl_ctl->sched[0], 0xf, BIT(0));
+
+	writel_relaxed(0, &mctl_ctl->hwlpctl);
+
+	/* Master settings */
+	u32 mstr_value = MSTR_DEVICECONFIG_X32 |
+			 MSTR_ACTIVE_RANKS(config->ranks);
+
+	if (config->bus_full_width)
+		mstr_value |= MSTR_BUSWIDTH_FULL;
+	else
+		mstr_value |= MSTR_BUSWIDTH_HALF;
+
+	/*
+	 * Geardown and 2T mode are always enabled here, but is controlled by a flag in boot0;
+	 * it has not been a problem so far, but may be suspect if a particular board isn't booting.
+	 */
+	switch (para->type) {
+	case SUNXI_DRAM_TYPE_DDR3:
+		mstr_value |= MSTR_DEVICETYPE_DDR3 | MSTR_BURST_LENGTH(8) |
+			      MSTR_2TMODE;
+		break;
+	case SUNXI_DRAM_TYPE_DDR4:
+		mstr_value |= MSTR_DEVICETYPE_DDR4 | MSTR_BURST_LENGTH(8) |
+			      MSTR_GEARDOWNMODE | MSTR_2TMODE;
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR3:
+		mstr_value |= MSTR_DEVICETYPE_LPDDR3 | MSTR_BURST_LENGTH(8);
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR4:
+		mstr_value |= MSTR_DEVICETYPE_LPDDR4 | MSTR_BURST_LENGTH(16);
+		break;
+	}
+
+	writel_relaxed(mstr_value, &mctl_ctl->mstr);
+
+	mctl_set_odtmap(para, config);
+	mctl_set_addrmap(config);
+	mctl_set_timing_params(para);
+
+	dsb();
+	writel(0, &mctl_ctl->pwrctl);
+
+	/* Disable automatic controller updates + automatic controller update requests */
+	setbits_le32(&mctl_ctl->dfiupd[0], BIT(31) | BIT(30));
+	setbits_le32(&mctl_ctl->zqctl[0], BIT(31) | BIT(30));
+	setbits_le32(&mctl_ctl->unk_0x2180, BIT(31) | BIT(30));
+	setbits_le32(&mctl_ctl->unk_0x3180, BIT(31) | BIT(30));
+	setbits_le32(&mctl_ctl->unk_0x4180, BIT(31) | BIT(30));
+
+	/*
+	 * Data bus inversion
+	 * Controlled by a flag in boot0, enabled by default here.
+	 */
+	if (para->type == SUNXI_DRAM_TYPE_DDR4 ||
+	    para->type == SUNXI_DRAM_TYPE_LPDDR4)
+		setbits_le32(&mctl_ctl->dbictl, BIT(2));
+}
+
+static void mctl_drive_odt_config(const struct dram_para *para)
+{
+	u32 val;
+	u64 base;
+	u32 i;
+
+	/* DX drive */
+	for (i = 0; i < 4; i++) {
+		base = SUNXI_DRAM_PHY0_BASE + 0x388 + 0x40 * i;
+		val = (para->dx_dri >> (i * 8)) & 0x1f;
+
+		writel_relaxed(val, base);
+		if (para->type == SUNXI_DRAM_TYPE_LPDDR4) {
+			if (para->tpr3 & 0x1f1f1f1f)
+				val = (para->tpr3 >> (i * 8)) & 0x1f;
+			else
+				val = 4;
+		}
+		writel_relaxed(val, base + 4);
+	}
+
+	/* CA drive */
+	for (i = 0; i < 2; i++) {
+		base = SUNXI_DRAM_PHY0_BASE + 0x340 + 0x8 * i;
+		val = (para->ca_dri >> (i * 8)) & 0x1f;
+
+		writel_relaxed(val, base);
+		writel_relaxed(val, base + 4);
+	}
+
+	/* DX ODT */
+	for (i = 0; i < 4; i++) {
+		base = SUNXI_DRAM_PHY0_BASE + 0x380 + 0x40 * i;
+		val = (para->dx_odt >> (i * 8)) & 0x1f;
+
+		if (para->type == SUNXI_DRAM_TYPE_DDR4 ||
+		    para->type == SUNXI_DRAM_TYPE_LPDDR3)
+			writel_relaxed(0, base);
+		else
+			writel_relaxed(val, base);
+
+		if (para->type == SUNXI_DRAM_TYPE_LPDDR4)
+			writel_relaxed(0, base + 4);
+		else
+			writel_relaxed(val, base + 4);
+	}
+	dsb();
+}
+
+static void mctl_phy_ca_bit_delay_compensation(const struct dram_para *para)
+{
+	u32 val, i;
+	u32 *ptr;
+
+	if (para->tpr10 & BIT(31)) {
+		val = para->tpr2;
+	} else {
+		val = ((para->tpr10 << 1) & 0x1e) |
+		      ((para->tpr10 << 5) & 0x1e00) |
+		      ((para->tpr10 << 9) & 0x1e0000) |
+		      ((para->tpr10 << 13) & 0x1e000000);
+
+		if (para->tpr10 >> 29 != 0)
+			val <<= 1;
+	}
+
+	ptr = (u32 *)(SUNXI_DRAM_PHY0_BASE + 0x780);
+	for (i = 0; i < 32; i++)
+		writel_relaxed((val >> 8) & 0x3f, &ptr[i]);
+
+	writel_relaxed(val & 0x3f, SUNXI_DRAM_PHY0_BASE + 0x7dc);
+	writel_relaxed(val & 0x3f, SUNXI_DRAM_PHY0_BASE + 0x7e0);
+
+	switch (para->type) {
+	case SUNXI_DRAM_TYPE_DDR3:
+		writel_relaxed((val >> 16) & 0x3f,
+			       SUNXI_DRAM_PHY0_BASE + 0x7b8);
+		writel_relaxed((val >> 24) & 0x3f,
+			       SUNXI_DRAM_PHY0_BASE + 0x784);
+		break;
+	case SUNXI_DRAM_TYPE_DDR4:
+		writel_relaxed((val >> 16) & 0x3f,
+			       SUNXI_DRAM_PHY0_BASE + 0x784);
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR3:
+		writel_relaxed((val >> 16) & 0x3f,
+			       SUNXI_DRAM_PHY0_BASE + 0x788);
+		writel_relaxed((val >> 24) & 0x3f,
+			       SUNXI_DRAM_PHY0_BASE + 0x790);
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR4:
+		writel_relaxed((val >> 16) & 0x3f,
+			       SUNXI_DRAM_PHY0_BASE + 0x790);
+		writel_relaxed((val >> 24) & 0x3f,
+			       SUNXI_DRAM_PHY0_BASE + 0x78c);
+		break;
+	}
+
+	dsb();
+}
+
+static void mctl_phy_init(const struct dram_para *para,
+			  const struct dram_config *config)
+{
+	struct sunxi_mctl_ctl_reg *mctl_ctl =
+		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
+	void *const prcm = (void *)SUNXI_PRCM_BASE;
+	void *const mctl_com = (void *)SUNXI_DRAM_COM_BASE;
+
+	u32 val, val2, i;
+	u32 *ptr;
+
+	/* Disable auto refresh. */
+	setbits_le32(&mctl_ctl->rfshctl3, BIT(0));
+
+	/* Set "phy_dbi_mode" to mark the DFI as implementing DBI functionality */
+	writel_relaxed(0, &mctl_ctl->pwrctl);
+	clrbits_le32(&mctl_ctl->dfimisc, 1);
+	writel_relaxed(0x20, &mctl_ctl->pwrctl);
+
+	/* PHY cold reset */
+	clrsetbits_le32(mctl_com + MCTL_COM_UNK_008, BIT(24), BIT(9));
+	udelay(1);
+	setbits_le32(mctl_com + MCTL_COM_UNK_008, BIT(24));
+
+	/* Not sure what this gates the power of. */
+	clrbits_le32(prcm + CCU_PRCM_SYS_PWROFF_GATING, BIT(4));
+
+	if (para->type == SUNXI_DRAM_TYPE_LPDDR4)
+		clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x4, BIT(7));
+
+	/* Note: Similar enumeration of values is used during read training */
+	if (config->bus_full_width)
+		val = 0xf;
+	else
+		val = 0x3;
+
+	clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 0x3c, 0xf, val);
+
+	switch (para->type) {
+	case SUNXI_DRAM_TYPE_DDR3:
+		val = 13;
+		val2 = 9;
+		break;
+	case SUNXI_DRAM_TYPE_DDR4:
+		val = 13;
+		val2 = 10;
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR3:
+		val = 14;
+		val2 = 8;
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR4:
+		if (para->tpr13 & BIT(28))
+			val = 22;
+		else
+			val = 20;
+
+		val2 = 10;
+		break;
+	}
+
+	writel_relaxed(val, SUNXI_DRAM_PHY0_BASE + 0x14);
+	writel_relaxed(val, SUNXI_DRAM_PHY0_BASE + 0x35c);
+	writel_relaxed(val, SUNXI_DRAM_PHY0_BASE + 0x368);
+	writel_relaxed(val, SUNXI_DRAM_PHY0_BASE + 0x374);
+	writel_relaxed(0, SUNXI_DRAM_PHY0_BASE + 0x18);
+	writel_relaxed(0, SUNXI_DRAM_PHY0_BASE + 0x360);
+	writel_relaxed(0, SUNXI_DRAM_PHY0_BASE + 0x36c);
+	writel_relaxed(0, SUNXI_DRAM_PHY0_BASE + 0x378);
+	writel_relaxed(val2, SUNXI_DRAM_PHY0_BASE + 0x1c);
+	writel_relaxed(val2, SUNXI_DRAM_PHY0_BASE + 0x364);
+	writel_relaxed(val2, SUNXI_DRAM_PHY0_BASE + 0x370);
+	writel_relaxed(val2, SUNXI_DRAM_PHY0_BASE + 0x37c);
+
+	/* Set up SDQ swizzle */
+	ptr = (u32 *)(SUNXI_DRAM_PHY0_BASE + 0xc0);
+	for (i = 0; i < ARRAY_SIZE(phy_init); i++)
+		writel_relaxed(phy_init[i], &ptr[i]);
+
+	/* Set VREF */
+	val = 0;
+	switch (para->type) {
+	case SUNXI_DRAM_TYPE_DDR3:
+		val = para->tpr6 & 0xff;
+		if (val == 0)
+			val = 0x80;
+		break;
+	case SUNXI_DRAM_TYPE_DDR4:
+		val = (para->tpr6 >> 8) & 0xff;
+		if (val == 0)
+			val = 0x80;
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR3:
+		val = (para->tpr6 >> 16) & 0xff;
+		if (val == 0)
+			val = 0x80;
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR4:
+		val = (para->tpr6 >> 24) & 0xff;
+		if (val == 0)
+			val = 0x33;
+		break;
+	}
+	writel_relaxed(val, SUNXI_DRAM_PHY0_BASE + 0x3dc);
+	writel_relaxed(val, SUNXI_DRAM_PHY0_BASE + 0x45c);
+
+	mctl_drive_odt_config(para);
+
+	if (para->tpr10 & TPR10_CA_BIT_DELAY)
+		mctl_phy_ca_bit_delay_compensation(para);
+
+	switch (para->type) {
+	case SUNXI_DRAM_TYPE_DDR3:
+		val = 2;
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR3:
+		val = 3;
+		break;
+	case SUNXI_DRAM_TYPE_DDR4:
+		val = 4;
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR4:
+		val = 5;
+		break;
+	}
+
+	clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 0x4, 0x7, val | 8);
+
+	if (para->clk <= 672)
+		writel_relaxed(0xf, SUNXI_DRAM_PHY0_BASE + 0x20);
+
+	if (para->clk > 500) {
+		val = 0;
+		val2 = 0;
+	} else {
+		val = 0x80;
+		val2 = 0x20;
+	}
+
+	clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 0x144, 0x80, val);
+	clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 0x14c, 0xe0, val2);
+
+	dsb();
+	clrbits_le32(mctl_com + MCTL_COM_UNK_008, BIT(9));
+	udelay(1);
+	clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x14c, BIT(3));
+
+	mctl_await_completion((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x180), BIT(2),
+			      BIT(2));
+
+	/*
+	 * This delay is controlled by a tpr13 flag in boot0; doesn't hurt
+	 * to always do it though.
+	 */
+	udelay(1000);
+	writel(0x37, SUNXI_DRAM_PHY0_BASE + 0x58);
+
+	setbits_le32(prcm + CCU_PRCM_SYS_PWROFF_GATING, BIT(4));
+}
+
+/* Helpers for updating mode registers */
+static inline void mctl_mr_write(u32 mrctrl0, u32 mrctrl1)
+{
+	struct sunxi_mctl_ctl_reg *mctl_ctl =
+		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
+
+	writel(mrctrl1, &mctl_ctl->mrctrl1);
+	writel(mrctrl0 | MRCTRL0_MR_WR | MRCTRL0_MR_RANKS_ALL,
+	       &mctl_ctl->mrctrl0);
+	mctl_await_completion(&mctl_ctl->mrctrl0, MRCTRL0_MR_WR, 0);
+}
+
+static inline void mctl_mr_write_lpddr4(u8 addr, u8 value)
+{
+	mctl_mr_write(0, MRCTRL1_MR_ADDR(addr) | MRCTRL1_MR_DATA(value));
+}
+
+static inline void mctl_mr_write_lpddr3(u8 addr, u8 value)
+{
+	/* Bit [7:6] are set by boot0, but undocumented */
+	mctl_mr_write(BIT(6) | BIT(7),
+		      MRCTRL1_MR_ADDR(addr) | MRCTRL1_MR_DATA(value));
+}
+
+static void mctl_dfi_init(const struct dram_para *para)
+{
+	void *const mctl_com = (void *)SUNXI_DRAM_COM_BASE;
+	struct sunxi_mctl_ctl_reg *mctl_ctl =
+		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
+
+	/* Unlock DFI registers? */
+	setbits_le32(mctl_com + MCTL_COM_MAER0, BIT(8));
+
+	/* Enable dfi_init_complete signal and trigger PHY init start request */
+	writel_relaxed(0, &mctl_ctl->swctl);
+	setbits_le32(&mctl_ctl->dfimisc, BIT(0));
+	setbits_le32(&mctl_ctl->dfimisc, BIT(5));
+	writel_relaxed(1, &mctl_ctl->swctl);
+	mctl_await_completion(&mctl_ctl->swstat, BIT(0), BIT(0));
+
+	/* Stop sending init request and wait for DFI initialization to complete. */
+	writel_relaxed(0, &mctl_ctl->swctl);
+	clrbits_le32(&mctl_ctl->dfimisc, BIT(5));
+	writel_relaxed(1, &mctl_ctl->swctl);
+	mctl_await_completion(&mctl_ctl->swstat, BIT(0), BIT(0));
+	mctl_await_completion(&mctl_ctl->dfistat, BIT(0), BIT(0));
+
+	/* Enter Software Exit from Self Refresh */
+	writel_relaxed(0, &mctl_ctl->swctl);
+	clrbits_le32(&mctl_ctl->pwrctl, BIT(5));
+	writel_relaxed(1, &mctl_ctl->swctl);
+	mctl_await_completion(&mctl_ctl->swstat, BIT(0), BIT(0));
+	mctl_await_completion(&mctl_ctl->statr, 0x3, 1);
+
+	udelay(200);
+
+	/* Disable dfi_init_complete signal */
+	writel_relaxed(0, &mctl_ctl->swctl);
+	clrbits_le32(&mctl_ctl->dfimisc, BIT(0));
+	writel_relaxed(1, &mctl_ctl->swctl);
+	mctl_await_completion(&mctl_ctl->swstat, BIT(0), BIT(0));
+
+	/* Write mode registers, fixed in the JEDEC spec */
+	switch (para->type) {
+	case SUNXI_DRAM_TYPE_DDR3:
+		mctl_mr_write(MRCTRL0_MR_ADDR(0), 0x1c70);	/* MR0 */
+		/*
+		 * outbuf en, TDQs dis, write leveling dis, out drv 40 Ohms,
+		 * DLL en, Rtt_nom 120 Ohms
+		 */
+		mctl_mr_write(MRCTRL0_MR_ADDR(1), 0x40);	/* MR1 */
+		/*
+		 * full array self-ref, CAS: 8 cyc, SRT w/ norm temp range,
+		 * dynamic ODT off
+		 */
+		mctl_mr_write(MRCTRL0_MR_ADDR(2), 0x18);	/* MR2 */
+		/* predef MPR pattern */
+		mctl_mr_write(MRCTRL0_MR_ADDR(3), 0);		/* MR3 */
+		break;
+	case SUNXI_DRAM_TYPE_DDR4:
+		mctl_mr_write(MRCTRL0_MR_ADDR(0), 0x840);
+		mctl_mr_write(MRCTRL0_MR_ADDR(1), 0x601);
+		mctl_mr_write(MRCTRL0_MR_ADDR(2), 0x8);
+		mctl_mr_write(MRCTRL0_MR_ADDR(3), 0);
+		mctl_mr_write(MRCTRL0_MR_ADDR(4), 0);
+		mctl_mr_write(MRCTRL0_MR_ADDR(5), 0x400);
+
+		mctl_mr_write(MRCTRL0_MR_ADDR(6), 0x862 | BIT(7));
+		mctl_mr_write(MRCTRL0_MR_ADDR(6), 0x862 | BIT(7));
+		mctl_mr_write(MRCTRL0_MR_ADDR(6), 0x862 & (~BIT(7)));
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR3:
+		mctl_mr_write_lpddr3(1, 0xc3);	/* MR1: nWR=8, BL8 */
+		mctl_mr_write_lpddr3(2, 0xa);	/* MR2: RL=12, WL=6 */
+		mctl_mr_write_lpddr3(3, 0x2);	/* MR3: 40 0hms PD/PU */
+		mctl_mr_write_lpddr3(11, para->mr11);
+		break;
+	case SUNXI_DRAM_TYPE_LPDDR4:
+		mctl_mr_write_lpddr4(0, 0);	/* MR0 */
+		mctl_mr_write_lpddr4(1, 0x34);	/* MR1 */
+		mctl_mr_write_lpddr4(2, 0x1b);	/* MR2 */
+		mctl_mr_write_lpddr4(3, 0x33);	/* MR3 */
+		mctl_mr_write_lpddr4(4, 0x3);	/* MR4 */
+		mctl_mr_write_lpddr4(11, para->mr11);
+		mctl_mr_write_lpddr4(12, para->mr12);
+		mctl_mr_write_lpddr4(13, para->mr13);
+		mctl_mr_write_lpddr4(14, para->mr14);
+		mctl_mr_write_lpddr4(22, para->tpr1);
+		break;
+	}
+
+	writel(0, SUNXI_DRAM_PHY0_BASE + 0x54);
+
+	/* Re-enable controller refresh */
+	writel(0, &mctl_ctl->swctl);
+	clrbits_le32(&mctl_ctl->rfshctl3, BIT(0));
+	writel(1, &mctl_ctl->swctl);
+}
+
+/* Slightly modified from H616 driver */
+static bool mctl_phy_read_calibration(const struct dram_config *config)
+{
+	bool result = true;
+	u32 val, tmp;
+
+	clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 0x30, 0x20);
+
+	setbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 1);
+
+	if (config->bus_full_width)
+		val = 0xf;
+	else
+		val = 3;
+
+	while ((readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x184) & val) != val) {
+		if (readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x184) & 0x20) {
+			result = false;
+			break;
+		}
+	}
+
+	clrbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 1);
+
+	clrbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 0x30);
+
+	if (config->ranks == 1) {
+		clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 0x30, 0x10);
+
+		setbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 1);
+
+		while ((readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x184) & val) !=
+		       val) {
+			if (readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x184) &
+			    0x20) {
+				result = false;
+				break;
+			}
+		}
+
+		clrbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 1);
+	}
+
+	clrbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 0x30);
+
+	val = readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x274) & 7;
+	tmp = readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x26c) & 7;
+	if (val < tmp)
+		val = tmp;
+	tmp = readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x32c) & 7;
+	if (val < tmp)
+		val = tmp;
+	tmp = readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x334) & 7;
+	if (val < tmp)
+		val = tmp;
+	clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 0x38, 0x7, (val + 2) & 7);
+
+	setbits_le32(SUNXI_DRAM_PHY0_BASE + 4, 0x20);
+
+	return result;
+}
+
+static inline void mctl_phy_dx_delay1_inner(u32 *base, u32 val1, u32 val2)
+{
+	u32 *ptr = base;
+
+	for (int i = 0; i < 9; i++) {
+		writel_relaxed(val1, ptr);
+		writel_relaxed(val1, ptr + 0x30);
+		ptr += 2;
+	}
+
+	writel_relaxed(val2, ptr + 1);
+	writel_relaxed(val2, ptr + 49);
+	writel_relaxed(val2, ptr);
+	writel_relaxed(val2, ptr + 48);
+}
+
+static inline void mctl_phy_dx_delay0_inner(u32 *base1, u32 *base2, u32 val1,
+					    u32 val2)
+{
+	u32 *ptr = base1;
+
+	for (int i = 0; i < 9; i++) {
+		writel_relaxed(val1, ptr);
+		writel_relaxed(val1, ptr + 0x30);
+		ptr += 2;
+	}
+
+	writel_relaxed(val2, base2);
+	writel_relaxed(val2, base2 + 48);
+	writel_relaxed(val2, ptr);
+	writel_relaxed(val2, base2 + 44);
+}
+
+/*
+ * This might be somewhat transferable to H616; whether or not people like
+ * the design is another question
+ */
+static void mctl_phy_dx_delay_compensation(const struct dram_para *para)
+{
+	if (para->tpr10 & TPR10_DX_BIT_DELAY1) {
+		clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x60, 1);
+		setbits_le32(SUNXI_DRAM_PHY0_BASE + 8, BIT(3));
+		clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x190, BIT(4));
+
+		if (para->type == SUNXI_DRAM_TYPE_DDR4)
+			clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x4, BIT(7));
+
+		mctl_phy_dx_delay1_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x484),
+					 para->tpr11 & 0x3f,
+					 para->para0 & 0x3f);
+		mctl_phy_dx_delay1_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x4d8),
+					 (para->tpr11 >> 8) & 0x3f,
+					 (para->para0 >> 8) & 0x3f);
+		mctl_phy_dx_delay1_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x604),
+					 (para->tpr11 >> 16) & 0x3f,
+					 (para->para0 >> 16) & 0x3f);
+		mctl_phy_dx_delay1_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x658),
+					 (para->tpr11 >> 24) & 0x3f,
+					 (para->para0 >> 24) & 0x3f);
+
+		setbits_le32(SUNXI_DRAM_PHY0_BASE + 0x60, 1);
+	}
+
+	if (para->tpr10 & TPR10_DX_BIT_DELAY0) {
+		clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x54, BIT(7));
+		clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x190, BIT(2));
+
+		mctl_phy_dx_delay0_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x480),
+					 (u32 *)(SUNXI_DRAM_PHY0_BASE + 0x528),
+					 para->tpr12 & 0x3f,
+					 para->tpr14 & 0x3f);
+
+		mctl_phy_dx_delay0_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x4d4),
+					 (u32 *)(SUNXI_DRAM_PHY0_BASE + 0x52c),
+					 (para->tpr12 >> 8) & 0x3f,
+					 (para->tpr14 >> 8) & 0x3f);
+
+		mctl_phy_dx_delay0_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x600),
+					 (u32 *)(SUNXI_DRAM_PHY0_BASE + 0x6a8),
+					 (para->tpr12 >> 16) & 0x3f,
+					 (para->tpr14 >> 16) & 0x3f);
+
+		mctl_phy_dx_delay0_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x6ac),
+					 (u32 *)(SUNXI_DRAM_PHY0_BASE + 0x528),
+					 (para->tpr12 >> 24) & 0x3f,
+					 (para->tpr14 >> 24) & 0x3f);
+
+		setbits_le32(SUNXI_DRAM_PHY0_BASE + 0x54, BIT(7));
+	}
+}
+
+static bool mctl_calibrate_phy(const struct dram_para *para,
+			       const struct dram_config *config)
+{
+	struct sunxi_mctl_ctl_reg *mctl_ctl =
+		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
+
+	int i;
+
+	/* TODO: Implement write levelling */
+	if (para->tpr10 & TPR10_READ_CALIBRATION) {
+		for (i = 0; i < 5; i++)
+			if (mctl_phy_read_calibration(config))
+				break;
+		if (i == 5) {
+			debug("read calibration failed\n");
+			return false;
+		}
+	}
+
+	/* TODO: Implement read training */
+	/* TODO: Implement write training */
+
+	mctl_phy_dx_delay_compensation(para);
+	/* TODO: Implement DFS */
+	clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x60, BIT(0));
+	clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x54, 7);
+
+	/* Q: Does self-refresh get disabled by a calibration? */
+	writel_relaxed(0, &mctl_ctl->swctl);
+	clrbits_le32(&mctl_ctl->rfshctl3, BIT(1));
+	writel_relaxed(1, &mctl_ctl->swctl);
+	mctl_await_completion(&mctl_ctl->swstat, BIT(0), BIT(0));
+
+	return true;
+}
+
+static bool mctl_core_init(const struct dram_para *para,
+			   const struct dram_config *config)
+{
+	mctl_clk_init(para->clk);
+	mctl_com_init(para, config);
+	mctl_phy_init(para, config);
+	mctl_dfi_init(para);
+
+	return mctl_calibrate_phy(para, config);
+}
+
+/* Heavily inspired from H616 driver. */
+static void auto_detect_ranks(const struct dram_para *para,
+			      struct dram_config *config)
+{
+	int i;
+
+	config->cols = 9;
+	config->rows = 14;
+	config->banks = 2;
+	config->bankgrps = 0;
+
+	/* Test ranks */
+	for (i = 1; i >= 0; i--) {
+		config->ranks = i;
+		config->bus_full_width = true;
+		debug("Testing ranks = %d, 32-bit bus: ", i);
+		if (mctl_core_init(para, config)) {
+			debug("OK\n");
+			break;
+		}
+
+		config->bus_full_width = false;
+		debug("Testing ranks = %d, 16-bit bus: ", i);
+		if (mctl_core_init(para, config)) {
+			debug("OK\n");
+			break;
+		}
+	}
+
+	if (i < 0)
+		debug("rank testing failed\n");
+}
+
+static void mctl_write_pattern(void)
+{
+	unsigned int i;
+	u32 *ptr, val;
+
+	ptr = (u32 *)CFG_SYS_SDRAM_BASE;
+	for (i = 0; i < 16; ptr++, i++) {
+		if (i & 1)
+			val = ~(ulong)ptr;
+		else
+			val = (ulong)ptr;
+		writel(val, ptr);
+	}
+}
+
+static bool mctl_check_pattern(ulong offset)
+{
+	unsigned int i;
+	u32 *ptr, val;
+
+	ptr = (u32 *)CFG_SYS_SDRAM_BASE;
+	for (i = 0; i < 16; ptr++, i++) {
+		if (i & 1)
+			val = ~(ulong)ptr;
+		else
+			val = (ulong)ptr;
+		if (val != *(ptr + offset / 4))
+			return false;
+	}
+
+	return true;
+}
+
+static void mctl_auto_detect_dram_size(const struct dram_para *para,
+				       struct dram_config *config)
+{
+	unsigned int shift;
+	u32 buffer[16];
+
+	/* max config for bankgrps on DDR4, minimum for everything else */
+	config->cols = 8;
+	config->banks = 2;
+	config->rows = 14;
+
+	shift = 1 + config->bus_full_width;
+	if (para->type == SUNXI_DRAM_TYPE_DDR4) {
+		config->bankgrps = 2;
+		mctl_core_init(para, config);
+
+		/* store content so it can be restored later. */
+		memcpy(buffer, (u32 *)CFG_SYS_SDRAM_BASE, sizeof(buffer));
+		mctl_write_pattern();
+
+		if (mctl_check_pattern(1ULL << (shift + 4)))
+			config->bankgrps = 1;
+
+		/* restore data */
+		memcpy((u32 *)CFG_SYS_SDRAM_BASE, buffer, sizeof(buffer));
+	} else {
+		/* No bank groups in (LP)DDR3/LPDDR4 */
+		config->bankgrps = 0;
+	}
+
+	/* reconfigure to make sure all active columns are accessible */
+	config->cols = 12;
+	mctl_core_init(para, config);
+
+	/* store data again as it might be moved */
+	memcpy(buffer, (u32 *)CFG_SYS_SDRAM_BASE, sizeof(buffer));
+	mctl_write_pattern();
+
+	/*
+	 * Detect column address bits. The last number of columns checked
+	 * is 11, if that doesn't match, is must be 12, no more checks needed.
+	 */
+	shift = 1 + config->bus_full_width + config->bankgrps;
+	for (config->cols = 8; config->cols < 12; config->cols++) {
+		if (mctl_check_pattern(1ULL << (config->cols + shift)))
+			break;
+	}
+	memcpy((u32 *)CFG_SYS_SDRAM_BASE, buffer, sizeof(buffer));
+
+	/* reconfigure to make sure that all active banks are accessible */
+	config->banks = 3;
+	mctl_core_init(para, config);
+
+	memcpy(buffer, (u32 *)CFG_SYS_SDRAM_BASE, sizeof(buffer));
+	mctl_write_pattern();
+
+	/* detect bank bits */
+	shift += config->cols;
+	for (config->banks = 2; config->banks < 3; config->banks++) {
+		if (mctl_check_pattern(1ULL << (config->banks + shift)))
+			break;
+	}
+	memcpy((u32 *)CFG_SYS_SDRAM_BASE, buffer, sizeof(buffer));
+
+	/* reconfigure to make sure that all active rows are accessible */
+	config->rows = 18;
+	mctl_core_init(para, config);
+
+	memcpy(buffer, (u32 *)CFG_SYS_SDRAM_BASE, sizeof(buffer));
+	mctl_write_pattern();
+
+	/* detect row address bits */
+	shift += config->banks;
+	for (config->rows = 14; config->rows < 18; config->rows++) {
+		if (mctl_check_pattern(1ULL << (config->rows + shift)))
+			break;
+	}
+	memcpy((u32 *)CFG_SYS_SDRAM_BASE, buffer, sizeof(buffer));
+}
+
+/* Modified from H616 driver to add banks and bank groups */
+static unsigned long calculate_dram_size(const struct dram_config *config)
+{
+	/* Bootrom only uses x32 or x16 bus widths */
+	u8 width = config->bus_full_width ? 4 : 2;
+
+	return (1ULL << (config->cols + config->rows + config->banks +
+			 config->bankgrps)) *
+	       width * (1ULL << config->ranks);
+}
+
+static const struct dram_para para = {
+	.clk = CONFIG_DRAM_CLK,
+#ifdef CONFIG_SUNXI_DRAM_DDR3
+	.type = SUNXI_DRAM_TYPE_DDR3,
+#elif defined(CONFIG_SUNXI_DRAM_DDR4)
+	.type = SUNXI_DRAM_TYPE_DDR4,
+#elif defined(CONFIG_SUNXI_DRAM_LPDDR3)
+	.type = SUNXI_DRAM_TYPE_LPDDR3,
+#elif defined(CONFIG_SUNXI_DRAM_LPDDR4)
+	.type = SUNXI_DRAM_TYPE_LPDDR4,
+#endif
+	/* TODO: Populate from config */
+	.dx_odt = CONFIG_DRAM_SUNXI_DX_ODT,
+	.dx_dri = CONFIG_DRAM_SUNXI_DX_DRI,
+	.ca_dri = CONFIG_DRAM_SUNXI_CA_DRI,
+	.para0 = CONFIG_DRAM_SUNXI_PARA0,
+	.mr11 = CONFIG_DRAM_SUNXI_MR11,
+	.mr12 = CONFIG_DRAM_SUNXI_MR12,
+	.mr13 = CONFIG_DRAM_SUNXI_MR13,
+	.mr14 = CONFIG_DRAM_SUNXI_MR14,
+	.tpr1 = CONFIG_DRAM_SUNXI_TPR1,
+	.tpr2 = CONFIG_DRAM_SUNXI_TPR2,
+	.tpr3 = CONFIG_DRAM_SUNXI_TPR3,
+	.tpr6 = CONFIG_DRAM_SUNXI_TPR6,
+	.tpr10 = CONFIG_DRAM_SUNXI_TPR10,
+	.tpr11 = CONFIG_DRAM_SUNXI_TPR11,
+	.tpr12 = CONFIG_DRAM_SUNXI_TPR12,
+	.tpr13 = CONFIG_DRAM_SUNXI_TPR13,
+	.tpr14 = CONFIG_DRAM_SUNXI_TPR14,
+};
+
+unsigned long sunxi_dram_init(void)
+{
+	struct dram_config config;
+
+	/* Writing to undocumented SYS_CFG area, according to user manual. */
+	setbits_le32(0x03000160, BIT(8));
+	clrbits_le32(0x03000168, 0x3f);
+
+	auto_detect_ranks(&para, &config);
+	mctl_auto_detect_dram_size(&para, &config);
+
+	if (!mctl_core_init(&para, &config))
+		return 0;
+
+	debug("cols = 2^%d, rows = 2^%d, banks = %d, bank groups = %d, ranks = %d, width = %d\n",
+	      config.cols, config.rows, 1U << config.banks,
+	      1U << config.bankgrps, 1U << config.ranks,
+	      16U << config.bus_full_width);
+
+	return calculate_dram_size(&config);
+}
diff --git a/arch/arm/mach-sunxi/dram_timings/Makefile b/arch/arm/mach-sunxi/dram_timings/Makefile
index 5f203419240..4dc1f29fc08 100644
--- a/arch/arm/mach-sunxi/dram_timings/Makefile
+++ b/arch/arm/mach-sunxi/dram_timings/Makefile
@@ -6,3 +6,5 @@ obj-$(CONFIG_SUNXI_DRAM_H6_DDR3_1333)	+= h6_ddr3_1333.o
 obj-$(CONFIG_SUNXI_DRAM_H616_DDR3_1333)	+= h616_ddr3_1333.o
 obj-$(CONFIG_SUNXI_DRAM_H616_LPDDR3)	+= h616_lpddr3.o
 obj-$(CONFIG_SUNXI_DRAM_H616_LPDDR4)	+= h616_lpddr4_2133.o
+obj-$(CONFIG_SUNXI_DRAM_A133_DDR4)	+= a133_ddr4.o
+obj-$(CONFIG_SUNXI_DRAM_A133_LPDDR4)	+= a133_lpddr4.o
diff --git a/arch/arm/mach-sunxi/dram_timings/a133_ddr4.c b/arch/arm/mach-sunxi/dram_timings/a133_ddr4.c
new file mode 100644
index 00000000000..dec208e22df
--- /dev/null
+++ b/arch/arm/mach-sunxi/dram_timings/a133_ddr4.c
@@ -0,0 +1,80 @@
+#include <asm/arch/cpu.h>
+#include <asm/arch/dram.h>
+
+void mctl_set_timing_params(const struct dram_para *para)
+{
+	struct sunxi_mctl_ctl_reg *const mctl_ctl =
+		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
+
+	u8 txsr = 4;
+	u8 tccd = 3;
+	u8 rd2wr = 5;
+	u8 tmrd = 4;
+	u8 tmrw = 0;
+	u8 wrlat = 5;
+	u8 rdlat = 7;
+	u8 wr2pre = 14;
+	u8 dfi_tphy_wrlat = 6;
+	u8 dfi_trddata_en = 10;
+
+	u8 tfaw = ns_to_t(35);
+	u8 trrd = max(ns_to_t(8), 2);
+	u8 txp = max(ns_to_t(6), 2);
+	u8 tmrd_pda = max(ns_to_t(10), 8);
+	u8 trp = ns_to_t(15);
+	u8 trc = ns_to_t(49);
+	u8 wr2rd_s = max(ns_to_t(3), 1) + 7;
+	u8 tras_min = ns_to_t(34);
+	u16 trefi_x32 = ns_to_t(7800) / 32;
+	u16 trfc_min = ns_to_t(350);
+	u16 txs_x32 = ns_to_t(360) / 32;
+	u16 tmod = max(ns_to_t(15), 12);
+	u8 tcke = max(ns_to_t(5), 2);
+	u8 tcksrx = max(ns_to_t(10), 3);
+	u8 txs_abort_x32 = ns_to_t(170) / 32;
+	u8 tras_max = ns_to_t(70200) / 1024;
+
+	u8 rd2pre = (trp < 5 ? 9 - trp : 4);
+	u8 wr2rd = trrd + 7;
+	u8 tckesr = tcke + 1;
+	u8 trcd = trp;
+	u8 trrd_s = txp;
+	u8 tcksre = tcksrx;
+
+	writel(tras_min | tras_max << 8 | tfaw << 16 | wr2pre << 24,
+	       &mctl_ctl->dramtmg[0]);
+	writel(trc | rd2pre << 8 | txp << 16, &mctl_ctl->dramtmg[1]);
+	writel(wr2rd | rd2wr << 8 | rdlat << 16 | wrlat << 24,
+	       &mctl_ctl->dramtmg[2]);
+	writel(tmod | tmrd << 12 | tmrw << 20, &mctl_ctl->dramtmg[3]);
+	writel(trp | trrd << 8 | tccd << 16 | trcd << 24,
+	       &mctl_ctl->dramtmg[4]);
+	writel(tcke | tckesr << 8 | tcksre << 16 | tcksrx << 24,
+	       &mctl_ctl->dramtmg[5]);
+	writel((txp + 2) | 0x20 << 16 | 0x20 << 24,
+	       &mctl_ctl->dramtmg[6]);
+	writel(txs_x32 | 0x10 << 8 | txs_abort_x32 << 16 | txs_abort_x32 << 24,
+	       &mctl_ctl->dramtmg[8]);
+	writel(wr2rd_s | trrd_s << 8 | 0x2 << 16, &mctl_ctl->dramtmg[9]);
+	writel(0xe0c05, &mctl_ctl->dramtmg[10]);
+	writel(0x440c021c, &mctl_ctl->dramtmg[11]);
+	writel(tmrd_pda, &mctl_ctl->dramtmg[12]);
+	writel(0xa100002, &mctl_ctl->dramtmg[13]);
+	writel(txsr, &mctl_ctl->dramtmg[14]);
+
+	clrsetbits_le32(&mctl_ctl->init[0], 0xc0000fff, 1008);
+	writel(0x1f20000, &mctl_ctl->init[1]);
+	clrsetbits_le32(&mctl_ctl->init[2], 0xff0f, 0xd05);
+	writel(0, &mctl_ctl->dfimisc);
+
+	writel(0x840 << 16 | 0x601, &mctl_ctl->init[3]);	/* MR0 / MR1 */
+	writel(0x8 << 16 | 0x0, &mctl_ctl->init[4]);		/* MR2 / MR3 */
+	writel(0x0 << 16 | 0x400, &mctl_ctl->init[6]);		/* MR4 / MR5 */
+	writel(0x826, &mctl_ctl->init[7]);			/* MR6 */
+
+	clrsetbits_le32(&mctl_ctl->rankctl, 0xff0, 0x660);
+	writel((dfi_tphy_wrlat - 1) | 0x2000000 | (dfi_trddata_en - 1) << 16 |
+	       0x808000, &mctl_ctl->dfitmg0);
+	writel(0x100202, &mctl_ctl->dfitmg1);
+	writel(trfc_min | trefi_x32 << 16, &mctl_ctl->rfshtmg);
+}
diff --git a/arch/arm/mach-sunxi/dram_timings/a133_lpddr4.c b/arch/arm/mach-sunxi/dram_timings/a133_lpddr4.c
new file mode 100644
index 00000000000..1e607381023
--- /dev/null
+++ b/arch/arm/mach-sunxi/dram_timings/a133_lpddr4.c
@@ -0,0 +1,102 @@
+#include <asm/arch/cpu.h>
+#include <asm/arch/dram.h>
+
+void mctl_set_timing_params(const struct dram_para *para)
+{
+	struct sunxi_mctl_ctl_reg *const mctl_ctl =
+		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
+
+	bool tpr13_flag1 = para->tpr13 & BIT(28);
+	bool tpr13_flag2 = para->tpr13 & BIT(3);
+	bool tpr13_flag3 = para->tpr13 & BIT(5);
+
+	u8 tccd		= 4;
+	u8 tfaw		= ns_to_t(40);
+	u8 trrd		= max(ns_to_t(10), 2);
+	u8 trcd		= max(ns_to_t(18), 2);
+	u8 trc		= ns_to_t(65);
+	u8 txp		= max(ns_to_t(8), 2);
+
+	u8 trp		= ns_to_t(21);
+	u8 tras_min	= ns_to_t(42);
+	u16 trefi_x32	= ns_to_t(3904) / 32;
+	u16 trfc_min	= ns_to_t(180);
+	u16 txsr	= ns_to_t(190);
+
+	u8 tmrw		= max(ns_to_t(14), 5);
+	u8 tmrd		= max(ns_to_t(14), 5);
+	u8 tmod		= 12;
+	u8 tcke		= max(ns_to_t(15), 2);
+	u8 tcksrx	= max(ns_to_t(2), 2);
+	u8 tcksre	= max(ns_to_t(5), 2);
+	u8 tckesr	= max(ns_to_t(15), 2);
+	u8 tras_max	= (trefi_x32 * 9) / 32;
+	u8 txs_x32	= 4;
+	u8 txsabort_x32 = 4;
+
+	u8 wrlat        = 5;
+	u8 wr2rd_s      = 8;
+	u8 trrd_s       = 2;
+	u8 tmrd_pda     = 8;
+
+	u8 wr2pre       = 24;
+	u8 rd2pre       = 4;
+	u8 wr2rd        = 14 + max(ns_to_t(tpr13_flag1 ? 10 : 12), 4);
+	u8 rd2wr        = 17 + ns_to_t(4) - ns_to_t(1);
+	u8 tphy_wrlat	= 5;
+
+	u8 rdlat	= 10;
+	u8 trddata_en	= 17;
+
+	if (tpr13_flag1) {
+		rdlat = 11;
+		trddata_en = 19;
+	}
+
+	writel(tras_min | tras_max << 8 | tfaw << 16 | wr2pre << 24,
+	       &mctl_ctl->dramtmg[0]);
+	writel(trc | rd2pre << 8 | txp << 16, &mctl_ctl->dramtmg[1]);
+	writel(wr2rd | rd2wr << 8 | rdlat << 16 | wrlat << 24,
+	       &mctl_ctl->dramtmg[2]);
+	writel(tmod | tmrd << 12 | tmrw << 20, &mctl_ctl->dramtmg[3]);
+	writel(trp | trrd << 8 | tccd << 16 | trcd << 24,
+	       &mctl_ctl->dramtmg[4]);
+	writel(tcke | tckesr << 8 | tcksre << 16 | tcksrx << 24,
+	       &mctl_ctl->dramtmg[5]);
+	writel((txp + 2) | 0x20 << 16 | 0x20 << 24, &mctl_ctl->dramtmg[6]);
+	writel(txs_x32 | 0x10 << 8 | txsabort_x32 << 16 | txsabort_x32 << 24,
+	       &mctl_ctl->dramtmg[8]);
+	writel(wr2rd_s | trrd_s << 8 | 0x2 << 16, &mctl_ctl->dramtmg[9]);
+	writel(0xe0c05, &mctl_ctl->dramtmg[10]);
+	writel(0x440c021c, &mctl_ctl->dramtmg[11]);
+	writel(tmrd_pda, &mctl_ctl->dramtmg[12]);
+	writel(0xa100002, &mctl_ctl->dramtmg[13]);
+	writel(txsr, &mctl_ctl->dramtmg[14]);
+
+	clrsetbits_le32(&mctl_ctl->init[0], 0xc0000fff, 1008);
+
+	if (tpr13_flag2)
+		writel(0x420000, &mctl_ctl->init[1]);
+	else
+		writel(0x1f20000, &mctl_ctl->init[1]);
+
+	clrsetbits_le32(&mctl_ctl->init[2], 0xff0f, 0xd05);
+	writel(0, &mctl_ctl->dfimisc);
+
+	writel(0x34 << 16 | 0x1b, &mctl_ctl->init[3]);		/* MR1/MR2 */
+	writel(0x33 << 16, &mctl_ctl->init[4]);			/* MR3 */
+	writel(para->mr11 << 16 | para->mr12, &mctl_ctl->init[6]);
+	writel(para->tpr1 << 16 | para->mr14, &mctl_ctl->init[7]);
+
+	clrsetbits_le32(&mctl_ctl->rankctl, 0xff0, 0x660);
+	if (!tpr13_flag3) {
+		tphy_wrlat -= 1;
+		trddata_en -= 1;
+	}
+
+	writel(tphy_wrlat | trddata_en << 16 | 0x808000 | 0x2000000,
+	       &mctl_ctl->dfitmg0);
+	writel(0x100202, &mctl_ctl->dfitmg1);
+
+	writel(trfc_min | trefi_x32 << 16, &mctl_ctl->rfshtmg);
+}
-- 
2.46.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 2/6] sunxi: add support for the Allwinner A100/A133 SoC
  2025-05-11  1:09 [PATCH v2 0/6] sunxi: Allwinner A133 SoC support Andre Przywara
  2025-05-11  1:09 ` [PATCH v2 1/6] sunxi: A133: add DRAM init code Andre Przywara
@ 2025-05-11  1:09 ` Andre Przywara
  2025-05-11  1:10 ` [PATCH v2 3/6] arm64: dts: allwinner: a100: Add CPU Operating Performance Points table Andre Przywara
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 15+ messages in thread
From: Andre Przywara @ 2025-05-11  1:09 UTC (permalink / raw)
  To: u-boot
  Cc: Jagan Teki, Cody Eksal, Philippe Simons, Sumit Garg, linux-sunxi,
	Tom Rini, Jernej Skrabec

The Allwinner A100 SoC has been around for a while, mostly on cheap
tablets, but didn't generate much interest in the community so far.
There were some efforts by two Allwinner employees in 2020, which led
to basic upstream Linux support for that SoC, although this momentum
dried up pretty quickly, leaving a lot of peripherals unsupported.

The A100 was silently replaced with the seemingly identical Allwinner
A133, which is reportedly a better bin of the A100. So far we assume
that both are compatible from a software perspective. There are some
more devices with the A133 out there now, so people are working on
filling the gaps, and adding U-Boot (and TF-A) support.

Based on the just added pinctrl, clock and DRAM support, this adds the
missing bits, mostly addresses and values for the SPL.

The A133 seems to be an predecessor to the H6, so we can share a lot of
code with that (and the H616 code), and just need to adjust some details.

Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
 arch/arm/cpu/armv8/fel_utils.S                    |  2 +-
 arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h |  7 +++++++
 arch/arm/mach-sunxi/Kconfig                       | 10 ++++++++--
 arch/arm/mach-sunxi/board.c                       |  4 ++++
 arch/arm/mach-sunxi/clock_sun50i_h6.c             |  3 ++-
 arch/arm/mach-sunxi/cpu_info.c                    |  2 ++
 board/sunxi/board.c                               |  4 ++--
 common/spl/Kconfig                                |  4 ++--
 8 files changed, 28 insertions(+), 8 deletions(-)

diff --git a/arch/arm/cpu/armv8/fel_utils.S b/arch/arm/cpu/armv8/fel_utils.S
index 044a7c16cc5..6a7ec9a7ec1 100644
--- a/arch/arm/cpu/armv8/fel_utils.S
+++ b/arch/arm/cpu/armv8/fel_utils.S
@@ -41,7 +41,7 @@ ENTRY(return_to_fel)
 	str	w2, [x1]
 
 	ldr	w0, =0xfa50392f		// CPU hotplug magic
-#ifdef CONFIG_MACH_SUN50I_H616
+#if defined(CONFIG_MACH_SUN50I_H616) || defined(CONFIG_MACH_SUN50I_A133)
 	ldr	w2, =(SUNXI_R_CPUCFG_BASE + 0x1c0)
 	str	w0, [x2], #0x4
 #elif CONFIG_MACH_SUN50I_H6
diff --git a/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h
index ccacc99d018..575dff68804 100644
--- a/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h
+++ b/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h
@@ -90,6 +90,13 @@
 #define CCM_PLL6_DEFAULT		0xe8216300
 #define CCM_PSI_AHB1_AHB2_DEFAULT	0x03000002
 #define CCM_APB1_DEFAULT		0x03000102
+
+#elif CONFIG_MACH_SUN50I_A133				/* A133 */
+
+#define CCM_PLL6_DEFAULT		0xb8003100
+#define CCM_PSI_AHB1_AHB2_DEFAULT	0x03000002
+#define CCM_AHB3_DEFAULT		0x03000002
+#define CCM_APB1_DEFAULT		0x03000102
 #endif
 
 /* apb2 bit field */
diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig
index 98b947f6e33..fc9253fff28 100644
--- a/arch/arm/mach-sunxi/Kconfig
+++ b/arch/arm/mach-sunxi/Kconfig
@@ -220,6 +220,7 @@ config SUNXI_SRAM_ADDRESS
 config SUNXI_RVBAR_ADDRESS
 	hex
 	depends on ARM64
+	default 0x08100040 if MACH_SUN50I_A133
 	default 0x09010040 if SUN50I_GEN_H6
 	default 0x017000a0
 	---help---
@@ -246,8 +247,8 @@ config SUNXI_RVBAR_ALTERNATIVE
 config SUNXI_BL31_BASE
 	hex
 	default 0x00044000 if MACH_SUN50I || MACH_SUN50I_H5
-	default 0x00104000 if MACH_SUN50I_H6
 	default 0x40000000 if MACH_SUN50I_H616
+	default 0x00104000 if SUN50I_GEN_H6
 	default 0x0
 	help
 	  Address where BL31 (TF-A) is loaded, or zero if BL31 is not used.
@@ -329,7 +330,7 @@ config MACH_SUNXI_H3_H5
 # TODO: try out A80's 8GiB DRAM space
 config SUNXI_DRAM_MAX_SIZE
 	hex
-	default 0x100000000 if MACH_SUN50I_H616
+	default 0x100000000 if MACH_SUN50I_H616 || MACH_SUN50I_A133
 	default 0xC0000000 if MACH_SUN50I || MACH_SUN50I_H5 || MACH_SUN50I_H6
 	default 0x80000000
 
@@ -529,6 +530,10 @@ config MACH_SUN50I_H616
 
 config MACH_SUN50I_A133
 	bool "sun50i (Allwinner A133)"
+	select ARM64
+	select DRAM_SUN50I_A133
+	select SUN50I_GEN_H6
+	imply OF_UPSTREAM
 
 endchoice
 
@@ -824,6 +829,7 @@ config SYS_CONFIG_NAME
 	default "sun50i" if MACH_SUN50I
 	default "sun50i" if MACH_SUN50I_H6
 	default "sun50i" if MACH_SUN50I_H616
+	default "sun50i" if MACH_SUN50I_A133
 
 config SYS_BOARD
 	default "sunxi"
diff --git a/arch/arm/mach-sunxi/board.c b/arch/arm/mach-sunxi/board.c
index b1bf51f40c5..08d55b3a0e3 100644
--- a/arch/arm/mach-sunxi/board.c
+++ b/arch/arm/mach-sunxi/board.c
@@ -137,6 +137,10 @@ static int gpio_init(void)
 	sunxi_gpio_set_cfgpin(SUNXI_GPH(0), SUN50I_H616_GPH_UART0);
 	sunxi_gpio_set_cfgpin(SUNXI_GPH(1), SUN50I_H616_GPH_UART0);
 	sunxi_gpio_set_pull(SUNXI_GPH(1), SUNXI_GPIO_PULL_UP);
+#elif CONFIG_CONS_INDEX == 1 && defined(CONFIG_MACH_SUN50I_A133)
+	sunxi_gpio_set_cfgpin(SUNXI_GPB(9), SUN50I_H616_GPH_UART0);
+	sunxi_gpio_set_cfgpin(SUNXI_GPB(10), SUN50I_H616_GPH_UART0);
+	sunxi_gpio_set_pull(SUNXI_GPB(10), SUNXI_GPIO_PULL_UP);
 #elif CONFIG_CONS_INDEX == 1 && defined(CONFIG_MACH_SUN8I_A83T)
 	sunxi_gpio_set_cfgpin(SUNXI_GPB(9), SUN8I_A83T_GPB_UART0);
 	sunxi_gpio_set_cfgpin(SUNXI_GPB(10), SUN8I_A83T_GPB_UART0);
diff --git a/arch/arm/mach-sunxi/clock_sun50i_h6.c b/arch/arm/mach-sunxi/clock_sun50i_h6.c
index 4c522f60810..3f375a51965 100644
--- a/arch/arm/mach-sunxi/clock_sun50i_h6.c
+++ b/arch/arm/mach-sunxi/clock_sun50i_h6.c
@@ -87,7 +87,8 @@ void clock_set_pll1(unsigned int clk)
 	/* clk = 24*n/p, p is ignored if clock is >288MHz */
 	val = CCM_PLL1_CTRL_EN | CCM_PLL1_LOCK_EN | CCM_PLL1_CLOCK_TIME_2;
 	val |= CCM_PLL1_CTRL_N(clk / 24000000);
-	if (IS_ENABLED(CONFIG_MACH_SUN50I_H616))
+	if (IS_ENABLED(CONFIG_MACH_SUN50I_H616) ||
+	    IS_ENABLED(CONFIG_MACH_SUN50I_A133))
 	       val |= CCM_PLL1_OUT_EN;
 	if (IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2))
 	       val |= CCM_PLL1_OUT_EN | CCM_PLL1_LDO_EN;
diff --git a/arch/arm/mach-sunxi/cpu_info.c b/arch/arm/mach-sunxi/cpu_info.c
index 310dca06e57..3f4735d4717 100644
--- a/arch/arm/mach-sunxi/cpu_info.c
+++ b/arch/arm/mach-sunxi/cpu_info.c
@@ -104,6 +104,8 @@ int print_cpuinfo(void)
 	puts("CPU:   Allwinner H6 (SUN50I)\n");
 #elif defined CONFIG_MACH_SUN50I_H616
 	puts("CPU:   Allwinner H616 (SUN50I)\n");
+#elif defined CONFIG_MACH_SUN50I_A133
+	puts("CPU:   Allwinner A133 (SUN50I)\n");
 #else
 #warning Please update cpu_info.c with correct CPU information
 	puts("CPU:   SUNXI Family\n");
diff --git a/board/sunxi/board.c b/board/sunxi/board.c
index ac9cefc6eac..d5568ea519e 100644
--- a/board/sunxi/board.c
+++ b/board/sunxi/board.c
@@ -114,7 +114,7 @@ void i2c_init_board(void)
 	clock_twi_onoff(5, 1);
 	sunxi_gpio_set_cfgpin(SUNXI_GPL(8), SUN50I_GPL_R_TWI);
 	sunxi_gpio_set_cfgpin(SUNXI_GPL(9), SUN50I_GPL_R_TWI);
-#elif CONFIG_MACH_SUN50I_H616
+#elif defined(CONFIG_MACH_SUN50I_H616)
 	clock_twi_onoff(5, 1);
 	sunxi_gpio_set_cfgpin(SUNXI_GPL(0), SUN50I_H616_GPL_R_TWI);
 	sunxi_gpio_set_cfgpin(SUNXI_GPL(1), SUN50I_H616_GPL_R_TWI);
@@ -435,7 +435,7 @@ static void mmc_pinmux_setup(int sdc)
 			sunxi_gpio_set_pull(pin, SUNXI_GPIO_PULL_UP);
 			sunxi_gpio_set_drv(pin, 2);
 		}
-#elif defined(CONFIG_MACH_SUN50I_H616)
+#elif defined(CONFIG_MACH_SUN50I_H616) || defined(CONFIG_MACH_SUN50I_A133)
 		/* SDC2: PC0-PC1, PC5-PC6, PC8-PC11, PC13-PC16 */
 		for (pin = SUNXI_GPC(0); pin <= SUNXI_GPC(16); pin++) {
 			if (pin > SUNXI_GPC(1) && pin < SUNXI_GPC(5))
diff --git a/common/spl/Kconfig b/common/spl/Kconfig
index b076f49ac00..590bdc3e8e4 100644
--- a/common/spl/Kconfig
+++ b/common/spl/Kconfig
@@ -80,7 +80,7 @@ config SPL_MAX_SIZE
 	default 0x1b000 if AM33XX && !TI_SECURE_DEVICE
 	default 0xec00 if OMAP34XX
 	default 0x10000 if ARCH_MX6 && !MX6_OCRAM_256KB
-	default 0xbfa0 if MACH_SUN50I_H616
+	default 0xbfa0 if MACH_SUN50I_H616 || MACH_SUN50I_A133
 	default 0x7000 if RCAR_GEN3
 	default 0x5fa0 if SUNXI_SRAM_ADDRESS = 0x0
 	default 0x7fa0 if ARCH_SUNXI
@@ -416,7 +416,7 @@ config SPL_STACK
 	default 0x91ffb8 if ARCH_MX6 && !MX6_OCRAM_256KB
 	default 0x118000 if MACH_SUN50I_H6
 	default 0x52a00 if MACH_SUN50I_H616
-	default 0x40000 if MACH_SUN8I_R528
+	default 0x40000 if MACH_SUN8I_R528 || MACH_SUN50I_A133
 	default 0x54000 if MACH_SUN50I || MACH_SUN50I_H5
 	default 0x18000 if MACH_SUN9I
 	default 0x8000 if ARCH_SUNXI
-- 
2.46.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 3/6] arm64: dts: allwinner: a100: Add CPU Operating Performance Points table
  2025-05-11  1:09 [PATCH v2 0/6] sunxi: Allwinner A133 SoC support Andre Przywara
  2025-05-11  1:09 ` [PATCH v2 1/6] sunxi: A133: add DRAM init code Andre Przywara
  2025-05-11  1:09 ` [PATCH v2 2/6] sunxi: add support for the Allwinner A100/A133 SoC Andre Przywara
@ 2025-05-11  1:10 ` Andre Przywara
  2025-05-12  9:24   ` Quentin Schulz
  2025-05-11  1:10 ` [PATCH v2 4/6] arm64: dts: allwinner: a100: set maximum MMC frequency Andre Przywara
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 15+ messages in thread
From: Andre Przywara @ 2025-05-11  1:10 UTC (permalink / raw)
  To: u-boot
  Cc: Jagan Teki, Cody Eksal, Philippe Simons, Sumit Garg, linux-sunxi,
	Tom Rini, Jernej Skrabec

Add an Operating Performance Points table for the CPU cores to
enable Dynamic Voltage & Frequency Scaling on the A100.

Signed-off-by: Shuosheng Huang <huangshuosheng@allwinnertech.com>
[masterr3c0rd@epochal.quest: fix typos in -cpu-opp, use compatible]
Signed-off-by: Cody Eksal <masterr3c0rd@epochal.quest>
Link: https://patch.msgid.link/20241031070232.1793078-14-masterr3c0rd@epochal.quest
Signed-off-by: Chen-Yu Tsai <wens@csie.org>

[ upstream commit: a8181e6861fec3068f393d77ff81b2aaf4ea4203 ]
---
 .../allwinner/sun50i-a100-allwinner-perf1.dts |  5 ++
 .../arm64/allwinner/sun50i-a100-cpu-opp.dtsi  | 90 +++++++++++++++++++
 .../src/arm64/allwinner/sun50i-a100.dtsi      |  8 ++
 3 files changed, 103 insertions(+)
 create mode 100644 dts/upstream/src/arm64/allwinner/sun50i-a100-cpu-opp.dtsi

diff --git a/dts/upstream/src/arm64/allwinner/sun50i-a100-allwinner-perf1.dts b/dts/upstream/src/arm64/allwinner/sun50i-a100-allwinner-perf1.dts
index a387bccdcef..a7e3be0155a 100644
--- a/dts/upstream/src/arm64/allwinner/sun50i-a100-allwinner-perf1.dts
+++ b/dts/upstream/src/arm64/allwinner/sun50i-a100-allwinner-perf1.dts
@@ -6,6 +6,7 @@
 /dts-v1/;
 
 #include "sun50i-a100.dtsi"
+#include "sun50i-a100-cpu-opp.dtsi"
 
 #include <dt-bindings/gpio/gpio.h>
 
@@ -38,6 +39,10 @@
 	status = "okay";
 };
 
+&cpu0 {
+	cpu-supply = <&reg_dcdc2>;
+};
+
 &pio {
 	vcc-pb-supply = <&reg_dcdc1>;
 	vcc-pc-supply = <&reg_eldo1>;
diff --git a/dts/upstream/src/arm64/allwinner/sun50i-a100-cpu-opp.dtsi b/dts/upstream/src/arm64/allwinner/sun50i-a100-cpu-opp.dtsi
new file mode 100644
index 00000000000..c6a2efa037d
--- /dev/null
+++ b/dts/upstream/src/arm64/allwinner/sun50i-a100-cpu-opp.dtsi
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+// Copyright (c) 2020 Yangtao Li <frank@allwinnertech.com>
+// Copyright (c) 2020 ShuoSheng Huang <huangshuosheng@allwinnertech.com>
+
+/ {
+	cpu_opp_table: opp-table-cpu {
+		compatible = "allwinner,sun50i-a100-operating-points";
+		nvmem-cells = <&cpu_speed_grade>;
+		opp-shared;
+
+		opp-408000000 {
+			clock-latency-ns = <244144>; /* 8 32k periods */
+			opp-hz = /bits/ 64 <408000000>;
+
+			opp-microvolt-speed0 = <900000>;
+			opp-microvolt-speed1 = <900000>;
+			opp-microvolt-speed2 = <900000>;
+		};
+
+		opp-600000000 {
+			clock-latency-ns = <244144>; /* 8 32k periods */
+			opp-hz = /bits/ 64 <600000000>;
+
+			opp-microvolt-speed0 = <900000>;
+			opp-microvolt-speed1 = <900000>;
+			opp-microvolt-speed2 = <900000>;
+		};
+
+		opp-816000000 {
+			clock-latency-ns = <244144>; /* 8 32k periods */
+			opp-hz = /bits/ 64 <816000000>;
+
+			opp-microvolt-speed0 = <940000>;
+			opp-microvolt-speed1 = <900000>;
+			opp-microvolt-speed2 = <900000>;
+		};
+
+		opp-1080000000 {
+			clock-latency-ns = <244144>; /* 8 32k periods */
+			opp-hz = /bits/ 64 <1080000000>;
+
+			opp-microvolt-speed0 = <1020000>;
+			opp-microvolt-speed1 = <980000>;
+			opp-microvolt-speed2 = <950000>;
+		};
+
+		opp-1200000000 {
+			clock-latency-ns = <244144>; /* 8 32k periods */
+			opp-hz = /bits/ 64 <1200000000>;
+
+			opp-microvolt-speed0 = <1100000>;
+			opp-microvolt-speed1 = <1020000>;
+			opp-microvolt-speed2 = <1000000>;
+		};
+
+		opp-1320000000 {
+			clock-latency-ns = <244144>; /* 8 32k periods */
+			opp-hz = /bits/ 64 <1320000000>;
+
+			opp-microvolt-speed0 = <1160000>;
+			opp-microvolt-speed1 = <1060000>;
+			opp-microvolt-speed2 = <1030000>;
+		};
+
+		opp-1464000000 {
+			clock-latency-ns = <244144>; /* 8 32k periods */
+			opp-hz = /bits/ 64 <1464000000>;
+
+			opp-microvolt-speed0 = <1180000>;
+			opp-microvolt-speed1 = <1180000>;
+			opp-microvolt-speed2 = <1130000>;
+		};
+	};
+};
+
+&cpu0 {
+	operating-points-v2 = <&cpu_opp_table>;
+};
+
+&cpu1 {
+	operating-points-v2 = <&cpu_opp_table>;
+};
+
+&cpu2 {
+	operating-points-v2 = <&cpu_opp_table>;
+};
+
+&cpu3 {
+	operating-points-v2 = <&cpu_opp_table>;
+};
diff --git a/dts/upstream/src/arm64/allwinner/sun50i-a100.dtsi b/dts/upstream/src/arm64/allwinner/sun50i-a100.dtsi
index a24adba201a..f9f6fea03b7 100644
--- a/dts/upstream/src/arm64/allwinner/sun50i-a100.dtsi
+++ b/dts/upstream/src/arm64/allwinner/sun50i-a100.dtsi
@@ -23,6 +23,7 @@
 			device_type = "cpu";
 			reg = <0x0>;
 			enable-method = "psci";
+			clocks = <&ccu CLK_CPUX>;
 		};
 
 		cpu1: cpu@1 {
@@ -30,6 +31,7 @@
 			device_type = "cpu";
 			reg = <0x1>;
 			enable-method = "psci";
+			clocks = <&ccu CLK_CPUX>;
 		};
 
 		cpu2: cpu@2 {
@@ -37,6 +39,7 @@
 			device_type = "cpu";
 			reg = <0x2>;
 			enable-method = "psci";
+			clocks = <&ccu CLK_CPUX>;
 		};
 
 		cpu3: cpu@3 {
@@ -44,6 +47,7 @@
 			device_type = "cpu";
 			reg = <0x3>;
 			enable-method = "psci";
+			clocks = <&ccu CLK_CPUX>;
 		};
 	};
 
@@ -175,6 +179,10 @@
 			ths_calibration: calib@14 {
 				reg = <0x14 8>;
 			};
+
+			cpu_speed_grade: cpu-speed-grade@1c {
+				reg = <0x1c 0x2>;
+			};
 		};
 
 		watchdog@30090a0 {
-- 
2.46.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 4/6] arm64: dts: allwinner: a100: set maximum MMC frequency
  2025-05-11  1:09 [PATCH v2 0/6] sunxi: Allwinner A133 SoC support Andre Przywara
                   ` (2 preceding siblings ...)
  2025-05-11  1:10 ` [PATCH v2 3/6] arm64: dts: allwinner: a100: Add CPU Operating Performance Points table Andre Przywara
@ 2025-05-11  1:10 ` Andre Przywara
  2025-05-11  1:10 ` [PATCH v2 5/6] arm64: dts: allwinner: a100: add Liontron H-A133L board support Andre Przywara
  2025-05-11  1:10 ` [PATCH v2 6/6] sunxi: add support for Liontron H-A133L board Andre Przywara
  5 siblings, 0 replies; 15+ messages in thread
From: Andre Przywara @ 2025-05-11  1:10 UTC (permalink / raw)
  To: u-boot
  Cc: Jagan Teki, Cody Eksal, Philippe Simons, Sumit Garg, linux-sunxi,
	Tom Rini, Jernej Skrabec

The manual for the Allwinner A133 SoC mentions that the maximum
supported MMC frequency is 150 MHz, for all of the MMC devices.

Describe that in the DT entry, to help drivers setting the right
interface frequency.

Fixes: fcfbb8d9ec58 ("arm64: allwinner: a100: Add MMC related nodes")
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Link: https://patch.msgid.link/20250505202416.23753-1-andre.przywara@arm.com
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
 dts/upstream/src/arm64/allwinner/sun50i-a100.dtsi | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/dts/upstream/src/arm64/allwinner/sun50i-a100.dtsi b/dts/upstream/src/arm64/allwinner/sun50i-a100.dtsi
index f9f6fea03b7..bd366389b23 100644
--- a/dts/upstream/src/arm64/allwinner/sun50i-a100.dtsi
+++ b/dts/upstream/src/arm64/allwinner/sun50i-a100.dtsi
@@ -252,6 +252,7 @@
 			interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>;
 			pinctrl-names = "default";
 			pinctrl-0 = <&mmc0_pins>;
+			max-frequency = <150000000>;
 			status = "disabled";
 			#address-cells = <1>;
 			#size-cells = <0>;
@@ -267,6 +268,7 @@
 			interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>;
 			pinctrl-names = "default";
 			pinctrl-0 = <&mmc1_pins>;
+			max-frequency = <150000000>;
 			status = "disabled";
 			#address-cells = <1>;
 			#size-cells = <0>;
@@ -282,6 +284,7 @@
 			interrupts = <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>;
 			pinctrl-names = "default";
 			pinctrl-0 = <&mmc2_pins>;
+			max-frequency = <150000000>;
 			status = "disabled";
 			#address-cells = <1>;
 			#size-cells = <0>;
-- 
2.46.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 5/6] arm64: dts: allwinner: a100: add Liontron H-A133L board support
  2025-05-11  1:09 [PATCH v2 0/6] sunxi: Allwinner A133 SoC support Andre Przywara
                   ` (3 preceding siblings ...)
  2025-05-11  1:10 ` [PATCH v2 4/6] arm64: dts: allwinner: a100: set maximum MMC frequency Andre Przywara
@ 2025-05-11  1:10 ` Andre Przywara
  2025-05-11  1:10 ` [PATCH v2 6/6] sunxi: add support for Liontron H-A133L board Andre Przywara
  5 siblings, 0 replies; 15+ messages in thread
From: Andre Przywara @ 2025-05-11  1:10 UTC (permalink / raw)
  To: u-boot
  Cc: Jagan Teki, Cody Eksal, Philippe Simons, Sumit Garg, linux-sunxi,
	Tom Rini, Jernej Skrabec

The H-A133L board is an industrial development board made by Liontron.
It contains a number of dedicated JST connectors, to connect external
peripherals. It features:

- Allwinner A133 SoC (4 * Arm Cortex-A53 cores at up to 1.6 GHz)
- 1 GiB, 2 GiB or 4 GiB of LPDDR4 DRAM
- between 16 and 128 GiB eMMC flash
- AXP707 PMIC (compatible to AXP803)
- 100 Mbit/s RJ45 Ethernet socket, using an JLSemi JL1101 PHY
- XR829 WIFI+Bluetooth chip
- 2 * USB 2.0 USB-A ports, plus three sets of USB pins on connectors
  (connected via a USB hub connected to USB1 on the SoC)
- microSD card slot
- 3.5mm A/V port
- 12V power supply
- connectors for an LVDS or MIPI-DSI panel

Add the devicetree describing the board's peripherals and their
connections.

Despite being a devboard, the manufacturer does not publish a schematic
(I asked), so the PMIC rail assignments were bases on BSP dumps,
educated guesses and some experimentation. Dropping the always-on
property from any of the rails carrying it will make the board hang as
soon as the kernel turns off unused regulators.

Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
 .../sun50i-a133-liontron-h-a133l.dts          | 211 ++++++++++++++++++
 1 file changed, 211 insertions(+)
 create mode 100644 dts/upstream/src/arm64/allwinner/sun50i-a133-liontron-h-a133l.dts

diff --git a/dts/upstream/src/arm64/allwinner/sun50i-a133-liontron-h-a133l.dts b/dts/upstream/src/arm64/allwinner/sun50i-a133-liontron-h-a133l.dts
new file mode 100644
index 00000000000..e71779578d3
--- /dev/null
+++ b/dts/upstream/src/arm64/allwinner/sun50i-a133-liontron-h-a133l.dts
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2025 Arm Ltd.
+ */
+
+/dts-v1/;
+
+#include "sun50i-a100.dtsi"
+#include "sun50i-a100-cpu-opp.dtsi"
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/leds/common.h>
+
+/{
+	model = "Liontron H-A133L";
+	compatible = "liontron,h-a133l", "allwinner,sun50i-a100";
+
+	aliases {
+		serial0 = &uart0;
+	};
+
+	chosen {
+		stdout-path = "serial0:115200n8";
+	};
+
+	leds {
+		compatible = "gpio-leds";
+
+		led {
+			function = LED_FUNCTION_POWER;
+			color = <LED_COLOR_ID_BLUE>;
+			gpios = <&pio 7 16 GPIO_ACTIVE_LOW>; /* PH16 */
+		};
+	};
+
+	reg_vcc5v: vcc5v {
+		/* board wide 5V supply from a 12V->5V regulator */
+		compatible = "regulator-fixed";
+		regulator-name = "vcc-5v";
+		regulator-min-microvolt = <5000000>;
+		regulator-max-microvolt = <5000000>;
+		regulator-always-on;
+	};
+
+	reg_usb1_vbus: regulator-usb1-vbus {
+		compatible = "regulator-fixed";
+		regulator-name = "usb1-vbus";
+		regulator-min-microvolt = <5000000>;
+		regulator-max-microvolt = <5000000>;
+		vin-supply = <&reg_vcc5v>;
+		enable-active-high;
+		gpio = <&r_pio 0 8 GPIO_ACTIVE_HIGH>; /* PL8 */
+	};
+};
+
+&cpu0 {
+	cpu-supply = <&reg_dcdc2>;
+};
+
+&ehci0 {
+	status = "okay";
+};
+
+&ehci1 {
+	status = "okay";
+};
+
+&mmc0 {
+	vmmc-supply = <&reg_dcdc1>;
+	cd-gpios = <&pio 5 6 GPIO_ACTIVE_LOW>; /* PF6 */
+	bus-width = <4>;
+	status = "okay";
+};
+
+&mmc2 {
+	vmmc-supply = <&reg_dcdc1>;
+	vqmmc-supply = <&reg_eldo1>;
+	cap-mmc-hw-reset;
+	non-removable;
+	bus-width = <8>;
+	mmc-ddr-1_8v;
+	mmc-hs200-1_8v;
+	status = "okay";
+};
+
+&ohci0 {
+	status = "okay";
+};
+
+&ohci1 {
+	status = "okay";
+};
+
+&pio {
+	vcc-pb-supply = <&reg_dcdc1>;
+	vcc-pc-supply = <&reg_eldo1>;
+	vcc-pf-supply = <&reg_dcdc1>;
+	vcc-ph-supply = <&reg_dcdc1>;
+};
+
+&r_i2c0 {
+	status = "okay";
+
+	axp803: pmic@34 {
+		compatible = "x-powers,axp803";
+		reg = <0x34>;
+		interrupt-parent = <&r_intc>;
+		interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
+	};
+};
+
+#include "axp803.dtsi"
+
+&ac_power_supply {
+	status = "okay";
+};
+
+&reg_aldo1 {
+	regulator-always-on;
+	regulator-min-microvolt = <1800000>;
+	regulator-max-microvolt = <1800000>;
+	regulator-name = "vcc-codec-avcc";
+};
+
+&reg_aldo2 {
+	regulator-always-on;
+	regulator-min-microvolt = <1800000>;
+	regulator-max-microvolt = <1800000>;
+	regulator-name = "vcc-dram-1";
+};
+
+&reg_aldo3 {
+	regulator-always-on;
+	regulator-min-microvolt = <3300000>;
+	regulator-max-microvolt = <3300000>;
+	regulator-name = "vcc-usb-pl";
+};
+
+&reg_dcdc1 {
+	regulator-always-on;
+	regulator-min-microvolt = <3300000>;
+	regulator-max-microvolt = <3300000>;
+	regulator-name = "vcc-io-usb-pd-emmc";
+};
+
+&reg_dcdc2 {
+	regulator-always-on;
+	regulator-min-microvolt = <810000>;
+	regulator-max-microvolt = <1200000>;
+	regulator-name = "vdd-cpux";
+};
+
+&reg_dcdc3 {
+	regulator-always-on;
+	regulator-min-microvolt = <900000>;
+	regulator-max-microvolt = <900000>;
+	regulator-name = "vdd-usb-cpus";
+};
+
+&reg_dcdc4 {
+	regulator-always-on;
+	regulator-min-microvolt = <950000>;
+	regulator-max-microvolt = <950000>;
+	regulator-name = "vdd-sys";
+};
+
+&reg_dcdc5 {
+	regulator-always-on;
+	regulator-min-microvolt = <1100000>;
+	regulator-max-microvolt = <1100000>;
+	regulator-name = "vcc-dram";
+};
+
+/* DCDC6 unused */
+/* DLDO3 unused */
+/* DLDO4 unused */
+
+&reg_eldo1 {
+	regulator-min-microvolt = <1800000>;
+	regulator-max-microvolt = <1800000>;
+	regulator-name = "vcc-pc-emmc";
+};
+
+/* ELDO2 unused */
+/* ELDO3 unused */
+
+&reg_fldo1 {
+	regulator-always-on;
+	regulator-min-microvolt = <900000>;
+	regulator-max-microvolt = <900000>;
+	regulator-name = "vdd-cpus-usb";
+};
+
+/* reg_drivevbus unused */
+/* dc1sw unused */
+
+&uart0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart0_pb_pins>;
+	status = "okay";
+};
+
+&usb_otg {
+	dr_mode = "host";       /* USB A type receptable, always powered */
+	status = "okay";
+};
+
+&usbphy {
+	status = "okay";
+	usb1_vbus-supply = <&reg_usb1_vbus>;
+};
-- 
2.46.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 6/6] sunxi: add support for Liontron H-A133L board
  2025-05-11  1:09 [PATCH v2 0/6] sunxi: Allwinner A133 SoC support Andre Przywara
                   ` (4 preceding siblings ...)
  2025-05-11  1:10 ` [PATCH v2 5/6] arm64: dts: allwinner: a100: add Liontron H-A133L board support Andre Przywara
@ 2025-05-11  1:10 ` Andre Przywara
  5 siblings, 0 replies; 15+ messages in thread
From: Andre Przywara @ 2025-05-11  1:10 UTC (permalink / raw)
  To: u-boot
  Cc: Jagan Teki, Cody Eksal, Philippe Simons, Sumit Garg, linux-sunxi,
	Tom Rini, Jernej Skrabec

The Liontron H-A133L is an industrial development board based on the
Allwinner A133 SoC. It uses LPDDR4 DRAM, eMMC, and an AXP707 PMIC.

Add a defconfig with the required DRAM settings.

Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
 configs/liontron-h-a133l_defconfig | 37 ++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)
 create mode 100644 configs/liontron-h-a133l_defconfig

diff --git a/configs/liontron-h-a133l_defconfig b/configs/liontron-h-a133l_defconfig
new file mode 100644
index 00000000000..4b769768e5f
--- /dev/null
+++ b/configs/liontron-h-a133l_defconfig
@@ -0,0 +1,37 @@
+CONFIG_ARM=y
+CONFIG_ARCH_SUNXI=y
+CONFIG_DEFAULT_DEVICE_TREE="allwinner/sun50i-a133-liontron-h-a133l"
+CONFIG_SPL=y
+CONFIG_DRAM_SUNXI_DX_ODT=0x7070707
+CONFIG_DRAM_SUNXI_DX_DRI=0xd0d0d0d
+CONFIG_DRAM_SUNXI_CA_DRI=0xe0e
+CONFIG_DRAM_SUNXI_PARA0=0xd0a050c
+CONFIG_DRAM_SUNXI_MR11=0x4
+CONFIG_DRAM_SUNXI_MR12=0x72
+CONFIG_DRAM_SUNXI_MR13=0x0
+CONFIG_DRAM_SUNXI_MR14=0x7
+CONFIG_DRAM_SUNXI_TPR1=0x26
+CONFIG_DRAM_SUNXI_TPR2=0x6060606
+CONFIG_DRAM_SUNXI_TPR3=0x84040404
+CONFIG_DRAM_SUNXI_TPR6=0x48000000
+CONFIG_DRAM_SUNXI_TPR10=0x273333
+CONFIG_DRAM_SUNXI_TPR11=0x231d151c
+CONFIG_DRAM_SUNXI_TPR12=0x1212110e
+CONFIG_DRAM_SUNXI_TPR13=0x7521
+CONFIG_DRAM_SUNXI_TPR14=0x2023211f
+CONFIG_MACH_SUN50I_A133=y
+CONFIG_DRAM_CLK=792
+CONFIG_MMC_SUNXI_SLOT_EXTRA=2
+CONFIG_SUNXI_DRAM_A133_LPDDR4=y
+CONFIG_R_I2C_ENABLE=y
+# CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set
+CONFIG_SPL_I2C=y
+CONFIG_SPL_SYS_I2C_LEGACY=y
+CONFIG_SYS_I2C_MVTWSI=y
+CONFIG_SYS_I2C_SLAVE=0x7f
+CONFIG_SYS_I2C_SPEED=400000
+CONFIG_AXP803_POWER=y
+CONFIG_AXP_DCDC5_VOLT=1100
+CONFIG_SUPPORT_EMMC_BOOT=y
+CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_OHCI_HCD=y
-- 
2.46.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 1/6] sunxi: A133: add DRAM init code
  2025-05-11  1:09 ` [PATCH v2 1/6] sunxi: A133: add DRAM init code Andre Przywara
@ 2025-05-11  9:23   ` Andre Przywara
  2025-05-12  9:21   ` Quentin Schulz
  2025-05-12 15:12   ` Cody Eksal
  2 siblings, 0 replies; 15+ messages in thread
From: Andre Przywara @ 2025-05-11  9:23 UTC (permalink / raw)
  To: Cody Eksal, Jernej Skrabec
  Cc: u-boot, Jagan Teki, Philippe Simons, Sumit Garg, linux-sunxi,
	Tom Rini

On Sun, 11 May 2025 02:09:58 +0100
Andre Przywara <andre.przywara@arm.com> wrote:

> From: Cody Eksal <masterr3c0rd@epochal.quest>
> 
> This adds preliminary support for the DRAM controller in the Allwinner
> A100/A133 SoCs.
> This is work in progress, and has rough edges, but works on at least
> three different boards. It contains support for DDR4 and LPDDR4.
> 
> [Andre: formatting fixes, adapt to mainline, drop unused parameters,
> 	remove struct struct sunxi_mctl_com_reg, hardcode MR registers,
> 	switch to mctl_check_pattern(), remove simple DRAM check]

I pushed a (just updated) branch to my github which has those changes
split up in separate patches, in case you want to track what has changed
exactly:
https://github.com/apritzel/u-boot/commits/a133-dram-WIP/

Cheers,
Andre


> ---
>  .../include/asm/arch-sunxi/cpu_sun50i_h6.h    |    4 +
>  arch/arm/include/asm/arch-sunxi/dram.h        |    2 +
>  .../include/asm/arch-sunxi/dram_sun50i_a133.h |  230 ++++
>  arch/arm/mach-sunxi/Kconfig                   |  104 +-
>  arch/arm/mach-sunxi/Makefile                  |    2 +
>  arch/arm/mach-sunxi/dram_sun50i_a133.c        | 1204 +++++++++++++++++
>  arch/arm/mach-sunxi/dram_timings/Makefile     |    2 +
>  arch/arm/mach-sunxi/dram_timings/a133_ddr4.c  |   80 ++
>  .../arm/mach-sunxi/dram_timings/a133_lpddr4.c |  102 ++
>  9 files changed, 1722 insertions(+), 8 deletions(-)
>  create mode 100644 arch/arm/include/asm/arch-sunxi/dram_sun50i_a133.h
>  create mode 100644 arch/arm/mach-sunxi/dram_sun50i_a133.c
>  create mode 100644 arch/arm/mach-sunxi/dram_timings/a133_ddr4.c
>  create mode 100644 arch/arm/mach-sunxi/dram_timings/a133_lpddr4.c
> 
> diff --git a/arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h
> index 8a3f465545a..2a9b086991c 100644
> --- a/arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h
> +++ b/arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h
> @@ -29,6 +29,10 @@
>  #define SUNXI_DRAM_COM_BASE		0x047FA000
>  #define SUNXI_DRAM_CTL0_BASE		0x047FB000
>  #define SUNXI_DRAM_PHY0_BASE		0x04800000
> +#elif CONFIG_MACH_SUN50I_A133
> +#define SUNXI_DRAM_COM_BASE		0x04810000
> +#define SUNXI_DRAM_CTL0_BASE		0x04820000
> +#define SUNXI_DRAM_PHY0_BASE		0x04830000
>  #endif
>  
>  #define SUNXI_TWI0_BASE			0x05002000
> diff --git a/arch/arm/include/asm/arch-sunxi/dram.h b/arch/arm/include/asm/arch-sunxi/dram.h
> index 9d21b492418..0708ae3ee3b 100644
> --- a/arch/arm/include/asm/arch-sunxi/dram.h
> +++ b/arch/arm/include/asm/arch-sunxi/dram.h
> @@ -31,6 +31,8 @@
>  #include <asm/arch/dram_sun50i_h6.h>
>  #elif defined(CONFIG_MACH_SUN50I_H616)
>  #include <asm/arch/dram_sun50i_h616.h>
> +#elif defined(CONFIG_DRAM_SUN50I_A133)
> +#include <asm/arch/dram_sun50i_a133.h>
>  #elif defined(CONFIG_MACH_SUNIV)
>  #include <asm/arch/dram_suniv.h>
>  #else
> diff --git a/arch/arm/include/asm/arch-sunxi/dram_sun50i_a133.h b/arch/arm/include/asm/arch-sunxi/dram_sun50i_a133.h
> new file mode 100644
> index 00000000000..a5fc6ad3656
> --- /dev/null
> +++ b/arch/arm/include/asm/arch-sunxi/dram_sun50i_a133.h
> @@ -0,0 +1,230 @@
> +// SPDX-License-Identifier:	GPL-2.0+
> +/*
> + * A133 dram controller register and constant defines
> + *
> + * (C) Copyright 2024  MasterR3C0RD <masterr3c0rd@epochal.quest>
> + */
> +
> +#ifndef _SUNXI_DRAM_SUN50I_A133_H
> +#define _SUNXI_DRAM_SUN50I_A133_H
> +
> +#include <linux/bitops.h>
> +
> +enum sunxi_dram_type {
> +	SUNXI_DRAM_TYPE_DDR3 = 3,
> +	SUNXI_DRAM_TYPE_DDR4,
> +	SUNXI_DRAM_TYPE_LPDDR3 = 7,
> +	SUNXI_DRAM_TYPE_LPDDR4
> +};
> +
> +static inline int ns_to_t(int nanoseconds)
> +{
> +	const unsigned int ctrl_freq = CONFIG_DRAM_CLK / 2;
> +
> +	return DIV_ROUND_UP(ctrl_freq * nanoseconds, 1000);
> +}
> +
> +/* MBUS part is largely the same as in H6, except for one special register */
> +#define MCTL_COM_UNK_008	0x008
> +/* NOTE: This register has the same importance as mctl_ctl->clken in H616 */
> +#define MCTL_COM_MAER0		0x020
> +
> +/*
> + * Controller registers seems to be the same or at least very similar
> + * to those in H6.
> + */
> +struct sunxi_mctl_ctl_reg {
> +	u32 mstr; 			/* 0x000 */
> +	u32 statr; 			/* 0x004 unused */
> +	u32 mstr1; 			/* 0x008 unused */
> +	u32 clken; 			/* 0x00c */
> +	u32 mrctrl0; 			/* 0x010 unused */
> +	u32 mrctrl1; 			/* 0x014 unused */
> +	u32 mrstatr; 			/* 0x018 unused */
> +	u32 mrctrl2; 			/* 0x01c unused */
> +	u32 derateen; 			/* 0x020 unused */
> +	u32 derateint; 			/* 0x024 unused */
> +	u8 reserved_0x028[8]; 		/* 0x028 */
> +	u32 pwrctl; 			/* 0x030 unused */
> +	u32 pwrtmg; 			/* 0x034 unused */
> +	u32 hwlpctl; 			/* 0x038 unused */
> +	u8 reserved_0x03c[20];		/* 0x03c */
> +	u32 rfshctl0;			/* 0x050 unused */
> +	u32 rfshctl1;			/* 0x054 unused */
> +	u8 reserved_0x058[8];		/* 0x05c */
> +	u32 rfshctl3;			/* 0x060 */
> +	u32 rfshtmg;			/* 0x064 */
> +	u8 reserved_0x068[104];		/* 0x068 */
> +	u32 init[8];			/* 0x0d0 */
> +	u32 dimmctl;			/* 0x0f0 unused */
> +	u32 rankctl;			/* 0x0f4 */
> +	u8 reserved_0x0f8[8];		/* 0x0f8 */
> +	u32 dramtmg[17];		/* 0x100 */
> +	u8 reserved_0x144[60];		/* 0x144 */
> +	u32 zqctl[3];			/* 0x180 */
> +	u32 zqstat;			/* 0x18c unused */
> +	u32 dfitmg0;			/* 0x190 */
> +	u32 dfitmg1;			/* 0x194 */
> +	u32 dfilpcfg[2];		/* 0x198 unused */
> +	u32 dfiupd[3];			/* 0x1a0 */
> +	u32 reserved_0x1ac;		/* 0x1ac */
> +	u32 dfimisc;			/* 0x1b0 */
> +	u32 dfitmg2;			/* 0x1b4 unused */
> +	u32 dfitmg3;			/* 0x1b8 unused */
> +	u32 dfistat;			/* 0x1bc */
> +	u32 dbictl;			/* 0x1c0 */
> +	u8 reserved_0x1c4[60];		/* 0x1c4 */
> +	u32 addrmap[12];		/* 0x200 */
> +	u8 reserved_0x230[16];		/* 0x230 */
> +	u32 odtcfg;			/* 0x240 */
> +	u32 odtmap;			/* 0x244 */
> +	u8 reserved_0x248[8];		/* 0x248 */
> +	u32 sched[2];			/* 0x250 */
> +	u8 reserved_0x258[180]; 	/* 0x258 */
> +	u32 dbgcmd;			/* 0x30c unused */
> +	u32 dbgstat;			/* 0x310 unused */
> +	u8 reserved_0x314[12];		/* 0x314 */
> +	u32 swctl;			/* 0x320 */
> +	u32 swstat;			/* 0x324 */
> +	u8 reserved_0x328[7768];	/* 0x328 */
> +	u32 unk_0x2180;			/* 0x2180 */
> +	u8 reserved_0x2184[188];	/* 0x2184 */
> +	u32 unk_0x2240;			/* 0x2240 */
> +	u8 reserved_0x2244[3900];	/* 0x2244 */
> +	u32 unk_0x3180;			/* 0x3180 */
> +	u8 reserved_0x3184[188];	/* 0x3184 */
> +	u32 unk_0x3240;			/* 0x3240 */
> +	u8 reserved_0x3244[3900];	/* 0x3244 */
> +	u32 unk_0x4180;			/* 0x4180 */
> +	u8 reserved_0x4184[188];	/* 0x4184 */
> +	u32 unk_0x4240;			/* 0x4240 */
> +};
> +
> +check_member(sunxi_mctl_ctl_reg, swstat, 0x324);
> +check_member(sunxi_mctl_ctl_reg, unk_0x4240, 0x4240);
> +
> +#define MSTR_DEVICETYPE_DDR3	BIT(0)
> +#define MSTR_DEVICETYPE_LPDDR2	BIT(2)
> +#define MSTR_DEVICETYPE_LPDDR3	BIT(3)
> +#define MSTR_DEVICETYPE_DDR4	BIT(4)
> +#define MSTR_DEVICETYPE_LPDDR4	BIT(5)
> +#define MSTR_DEVICETYPE_MASK	GENMASK(5, 0)
> +#define MSTR_GEARDOWNMODE	BIT(0)		/* Same as MSTR_DEVICETYPE_DDR3, only used for DDR4 */
> +#define MSTR_2TMODE		BIT(10)
> +#define MSTR_BUSWIDTH_FULL	(0 << 12)
> +#define MSTR_BUSWIDTH_HALF	(1 << 12)
> +#define MSTR_ACTIVE_RANKS(x)	(((x == 1) ? 3 : 1) << 24)
> +#define MSTR_BURST_LENGTH(x)	(((x) >> 1) << 16)
> +#define MSTR_DEVICECONFIG_X32	(3 << 30)
> +
> +#define TPR10_CA_BIT_DELAY	BIT(16)
> +#define TPR10_DX_BIT_DELAY0	BIT(17)
> +#define TPR10_DX_BIT_DELAY1	BIT(18)
> +#define TPR10_WRITE_LEVELING	BIT(20)
> +#define TPR10_READ_CALIBRATION	BIT(21)
> +#define TPR10_READ_TRAINING	BIT(22)
> +#define TPR10_WRITE_TRAINING	BIT(23)
> +
> +/* MRCTRL constants */
> +#define MRCTRL0_MR_RANKS_ALL	(3 << 4)
> +#define MRCTRL0_MR_ADDR(x)	(x << 12)
> +#define MRCTRL0_MR_WR		BIT(31)
> +
> +#define MRCTRL1_MR_ADDR(x)	(x << 8)
> +#define MRCTRL1_MR_DATA(x)	(x)
> +
> +/* ADDRMAP constants */
> +#define ADDRMAP_DISABLED_3F_B(b)	(0x3f + b)
> +#define ADDRMAP_DISABLED_1F_B(b)	(0x1f + b)
> +#define ADDRMAP_DISABLED_0F_B(b)	(0x0f + b)
> +
> +#define _ADDRMAP_VALUE(a,x,b)		(((a) - b) << (x * 8))
> +
> +/*
> + * Bx = internal base
> + * The selected HIF address bit for each address bit is determined
> + * by adding the internal base to the value of each field
> + * */
> +
> +#define ADDRMAP0_CS0_B6(v)	_ADDRMAP_VALUE(v, 0, 6)
> +
> +#define ADDRMAP1_BANK0_B2(v) 	_ADDRMAP_VALUE(v, 0, 2)
> +#define ADDRMAP1_BANK1_B3(v)	_ADDRMAP_VALUE(v, 1, 3)
> +#define ADDRMAP1_BANK2_B4(v)	_ADDRMAP_VALUE(v, 2, 4)
> +
> +#define ADDRMAP2_COL2_B2(v)	_ADDRMAP_VALUE(v, 0, 2)
> +#define ADDRMAP2_COL3_B3(v)	_ADDRMAP_VALUE(v, 1, 3)
> +#define ADDRMAP2_COL4_B4(v)	_ADDRMAP_VALUE(v, 2, 4)
> +#define ADDRMAP2_COL5_B5(v)	_ADDRMAP_VALUE(v, 3, 5)
> +
> +#define ADDRMAP3_COL6_B6(v)	_ADDRMAP_VALUE(v, 0, 6)
> +#define ADDRMAP3_COL7_B7(v)	_ADDRMAP_VALUE(v, 1, 7)
> +#define ADDRMAP3_COL8_B8(v)	_ADDRMAP_VALUE(v, 2, 8)
> +#define ADDRMAP3_COL9_B9(v)	_ADDRMAP_VALUE(v, 3, 9)
> +
> +#define ADDRMAP4_COL10_B10(v)	_ADDRMAP_VALUE(v, 0, 10)
> +#define ADDRMAP4_COL11_B11(v)	_ADDRMAP_VALUE(v, 1, 11)
> +
> +#define ADDRMAP5_ROW0_B6(v)	_ADDRMAP_VALUE(v, 0, 6)
> +#define ADDRMAP5_ROW1_B7(v)	_ADDRMAP_VALUE(v, 1, 7)
> +#define ADDRMAP5_ROW2_10_B8(v)	_ADDRMAP_VALUE(v, 2, 8)
> +#define ADDRMAP5_ROW11_B17(v)	_ADDRMAP_VALUE(v, 3, 17)
> +
> +#define ADDRMAP6_ROW12_B18(v)	_ADDRMAP_VALUE(v, 0, 18)
> +#define ADDRMAP6_ROW13_B19(v)	_ADDRMAP_VALUE(v, 1, 19)
> +#define ADDRMAP6_ROW14_B20(v)	_ADDRMAP_VALUE(v, 2, 20)
> +#define ADDRMAP6_ROW15_B21(v)	_ADDRMAP_VALUE(v, 3, 21)
> +
> +#define ADDRMAP7_ROW16_B22(v)	_ADDRMAP_VALUE(v, 0, 22)
> +#define ADDRMAP7_ROW17_B23(v)	_ADDRMAP_VALUE(v, 1, 23)
> +
> +#define ADDRMAP8_BG0_B2(v)	_ADDRMAP_VALUE(v, 0, 2)
> +#define ADDRMAP8_BG1_B3(v)	_ADDRMAP_VALUE(v, 1, 3)
> +
> +/* These are only used if ADDRMAP5_ROW_BITS_2_10 = ADDRMAP_DISABLED_0F */
> +#define ADDRMAP9_ROW2_B8(v)	_ADDRMAP_VALUE(v, 0, 8)
> +#define ADDRMAP9_ROW3_B9(v)	_ADDRMAP_VALUE(v, 1, 9)
> +#define ADDRMAP9_ROW4_B10(v)	_ADDRMAP_VALUE(v, 2, 10)
> +#define ADDRMAP9_ROW5_B11(v)	_ADDRMAP_VALUE(v, 3, 11)
> +
> +#define ADDRMAP10_ROW6_B12(v)	_ADDRMAP_VALUE(v, 0, 12)
> +#define ADDRMAP10_ROW7_B13(v)	_ADDRMAP_VALUE(v, 1, 13)
> +#define ADDRMAP10_ROW8_B14(v)	_ADDRMAP_VALUE(v, 2, 14)
> +#define ADDRMAP10_ROW9_B15(v)	_ADDRMAP_VALUE(v, 3, 15)
> +
> +#define ADDRMAP11_ROW10_B16(v)	_ADDRMAP_VALUE(v, 0, 16)
> +
> +struct dram_para {
> +	uint32_t clk;
> +	enum sunxi_dram_type type;
> +	uint32_t dx_odt;
> +	uint32_t dx_dri;
> +	uint32_t ca_dri;
> +	uint32_t para0;
> +	uint32_t mr11;
> +	uint32_t mr12;
> +	uint32_t mr13;
> +	uint32_t mr14;
> +	uint32_t tpr1;
> +	uint32_t tpr2;
> +	uint32_t tpr3;
> +	uint32_t tpr6;
> +	uint32_t tpr10;
> +	uint32_t tpr11;
> +	uint32_t tpr12;
> +	uint32_t tpr13;
> +	uint32_t tpr14;
> +};
> +
> +void mctl_set_timing_params(const struct dram_para *para);
> +
> +struct dram_config {
> +	u8 cols;		/* Column bits */
> +	u8 rows;		/* Row bits */
> +	u8 ranks;		/* Rank bits (different from H616!) */
> +	u8 banks;		/* Bank bits */
> +	u8 bankgrps;		/* Bank group bits */
> +	u8 bus_full_width;	/* 1 = x32, 0 = x16 */
> +};
> +
> +#endif /* _SUNXI_DRAM_SUN50I_A133_H */
> diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig
> index c3718126cec..98b947f6e33 100644
> --- a/arch/arm/mach-sunxi/Kconfig
> +++ b/arch/arm/mach-sunxi/Kconfig
> @@ -51,7 +51,13 @@ config DRAM_SUN50I_H616
>  	  Select this dram controller driver for some sun50i platforms,
>  	  like H616.
>  
> -if DRAM_SUN50I_H616
> +config DRAM_SUN50I_A133
> +	bool
> +	help
> +	  Select this dram controller driver for some sun50i platforms,
> +	  like A133.
> +
> +if DRAM_SUN50I_H616 || DRAM_SUN50I_A133
>  config DRAM_SUNXI_DX_ODT
>  	hex "DRAM DX ODT parameter"
>  	help
> @@ -73,18 +79,64 @@ config DRAM_SUNXI_ODT_EN
>  	help
>  	  ODT EN value from vendor DRAM settings.
>  
> +config DRAM_SUNXI_PARA0
> +	hex "DRAM PARA0 parameter"
> +	depends on DRAM_SUN50I_A133
> +	help
> +	  PARA0 value from vendor DRAM settings.
> +
> +config DRAM_SUNXI_MR11
> +	hex "DRAM MR11 parameter"
> +	depends on DRAM_SUN50I_A133
> +	default 0x0
> +	help
> +	  MR11 value from vendor DRAM settings.
> +
> +config DRAM_SUNXI_MR12
> +	hex "DRAM MR12 parameter"
> +	depends on DRAM_SUN50I_A133
> +	default 0x0
> +	help
> +	  MR12 value from vendor DRAM settings.
> +
> +config DRAM_SUNXI_MR13
> +	hex "DRAM MR13 parameter"
> +	depends on DRAM_SUN50I_A133
> +	default 0x0
> +	help
> +	  MR13 value from vendor DRAM settings.
> +
> +config DRAM_SUNXI_MR14
> +	hex "DRAM MR14 parameter"
> +	depends on DRAM_SUN50I_A133
> +	default 0x0
> +	help
> +	  MR14 value from vendor DRAM settings.
> +
>  config DRAM_SUNXI_TPR0
>  	hex "DRAM TPR0 parameter"
>  	default 0x0
>  	help
>  	  TPR0 value from vendor DRAM settings.
>  
> +config DRAM_SUNXI_TPR1
> +	hex "DRAM TPR1 parameter"
> +	default 0x0
> +	help
> +	  TPR1 value from vendor DRAM settings.
> +
>  config DRAM_SUNXI_TPR2
>  	hex "DRAM TPR2 parameter"
>  	default 0x0
>  	help
>  	  TPR2 value from vendor DRAM settings.
>  
> +config DRAM_SUNXI_TPR3
> +	hex "DRAM TPR3 parameter"
> +	default 0x0
> +	help
> +	  TPR3 value from vendor DRAM settings.
> +
>  config DRAM_SUNXI_TPR6
>  	hex "DRAM TPR6 parameter"
>  	default 0x3300c080
> @@ -109,6 +161,20 @@ config DRAM_SUNXI_TPR12
>  	help
>  	  TPR12 value from vendor DRAM settings.
>  
> +config DRAM_SUNXI_TPR13
> +	hex "DRAM TPR13 parameter"
> +	depends on DRAM_SUN50I_A133
> +	default 0x0
> +	help
> +	  TPR13 value from vendor DRAM settings.
> +
> +config DRAM_SUNXI_TPR14
> +	hex "DRAM TPR14 parameter"
> +	depends on DRAM_SUN50I_A133
> +	default 0x0
> +	help
> +	  TPR14 value from vendor DRAM settings.
> +
>  choice
>  	prompt "DRAM PHY pin mapping selection"
>  	default DRAM_SUNXI_PHY_ADDR_MAP_0
> @@ -116,7 +182,8 @@ choice
>  config DRAM_SUNXI_PHY_ADDR_MAP_0
>  	bool "DRAM PHY address map 0"
>  	help
> -	  This pin mapping selection should be used by the H313, H616, H618.
> +	  This pin mapping selection should be used by the H313, H616, H618,
> +	  and A133, R818 SoCs.
>  
>  config DRAM_SUNXI_PHY_ADDR_MAP_1
>  	bool "DRAM PHY address map 1"
> @@ -500,7 +567,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 || DRAM_SUN50I_H6 || DRAM_SUN50I_H616
> +if SUNXI_DRAM_DW || DRAM_SUN50I_H6 || DRAM_SUN50I_H616 || DRAM_SUN50I_A133
>  config SUNXI_DRAM_DDR3
>  	bool
>  
> @@ -513,6 +580,9 @@ config SUNXI_DRAM_LPDDR3
>  config SUNXI_DRAM_LPDDR4
>  	bool
>  
> +config SUNXI_DRAM_DDR4
> +	bool
> +
>  choice
>  	prompt "DRAM Type and Timing"
>  	default SUNXI_DRAM_DDR3_1333 if !MACH_SUN8I_V3S
> @@ -521,6 +591,7 @@ choice
>  config SUNXI_DRAM_DDR3_1333
>  	bool "DDR3 1333"
>  	select SUNXI_DRAM_DDR3
> +	depends on !DRAM_SUN50I_A133
>  	---help---
>  	This option is the original only supported memory type, which suits
>  	many H3/H5/A64 boards available now.
> @@ -528,6 +599,7 @@ config SUNXI_DRAM_DDR3_1333
>  config SUNXI_DRAM_LPDDR3_STOCK
>  	bool "LPDDR3 with Allwinner stock configuration"
>  	select SUNXI_DRAM_LPDDR3
> +	depends on !DRAM_SUN50I_A133
>  	---help---
>  	This option is the LPDDR3 timing used by the stock boot0 by
>  	Allwinner.
> @@ -551,7 +623,7 @@ config SUNXI_DRAM_H6_DDR3_1333
>  config SUNXI_DRAM_H616_LPDDR3
>  	bool "LPDDR3 DRAM chips on the H616 DRAM controller"
>  	select SUNXI_DRAM_LPDDR3
> -	depends on DRAM_SUN50I_H616
> +	depends on DRAM_SUN50I_H616 || DRAM_SUN50I_A133
>  	help
>  	  This option is the LPDDR3 timing used by the stock boot0 by
>  	  Allwinner.
> @@ -559,7 +631,7 @@ config SUNXI_DRAM_H616_LPDDR3
>  config SUNXI_DRAM_H616_LPDDR4
>  	bool "LPDDR4 DRAM chips on the H616 DRAM controller"
>  	select SUNXI_DRAM_LPDDR4
> -	depends on DRAM_SUN50I_H616
> +	depends on DRAM_SUN50I_H616 || DRAM_SUN50I_A133
>  	help
>  	  This option is the LPDDR4 timing used by the stock boot0 by
>  	  Allwinner.
> @@ -567,11 +639,27 @@ config SUNXI_DRAM_H616_LPDDR4
>  config SUNXI_DRAM_H616_DDR3_1333
>  	bool "DDR3-1333 boot0 timings on the H616 DRAM controller"
>  	select SUNXI_DRAM_DDR3
> -	depends on DRAM_SUN50I_H616
> +	depends on DRAM_SUN50I_H616 || DRAM_SUN50I_A133
>  	help
>  	  This option is the DDR3 timing used by the boot0 on H616 TV boxes
>  	  which use a DDR3-1333 timing.
>  
> +config SUNXI_DRAM_A133_DDR4
> +	bool "DDR4 boot0 timings on the A133 DRAM controller"
> +	select SUNXI_DRAM_DDR4
> +	depends on DRAM_SUN50I_A133
> +	help
> +	  This option is the DDR4 timing used by the boot0 on A133 devices
> +	  which use a DDR4 timing.
> +
> +config SUNXI_DRAM_A133_LPDDR4
> +	bool "LPDDR4 boot0 timings on the A133 DRAM controller"
> +	select SUNXI_DRAM_LPDDR4
> +	depends on DRAM_SUN50I_A133
> +	help
> +	  This option is the LPDDR4 timing used by the boot0 on A133 devices
> +	  which use an LPDDR4 timing.
> +
>  config SUNXI_DRAM_DDR2_V3S
>  	bool "DDR2 found in V3s chip"
>  	select SUNXI_DRAM_DDR2
> @@ -599,7 +687,7 @@ config DRAM_CLK
>  		       MACH_SUN8I_V3S
>  	default 672 if MACH_SUN50I
>  	default 744 if MACH_SUN50I_H6
> -	default 720 if MACH_SUN50I_H616
> +	default 720 if MACH_SUN50I_H616 || MACH_SUN50I_A133
>  	---help---
>  	Set the dram clock speed, valid range 240 - 480 (prior to sun9i),
>  	must be a multiple of 24. For the sun9i (A80), the tested values
> @@ -616,7 +704,7 @@ endif
>  
>  config DRAM_ZQ
>  	int "sunxi dram zq value"
> -	depends on !MACH_SUN50I_H616
> +	depends on !MACH_SUN50I_H616 && !MACH_SUN50I_A133
>  	default 123 if MACH_SUN4I || MACH_SUN5I || MACH_SUN6I || \
>  		       MACH_SUN8I_A23 || MACH_SUN8I_A33 || MACH_SUN8I_A83T
>  	default 127 if MACH_SUN7I
> diff --git a/arch/arm/mach-sunxi/Makefile b/arch/arm/mach-sunxi/Makefile
> index a33cd5b0f07..8eff20b77bf 100644
> --- a/arch/arm/mach-sunxi/Makefile
> +++ b/arch/arm/mach-sunxi/Makefile
> @@ -45,4 +45,6 @@ obj-$(CONFIG_DRAM_SUN50I_H6)	+= dram_sun50i_h6.o dram_dw_helpers.o
>  obj-$(CONFIG_DRAM_SUN50I_H6)	+= dram_timings/
>  obj-$(CONFIG_DRAM_SUN50I_H616)	+= dram_sun50i_h616.o dram_dw_helpers.o
>  obj-$(CONFIG_DRAM_SUN50I_H616)	+= dram_timings/
> +obj-$(CONFIG_DRAM_SUN50I_A133)	+= dram_sun50i_a133.o
> +obj-$(CONFIG_DRAM_SUN50I_A133)	+= dram_timings/
>  endif
> diff --git a/arch/arm/mach-sunxi/dram_sun50i_a133.c b/arch/arm/mach-sunxi/dram_sun50i_a133.c
> new file mode 100644
> index 00000000000..a0fca3738f4
> --- /dev/null
> +++ b/arch/arm/mach-sunxi/dram_sun50i_a133.c
> @@ -0,0 +1,1204 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * sun50i A133 platform dram controller driver
> + *
> + * Controller and PHY appear to be quite similar to that of the H616;
> + * however certain offsets, timings, and other details are different enough that
> + * the original code does not work as expected. Some device flags and
> + * calibrations are not yet implemented, and configuration aside from DDR4
> + * have not been tested.
> + *
> + * (C) Copyright 2024 MasterR3C0RD <masterr3c0rd@epochal.quest>
> + *
> + * Uses code from H616 driver, which is
> + * (C) Copyright 2020 Jernej Skrabec <jernej.skrabec@siol.net>
> + *
> + */
> +
> +//#define DEBUG
> +
> +#include <asm/arch/clock.h>
> +#include <asm/arch/cpu.h>
> +#include <asm/arch/dram.h>
> +#include <asm/arch/prcm.h>
> +#include <asm/io.h>
> +#include <init.h>
> +#include <linux/bitops.h>
> +#include <linux/delay.h>
> +#include <log.h>
> +
> +#ifdef CONFIG_DRAM_SUNXI_PHY_ADDR_MAP_1
> +static const u8 phy_init[] = {
> +#ifdef CONFIG_SUNXI_DRAM_DDR3
> +	0x0c, 0x08, 0x19, 0x18, 0x10, 0x06, 0x0a, 0x03, 0x0e,
> +	0x00, 0x0b, 0x05, 0x09, 0x1a, 0x04, 0x13, 0x16, 0x11,
> +	0x01, 0x15, 0x0d, 0x07, 0x12, 0x17, 0x14, 0x02, 0x0f
> +#elif CONFIG_SUNXI_DRAM_DDR4
> +	0x19, 0x1a, 0x04, 0x12, 0x09, 0x06, 0x08, 0x0a, 0x16,
> +	0x17, 0x18, 0x0f, 0x0c, 0x13, 0x02, 0x05, 0x01, 0x11,
> +	0x0e, 0x00, 0x0b, 0x07, 0x03, 0x14, 0x15, 0x0d, 0x10
> +#elif CONFIG_SUNXI_DRAM_LPDDR3
> +	0x08, 0x03, 0x02, 0x00, 0x18, 0x19, 0x09, 0x01, 0x06,
> +	0x17, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
> +	0x12, 0x13, 0x14, 0x15, 0x16, 0x04, 0x05, 0x07, 0x1a
> +#elif CONFIG_SUNXI_DRAM_LPDDR4
> +	0x01, 0x05, 0x02, 0x00, 0x19, 0x03, 0x06, 0x07, 0x08,
> +	0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
> +	0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x04, 0x1a
> +#endif
> +};
> +#else
> +static const u8 phy_init[] = {
> +#ifdef CONFIG_SUNXI_DRAM_DDR3
> +	0x03, 0x19, 0x18, 0x02, 0x10, 0x15, 0x16, 0x07, 0x06,
> +	0x0e, 0x05, 0x08, 0x0d, 0x04, 0x17, 0x1a, 0x13, 0x11,
> +	0x12, 0x14, 0x00, 0x01, 0x0c, 0x0a, 0x09, 0x0b, 0x0f
> +#elif CONFIG_SUNXI_DRAM_DDR4
> +	0x13, 0x17, 0x0e, 0x01, 0x06, 0x12, 0x14, 0x07, 0x09,
> +	0x02, 0x0f, 0x00, 0x0d, 0x05, 0x16, 0x0c, 0x0a, 0x11,
> +	0x04, 0x03, 0x18, 0x15, 0x08, 0x10, 0x0b, 0x19, 0x1a
> +#elif CONFIG_SUNXI_DRAM_LPDDR3
> +	0x05, 0x06, 0x17, 0x02, 0x19, 0x18, 0x04, 0x07, 0x03,
> +	0x01, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
> +	0x12, 0x13, 0x14, 0x15, 0x16, 0x08, 0x09, 0x00, 0x1a
> +#elif CONFIG_SUNXI_DRAM_LPDDR4
> +	0x01, 0x03, 0x02, 0x19, 0x17, 0x00, 0x06, 0x07, 0x08,
> +	0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
> +	0x12, 0x13, 0x14, 0x15, 0x16, 0x04, 0x18, 0x05, 0x1a
> +#endif
> +};
> +#endif
> +
> +static void mctl_clk_init(u32 clk)
> +{
> +	void * const ccm = (void *)SUNXI_CCM_BASE;
> +
> +	/* Place all DRAM blocks into reset */
> +	clrbits_le32(ccm + CCU_H6_MBUS_CFG, MBUS_ENABLE);
> +	clrbits_le32(ccm + CCU_H6_MBUS_CFG, MBUS_RESET);
> +	clrbits_le32(ccm + CCU_H6_DRAM_GATE_RESET, BIT(GATE_SHIFT));
> +	clrbits_le32(ccm + CCU_H6_DRAM_GATE_RESET, BIT(RESET_SHIFT));
> +	clrbits_le32(ccm + CCU_H6_PLL5_CFG, CCM_PLL5_CTRL_EN);
> +	clrbits_le32(ccm + CCU_H6_DRAM_CLK_CFG, DRAM_MOD_RESET);
> +	udelay(5);
> +
> +	/* Set up PLL5 clock, used for DRAM */
> +	clrsetbits_le32(ccm + CCU_H6_PLL5_CFG, 0xff03,
> +			CCM_PLL5_CTRL_N((clk * 2) / 24) | CCM_PLL5_CTRL_EN);
> +	setbits_le32(ccm + CCU_H6_PLL5_CFG, BIT(24));
> +	clrsetbits_le32(ccm + CCU_H6_PLL5_CFG, 0x3,
> +			CCM_PLL5_LOCK_EN | CCM_PLL5_CTRL_EN | BIT(30));
> +	clrbits_le32(ccm + CCU_H6_PLL5_CFG, 0x3 | BIT(30));
> +	mctl_await_completion(ccm + CCU_H6_PLL5_CFG,
> +			      CCM_PLL5_LOCK, CCM_PLL5_LOCK);
> +
> +	/* Enable DRAM clock and gate*/
> +	clrbits_le32(ccm + CCU_H6_DRAM_CLK_CFG, BIT(24) | BIT(25));
> +	clrsetbits_le32(ccm + CCU_H6_DRAM_CLK_CFG, 0x1f, BIT(1) | BIT(0));
> +	setbits_le32(ccm + CCU_H6_DRAM_CLK_CFG, DRAM_CLK_UPDATE);
> +	setbits_le32(ccm + CCU_H6_DRAM_GATE_RESET, BIT(RESET_SHIFT));
> +	setbits_le32(ccm + CCU_H6_DRAM_GATE_RESET, BIT(GATE_SHIFT));
> +
> +	/* Re-enable MBUS and reset the DRAM module */
> +	setbits_le32(ccm + CCU_H6_MBUS_CFG, MBUS_RESET);
> +	setbits_le32(ccm + CCU_H6_MBUS_CFG, MBUS_ENABLE);
> +	setbits_le32(ccm + CCU_H6_DRAM_CLK_CFG, DRAM_MOD_RESET);
> +	udelay(5);
> +}
> +
> +static void mctl_set_odtmap(const struct dram_para *para,
> +			    const struct dram_config *config)
> +{
> +	struct sunxi_mctl_ctl_reg *mctl_ctl =
> +		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> +
> +	u32 val, temp1, temp2;
> +
> +	/* Set ODT/rank mappings*/
> +	if (config->bus_full_width)
> +		writel_relaxed(0x0201, &mctl_ctl->odtmap);
> +	else
> +		writel_relaxed(0x0303, &mctl_ctl->odtmap);
> +
> +	switch (para->type) {
> +	case SUNXI_DRAM_TYPE_DDR3:
> +		val = 0x06000400;
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR3:
> +		/* TODO: What's the purpose of these values? */
> +		temp1 = para->clk * 7 / 2000;
> +		if (para->clk < 400)
> +			temp2 = 0x3;
> +		else
> +			temp2 = 0x4;
> +
> +		val = 0x400 | (temp2 - temp1) << 16 | temp1 << 24;
> +		break;
> +	case SUNXI_DRAM_TYPE_DDR4:
> +		/* MR4: CS to CMD / ADDR Latency   and  write preamble */
> +		val = 0x400 | (0x000 << 10 & 0x70000) |
> +		      (((0x0000 >> 12) & 1) + 6) << 24;
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR4:
> +		val = 0x4000400;
> +		break;
> +	}
> +
> +	writel_relaxed(val, &mctl_ctl->odtcfg);
> +	/* Documented as ODTCFG_SHADOW */
> +	writel_relaxed(val, &mctl_ctl->unk_0x2240);
> +	/* Offset's interesting; additional undocumented shadows? */
> +	writel_relaxed(val, &mctl_ctl->unk_0x3240);
> +	writel_relaxed(val, &mctl_ctl->unk_0x4240);
> +}
> +
> +/*
> + * This function produces address mapping parameters, used internally by the
> + * controller to map address lines to HIF addresses. HIF addresses are word
> + * addresses, not byte addresses;
> + * In other words, DDR address 0x400 maps to HIF address 0x100.
> + *
> + * This implementation sets up a reasonable mapping where HIF address
> + * ordering (LSB->MSB) is as such:
> + * - Bank Groups
> + * - Columns
> + * - Banks
> + * - Rows
> + * - Ranks
> + *
> + * TODO: Handle 1.5GB + 3GB configurations. Info about these is stored in
> + * upper bits of TPR13 after autoscan in boot0, and then some extra logic
> + * happens in the address mapping
> + */
> +#define INITIAL_HIF_OFFSET 3
> +
> +static void mctl_set_addrmap(const struct dram_config *config)
> +{
> +	struct sunxi_mctl_ctl_reg *mctl_ctl =
> +		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> +
> +	u8 bankgrp_bits = config->bankgrps;
> +	u8 col_bits = config->cols;
> +	u8 bank_bits = config->banks;
> +	u8 row_bits = config->rows;
> +	u8 rank_bits = config->ranks;
> +
> +	unsigned int i, hif_offset, hif_bits[6];
> +
> +	/*
> +	 * When the bus is half width, we need to adjust address mapping,
> +	 * as COL[0] will be reallocated as part of the byte address,
> +	 * offsetting the column address mapping values by 1
> +	 */
> +	if (!config->bus_full_width)
> +		col_bits--;
> +
> +	/* Match boot0's DRAM requirements */
> +	if (bankgrp_bits > 2)
> +		panic("invalid dram configuration (bankgrps_bits = %d)",
> +		      bankgrp_bits);
> +	if (col_bits < 8 || col_bits > 12)
> +		panic("invalid dram configuration (col_bits = %d)", col_bits);
> +
> +	if (bank_bits < 2 || bank_bits > 3)
> +		panic("invalid dram configuration (bank_bits = %d)", bank_bits);
> +
> +	if (row_bits < 14 || row_bits > 18)
> +		panic("invalid dram configuration (row_bits = %d)", row_bits);
> +
> +	if (rank_bits > 1)
> +		panic("invalid dram configuration (rank_bits = %d)", rank_bits);
> +
> +	/*
> +	 * Col[0:1] + HIF[0:1] (hardwired), Col[2] = HIF[2] (required)
> +	 * Thus, we start allocating from HIF[3] onwards
> +	 */
> +	hif_offset = INITIAL_HIF_OFFSET;
> +
> +	/* BG[bankgrp_bits:0] = HIF[3 + bankgrp_bits:3]*/
> +	switch (bankgrp_bits) {
> +	case 0:
> +		writel_relaxed(ADDRMAP8_BG0_B2(ADDRMAP_DISABLED_1F_B(2)) |
> +			       ADDRMAP8_BG1_B3(ADDRMAP_DISABLED_1F_B(3)),
> +			&mctl_ctl->addrmap[8]);
> +		break;
> +	case 1:
> +		writel_relaxed(ADDRMAP8_BG0_B2(hif_offset) |
> +			       ADDRMAP8_BG1_B3(ADDRMAP_DISABLED_1F_B(3)),
> +			&mctl_ctl->addrmap[8]);
> +		break;
> +	case 2:
> +		writel_relaxed(ADDRMAP8_BG0_B2(hif_offset) |
> +			       ADDRMAP8_BG1_B3(hif_offset + 1),
> +			       &mctl_ctl->addrmap[8]);
> +		break;
> +	default:
> +		panic("invalid dram configuration (bankgrp_bits = %d)",
> +		      bankgrp_bits);
> +	}
> +
> +	hif_offset += bankgrp_bits;
> +
> +	/* Col[2] = HIF[2], Col[5:3] = HIF[offset + 2:offset] */
> +	writel_relaxed(ADDRMAP2_COL2_B2(2) | ADDRMAP2_COL3_B3(hif_offset) |
> +		       ADDRMAP2_COL4_B4(hif_offset + 1) |
> +		       ADDRMAP2_COL5_B5(hif_offset + 2),
> +		       &mctl_ctl->addrmap[2]);
> +
> +	/* Col[col_bits:6] = HIF[col_bits + offset - 3:offset - 3] */
> +	for (i = 6; i < 12; i++) {
> +		if (i < col_bits)
> +			hif_bits[i - 6] = hif_offset + (i - INITIAL_HIF_OFFSET);
> +		else
> +			hif_bits[i - 6] = ADDRMAP_DISABLED_1F_B(i);
> +	}
> +
> +	writel_relaxed(ADDRMAP3_COL6_B6(hif_bits[0]) |
> +		       ADDRMAP3_COL7_B7(hif_bits[1]) |
> +		       ADDRMAP3_COL8_B8(hif_bits[2]) |
> +		       ADDRMAP3_COL9_B9(hif_bits[3]),
> +		       &mctl_ctl->addrmap[3]);
> +
> +	writel_relaxed(ADDRMAP4_COL10_B10(hif_bits[4]) |
> +		       ADDRMAP4_COL11_B11(hif_bits[5]),
> +		       &mctl_ctl->addrmap[4]);
> +
> +	hif_offset = bankgrp_bits + col_bits;
> +
> +	/* Bank[bank_bits:0] = HIF[bank_bits + offset:offset] */
> +	if (bank_bits == 3)
> +		writel_relaxed(ADDRMAP1_BANK0_B2(hif_offset) |
> +			       ADDRMAP1_BANK1_B3(hif_offset + 1) |
> +			       ADDRMAP1_BANK2_B4(hif_offset + 2),
> +			       &mctl_ctl->addrmap[1]);
> +	else
> +		writel_relaxed(ADDRMAP1_BANK0_B2(hif_offset) |
> +			       ADDRMAP1_BANK1_B3(hif_offset + 1) |
> +			       ADDRMAP1_BANK2_B4(ADDRMAP_DISABLED_1F_B(4)),
> +			       &mctl_ctl->addrmap[1]);
> +
> +	hif_offset += bank_bits;
> +
> +	/* Row[11:0] = HIF[11 + offset:offset] */
> +	writel_relaxed(ADDRMAP5_ROW0_B6(hif_offset) |
> +		       ADDRMAP5_ROW1_B7(hif_offset + 1) |
> +		       ADDRMAP5_ROW2_10_B8(hif_offset + 2) |
> +		       ADDRMAP5_ROW11_B17(hif_offset + 11),
> +		       &mctl_ctl->addrmap[5]);
> +
> +	/*
> +	 * There's some complexity here because of a special case
> +	 * in boot0 code that appears to work around a hardware bug.
> +	 * For (col_bits, row_bits, rank_bits) = (10, 16, 1), we have to
> +	 * place CS[0] in the position we would normally place ROW[14],
> +	 * and shift ROW[14] and ROW[15] over by one. Using the bit following
> +	 * ROW[15], as would be standard here, seems to cause nonsensical
> +	 * aliasing patterns.
> +	 *
> +	 * Aside from this case, mapping is simple:
> +	 * Row[row_bits:12] = HIF[offset + row_bits:offset + 12]
> +	 */
> +	for (i = 12; i < 18; i++) {
> +		if (i >= row_bits)
> +			hif_bits[i - 12] = ADDRMAP_DISABLED_0F_B(6 + i);
> +		else if (rank_bits != 1 || col_bits != 10 || row_bits != 16 ||
> +			 i < 14)
> +			hif_bits[i - 12] = hif_offset + i;
> +		else
> +			hif_bits[i - 12] = hif_offset + i + 1;
> +	}
> +
> +	writel_relaxed(ADDRMAP6_ROW12_B18(hif_bits[0]) |
> +		       ADDRMAP6_ROW13_B19(hif_bits[1]) |
> +		       ADDRMAP6_ROW14_B20(hif_bits[2]) |
> +		       ADDRMAP6_ROW15_B21(hif_bits[3]),
> +		       &mctl_ctl->addrmap[6]);
> +
> +	writel_relaxed(ADDRMAP7_ROW16_B22(hif_bits[4]) |
> +		       ADDRMAP7_ROW17_B23(hif_bits[5]),
> +		       &mctl_ctl->addrmap[7]);
> +
> +	hif_offset += row_bits;
> +
> +	/*
> +	 * Ranks
> +	 * Most cases: CS[0] = HIF[offset]
> +	 * Special case (see above): CS[0] = HIF[offset - 2]
> +	 */
> +	if (rank_bits == 0)
> +		writel_relaxed(ADDRMAP0_CS0_B6(ADDRMAP_DISABLED_1F_B(6)),
> +			       &mctl_ctl->addrmap[0]);
> +	else if (col_bits == 10 && row_bits == 16)
> +		writel_relaxed(ADDRMAP0_CS0_B6(hif_offset - 2),
> +			       &mctl_ctl->addrmap[0]);
> +	else
> +		writel_relaxed(ADDRMAP0_CS0_B6(hif_offset),
> +			       &mctl_ctl->addrmap[0]);
> +}
> +
> +static void mctl_com_init(const struct dram_para *para,
> +			  const struct dram_config *config)
> +{
> +	void *const mctl_com = (void *)SUNXI_DRAM_COM_BASE;
> +	struct sunxi_mctl_ctl_reg *mctl_ctl =
> +		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> +
> +	/* Might control power/reset of DDR-related blocks */
> +	clrsetbits_le32(mctl_com + MCTL_COM_UNK_008, BIT(24), BIT(25) | BIT(9));
> +
> +	/* Unlock mctl_ctl registers */
> +	setbits_le32(mctl_com + MCTL_COM_MAER0, BIT(15));
> +
> +	if (para->type == SUNXI_DRAM_TYPE_LPDDR4)
> +		setbits_le32(0x03102ea8, BIT(0));
> +
> +	clrsetbits_le32(&mctl_ctl->sched[0], 0xff << 8, 0x30 << 8);
> +	if (!(para->tpr13 & BIT(28)))
> +		clrsetbits_le32(&mctl_ctl->sched[0], 0xf, BIT(0));
> +
> +	writel_relaxed(0, &mctl_ctl->hwlpctl);
> +
> +	/* Master settings */
> +	u32 mstr_value = MSTR_DEVICECONFIG_X32 |
> +			 MSTR_ACTIVE_RANKS(config->ranks);
> +
> +	if (config->bus_full_width)
> +		mstr_value |= MSTR_BUSWIDTH_FULL;
> +	else
> +		mstr_value |= MSTR_BUSWIDTH_HALF;
> +
> +	/*
> +	 * Geardown and 2T mode are always enabled here, but is controlled by a flag in boot0;
> +	 * it has not been a problem so far, but may be suspect if a particular board isn't booting.
> +	 */
> +	switch (para->type) {
> +	case SUNXI_DRAM_TYPE_DDR3:
> +		mstr_value |= MSTR_DEVICETYPE_DDR3 | MSTR_BURST_LENGTH(8) |
> +			      MSTR_2TMODE;
> +		break;
> +	case SUNXI_DRAM_TYPE_DDR4:
> +		mstr_value |= MSTR_DEVICETYPE_DDR4 | MSTR_BURST_LENGTH(8) |
> +			      MSTR_GEARDOWNMODE | MSTR_2TMODE;
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR3:
> +		mstr_value |= MSTR_DEVICETYPE_LPDDR3 | MSTR_BURST_LENGTH(8);
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR4:
> +		mstr_value |= MSTR_DEVICETYPE_LPDDR4 | MSTR_BURST_LENGTH(16);
> +		break;
> +	}
> +
> +	writel_relaxed(mstr_value, &mctl_ctl->mstr);
> +
> +	mctl_set_odtmap(para, config);
> +	mctl_set_addrmap(config);
> +	mctl_set_timing_params(para);
> +
> +	dsb();
> +	writel(0, &mctl_ctl->pwrctl);
> +
> +	/* Disable automatic controller updates + automatic controller update requests */
> +	setbits_le32(&mctl_ctl->dfiupd[0], BIT(31) | BIT(30));
> +	setbits_le32(&mctl_ctl->zqctl[0], BIT(31) | BIT(30));
> +	setbits_le32(&mctl_ctl->unk_0x2180, BIT(31) | BIT(30));
> +	setbits_le32(&mctl_ctl->unk_0x3180, BIT(31) | BIT(30));
> +	setbits_le32(&mctl_ctl->unk_0x4180, BIT(31) | BIT(30));
> +
> +	/*
> +	 * Data bus inversion
> +	 * Controlled by a flag in boot0, enabled by default here.
> +	 */
> +	if (para->type == SUNXI_DRAM_TYPE_DDR4 ||
> +	    para->type == SUNXI_DRAM_TYPE_LPDDR4)
> +		setbits_le32(&mctl_ctl->dbictl, BIT(2));
> +}
> +
> +static void mctl_drive_odt_config(const struct dram_para *para)
> +{
> +	u32 val;
> +	u64 base;
> +	u32 i;
> +
> +	/* DX drive */
> +	for (i = 0; i < 4; i++) {
> +		base = SUNXI_DRAM_PHY0_BASE + 0x388 + 0x40 * i;
> +		val = (para->dx_dri >> (i * 8)) & 0x1f;
> +
> +		writel_relaxed(val, base);
> +		if (para->type == SUNXI_DRAM_TYPE_LPDDR4) {
> +			if (para->tpr3 & 0x1f1f1f1f)
> +				val = (para->tpr3 >> (i * 8)) & 0x1f;
> +			else
> +				val = 4;
> +		}
> +		writel_relaxed(val, base + 4);
> +	}
> +
> +	/* CA drive */
> +	for (i = 0; i < 2; i++) {
> +		base = SUNXI_DRAM_PHY0_BASE + 0x340 + 0x8 * i;
> +		val = (para->ca_dri >> (i * 8)) & 0x1f;
> +
> +		writel_relaxed(val, base);
> +		writel_relaxed(val, base + 4);
> +	}
> +
> +	/* DX ODT */
> +	for (i = 0; i < 4; i++) {
> +		base = SUNXI_DRAM_PHY0_BASE + 0x380 + 0x40 * i;
> +		val = (para->dx_odt >> (i * 8)) & 0x1f;
> +
> +		if (para->type == SUNXI_DRAM_TYPE_DDR4 ||
> +		    para->type == SUNXI_DRAM_TYPE_LPDDR3)
> +			writel_relaxed(0, base);
> +		else
> +			writel_relaxed(val, base);
> +
> +		if (para->type == SUNXI_DRAM_TYPE_LPDDR4)
> +			writel_relaxed(0, base + 4);
> +		else
> +			writel_relaxed(val, base + 4);
> +	}
> +	dsb();
> +}
> +
> +static void mctl_phy_ca_bit_delay_compensation(const struct dram_para *para)
> +{
> +	u32 val, i;
> +	u32 *ptr;
> +
> +	if (para->tpr10 & BIT(31)) {
> +		val = para->tpr2;
> +	} else {
> +		val = ((para->tpr10 << 1) & 0x1e) |
> +		      ((para->tpr10 << 5) & 0x1e00) |
> +		      ((para->tpr10 << 9) & 0x1e0000) |
> +		      ((para->tpr10 << 13) & 0x1e000000);
> +
> +		if (para->tpr10 >> 29 != 0)
> +			val <<= 1;
> +	}
> +
> +	ptr = (u32 *)(SUNXI_DRAM_PHY0_BASE + 0x780);
> +	for (i = 0; i < 32; i++)
> +		writel_relaxed((val >> 8) & 0x3f, &ptr[i]);
> +
> +	writel_relaxed(val & 0x3f, SUNXI_DRAM_PHY0_BASE + 0x7dc);
> +	writel_relaxed(val & 0x3f, SUNXI_DRAM_PHY0_BASE + 0x7e0);
> +
> +	switch (para->type) {
> +	case SUNXI_DRAM_TYPE_DDR3:
> +		writel_relaxed((val >> 16) & 0x3f,
> +			       SUNXI_DRAM_PHY0_BASE + 0x7b8);
> +		writel_relaxed((val >> 24) & 0x3f,
> +			       SUNXI_DRAM_PHY0_BASE + 0x784);
> +		break;
> +	case SUNXI_DRAM_TYPE_DDR4:
> +		writel_relaxed((val >> 16) & 0x3f,
> +			       SUNXI_DRAM_PHY0_BASE + 0x784);
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR3:
> +		writel_relaxed((val >> 16) & 0x3f,
> +			       SUNXI_DRAM_PHY0_BASE + 0x788);
> +		writel_relaxed((val >> 24) & 0x3f,
> +			       SUNXI_DRAM_PHY0_BASE + 0x790);
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR4:
> +		writel_relaxed((val >> 16) & 0x3f,
> +			       SUNXI_DRAM_PHY0_BASE + 0x790);
> +		writel_relaxed((val >> 24) & 0x3f,
> +			       SUNXI_DRAM_PHY0_BASE + 0x78c);
> +		break;
> +	}
> +
> +	dsb();
> +}
> +
> +static void mctl_phy_init(const struct dram_para *para,
> +			  const struct dram_config *config)
> +{
> +	struct sunxi_mctl_ctl_reg *mctl_ctl =
> +		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> +	void *const prcm = (void *)SUNXI_PRCM_BASE;
> +	void *const mctl_com = (void *)SUNXI_DRAM_COM_BASE;
> +
> +	u32 val, val2, i;
> +	u32 *ptr;
> +
> +	/* Disable auto refresh. */
> +	setbits_le32(&mctl_ctl->rfshctl3, BIT(0));
> +
> +	/* Set "phy_dbi_mode" to mark the DFI as implementing DBI functionality */
> +	writel_relaxed(0, &mctl_ctl->pwrctl);
> +	clrbits_le32(&mctl_ctl->dfimisc, 1);
> +	writel_relaxed(0x20, &mctl_ctl->pwrctl);
> +
> +	/* PHY cold reset */
> +	clrsetbits_le32(mctl_com + MCTL_COM_UNK_008, BIT(24), BIT(9));
> +	udelay(1);
> +	setbits_le32(mctl_com + MCTL_COM_UNK_008, BIT(24));
> +
> +	/* Not sure what this gates the power of. */
> +	clrbits_le32(prcm + CCU_PRCM_SYS_PWROFF_GATING, BIT(4));
> +
> +	if (para->type == SUNXI_DRAM_TYPE_LPDDR4)
> +		clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x4, BIT(7));
> +
> +	/* Note: Similar enumeration of values is used during read training */
> +	if (config->bus_full_width)
> +		val = 0xf;
> +	else
> +		val = 0x3;
> +
> +	clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 0x3c, 0xf, val);
> +
> +	switch (para->type) {
> +	case SUNXI_DRAM_TYPE_DDR3:
> +		val = 13;
> +		val2 = 9;
> +		break;
> +	case SUNXI_DRAM_TYPE_DDR4:
> +		val = 13;
> +		val2 = 10;
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR3:
> +		val = 14;
> +		val2 = 8;
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR4:
> +		if (para->tpr13 & BIT(28))
> +			val = 22;
> +		else
> +			val = 20;
> +
> +		val2 = 10;
> +		break;
> +	}
> +
> +	writel_relaxed(val, SUNXI_DRAM_PHY0_BASE + 0x14);
> +	writel_relaxed(val, SUNXI_DRAM_PHY0_BASE + 0x35c);
> +	writel_relaxed(val, SUNXI_DRAM_PHY0_BASE + 0x368);
> +	writel_relaxed(val, SUNXI_DRAM_PHY0_BASE + 0x374);
> +	writel_relaxed(0, SUNXI_DRAM_PHY0_BASE + 0x18);
> +	writel_relaxed(0, SUNXI_DRAM_PHY0_BASE + 0x360);
> +	writel_relaxed(0, SUNXI_DRAM_PHY0_BASE + 0x36c);
> +	writel_relaxed(0, SUNXI_DRAM_PHY0_BASE + 0x378);
> +	writel_relaxed(val2, SUNXI_DRAM_PHY0_BASE + 0x1c);
> +	writel_relaxed(val2, SUNXI_DRAM_PHY0_BASE + 0x364);
> +	writel_relaxed(val2, SUNXI_DRAM_PHY0_BASE + 0x370);
> +	writel_relaxed(val2, SUNXI_DRAM_PHY0_BASE + 0x37c);
> +
> +	/* Set up SDQ swizzle */
> +	ptr = (u32 *)(SUNXI_DRAM_PHY0_BASE + 0xc0);
> +	for (i = 0; i < ARRAY_SIZE(phy_init); i++)
> +		writel_relaxed(phy_init[i], &ptr[i]);
> +
> +	/* Set VREF */
> +	val = 0;
> +	switch (para->type) {
> +	case SUNXI_DRAM_TYPE_DDR3:
> +		val = para->tpr6 & 0xff;
> +		if (val == 0)
> +			val = 0x80;
> +		break;
> +	case SUNXI_DRAM_TYPE_DDR4:
> +		val = (para->tpr6 >> 8) & 0xff;
> +		if (val == 0)
> +			val = 0x80;
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR3:
> +		val = (para->tpr6 >> 16) & 0xff;
> +		if (val == 0)
> +			val = 0x80;
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR4:
> +		val = (para->tpr6 >> 24) & 0xff;
> +		if (val == 0)
> +			val = 0x33;
> +		break;
> +	}
> +	writel_relaxed(val, SUNXI_DRAM_PHY0_BASE + 0x3dc);
> +	writel_relaxed(val, SUNXI_DRAM_PHY0_BASE + 0x45c);
> +
> +	mctl_drive_odt_config(para);
> +
> +	if (para->tpr10 & TPR10_CA_BIT_DELAY)
> +		mctl_phy_ca_bit_delay_compensation(para);
> +
> +	switch (para->type) {
> +	case SUNXI_DRAM_TYPE_DDR3:
> +		val = 2;
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR3:
> +		val = 3;
> +		break;
> +	case SUNXI_DRAM_TYPE_DDR4:
> +		val = 4;
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR4:
> +		val = 5;
> +		break;
> +	}
> +
> +	clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 0x4, 0x7, val | 8);
> +
> +	if (para->clk <= 672)
> +		writel_relaxed(0xf, SUNXI_DRAM_PHY0_BASE + 0x20);
> +
> +	if (para->clk > 500) {
> +		val = 0;
> +		val2 = 0;
> +	} else {
> +		val = 0x80;
> +		val2 = 0x20;
> +	}
> +
> +	clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 0x144, 0x80, val);
> +	clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 0x14c, 0xe0, val2);
> +
> +	dsb();
> +	clrbits_le32(mctl_com + MCTL_COM_UNK_008, BIT(9));
> +	udelay(1);
> +	clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x14c, BIT(3));
> +
> +	mctl_await_completion((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x180), BIT(2),
> +			      BIT(2));
> +
> +	/*
> +	 * This delay is controlled by a tpr13 flag in boot0; doesn't hurt
> +	 * to always do it though.
> +	 */
> +	udelay(1000);
> +	writel(0x37, SUNXI_DRAM_PHY0_BASE + 0x58);
> +
> +	setbits_le32(prcm + CCU_PRCM_SYS_PWROFF_GATING, BIT(4));
> +}
> +
> +/* Helpers for updating mode registers */
> +static inline void mctl_mr_write(u32 mrctrl0, u32 mrctrl1)
> +{
> +	struct sunxi_mctl_ctl_reg *mctl_ctl =
> +		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> +
> +	writel(mrctrl1, &mctl_ctl->mrctrl1);
> +	writel(mrctrl0 | MRCTRL0_MR_WR | MRCTRL0_MR_RANKS_ALL,
> +	       &mctl_ctl->mrctrl0);
> +	mctl_await_completion(&mctl_ctl->mrctrl0, MRCTRL0_MR_WR, 0);
> +}
> +
> +static inline void mctl_mr_write_lpddr4(u8 addr, u8 value)
> +{
> +	mctl_mr_write(0, MRCTRL1_MR_ADDR(addr) | MRCTRL1_MR_DATA(value));
> +}
> +
> +static inline void mctl_mr_write_lpddr3(u8 addr, u8 value)
> +{
> +	/* Bit [7:6] are set by boot0, but undocumented */
> +	mctl_mr_write(BIT(6) | BIT(7),
> +		      MRCTRL1_MR_ADDR(addr) | MRCTRL1_MR_DATA(value));
> +}
> +
> +static void mctl_dfi_init(const struct dram_para *para)
> +{
> +	void *const mctl_com = (void *)SUNXI_DRAM_COM_BASE;
> +	struct sunxi_mctl_ctl_reg *mctl_ctl =
> +		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> +
> +	/* Unlock DFI registers? */
> +	setbits_le32(mctl_com + MCTL_COM_MAER0, BIT(8));
> +
> +	/* Enable dfi_init_complete signal and trigger PHY init start request */
> +	writel_relaxed(0, &mctl_ctl->swctl);
> +	setbits_le32(&mctl_ctl->dfimisc, BIT(0));
> +	setbits_le32(&mctl_ctl->dfimisc, BIT(5));
> +	writel_relaxed(1, &mctl_ctl->swctl);
> +	mctl_await_completion(&mctl_ctl->swstat, BIT(0), BIT(0));
> +
> +	/* Stop sending init request and wait for DFI initialization to complete. */
> +	writel_relaxed(0, &mctl_ctl->swctl);
> +	clrbits_le32(&mctl_ctl->dfimisc, BIT(5));
> +	writel_relaxed(1, &mctl_ctl->swctl);
> +	mctl_await_completion(&mctl_ctl->swstat, BIT(0), BIT(0));
> +	mctl_await_completion(&mctl_ctl->dfistat, BIT(0), BIT(0));
> +
> +	/* Enter Software Exit from Self Refresh */
> +	writel_relaxed(0, &mctl_ctl->swctl);
> +	clrbits_le32(&mctl_ctl->pwrctl, BIT(5));
> +	writel_relaxed(1, &mctl_ctl->swctl);
> +	mctl_await_completion(&mctl_ctl->swstat, BIT(0), BIT(0));
> +	mctl_await_completion(&mctl_ctl->statr, 0x3, 1);
> +
> +	udelay(200);
> +
> +	/* Disable dfi_init_complete signal */
> +	writel_relaxed(0, &mctl_ctl->swctl);
> +	clrbits_le32(&mctl_ctl->dfimisc, BIT(0));
> +	writel_relaxed(1, &mctl_ctl->swctl);
> +	mctl_await_completion(&mctl_ctl->swstat, BIT(0), BIT(0));
> +
> +	/* Write mode registers, fixed in the JEDEC spec */
> +	switch (para->type) {
> +	case SUNXI_DRAM_TYPE_DDR3:
> +		mctl_mr_write(MRCTRL0_MR_ADDR(0), 0x1c70);	/* MR0 */
> +		/*
> +		 * outbuf en, TDQs dis, write leveling dis, out drv 40 Ohms,
> +		 * DLL en, Rtt_nom 120 Ohms
> +		 */
> +		mctl_mr_write(MRCTRL0_MR_ADDR(1), 0x40);	/* MR1 */
> +		/*
> +		 * full array self-ref, CAS: 8 cyc, SRT w/ norm temp range,
> +		 * dynamic ODT off
> +		 */
> +		mctl_mr_write(MRCTRL0_MR_ADDR(2), 0x18);	/* MR2 */
> +		/* predef MPR pattern */
> +		mctl_mr_write(MRCTRL0_MR_ADDR(3), 0);		/* MR3 */
> +		break;
> +	case SUNXI_DRAM_TYPE_DDR4:
> +		mctl_mr_write(MRCTRL0_MR_ADDR(0), 0x840);
> +		mctl_mr_write(MRCTRL0_MR_ADDR(1), 0x601);
> +		mctl_mr_write(MRCTRL0_MR_ADDR(2), 0x8);
> +		mctl_mr_write(MRCTRL0_MR_ADDR(3), 0);
> +		mctl_mr_write(MRCTRL0_MR_ADDR(4), 0);
> +		mctl_mr_write(MRCTRL0_MR_ADDR(5), 0x400);
> +
> +		mctl_mr_write(MRCTRL0_MR_ADDR(6), 0x862 | BIT(7));
> +		mctl_mr_write(MRCTRL0_MR_ADDR(6), 0x862 | BIT(7));
> +		mctl_mr_write(MRCTRL0_MR_ADDR(6), 0x862 & (~BIT(7)));
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR3:
> +		mctl_mr_write_lpddr3(1, 0xc3);	/* MR1: nWR=8, BL8 */
> +		mctl_mr_write_lpddr3(2, 0xa);	/* MR2: RL=12, WL=6 */
> +		mctl_mr_write_lpddr3(3, 0x2);	/* MR3: 40 0hms PD/PU */
> +		mctl_mr_write_lpddr3(11, para->mr11);
> +		break;
> +	case SUNXI_DRAM_TYPE_LPDDR4:
> +		mctl_mr_write_lpddr4(0, 0);	/* MR0 */
> +		mctl_mr_write_lpddr4(1, 0x34);	/* MR1 */
> +		mctl_mr_write_lpddr4(2, 0x1b);	/* MR2 */
> +		mctl_mr_write_lpddr4(3, 0x33);	/* MR3 */
> +		mctl_mr_write_lpddr4(4, 0x3);	/* MR4 */
> +		mctl_mr_write_lpddr4(11, para->mr11);
> +		mctl_mr_write_lpddr4(12, para->mr12);
> +		mctl_mr_write_lpddr4(13, para->mr13);
> +		mctl_mr_write_lpddr4(14, para->mr14);
> +		mctl_mr_write_lpddr4(22, para->tpr1);
> +		break;
> +	}
> +
> +	writel(0, SUNXI_DRAM_PHY0_BASE + 0x54);
> +
> +	/* Re-enable controller refresh */
> +	writel(0, &mctl_ctl->swctl);
> +	clrbits_le32(&mctl_ctl->rfshctl3, BIT(0));
> +	writel(1, &mctl_ctl->swctl);
> +}
> +
> +/* Slightly modified from H616 driver */
> +static bool mctl_phy_read_calibration(const struct dram_config *config)
> +{
> +	bool result = true;
> +	u32 val, tmp;
> +
> +	clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 0x30, 0x20);
> +
> +	setbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 1);
> +
> +	if (config->bus_full_width)
> +		val = 0xf;
> +	else
> +		val = 3;
> +
> +	while ((readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x184) & val) != val) {
> +		if (readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x184) & 0x20) {
> +			result = false;
> +			break;
> +		}
> +	}
> +
> +	clrbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 1);
> +
> +	clrbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 0x30);
> +
> +	if (config->ranks == 1) {
> +		clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 0x30, 0x10);
> +
> +		setbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 1);
> +
> +		while ((readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x184) & val) !=
> +		       val) {
> +			if (readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x184) &
> +			    0x20) {
> +				result = false;
> +				break;
> +			}
> +		}
> +
> +		clrbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 1);
> +	}
> +
> +	clrbits_le32(SUNXI_DRAM_PHY0_BASE + 8, 0x30);
> +
> +	val = readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x274) & 7;
> +	tmp = readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x26c) & 7;
> +	if (val < tmp)
> +		val = tmp;
> +	tmp = readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x32c) & 7;
> +	if (val < tmp)
> +		val = tmp;
> +	tmp = readl_relaxed(SUNXI_DRAM_PHY0_BASE + 0x334) & 7;
> +	if (val < tmp)
> +		val = tmp;
> +	clrsetbits_le32(SUNXI_DRAM_PHY0_BASE + 0x38, 0x7, (val + 2) & 7);
> +
> +	setbits_le32(SUNXI_DRAM_PHY0_BASE + 4, 0x20);
> +
> +	return result;
> +}
> +
> +static inline void mctl_phy_dx_delay1_inner(u32 *base, u32 val1, u32 val2)
> +{
> +	u32 *ptr = base;
> +
> +	for (int i = 0; i < 9; i++) {
> +		writel_relaxed(val1, ptr);
> +		writel_relaxed(val1, ptr + 0x30);
> +		ptr += 2;
> +	}
> +
> +	writel_relaxed(val2, ptr + 1);
> +	writel_relaxed(val2, ptr + 49);
> +	writel_relaxed(val2, ptr);
> +	writel_relaxed(val2, ptr + 48);
> +}
> +
> +static inline void mctl_phy_dx_delay0_inner(u32 *base1, u32 *base2, u32 val1,
> +					    u32 val2)
> +{
> +	u32 *ptr = base1;
> +
> +	for (int i = 0; i < 9; i++) {
> +		writel_relaxed(val1, ptr);
> +		writel_relaxed(val1, ptr + 0x30);
> +		ptr += 2;
> +	}
> +
> +	writel_relaxed(val2, base2);
> +	writel_relaxed(val2, base2 + 48);
> +	writel_relaxed(val2, ptr);
> +	writel_relaxed(val2, base2 + 44);
> +}
> +
> +/*
> + * This might be somewhat transferable to H616; whether or not people like
> + * the design is another question
> + */
> +static void mctl_phy_dx_delay_compensation(const struct dram_para *para)
> +{
> +	if (para->tpr10 & TPR10_DX_BIT_DELAY1) {
> +		clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x60, 1);
> +		setbits_le32(SUNXI_DRAM_PHY0_BASE + 8, BIT(3));
> +		clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x190, BIT(4));
> +
> +		if (para->type == SUNXI_DRAM_TYPE_DDR4)
> +			clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x4, BIT(7));
> +
> +		mctl_phy_dx_delay1_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x484),
> +					 para->tpr11 & 0x3f,
> +					 para->para0 & 0x3f);
> +		mctl_phy_dx_delay1_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x4d8),
> +					 (para->tpr11 >> 8) & 0x3f,
> +					 (para->para0 >> 8) & 0x3f);
> +		mctl_phy_dx_delay1_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x604),
> +					 (para->tpr11 >> 16) & 0x3f,
> +					 (para->para0 >> 16) & 0x3f);
> +		mctl_phy_dx_delay1_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x658),
> +					 (para->tpr11 >> 24) & 0x3f,
> +					 (para->para0 >> 24) & 0x3f);
> +
> +		setbits_le32(SUNXI_DRAM_PHY0_BASE + 0x60, 1);
> +	}
> +
> +	if (para->tpr10 & TPR10_DX_BIT_DELAY0) {
> +		clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x54, BIT(7));
> +		clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x190, BIT(2));
> +
> +		mctl_phy_dx_delay0_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x480),
> +					 (u32 *)(SUNXI_DRAM_PHY0_BASE + 0x528),
> +					 para->tpr12 & 0x3f,
> +					 para->tpr14 & 0x3f);
> +
> +		mctl_phy_dx_delay0_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x4d4),
> +					 (u32 *)(SUNXI_DRAM_PHY0_BASE + 0x52c),
> +					 (para->tpr12 >> 8) & 0x3f,
> +					 (para->tpr14 >> 8) & 0x3f);
> +
> +		mctl_phy_dx_delay0_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x600),
> +					 (u32 *)(SUNXI_DRAM_PHY0_BASE + 0x6a8),
> +					 (para->tpr12 >> 16) & 0x3f,
> +					 (para->tpr14 >> 16) & 0x3f);
> +
> +		mctl_phy_dx_delay0_inner((u32 *)(SUNXI_DRAM_PHY0_BASE + 0x6ac),
> +					 (u32 *)(SUNXI_DRAM_PHY0_BASE + 0x528),
> +					 (para->tpr12 >> 24) & 0x3f,
> +					 (para->tpr14 >> 24) & 0x3f);
> +
> +		setbits_le32(SUNXI_DRAM_PHY0_BASE + 0x54, BIT(7));
> +	}
> +}
> +
> +static bool mctl_calibrate_phy(const struct dram_para *para,
> +			       const struct dram_config *config)
> +{
> +	struct sunxi_mctl_ctl_reg *mctl_ctl =
> +		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> +
> +	int i;
> +
> +	/* TODO: Implement write levelling */
> +	if (para->tpr10 & TPR10_READ_CALIBRATION) {
> +		for (i = 0; i < 5; i++)
> +			if (mctl_phy_read_calibration(config))
> +				break;
> +		if (i == 5) {
> +			debug("read calibration failed\n");
> +			return false;
> +		}
> +	}
> +
> +	/* TODO: Implement read training */
> +	/* TODO: Implement write training */
> +
> +	mctl_phy_dx_delay_compensation(para);
> +	/* TODO: Implement DFS */
> +	clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x60, BIT(0));
> +	clrbits_le32(SUNXI_DRAM_PHY0_BASE + 0x54, 7);
> +
> +	/* Q: Does self-refresh get disabled by a calibration? */
> +	writel_relaxed(0, &mctl_ctl->swctl);
> +	clrbits_le32(&mctl_ctl->rfshctl3, BIT(1));
> +	writel_relaxed(1, &mctl_ctl->swctl);
> +	mctl_await_completion(&mctl_ctl->swstat, BIT(0), BIT(0));
> +
> +	return true;
> +}
> +
> +static bool mctl_core_init(const struct dram_para *para,
> +			   const struct dram_config *config)
> +{
> +	mctl_clk_init(para->clk);
> +	mctl_com_init(para, config);
> +	mctl_phy_init(para, config);
> +	mctl_dfi_init(para);
> +
> +	return mctl_calibrate_phy(para, config);
> +}
> +
> +/* Heavily inspired from H616 driver. */
> +static void auto_detect_ranks(const struct dram_para *para,
> +			      struct dram_config *config)
> +{
> +	int i;
> +
> +	config->cols = 9;
> +	config->rows = 14;
> +	config->banks = 2;
> +	config->bankgrps = 0;
> +
> +	/* Test ranks */
> +	for (i = 1; i >= 0; i--) {
> +		config->ranks = i;
> +		config->bus_full_width = true;
> +		debug("Testing ranks = %d, 32-bit bus: ", i);
> +		if (mctl_core_init(para, config)) {
> +			debug("OK\n");
> +			break;
> +		}
> +
> +		config->bus_full_width = false;
> +		debug("Testing ranks = %d, 16-bit bus: ", i);
> +		if (mctl_core_init(para, config)) {
> +			debug("OK\n");
> +			break;
> +		}
> +	}
> +
> +	if (i < 0)
> +		debug("rank testing failed\n");
> +}
> +
> +static void mctl_write_pattern(void)
> +{
> +	unsigned int i;
> +	u32 *ptr, val;
> +
> +	ptr = (u32 *)CFG_SYS_SDRAM_BASE;
> +	for (i = 0; i < 16; ptr++, i++) {
> +		if (i & 1)
> +			val = ~(ulong)ptr;
> +		else
> +			val = (ulong)ptr;
> +		writel(val, ptr);
> +	}
> +}
> +
> +static bool mctl_check_pattern(ulong offset)
> +{
> +	unsigned int i;
> +	u32 *ptr, val;
> +
> +	ptr = (u32 *)CFG_SYS_SDRAM_BASE;
> +	for (i = 0; i < 16; ptr++, i++) {
> +		if (i & 1)
> +			val = ~(ulong)ptr;
> +		else
> +			val = (ulong)ptr;
> +		if (val != *(ptr + offset / 4))
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
> +static void mctl_auto_detect_dram_size(const struct dram_para *para,
> +				       struct dram_config *config)
> +{
> +	unsigned int shift;
> +	u32 buffer[16];
> +
> +	/* max config for bankgrps on DDR4, minimum for everything else */
> +	config->cols = 8;
> +	config->banks = 2;
> +	config->rows = 14;
> +
> +	shift = 1 + config->bus_full_width;
> +	if (para->type == SUNXI_DRAM_TYPE_DDR4) {
> +		config->bankgrps = 2;
> +		mctl_core_init(para, config);
> +
> +		/* store content so it can be restored later. */
> +		memcpy(buffer, (u32 *)CFG_SYS_SDRAM_BASE, sizeof(buffer));
> +		mctl_write_pattern();
> +
> +		if (mctl_check_pattern(1ULL << (shift + 4)))
> +			config->bankgrps = 1;
> +
> +		/* restore data */
> +		memcpy((u32 *)CFG_SYS_SDRAM_BASE, buffer, sizeof(buffer));
> +	} else {
> +		/* No bank groups in (LP)DDR3/LPDDR4 */
> +		config->bankgrps = 0;
> +	}
> +
> +	/* reconfigure to make sure all active columns are accessible */
> +	config->cols = 12;
> +	mctl_core_init(para, config);
> +
> +	/* store data again as it might be moved */
> +	memcpy(buffer, (u32 *)CFG_SYS_SDRAM_BASE, sizeof(buffer));
> +	mctl_write_pattern();
> +
> +	/*
> +	 * Detect column address bits. The last number of columns checked
> +	 * is 11, if that doesn't match, is must be 12, no more checks needed.
> +	 */
> +	shift = 1 + config->bus_full_width + config->bankgrps;
> +	for (config->cols = 8; config->cols < 12; config->cols++) {
> +		if (mctl_check_pattern(1ULL << (config->cols + shift)))
> +			break;
> +	}
> +	memcpy((u32 *)CFG_SYS_SDRAM_BASE, buffer, sizeof(buffer));
> +
> +	/* reconfigure to make sure that all active banks are accessible */
> +	config->banks = 3;
> +	mctl_core_init(para, config);
> +
> +	memcpy(buffer, (u32 *)CFG_SYS_SDRAM_BASE, sizeof(buffer));
> +	mctl_write_pattern();
> +
> +	/* detect bank bits */
> +	shift += config->cols;
> +	for (config->banks = 2; config->banks < 3; config->banks++) {
> +		if (mctl_check_pattern(1ULL << (config->banks + shift)))
> +			break;
> +	}
> +	memcpy((u32 *)CFG_SYS_SDRAM_BASE, buffer, sizeof(buffer));
> +
> +	/* reconfigure to make sure that all active rows are accessible */
> +	config->rows = 18;
> +	mctl_core_init(para, config);
> +
> +	memcpy(buffer, (u32 *)CFG_SYS_SDRAM_BASE, sizeof(buffer));
> +	mctl_write_pattern();
> +
> +	/* detect row address bits */
> +	shift += config->banks;
> +	for (config->rows = 14; config->rows < 18; config->rows++) {
> +		if (mctl_check_pattern(1ULL << (config->rows + shift)))
> +			break;
> +	}
> +	memcpy((u32 *)CFG_SYS_SDRAM_BASE, buffer, sizeof(buffer));
> +}
> +
> +/* Modified from H616 driver to add banks and bank groups */
> +static unsigned long calculate_dram_size(const struct dram_config *config)
> +{
> +	/* Bootrom only uses x32 or x16 bus widths */
> +	u8 width = config->bus_full_width ? 4 : 2;
> +
> +	return (1ULL << (config->cols + config->rows + config->banks +
> +			 config->bankgrps)) *
> +	       width * (1ULL << config->ranks);
> +}
> +
> +static const struct dram_para para = {
> +	.clk = CONFIG_DRAM_CLK,
> +#ifdef CONFIG_SUNXI_DRAM_DDR3
> +	.type = SUNXI_DRAM_TYPE_DDR3,
> +#elif defined(CONFIG_SUNXI_DRAM_DDR4)
> +	.type = SUNXI_DRAM_TYPE_DDR4,
> +#elif defined(CONFIG_SUNXI_DRAM_LPDDR3)
> +	.type = SUNXI_DRAM_TYPE_LPDDR3,
> +#elif defined(CONFIG_SUNXI_DRAM_LPDDR4)
> +	.type = SUNXI_DRAM_TYPE_LPDDR4,
> +#endif
> +	/* TODO: Populate from config */
> +	.dx_odt = CONFIG_DRAM_SUNXI_DX_ODT,
> +	.dx_dri = CONFIG_DRAM_SUNXI_DX_DRI,
> +	.ca_dri = CONFIG_DRAM_SUNXI_CA_DRI,
> +	.para0 = CONFIG_DRAM_SUNXI_PARA0,
> +	.mr11 = CONFIG_DRAM_SUNXI_MR11,
> +	.mr12 = CONFIG_DRAM_SUNXI_MR12,
> +	.mr13 = CONFIG_DRAM_SUNXI_MR13,
> +	.mr14 = CONFIG_DRAM_SUNXI_MR14,
> +	.tpr1 = CONFIG_DRAM_SUNXI_TPR1,
> +	.tpr2 = CONFIG_DRAM_SUNXI_TPR2,
> +	.tpr3 = CONFIG_DRAM_SUNXI_TPR3,
> +	.tpr6 = CONFIG_DRAM_SUNXI_TPR6,
> +	.tpr10 = CONFIG_DRAM_SUNXI_TPR10,
> +	.tpr11 = CONFIG_DRAM_SUNXI_TPR11,
> +	.tpr12 = CONFIG_DRAM_SUNXI_TPR12,
> +	.tpr13 = CONFIG_DRAM_SUNXI_TPR13,
> +	.tpr14 = CONFIG_DRAM_SUNXI_TPR14,
> +};
> +
> +unsigned long sunxi_dram_init(void)
> +{
> +	struct dram_config config;
> +
> +	/* Writing to undocumented SYS_CFG area, according to user manual. */
> +	setbits_le32(0x03000160, BIT(8));
> +	clrbits_le32(0x03000168, 0x3f);
> +
> +	auto_detect_ranks(&para, &config);
> +	mctl_auto_detect_dram_size(&para, &config);
> +
> +	if (!mctl_core_init(&para, &config))
> +		return 0;
> +
> +	debug("cols = 2^%d, rows = 2^%d, banks = %d, bank groups = %d, ranks = %d, width = %d\n",
> +	      config.cols, config.rows, 1U << config.banks,
> +	      1U << config.bankgrps, 1U << config.ranks,
> +	      16U << config.bus_full_width);
> +
> +	return calculate_dram_size(&config);
> +}
> diff --git a/arch/arm/mach-sunxi/dram_timings/Makefile b/arch/arm/mach-sunxi/dram_timings/Makefile
> index 5f203419240..4dc1f29fc08 100644
> --- a/arch/arm/mach-sunxi/dram_timings/Makefile
> +++ b/arch/arm/mach-sunxi/dram_timings/Makefile
> @@ -6,3 +6,5 @@ obj-$(CONFIG_SUNXI_DRAM_H6_DDR3_1333)	+= h6_ddr3_1333.o
>  obj-$(CONFIG_SUNXI_DRAM_H616_DDR3_1333)	+= h616_ddr3_1333.o
>  obj-$(CONFIG_SUNXI_DRAM_H616_LPDDR3)	+= h616_lpddr3.o
>  obj-$(CONFIG_SUNXI_DRAM_H616_LPDDR4)	+= h616_lpddr4_2133.o
> +obj-$(CONFIG_SUNXI_DRAM_A133_DDR4)	+= a133_ddr4.o
> +obj-$(CONFIG_SUNXI_DRAM_A133_LPDDR4)	+= a133_lpddr4.o
> diff --git a/arch/arm/mach-sunxi/dram_timings/a133_ddr4.c b/arch/arm/mach-sunxi/dram_timings/a133_ddr4.c
> new file mode 100644
> index 00000000000..dec208e22df
> --- /dev/null
> +++ b/arch/arm/mach-sunxi/dram_timings/a133_ddr4.c
> @@ -0,0 +1,80 @@
> +#include <asm/arch/cpu.h>
> +#include <asm/arch/dram.h>
> +
> +void mctl_set_timing_params(const struct dram_para *para)
> +{
> +	struct sunxi_mctl_ctl_reg *const mctl_ctl =
> +		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> +
> +	u8 txsr = 4;
> +	u8 tccd = 3;
> +	u8 rd2wr = 5;
> +	u8 tmrd = 4;
> +	u8 tmrw = 0;
> +	u8 wrlat = 5;
> +	u8 rdlat = 7;
> +	u8 wr2pre = 14;
> +	u8 dfi_tphy_wrlat = 6;
> +	u8 dfi_trddata_en = 10;
> +
> +	u8 tfaw = ns_to_t(35);
> +	u8 trrd = max(ns_to_t(8), 2);
> +	u8 txp = max(ns_to_t(6), 2);
> +	u8 tmrd_pda = max(ns_to_t(10), 8);
> +	u8 trp = ns_to_t(15);
> +	u8 trc = ns_to_t(49);
> +	u8 wr2rd_s = max(ns_to_t(3), 1) + 7;
> +	u8 tras_min = ns_to_t(34);
> +	u16 trefi_x32 = ns_to_t(7800) / 32;
> +	u16 trfc_min = ns_to_t(350);
> +	u16 txs_x32 = ns_to_t(360) / 32;
> +	u16 tmod = max(ns_to_t(15), 12);
> +	u8 tcke = max(ns_to_t(5), 2);
> +	u8 tcksrx = max(ns_to_t(10), 3);
> +	u8 txs_abort_x32 = ns_to_t(170) / 32;
> +	u8 tras_max = ns_to_t(70200) / 1024;
> +
> +	u8 rd2pre = (trp < 5 ? 9 - trp : 4);
> +	u8 wr2rd = trrd + 7;
> +	u8 tckesr = tcke + 1;
> +	u8 trcd = trp;
> +	u8 trrd_s = txp;
> +	u8 tcksre = tcksrx;
> +
> +	writel(tras_min | tras_max << 8 | tfaw << 16 | wr2pre << 24,
> +	       &mctl_ctl->dramtmg[0]);
> +	writel(trc | rd2pre << 8 | txp << 16, &mctl_ctl->dramtmg[1]);
> +	writel(wr2rd | rd2wr << 8 | rdlat << 16 | wrlat << 24,
> +	       &mctl_ctl->dramtmg[2]);
> +	writel(tmod | tmrd << 12 | tmrw << 20, &mctl_ctl->dramtmg[3]);
> +	writel(trp | trrd << 8 | tccd << 16 | trcd << 24,
> +	       &mctl_ctl->dramtmg[4]);
> +	writel(tcke | tckesr << 8 | tcksre << 16 | tcksrx << 24,
> +	       &mctl_ctl->dramtmg[5]);
> +	writel((txp + 2) | 0x20 << 16 | 0x20 << 24,
> +	       &mctl_ctl->dramtmg[6]);
> +	writel(txs_x32 | 0x10 << 8 | txs_abort_x32 << 16 | txs_abort_x32 << 24,
> +	       &mctl_ctl->dramtmg[8]);
> +	writel(wr2rd_s | trrd_s << 8 | 0x2 << 16, &mctl_ctl->dramtmg[9]);
> +	writel(0xe0c05, &mctl_ctl->dramtmg[10]);
> +	writel(0x440c021c, &mctl_ctl->dramtmg[11]);
> +	writel(tmrd_pda, &mctl_ctl->dramtmg[12]);
> +	writel(0xa100002, &mctl_ctl->dramtmg[13]);
> +	writel(txsr, &mctl_ctl->dramtmg[14]);
> +
> +	clrsetbits_le32(&mctl_ctl->init[0], 0xc0000fff, 1008);
> +	writel(0x1f20000, &mctl_ctl->init[1]);
> +	clrsetbits_le32(&mctl_ctl->init[2], 0xff0f, 0xd05);
> +	writel(0, &mctl_ctl->dfimisc);
> +
> +	writel(0x840 << 16 | 0x601, &mctl_ctl->init[3]);	/* MR0 / MR1 */
> +	writel(0x8 << 16 | 0x0, &mctl_ctl->init[4]);		/* MR2 / MR3 */
> +	writel(0x0 << 16 | 0x400, &mctl_ctl->init[6]);		/* MR4 / MR5 */
> +	writel(0x826, &mctl_ctl->init[7]);			/* MR6 */
> +
> +	clrsetbits_le32(&mctl_ctl->rankctl, 0xff0, 0x660);
> +	writel((dfi_tphy_wrlat - 1) | 0x2000000 | (dfi_trddata_en - 1) << 16 |
> +	       0x808000, &mctl_ctl->dfitmg0);
> +	writel(0x100202, &mctl_ctl->dfitmg1);
> +	writel(trfc_min | trefi_x32 << 16, &mctl_ctl->rfshtmg);
> +}
> diff --git a/arch/arm/mach-sunxi/dram_timings/a133_lpddr4.c b/arch/arm/mach-sunxi/dram_timings/a133_lpddr4.c
> new file mode 100644
> index 00000000000..1e607381023
> --- /dev/null
> +++ b/arch/arm/mach-sunxi/dram_timings/a133_lpddr4.c
> @@ -0,0 +1,102 @@
> +#include <asm/arch/cpu.h>
> +#include <asm/arch/dram.h>
> +
> +void mctl_set_timing_params(const struct dram_para *para)
> +{
> +	struct sunxi_mctl_ctl_reg *const mctl_ctl =
> +		(struct sunxi_mctl_ctl_reg *)SUNXI_DRAM_CTL0_BASE;
> +
> +	bool tpr13_flag1 = para->tpr13 & BIT(28);
> +	bool tpr13_flag2 = para->tpr13 & BIT(3);
> +	bool tpr13_flag3 = para->tpr13 & BIT(5);
> +
> +	u8 tccd		= 4;
> +	u8 tfaw		= ns_to_t(40);
> +	u8 trrd		= max(ns_to_t(10), 2);
> +	u8 trcd		= max(ns_to_t(18), 2);
> +	u8 trc		= ns_to_t(65);
> +	u8 txp		= max(ns_to_t(8), 2);
> +
> +	u8 trp		= ns_to_t(21);
> +	u8 tras_min	= ns_to_t(42);
> +	u16 trefi_x32	= ns_to_t(3904) / 32;
> +	u16 trfc_min	= ns_to_t(180);
> +	u16 txsr	= ns_to_t(190);
> +
> +	u8 tmrw		= max(ns_to_t(14), 5);
> +	u8 tmrd		= max(ns_to_t(14), 5);
> +	u8 tmod		= 12;
> +	u8 tcke		= max(ns_to_t(15), 2);
> +	u8 tcksrx	= max(ns_to_t(2), 2);
> +	u8 tcksre	= max(ns_to_t(5), 2);
> +	u8 tckesr	= max(ns_to_t(15), 2);
> +	u8 tras_max	= (trefi_x32 * 9) / 32;
> +	u8 txs_x32	= 4;
> +	u8 txsabort_x32 = 4;
> +
> +	u8 wrlat        = 5;
> +	u8 wr2rd_s      = 8;
> +	u8 trrd_s       = 2;
> +	u8 tmrd_pda     = 8;
> +
> +	u8 wr2pre       = 24;
> +	u8 rd2pre       = 4;
> +	u8 wr2rd        = 14 + max(ns_to_t(tpr13_flag1 ? 10 : 12), 4);
> +	u8 rd2wr        = 17 + ns_to_t(4) - ns_to_t(1);
> +	u8 tphy_wrlat	= 5;
> +
> +	u8 rdlat	= 10;
> +	u8 trddata_en	= 17;
> +
> +	if (tpr13_flag1) {
> +		rdlat = 11;
> +		trddata_en = 19;
> +	}
> +
> +	writel(tras_min | tras_max << 8 | tfaw << 16 | wr2pre << 24,
> +	       &mctl_ctl->dramtmg[0]);
> +	writel(trc | rd2pre << 8 | txp << 16, &mctl_ctl->dramtmg[1]);
> +	writel(wr2rd | rd2wr << 8 | rdlat << 16 | wrlat << 24,
> +	       &mctl_ctl->dramtmg[2]);
> +	writel(tmod | tmrd << 12 | tmrw << 20, &mctl_ctl->dramtmg[3]);
> +	writel(trp | trrd << 8 | tccd << 16 | trcd << 24,
> +	       &mctl_ctl->dramtmg[4]);
> +	writel(tcke | tckesr << 8 | tcksre << 16 | tcksrx << 24,
> +	       &mctl_ctl->dramtmg[5]);
> +	writel((txp + 2) | 0x20 << 16 | 0x20 << 24, &mctl_ctl->dramtmg[6]);
> +	writel(txs_x32 | 0x10 << 8 | txsabort_x32 << 16 | txsabort_x32 << 24,
> +	       &mctl_ctl->dramtmg[8]);
> +	writel(wr2rd_s | trrd_s << 8 | 0x2 << 16, &mctl_ctl->dramtmg[9]);
> +	writel(0xe0c05, &mctl_ctl->dramtmg[10]);
> +	writel(0x440c021c, &mctl_ctl->dramtmg[11]);
> +	writel(tmrd_pda, &mctl_ctl->dramtmg[12]);
> +	writel(0xa100002, &mctl_ctl->dramtmg[13]);
> +	writel(txsr, &mctl_ctl->dramtmg[14]);
> +
> +	clrsetbits_le32(&mctl_ctl->init[0], 0xc0000fff, 1008);
> +
> +	if (tpr13_flag2)
> +		writel(0x420000, &mctl_ctl->init[1]);
> +	else
> +		writel(0x1f20000, &mctl_ctl->init[1]);
> +
> +	clrsetbits_le32(&mctl_ctl->init[2], 0xff0f, 0xd05);
> +	writel(0, &mctl_ctl->dfimisc);
> +
> +	writel(0x34 << 16 | 0x1b, &mctl_ctl->init[3]);		/* MR1/MR2 */
> +	writel(0x33 << 16, &mctl_ctl->init[4]);			/* MR3 */
> +	writel(para->mr11 << 16 | para->mr12, &mctl_ctl->init[6]);
> +	writel(para->tpr1 << 16 | para->mr14, &mctl_ctl->init[7]);
> +
> +	clrsetbits_le32(&mctl_ctl->rankctl, 0xff0, 0x660);
> +	if (!tpr13_flag3) {
> +		tphy_wrlat -= 1;
> +		trddata_en -= 1;
> +	}
> +
> +	writel(tphy_wrlat | trddata_en << 16 | 0x808000 | 0x2000000,
> +	       &mctl_ctl->dfitmg0);
> +	writel(0x100202, &mctl_ctl->dfitmg1);
> +
> +	writel(trfc_min | trefi_x32 << 16, &mctl_ctl->rfshtmg);
> +}


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 1/6] sunxi: A133: add DRAM init code
  2025-05-11  1:09 ` [PATCH v2 1/6] sunxi: A133: add DRAM init code Andre Przywara
  2025-05-11  9:23   ` Andre Przywara
@ 2025-05-12  9:21   ` Quentin Schulz
  2025-05-12 10:49     ` Andre Przywara
  2025-05-12 15:12   ` Cody Eksal
  2 siblings, 1 reply; 15+ messages in thread
From: Quentin Schulz @ 2025-05-12  9:21 UTC (permalink / raw)
  To: Andre Przywara, u-boot
  Cc: Jagan Teki, Cody Eksal, Philippe Simons, Sumit Garg, linux-sunxi,
	Tom Rini, Jernej Skrabec

Hi Andre,

On 5/11/25 3:09 AM, Andre Przywara wrote:
> From: Cody Eksal <masterr3c0rd@epochal.quest>
> 
> This adds preliminary support for the DRAM controller in the Allwinner
> A100/A133 SoCs.
> This is work in progress, and has rough edges, but works on at least
> three different boards. It contains support for DDR4 and LPDDR4.
> 
> [Andre: formatting fixes, adapt to mainline, drop unused parameters,
> 	remove struct struct sunxi_mctl_com_reg, hardcode MR registers,
> 	switch to mctl_check_pattern(), remove simple DRAM check]

Missing Cody's and your Signed-off-by.

Cheers,
Quentin

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 3/6] arm64: dts: allwinner: a100: Add CPU Operating Performance Points table
  2025-05-11  1:10 ` [PATCH v2 3/6] arm64: dts: allwinner: a100: Add CPU Operating Performance Points table Andre Przywara
@ 2025-05-12  9:24   ` Quentin Schulz
  2025-05-12 10:46     ` Andre Przywara
  0 siblings, 1 reply; 15+ messages in thread
From: Quentin Schulz @ 2025-05-12  9:24 UTC (permalink / raw)
  To: Andre Przywara, u-boot
  Cc: Jagan Teki, Cody Eksal, Philippe Simons, Sumit Garg, linux-sunxi,
	Tom Rini, Jernej Skrabec

Hi Andre,

On 5/11/25 3:10 AM, Andre Przywara wrote:
> Add an Operating Performance Points table for the CPU cores to
> enable Dynamic Voltage & Frequency Scaling on the A100.
> 
> Signed-off-by: Shuosheng Huang <huangshuosheng@allwinnertech.com>
> [masterr3c0rd@epochal.quest: fix typos in -cpu-opp, use compatible]
> Signed-off-by: Cody Eksal <masterr3c0rd@epochal.quest>
> Link: https://patch.msgid.link/20241031070232.1793078-14-masterr3c0rd@epochal.quest
> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
> 
> [ upstream commit: a8181e6861fec3068f393d77ff81b2aaf4ea4203 ]

Did you use tools/update-subtree.sh to cherry-pick those commits? The 
changelog seems odd to me compared to what we usually get when using 
that tool. Modifications of dts/upstream should only be done via this tool.

Same remark for the other DTS changes in this series.

Cheers,
Quentin

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 3/6] arm64: dts: allwinner: a100: Add CPU Operating Performance Points table
  2025-05-12  9:24   ` Quentin Schulz
@ 2025-05-12 10:46     ` Andre Przywara
  2025-05-12 15:58       ` Quentin Schulz
  0 siblings, 1 reply; 15+ messages in thread
From: Andre Przywara @ 2025-05-12 10:46 UTC (permalink / raw)
  To: Quentin Schulz
  Cc: u-boot, Jagan Teki, Cody Eksal, Philippe Simons, Sumit Garg,
	linux-sunxi, Tom Rini, Jernej Skrabec

On Mon, 12 May 2025 11:24:07 +0200
Quentin Schulz <quentin.schulz@cherry.de> wrote:

> Hi Andre,
> 
> On 5/11/25 3:10 AM, Andre Przywara wrote:
> > Add an Operating Performance Points table for the CPU cores to
> > enable Dynamic Voltage & Frequency Scaling on the A100.
> > 
> > Signed-off-by: Shuosheng Huang <huangshuosheng@allwinnertech.com>
> > [masterr3c0rd@epochal.quest: fix typos in -cpu-opp, use compatible]
> > Signed-off-by: Cody Eksal <masterr3c0rd@epochal.quest>
> > Link: https://patch.msgid.link/20241031070232.1793078-14-masterr3c0rd@epochal.quest
> > Signed-off-by: Chen-Yu Tsai <wens@csie.org>
> > 
> > [ upstream commit: a8181e6861fec3068f393d77ff81b2aaf4ea4203 ]  
> 
> Did you use tools/update-subtree.sh to cherry-pick those commits? The 

No, I didn't, just remembered that afterwards. This would only work for
this patch anyways, since the other two DT patches are not yet merged in a
tagged release (the last one isn't even reviewed yet), so I just added them
here for completeness, to give people an idea of how the defconfig would
look like and to allow compile testing.

The plan was to maybe merge the first two patches (the DRAM code and
Kconfig bits), then wait for the DT bits to trickle in.

Thanks!
Andre

> changelog seems odd to me compared to what we usually get when using 
> that tool. Modifications of dts/upstream should only be done via this tool.
> 
> Same remark for the other DTS changes in this series.
> 
> Cheers,
> Quentin


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 1/6] sunxi: A133: add DRAM init code
  2025-05-12  9:21   ` Quentin Schulz
@ 2025-05-12 10:49     ` Andre Przywara
  0 siblings, 0 replies; 15+ messages in thread
From: Andre Przywara @ 2025-05-12 10:49 UTC (permalink / raw)
  To: Quentin Schulz, Cody Eksal
  Cc: u-boot, Jagan Teki, Philippe Simons, Sumit Garg, linux-sunxi,
	Tom Rini, Jernej Skrabec

On Mon, 12 May 2025 11:21:33 +0200
Quentin Schulz <quentin.schulz@cherry.de> wrote:

> Hi Andre,
> 
> On 5/11/25 3:09 AM, Andre Przywara wrote:
> > From: Cody Eksal <masterr3c0rd@epochal.quest>
> > 
> > This adds preliminary support for the DRAM controller in the Allwinner
> > A100/A133 SoCs.
> > This is work in progress, and has rough edges, but works on at least
> > three different boards. It contains support for DDR4 and LPDDR4.
> > 
> > [Andre: formatting fixes, adapt to mainline, drop unused parameters,
> > 	remove struct struct sunxi_mctl_com_reg, hardcode MR registers,
> > 	switch to mctl_check_pattern(), remove simple DRAM check]  
> 
> Missing Cody's and your Signed-off-by.

Yeah, I know, I am hoping for Cody to reply to this mail with the line,
since it was also missing in his github, and I don't think I can just slap
SoB lines on someone's patches.
I will add mine then as well.

Thanks,
Andre

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 1/6] sunxi: A133: add DRAM init code
  2025-05-11  1:09 ` [PATCH v2 1/6] sunxi: A133: add DRAM init code Andre Przywara
  2025-05-11  9:23   ` Andre Przywara
  2025-05-12  9:21   ` Quentin Schulz
@ 2025-05-12 15:12   ` Cody Eksal
  2025-05-26 13:45     ` Parthiban
  2 siblings, 1 reply; 15+ messages in thread
From: Cody Eksal @ 2025-05-12 15:12 UTC (permalink / raw)
  To: Andre Przywara
  Cc: u-boot, Jagan Teki, Philippe Simons, Sumit Garg, linux-sunxi,
	Tom Rini, Jernej Skrabec

On 2025/05/10 10:09 pm, Andre Przywara wrote:
> From: Cody Eksal <masterr3c0rd@epochal.quest>
> 
> This adds preliminary support for the DRAM controller in the Allwinner
> A100/A133 SoCs.
> This is work in progress, and has rough edges, but works on at least
> three different boards. It contains support for DDR4 and LPDDR4.
> 
> [Andre: formatting fixes, adapt to mainline, drop unused parameters,
> 	remove struct struct sunxi_mctl_com_reg, hardcode MR registers,
> 	switch to mctl_check_pattern(), remove simple DRAM check]

Thank you Andre for cleaning up my patches. Life has a nasty way of
getting in the way when it's least convenient.

Signed-off-by: Cody Eksal <masterr3c0rd@epochal.quest>

- Cody

> ---
>  .../include/asm/arch-sunxi/cpu_sun50i_h6.h    |    4 +
>  arch/arm/include/asm/arch-sunxi/dram.h        |    2 +
>  .../include/asm/arch-sunxi/dram_sun50i_a133.h |  230 ++++
>  arch/arm/mach-sunxi/Kconfig                   |  104 +-
>  arch/arm/mach-sunxi/Makefile                  |    2 +
>  arch/arm/mach-sunxi/dram_sun50i_a133.c        | 1204 +++++++++++++++++
>  arch/arm/mach-sunxi/dram_timings/Makefile     |    2 +
>  arch/arm/mach-sunxi/dram_timings/a133_ddr4.c  |   80 ++
>  .../arm/mach-sunxi/dram_timings/a133_lpddr4.c |  102 ++
>  9 files changed, 1722 insertions(+), 8 deletions(-)
>  create mode 100644 arch/arm/include/asm/arch-sunxi/dram_sun50i_a133.h
>  create mode 100644 arch/arm/mach-sunxi/dram_sun50i_a133.c
>  create mode 100644 arch/arm/mach-sunxi/dram_timings/a133_ddr4.c
>  create mode 100644 arch/arm/mach-sunxi/dram_timings/a133_lpddr4.c
> 

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 3/6] arm64: dts: allwinner: a100: Add CPU Operating Performance Points table
  2025-05-12 10:46     ` Andre Przywara
@ 2025-05-12 15:58       ` Quentin Schulz
  0 siblings, 0 replies; 15+ messages in thread
From: Quentin Schulz @ 2025-05-12 15:58 UTC (permalink / raw)
  To: Andre Przywara
  Cc: u-boot, Jagan Teki, Cody Eksal, Philippe Simons, Sumit Garg,
	linux-sunxi, Tom Rini, Jernej Skrabec

Hi Andre,

On 5/12/25 12:46 PM, Andre Przywara wrote:
> On Mon, 12 May 2025 11:24:07 +0200
> Quentin Schulz <quentin.schulz@cherry.de> wrote:
> 
>> Hi Andre,
>>
>> On 5/11/25 3:10 AM, Andre Przywara wrote:
>>> Add an Operating Performance Points table for the CPU cores to
>>> enable Dynamic Voltage & Frequency Scaling on the A100.
>>>
>>> Signed-off-by: Shuosheng Huang <huangshuosheng@allwinnertech.com>
>>> [masterr3c0rd@epochal.quest: fix typos in -cpu-opp, use compatible]
>>> Signed-off-by: Cody Eksal <masterr3c0rd@epochal.quest>
>>> Link: https://patch.msgid.link/20241031070232.1793078-14-masterr3c0rd@epochal.quest
>>> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
>>>
>>> [ upstream commit: a8181e6861fec3068f393d77ff81b2aaf4ea4203 ]
>>
>> Did you use tools/update-subtree.sh to cherry-pick those commits? The
> 
> No, I didn't, just remembered that afterwards. This would only work for
> this patch anyways, since the other two DT patches are not yet merged in a
> tagged release (the last one isn't even reviewed yet), so I just added them
> here for completeness, to give people an idea of how the defconfig would
> look like and to allow compile testing.
> 

Considering you're the sunxi maintainer, I assume you wouldn't merge the 
patches knowing that they are not fitting the expected workflow so I'm 
not too worried :)

> The plan was to maybe merge the first two patches (the DRAM code and
> Kconfig bits), then wait for the DT bits to trickle in.
> 

Sounds like a plan! Have fun :)

Cheers,
Quentin

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 1/6] sunxi: A133: add DRAM init code
  2025-05-12 15:12   ` Cody Eksal
@ 2025-05-26 13:45     ` Parthiban
  0 siblings, 0 replies; 15+ messages in thread
From: Parthiban @ 2025-05-26 13:45 UTC (permalink / raw)
  To: Cody Eksal, Andre Przywara
  Cc: parthiban, u-boot, Jagan Teki, Philippe Simons, Sumit Garg,
	linux-sunxi, Tom Rini, Jernej Skrabec

On 5/12/25 8:42 PM, Cody Eksal wrote:
> On 2025/05/10 10:09 pm, Andre Przywara wrote:
>> From: Cody Eksal <masterr3c0rd@epochal.quest>
>>
>> This adds preliminary support for the DRAM controller in the Allwinner
>> A100/A133 SoCs.
>> This is work in progress, and has rough edges, but works on at least
>> three different boards. It contains support for DDR4 and LPDDR4.
>>
>> [Andre: formatting fixes, adapt to mainline, drop unused parameters,
>>     remove struct struct sunxi_mctl_com_reg, hardcode MR registers,
>>     switch to mctl_check_pattern(), remove simple DRAM check]
> 
> Thank you Andre for cleaning up my patches. Life has a nasty way of
> getting in the way when it's least convenient.
> 
> Signed-off-by: Cody Eksal <masterr3c0rd@epochal.quest>
Thanks Andre and Cody for the cleanup and work here. I tried booting
this on my A133 helper board with 1GB RAM and it worked fine. I have
a custom PCB with 4GB which fails to boot fully. I have added the
log and defconfig below.

I have added additional debug in the u-boot booting, i.e SPL booted
and u-boot as well (after BL31), but failed to resume from the relocated
address.

Previous version i.e 2024.10.x tree from Cody boots fine, but when I
was able to work only upto 1GB address, accessing beyond that crashes
in the kernel boot flow.

Did I miss some configuration?

vendor u-boot DRAM related snippet from console:
[759]dram_para[0]:0x318
[761]dram_para[1]:0x8
[763]dram_para[2]:0x7070707
[766]dram_para[3]:0xd0d0d0d
[769]dram_para[4]:0xe0e
[771]dram_para[5]:0xd0a050c
[774]dram_para[6]:0x310a
[777]dram_para[7]:0x10001000
[779]dram_para[8]:0x0
[782]dram_para[9]:0x34
[784]dram_para[10]:0x1b
[786]dram_para[11]:0x33
[789]dram_para[12]:0x3
[791]dram_para[13]:0x0
[793]dram_para[14]:0x0
[795]dram_para[15]:0x4
[798]dram_para[16]:0x72
[800]dram_para[17]:0x0
[802]dram_para[18]:0x7
[805]dram_para[19]:0x0
[807]dram_para[20]:0x0
[809]dram_para[21]:0x26
[812]dram_para[22]:0x6060606
[815]dram_para[23]:0x84040404
[818]dram_para[24]:0x0
[820]dram_para[25]:0x0
[822]dram_para[26]:0x48010101
[825]dram_para[27]:0x273333
[828]dram_para[28]:0x1e19131c
[831]dram_para[29]:0x14141312
[834]dram_para[30]:0x7521
[836]dram_para[31]:0x2023211f
[844]DRAM BOOT DRIVE INFO: V0.69
[863]DRAM_VCC set to 1100 mv
[866]DRAM CLK =792 MHZ
[868]DRAM Type =8 (3:DDR3,4:DDR4,7:LPDDR3,8:LPDDR4)
[878]DRAM SIZE =4096 MBytes, para1 = 310a, para2 = 10001000, tpr13 = 7521
[890]DRAM simple test OK.
[893]dram size =4096

Upstream defconfig used:
CONFIG_DRAM_SUNXI_DX_ODT=0x7070707
CONFIG_DRAM_SUNXI_DX_DRI=0xd0d0d0d
CONFIG_DRAM_SUNXI_CA_DRI=0xe0e
CONFIG_DRAM_SUNXI_PARA0=0xd0a050c
CONFIG_DRAM_SUNXI_MR11=0x4
CONFIG_DRAM_SUNXI_MR12=0x72
CONFIG_DRAM_SUNXI_MR14=0x7
CONFIG_DRAM_SUNXI_TPR1=0x26
CONFIG_DRAM_SUNXI_TPR2=0x6060606
CONFIG_DRAM_SUNXI_TPR3=0x84040404
CONFIG_DRAM_SUNXI_TPR6=0x48010101
CONFIG_DRAM_SUNXI_TPR10=0x273333
CONFIG_DRAM_SUNXI_TPR11=0x1e19131c
CONFIG_DRAM_SUNXI_TPR12=0x14141312
CONFIG_DRAM_SUNXI_TPR13=0x7521
CONFIG_DRAM_SUNXI_TPR14=0x2023211f
CONFIG_MACH_SUN50I_A133=y
CONFIG_SUNXI_DRAM_A133_LPDDR4=y
CONFIG_DRAM_CLK=792

Upstream boot log:
U-Boot SPL 2025.07-rc2-00008-g9dab0d385a9d (May 26 2025 - 19:06:29 +0530)                                                                                                                                  [1/37737]
DRAM: 4096 MiB                                                                                                                                                                                                      
Trying to boot from FEL
NOTICE:  BL31: v2.12.0(debug):v2.13-rc1-3-g35e7dd0a6-dirty
NOTICE:  BL31: Built : 20:27:56, May 22 2025
NOTICE:  BL31: Detected Allwinner A100/A133 SoC (1855)
NOTICE:  BL31: Found U-Boot DTB at 0xa0a2e80, model: MEC electronics A133 DM2
INFO:    ARM GICv2 driver initialized
INFO:    Configuring SPC Controller
INFO:    Probing for PMIC on I2C
INFO:    PMIC: found AXP717
INFO:    BL31: Platform setup done
INFO:    BL31: Initializing runtime services
INFO:    BL31: cortex_a53: CPU workaround for erratum 855873 was applied
INFO:    BL31: cortex_a53: CPU workaround for erratum 1530924 was applied
INFO:    PSCI: Suspend is unavailable
INFO:    BL31: Preparing for EL3 exit to normal world 
INFO:    Entry point address = 0x4a000000
INFO:    SPSR = 0x3c9
initcall_run_f 899


U-Boot 2025.07-rc2-00008-g9dab0d385a9d (May 26 2025 - 19:06:29 +0530) Allwinner Technology

U-Boot code: 4A000000 -> 4A094C48  BSS: -> 4A09EE70
CPU:   Allwinner A133 (SUN50I)
Model: MEC electronics A133 DM2
initcall_run_f 960
DRAM:  initcall_run_f 963
initcall_run_f 968
Monitor len: 0009ee70
Ram size: 100000000
Ram top: 100000000
initcall_run_f 990
initcall_run_f 998
Reserving 635k for U-Boot at: fff51000
initcall_run_f 1005
Reserving 65664k for malloc() at: fbf31000
Reserving 120 Bytes for Board Info at: fbf30f80
Reserving 368 Bytes for Global Data at: fbf30e10
Reserving 19968 Bytes for FDT at: fbf2c010
initcall_run_f 1015
initcall_run_f 1020
initcall_run_f 1022

RAM Configuration:
Bank #0: 40000000 4 GiB

DRAM:  4 GiB
initcall_run_f 1024
initcall_run_f 1026
New Stack Pointer is: fbf2c000
Relocation Offset is: b5f51000
Relocating to fff51000, new gd at fbf30e10, sp at fbf2c000
initcall_run_f 1055

Thanks,
Parthiban

> 
> - Cody
> 
>> ---
>>  .../include/asm/arch-sunxi/cpu_sun50i_h6.h    |    4 +
>>  arch/arm/include/asm/arch-sunxi/dram.h        |    2 +
>>  .../include/asm/arch-sunxi/dram_sun50i_a133.h |  230 ++++
>>  arch/arm/mach-sunxi/Kconfig                   |  104 +-
>>  arch/arm/mach-sunxi/Makefile                  |    2 +
>>  arch/arm/mach-sunxi/dram_sun50i_a133.c        | 1204 +++++++++++++++++
>>  arch/arm/mach-sunxi/dram_timings/Makefile     |    2 +
>>  arch/arm/mach-sunxi/dram_timings/a133_ddr4.c  |   80 ++
>>  .../arm/mach-sunxi/dram_timings/a133_lpddr4.c |  102 ++
>>  9 files changed, 1722 insertions(+), 8 deletions(-)
>>  create mode 100644 arch/arm/include/asm/arch-sunxi/dram_sun50i_a133.h
>>  create mode 100644 arch/arm/mach-sunxi/dram_sun50i_a133.c
>>  create mode 100644 arch/arm/mach-sunxi/dram_timings/a133_ddr4.c
>>  create mode 100644 arch/arm/mach-sunxi/dram_timings/a133_lpddr4.c
>>
> 


^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2025-05-26 13:45 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-05-11  1:09 [PATCH v2 0/6] sunxi: Allwinner A133 SoC support Andre Przywara
2025-05-11  1:09 ` [PATCH v2 1/6] sunxi: A133: add DRAM init code Andre Przywara
2025-05-11  9:23   ` Andre Przywara
2025-05-12  9:21   ` Quentin Schulz
2025-05-12 10:49     ` Andre Przywara
2025-05-12 15:12   ` Cody Eksal
2025-05-26 13:45     ` Parthiban
2025-05-11  1:09 ` [PATCH v2 2/6] sunxi: add support for the Allwinner A100/A133 SoC Andre Przywara
2025-05-11  1:10 ` [PATCH v2 3/6] arm64: dts: allwinner: a100: Add CPU Operating Performance Points table Andre Przywara
2025-05-12  9:24   ` Quentin Schulz
2025-05-12 10:46     ` Andre Przywara
2025-05-12 15:58       ` Quentin Schulz
2025-05-11  1:10 ` [PATCH v2 4/6] arm64: dts: allwinner: a100: set maximum MMC frequency Andre Przywara
2025-05-11  1:10 ` [PATCH v2 5/6] arm64: dts: allwinner: a100: add Liontron H-A133L board support Andre Przywara
2025-05-11  1:10 ` [PATCH v2 6/6] sunxi: add support for Liontron H-A133L board Andre Przywara

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox