[RFC PATCH v1 09/14] ram: rockchip: rk3576: add DRAM controller and PHY driver
Johan Axelsson
johan.axelsson at proton.me
Mon May 25 03:29:33 CEST 2026
Add the TPL DRAM initialization driver for RK3576. This replaces
Rockchip's DDR blob with an open-source implementation targeting
upstream U-Boot.
Hardware: Synopsys uMCTL2 DDRCTL + Synopsys LPDDR4/4X/5 combo PHY.
Two independent channels (ch0/ch1), each with one DDRCTL + one PHY.
Supported DRAM types: LPDDR4, LPDDR4X, LPDDR5.
Key implementation notes:
PHY training: the RK3576 combo PHY includes a hardware training
engine (phy_train_en / phy_train_done in SCHD_TRAIN_CON0[0:1]).
No Synopsys training firmware (.bin) is needed. This is confirmed
by TRM §7.6.4-7.6.5 and matches the approach used on RK3568.
Init sequence: follows TRM §7.6.2 (LPDDR4, 37 steps) and §7.6.3
(LPDDR5, 42 steps). LPDDR5 deviations are noted at each step:
WCK2CK sync setup (step 26/28), CS toggle (step 28), ZQ calibration,
WCK always-on mode (step 42).
ZQ calibration: phy_zq_calibrate() runs the PHY impedance engine
(ZQ_CON0/CON1) per TRM §7.6.2 step 29 / §7.6.3 step 33.
Geometry storage: sdram_org_config() encodes cap_info into the
Rockchip sys_reg format and writes PMU1GRF OS_REG2/3 for SPL and
Linux to consume (W10).
SPL/U-Boot stub: rk3576_dmc_get_info() reads DRAM size back from
OS_REG2 via rockchip_sdram_size().
Timing tables: included from sdram-rk3576-lpddr5-detect-2133.inc
and sdram-rk3576-lpddr4-detect-1560.inc via sdram_configs[].
Modelled on the RK3568 RFC series (20260517-rk3568-raminit-v1,
Pavel Golikov). Addresses Flipper One open DRAM init task
(flipperdevices/flipperone-linux-build-scripts#56).
Signed-off-by: Johan Axelsson <johan.axelsson at proton.me>
---
drivers/ram/rockchip/sdram_rk3576.c | 655 +++++++++++++++++++++++++++-
1 file changed, 646 insertions(+), 9 deletions(-)
diff --git a/drivers/ram/rockchip/sdram_rk3576.c b/drivers/ram/rockchip/sdram_rk3576.c
index 5a66032ef8f..502dcaeacab 100644
--- a/drivers/ram/rockchip/sdram_rk3576.c
+++ b/drivers/ram/rockchip/sdram_rk3576.c
@@ -1,20 +1,657 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * (C) Copyright 2024 Rockchip Electronics Co., Ltd.
+ * RK3576 DRAM controller + PHY driver.
+ *
+ * Implements LPDDR4/4X/5 initialization without Rockchip DDR blob.
+ * TPL (CONFIG_XPL_BUILD) section: full init sequence.
+ * SPL / U-Boot proper section: read back geometry from PMU1GRF.
+ *
+ * TRM references:
+ * §7.6.2 LPDDR4/4X software initialization procedure (37 steps)
+ * §7.6.3 LPDDR5 software initialization procedure (42 steps)
+ * §7.4.6 DDRPHY detail register descriptions (pp.808-980)
+ * §7.4.3 DDRCTL register field descriptions
+ *
+ * Template: RK3568 RFC series 20260517-rk3568-raminit-v1 (Pavel Golikov)
+ * RK3576 divergence: newer LPDDR4/4X/5 combo PHY with hardware training
+ * engine (phy_train_en / phy_train_done) — no Synopsys training firmware.
*/
+#include <config.h>
#include <dm.h>
#include <ram.h>
+#include <syscon.h>
+#include <asm/io.h>
+#include <asm/arch-rockchip/clock.h>
#include <asm/arch-rockchip/sdram.h>
+#include <asm/arch-rockchip/sdram_rk3576.h>
+#include <linux/delay.h>
-#define PMU1GRF_BASE 0x26026000
-#define OS_REG2_REG 0x208
+/* -----------------------------------------------------------------------
+ * TPL section — full DDRCTL + DDRPHY initialization
+ * -----------------------------------------------------------------------
+ */
+
+#if defined(CONFIG_TPL_BUILD) || \
+ (!defined(CONFIG_TPL) && defined(CONFIG_XPL_BUILD))
+
+/*
+ * Precalculated timing tables for supported DRAM configurations.
+ * Each .inc file contributes one or more rk3576_sdram_params entries.
+ * W8 / W9 work items.
+ */
+static const struct rk3576_sdram_params sdram_configs[] = {
+#if defined(CONFIG_RAM_ROCKCHIP_LPDDR5)
+# include "sdram-rk3576-lpddr5-detect-2133.inc"
+#endif
+#if defined(CONFIG_RAM_ROCKCHIP_LPDDR4)
+# include "sdram-rk3576-lpddr4-detect-1560.inc"
+#endif
+};
+
+/* -----------------------------------------------------------------------
+ * SWCTL protocol — every quasi-dynamic DDRCTL register write must use:
+ * swctl_unlock → write register(s) → swctl_lock
+ * (TRM §7.4.3 DDRCTL_SWCTL / DDRCTL_SWSTAT)
+ * -----------------------------------------------------------------------
+ */
+
+static void swctl_unlock(void __iomem *ctl)
+{
+ writel(0, ctl + DDRCTL_SWCTL);
+}
+
+static void swctl_lock(void __iomem *ctl)
+{
+ writel(SWCTL_SW_DONE, ctl + DDRCTL_SWCTL);
+ while (!(readl(ctl + DDRCTL_SWSTAT) & SWSTAT_SW_DONE_ACK))
+ ;
+}
+
+/* -----------------------------------------------------------------------
+ * DDR_GRF write helper — Rockchip write-mask convention:
+ * bits[31:16] are the write-enable mask for bits[15:0]
+ * -----------------------------------------------------------------------
+ */
+
+static void grf_writemask(void __iomem *grf, u32 off, u32 mask, u32 val)
+{
+ writel((mask << 16) | (val & mask), grf + off);
+}
+
+/* -----------------------------------------------------------------------
+ * DDRPHY 2x clock gate control via DDR_GRF (TRM §7.6.2 steps 12/30/34)
+ * Applies to both PHY_CON0 and PHY_CON1 for the given channel.
+ * enable=1: clock flows; enable=0: gate before frequency change.
+ * -----------------------------------------------------------------------
+ */
+
+static void phy_clkgate_set(void __iomem *grf, int ch, int enable)
+{
+ u32 con0 = ch ? DDR_GRF_CHB_PHY_CON0 : DDR_GRF_CHA_PHY_CON0;
+ u32 con1 = ch ? DDR_GRF_CHB_PHY_CON1 : DDR_GRF_CHA_PHY_CON1;
+ u32 val = enable ? PHY_CON_DDRPHY2XCLKGATE_EN : 0;
+
+ grf_writemask(grf, con0, PHY_CON_DDRPHY2XCLKGATE_EN, val);
+ grf_writemask(grf, con1, PHY_CON_DDRPHY2XCLKGATE_EN, val);
+}
+
+/* -----------------------------------------------------------------------
+ * dfi_init_complete source mux via DDR_GRF (TRM §7.6.2 step 31)
+ *
+ * Before turning MDLL on (ctrl_dll_on=1), dfi_init_complete must be
+ * sourced from DDR_GRF rather than from the PHY, because MDLL power-on
+ * will momentarily pull dfi_init_complete low, which would confuse DDRCTL.
+ *
+ * from_grf=1: set GRF_CHA/CHB_DDRPHY_CON0[5:4] = 0b11 (both bits)
+ * from_grf=0: clear bit[4] (restore normal DDRCTL-driven path)
+ * -----------------------------------------------------------------------
+ */
+
+static void grf_dfi_init_comp_sel(void __iomem *grf, int ch, int from_grf)
+{
+ u32 reg = ch ? GRF_CHB_DDRPHY_CON0 : GRF_CHA_DDRPHY_CON0;
+
+ if (from_grf) {
+ /*
+ * TRM step 31: set bit[5] first, then bit[4].
+ * Both bits together select the GRF-driven path.
+ */
+ grf_writemask(grf, reg, DDRPHY_CON0_DFI_INIT_COMP_SEL_MASK,
+ DDRPHY_CON0_DFI_INIT_COMP_FROM_PHY);
+ } else {
+ /* Step 34: clear bit[4] to restore DDRCTL DFI output */
+ grf_writemask(grf, reg, DDRPHY_CON0_DFI_INIT_COMP_SEL_MASK, 0);
+ }
+}
+
+/* -----------------------------------------------------------------------
+ * DDRPHY mode registers (TRM §7.6.2 step 11 / §7.6.3 step 11)
+ *
+ * GNR_CON0.ctrl_ddr_mode: 0=LPDDR4, 2=LPDDR5
+ * CLKMODE_CON.ctrl_phy_mode: 1=LPDDR4 1:2:2, 4=LPDDR5 1:1:4
+ * CLKMODE_CON.ctrl_phy_clk_2x: always 1 (PHY 2x clock enabled)
+ * -----------------------------------------------------------------------
+ */
+
+static void phy_set_mode(void __iomem *phy, unsigned int dram_type)
+{
+ u32 gnr_mode, clkmode;
+
+ if (dram_type == LPDDR5) {
+ /* GNR_CON0: ctrl_ddr_mode=2, wdqs_oen_mode=1 (TRM p.808/821) */
+ gnr_mode = GNR_CON0_DDR_MODE_LPDDR5 | GNR_CON0_WDQS_OEN_MODE;
+ /* CLKMODE_CON: 1:1:4 CLK_DFI:CK:WCK (Flipper One mode) */
+ clkmode = CLKMODE_CON_PHY_CLK_2X | CLKMODE_CON_PHY_MODE_LP5_1_4;
+ } else {
+ /* LPDDR4/4X: ctrl_ddr_mode=0, 1:2:2 ratio */
+ gnr_mode = GNR_CON0_DDR_MODE_LPDDR4;
+ clkmode = CLKMODE_CON_PHY_CLK_2X | CLKMODE_CON_PHY_MODE_LPDDR4;
+ }
+
+ clrsetbits_le32(phy + DDRPHY_GNR_CON0,
+ GNR_CON0_CTRL_DDR_MODE_MASK | GNR_CON0_WDQS_OEN_MODE,
+ gnr_mode);
+ clrsetbits_le32(phy + DDRPHY_CLKMODE_CON,
+ CLKMODE_CON_PHY_CLK_2X | CLKMODE_CON_PHY_MODE_MASK,
+ clkmode);
+}
+
+/* -----------------------------------------------------------------------
+ * DDRCTL: write per-frequency timing registers (TRM §7.6.2 step 15)
+ * Must be called for each of the RK3576_DDRCTL_NFREQS frequency sets.
+ * -----------------------------------------------------------------------
+ */
+
+static void ctl_set_freq_params(void __iomem *ctl, int fi,
+ const struct rk3576_ddrctl_freq_params *p)
+{
+ u32 b = DDRCTL_FREQ_BASE(fi);
+
+#define WF(r, v) writel((v), ctl + b + (r))
+ WF(DDRCTL_DRAMSET1TMG0, p->dramset1tmg0);
+ WF(DDRCTL_DRAMSET1TMG1, p->dramset1tmg1);
+ WF(DDRCTL_DRAMSET1TMG2, p->dramset1tmg2);
+ WF(DDRCTL_DRAMSET1TMG3, p->dramset1tmg3);
+ WF(DDRCTL_DRAMSET1TMG4, p->dramset1tmg4);
+ WF(DDRCTL_DRAMSET1TMG5, p->dramset1tmg5);
+ WF(DDRCTL_DRAMSET1TMG6, p->dramset1tmg6);
+ WF(DDRCTL_DRAMSET1TMG7, p->dramset1tmg7);
+ WF(DDRCTL_DRAMSET1TMG8, p->dramset1tmg8);
+ WF(DDRCTL_DRAMSET1TMG9, p->dramset1tmg9);
+ WF(DDRCTL_DRAMSET1TMG12, p->dramset1tmg12);
+ WF(DDRCTL_DRAMSET1TMG13, p->dramset1tmg13);
+ WF(DDRCTL_DRAMSET1TMG14, p->dramset1tmg14);
+ WF(DDRCTL_DRAMSET1TMG23, p->dramset1tmg23);
+ WF(DDRCTL_DRAMSET1TMG24, p->dramset1tmg24);
+ WF(DDRCTL_DRAMSET1TMG25, p->dramset1tmg25);
+ WF(DDRCTL_DRAMSET1TMG30, p->dramset1tmg30);
+ WF(DDRCTL_INITMR0, p->initmr0);
+ WF(DDRCTL_INITMR1, p->initmr1);
+ WF(DDRCTL_INITMR2, p->initmr2);
+ WF(DDRCTL_INITMR3, p->initmr3);
+ WF(DDRCTL_DFITMG0, p->dfitmg0);
+ WF(DDRCTL_DFITMG1, p->dfitmg1);
+ WF(DDRCTL_DFITMG2, p->dfitmg2);
+ WF(DDRCTL_DFITMG4, p->dfitmg4);
+ WF(DDRCTL_DFITMG5, p->dfitmg5);
+ WF(DDRCTL_DFILPTMG0, p->dfilptmg0);
+ WF(DDRCTL_DFILPTMG1, p->dfilptmg1);
+ WF(DDRCTL_DFIUPDTMG0, p->dfiupdtmg0);
+ WF(DDRCTL_DFIUPDTMG1, p->dfiupdtmg1);
+ WF(DDRCTL_DFIMSGTMG0, p->dfimsgtmg0);
+ WF(DDRCTL_RFSHSET1TMG0, p->rfshset1tmg0);
+ WF(DDRCTL_RFSHSET1TMG1, p->rfshset1tmg1);
+ WF(DDRCTL_RFSHSET1TMG2, p->rfshset1tmg2);
+ WF(DDRCTL_RFSHSET1TMG4, p->rfshset1tmg4);
+ WF(DDRCTL_ZQSET1TMG0, p->zqset1tmg0);
+ WF(DDRCTL_ZQSET1TMG1, p->zqset1tmg1);
+ WF(DDRCTL_PERFHPR1, p->perfhpr1);
+ WF(DDRCTL_PERFLPR1, p->perflpr1);
+ WF(DDRCTL_PERFWR1, p->perfwr1);
+ WF(DDRCTL_RANKTMG0, p->ranktmg0);
+ WF(DDRCTL_RANKTMG1, p->ranktmg1);
+ WF(DDRCTL_PWRTMG, p->pwrtmg);
+#undef WF
+}
+
+/* -----------------------------------------------------------------------
+ * DDRCTL: write global (static) registers (TRM §7.6.2 step 15)
+ * These are programmed once and are not per-frequency.
+ * -----------------------------------------------------------------------
+ */
+
+static void ctl_set_global_params(void __iomem *ctl,
+ const struct rk3576_ddrctl_global_params *g)
+{
+ writel(g->mstr0, ctl + DDRCTL_MSTR0);
+ writel(g->rankctl, ctl + DDRCTL_RANKCTL);
+ writel(g->dbictl, ctl + DDRCTL_DBICTL);
+ writel(g->odtmap, ctl + DDRCTL_ODTMAP);
+ writel(g->inittmg0, ctl + DDRCTL_INITTMG0);
+ writel(g->inittmg1, ctl + DDRCTL_INITTMG1);
+ writel(g->rfshmod0, ctl + DDRCTL_RFSHMOD0);
+ writel(g->sched0, ctl + DDRCTL_SCHED0);
+ writel(g->sched1, ctl + DDRCTL_SCHED1);
+ writel(g->sched3, ctl + DDRCTL_SCHED3);
+ writel(g->sched4, ctl + DDRCTL_SCHED4);
+ writel(g->dfilpcfg0, ctl + DDRCTL_DFILPCFG0);
+ writel(g->dfiupd0, ctl + DDRCTL_DFIUPD0);
+ writel(g->dfiphymstr, ctl + DDRCTL_DFIPHYMSTR);
+ writel(g->zqctl0, ctl + DDRCTL_ZQCTL0);
+ writel(g->zqctl1, ctl + DDRCTL_ZQCTL1);
+}
+
+/* -----------------------------------------------------------------------
+ * DDRPHY MDLL turn-on and locking (TRM §7.6.2 steps 32-33 / §7.6.3 36-37)
+ *
+ * Protocol: set ctrl_dll_on, pulse ctrl_start 0→1, poll ctrl_locked.
+ * -----------------------------------------------------------------------
+ */
+
+static void phy_mdll_lock(void __iomem *phy)
+{
+ /* Step 32/36: turn on master DLL */
+ setbits_le32(phy + DDRPHY_MDLL_CON0, MDLL_CON0_CTRL_DLL_ON);
+
+ /* Step 33/37: start locking — ctrl_start 0 → 1 */
+ clrbits_le32(phy + DDRPHY_MDLL_CON0, MDLL_CON0_CTRL_START);
+ setbits_le32(phy + DDRPHY_MDLL_CON0, MDLL_CON0_CTRL_START);
+
+ /* Poll for stable DLL lock (TRM p.835: ctrl_locked is stable lock) */
+ while (!(readl(phy + DDRPHY_MDLL_CON1) & MDLL_CON1_CTRL_LOCKED))
+ ;
+}
+
+/* -----------------------------------------------------------------------
+ * PHY hardware training engine (TRM §7.6.4 LP4 / §7.6.5 LP5)
+ *
+ * Hardware engine performs: write levelling → gate training → read/write
+ * training automatically when phy_train_en is asserted.
+ * -----------------------------------------------------------------------
+ */
+
+static int phy_train(void __iomem *phy)
+{
+ setbits_le32(phy + DDRPHY_SCHD_TRAIN_CON0, TRAIN_CON0_PHY_TRAIN_EN);
+ while (!(readl(phy + DDRPHY_SCHD_TRAIN_CON0) & TRAIN_CON0_PHY_TRAIN_DONE))
+ ;
+ clrbits_le32(phy + DDRPHY_SCHD_TRAIN_CON0, TRAIN_CON0_PHY_TRAIN_EN);
+
+ if (readl(phy + DDRPHY_CAL_FAIL_STAT0) & CAL_FAIL_STAT0_ANY_FAIL)
+ return -EIO;
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------
+ * Post-training PHY settings (TRM §7.6.2 step 37 / §7.6.3 step 42)
+ * -----------------------------------------------------------------------
+ */
+
+static void phy_post_training(void __iomem *phy)
+{
+ /* Re-enable DLL auto-locking (clkm_cg_en_sw back to 0) */
+ clrbits_le32(phy + DDRPHY_MDLL_CON0, MDLL_CON0_CLKM_CG_EN_SW);
+
+ /* Cycle-based write training off for normal operation */
+ clrbits_le32(phy + DDRPHY_CAL_CON5, CAL_CON5_WRTRN_CYC_MODE);
+
+ /* Enable VT compensation (tracks on-chip voltage/temp variation) */
+ setbits_le32(phy + DDRPHY_CAL_CON0, CAL_CON0_CAL_VTC_EN);
+
+ /*
+ * Re-interpret future dfi_lvl_periodic=0 write training as DVFS
+ * write training (searches tDQS2DQ variation, not full window).
+ */
+ setbits_le32(phy + DDRPHY_CAL_CON0, CAL_CON0_DVFS_WR_TRAIN_EN);
+
+ /* Push DLL updates to slave DLLs: toggle ctrl_resync 0 → 1 → 0 */
+ setbits_le32(phy + DDRPHY_OFFSETD_CON0, OFFSETD_CON0_CTRL_RESYNC);
+ clrbits_le32(phy + DDRPHY_OFFSETD_CON0, OFFSETD_CON0_CTRL_RESYNC);
+}
+
+/* -----------------------------------------------------------------------
+ * ZQ impedance calibration (TRM §7.6.2 step 29 / §7.6.3 step 33)
+ *
+ * Called after DDRCTL reaches normal operating mode and before the
+ * frequency change to target speed (pp.1071/1073).
+ * -----------------------------------------------------------------------
+ */
+
+static void phy_zq_calibrate(void __iomem *phy)
+{
+ /* Enable ZQ clock divider then start manual calibration */
+ setbits_le32(phy + DDRPHY_ZQ_CON0, ZQ_CON0_ZQ_CLK_DIV_EN);
+ setbits_le32(phy + DDRPHY_ZQ_CON0, ZQ_CON0_ZQ_MANUAL_STR);
+
+ /* Poll until calibration completes */
+ while (!(readl(phy + DDRPHY_ZQ_CON1) & ZQ_CON1_ZQ_DONE))
+ ;
+
+ /* Clear calibration trigger, then disable clock divider */
+ clrbits_le32(phy + DDRPHY_ZQ_CON0, ZQ_CON0_ZQ_MANUAL_STR);
+ clrbits_le32(phy + DDRPHY_ZQ_CON0, ZQ_CON0_ZQ_CLK_DIV_EN);
+}
+
+/* -----------------------------------------------------------------------
+ * Per-channel initialization
+ *
+ * Follows TRM §7.6.2 step numbering (LPDDR4/4X).
+ * Steps 1-10 (PLL, CRU resets) are performed by the SoC BootROM before
+ * TPL runs; this function starts at step 11.
+ * LPDDR5 deviations are noted at each relevant step.
+ * -----------------------------------------------------------------------
+ */
+
+static int ddr_init_channel(int ch, void __iomem *ctl, void __iomem *phy,
+ void __iomem *grf,
+ const struct rk3576_sdram_params *params)
+{
+ void __iomem *ddrcru;
+ unsigned int dram_type = params->base.dramtype;
+ int lpddr5 = (dram_type == LPDDR5);
+ int i, ret;
+
+ ddrcru = (void __iomem *)(ulong)(ch ? RK3576_DDR1CRU_BASE
+ : RK3576_DDR0CRU_BASE);
+
+ /*
+ * Step 11: Configure DDRPHY clock ratio and DRAM type mode.
+ * GNR_CON0.ctrl_ddr_mode, CLKMODE_CON.ctrl_phy_mode/ctrl_phy_clk_2x.
+ */
+ phy_set_mode(phy, dram_type);
+
+ /*
+ * Steps 12-13: Frequency switch from boot clock (D0APLL, 24 MHz) to
+ * the target frequency (D0BPLL, configured by clock driver).
+ *
+ * a) Disable PHY 2x clock gate before switching.
+ * b) For LPDDR5: assert div_rst_n (prevents PHY metastability).
+ * TODO: div_rst_n CRU bit — needs TRM Part 1 CRU chapter.
+ * c) Switch clock mux: DDR0/1CRU_CLKSEL_CON00[0] = 1 → D0B/D1BPLL.
+ * Write-mask convention: bit[16] = write-enable for bit[0].
+ * d) For LPDDR5: de-assert div_rst_n after clock settles.
+ * e) Re-enable PHY 2x clock gate.
+ */
+ phy_clkgate_set(grf, ch, 0);
+ writel(BIT(16) | DDRCRU_CLKSEL_D0BPLL, ddrcru + DDRCRU_CLKSEL_CON00);
+ phy_clkgate_set(grf, ch, 1);
+
+ /*
+ * Steps 14-16: De-assert DDRCTL and DDRPHY resets (presetn,
+ * core_ddrc_rstn, aresetn_i). Handled by BootROM; TPL assumes
+ * resets are already released before sdram_init() is called.
+ * TODO: verify with logic analyser on first hardware bring-up.
+ */
+
+ /*
+ * Step 15: Program DDRCTL global static registers.
+ * These must be written before core reset release in a cold-boot
+ * scenario; on warm-boot they overwrite the previous config.
+ */
+ ctl_set_global_params(ctl, ¶ms->ctl);
+
+ /* Step 15: Program all 4 per-frequency timing register banks. */
+ for (i = 0; i < RK3576_DDRCTL_NFREQS; i++)
+ ctl_set_freq_params(ctl, i, ¶ms->freq[i]);
+
+ /*
+ * Step 17: Disable auto-refresh, self-refresh, and power-down.
+ * Must be done while controller is in init mode (before step 26).
+ */
+ setbits_le32(ctl + DDRCTL_RFSHCTL0, RFSHCTL0_DIS_AUTO_REFRESH);
+ clrbits_le32(ctl + DDRCTL_PWRCTL, PWRCTL_SELFREF_EN | PWRCTL_SELFREF_SW);
+
+ /*
+ * Steps 18-20: Quasi-dynamic write — disable dfi_init_complete_en.
+ * SWCTL=0 (unlock), write DFIMISC, SWCTL=1 (lock + wait ack).
+ */
+ swctl_unlock(ctl);
+ clrbits_le32(ctl + DDRCTL_DFIMISC, DFIMISC_DFI_INIT_COMPLETE_EN);
+ swctl_lock(ctl);
+
+ /*
+ * Step 21: Enable all 5 AXI ports.
+ * DDRCTL_PCTRL(i).port_en = 1.
+ */
+ for (i = 0; i < DDRCTL_NPORTS; i++)
+ setbits_le32(ctl + DDRCTL_PCTRL(i), PCTRL_PORT_EN);
+
+ /*
+ * Step 22: Set DDRPHY operational registers.
+ * Use PHY-initiated DFI update mode (upd_mode=0).
+ * Latency and burst-length registers are at reset-safe defaults;
+ * the W9 timing table will supply correct values via phy params.
+ */
+ clrbits_le32(phy + DDRPHY_OFFSETD_CON0, OFFSETD_CON0_UPD_MODE);
+
+ /*
+ * Step 23: PHY IO settings before ZQ calibration.
+ * ctrl_pulld_dqs default (reset 0x3) keeps DQS pulled correctly.
+ * No additional writes needed at this stage.
+ */
+
+ /*
+ * Step 24: DFI initialization start.
+ *
+ * a) For LPDDR5: force DLL lock value for low-frequency operation
+ * (ctrl_force = 0x2ef, per TRM §7.6.3 step 24).
+ * b) Turn off master DLL (ctrl_dll_on = 0) on PHY.
+ * c) Quasi-dynamic write: assert DFIMISC.dfi_init_start.
+ * DDRCTL then drives dfi_init_start to PHY; PHY initializes
+ * and responds with dfi_init_complete when ready.
+ * d) Poll DFISTAT.dfi_init_complete = 1.
+ */
+ if (lpddr5)
+ clrsetbits_le32(phy + DDRPHY_MDLL_CON0,
+ MDLL_CON0_CTRL_FORCE_MASK | MDLL_CON0_CTRL_DLL_ON,
+ MDLL_CON0_CTRL_FORCE_LOW_FREQ);
+ else
+ clrbits_le32(phy + DDRPHY_MDLL_CON0, MDLL_CON0_CTRL_DLL_ON);
+
+ swctl_unlock(ctl);
+ setbits_le32(ctl + DDRCTL_DFIMISC, DFIMISC_DFI_INIT_START);
+ swctl_lock(ctl);
+
+ while (!(readl(ctl + DDRCTL_DFISTAT) & DFISTAT_DFI_INIT_COMPLETE))
+ ;
+
+ /*
+ * LP5 step 26: enable software intervention before SDRAM auto-init.
+ * MRCTRL0.sw_init_int=1 allows SW to inject MRS before the DDRCTL
+ * init sequence starts (needed for the CS toggle in step 28).
+ * TRM §7.6.3 step 26 (p.1073).
+ */
+ if (lpddr5)
+ writel(MRCTRL0_SW_INIT_INT, ctl + DDRCTL_MRCTRL0);
+
+ /*
+ * Step 25 (LP4) / Step 27 (LP5): clear dfi_init_start, enable
+ * dfi_init_complete_en. DDRCTL now drives the DRAM init sequence
+ * (CKE, MRS, etc.) autonomously via DFI commands.
+ */
+ swctl_unlock(ctl);
+ clrbits_le32(ctl + DDRCTL_DFIMISC, DFIMISC_DFI_INIT_START);
+ setbits_le32(ctl + DDRCTL_DFIMISC, DFIMISC_DFI_INIT_COMPLETE_EN);
+ swctl_lock(ctl);
+
+ /*
+ * LP5 step 28: CS toggle to assert power-down exit asynchronously.
+ * Toggle CHA/CHB CS: 0→1 (0xFF) then 1→0 (0x00).
+ * TRM §7.6.3 step 28 (p.1073); only the current channel's CON2.
+ */
+ if (lpddr5) {
+ u32 con2 = ch ? DDR_GRF_CHB_CON2 : DDR_GRF_CHA_CON2;
+
+ grf_writemask(grf, con2, 0xff, 0xff);
+ grf_writemask(grf, con2, 0xff, 0x00);
+ }
+
+ /*
+ * Step 26 (LP4) / Step 30 (LP5): wait for DDRCTL normal operating mode.
+ * DDRCTL_STAT.operating_mode = 1.
+ */
+ while ((readl(ctl + DDRCTL_STAT) & STAT_OPERATING_MODE_MASK) !=
+ STAT_OPERATING_MODE_NORMAL)
+ ;
+
+ /*
+ * Steps 27-28 (LP4) / Steps 31-32 (LP5): vref and ODT settings.
+ * Using PHY reset defaults for initial bring-up; no explicit writes.
+ */
+
+ /*
+ * Step 29 (LP4) / Step 33 (LP5): ZQ impedance calibration.
+ * TRM §7.6.2 step 29 / §7.6.3 step 33 (pp.1071/1073).
+ */
+ phy_zq_calibrate(phy);
+
+ /*
+ * Step 30 (LP4) / Step 34 (LP5): frequency change to target speed.
+ * DFI init protocol: gate PHY 2x clock, clear dfi_init_start, poll
+ * dfi_init_complete=1, set dfi_init_complete_en, ungate clock.
+ */
+ phy_clkgate_set(grf, ch, 0);
+
+ swctl_unlock(ctl);
+ clrbits_le32(ctl + DDRCTL_DFIMISC, DFIMISC_DFI_INIT_START);
+ swctl_lock(ctl);
+
+ while (!(readl(ctl + DDRCTL_DFISTAT) & DFISTAT_DFI_INIT_COMPLETE))
+ ;
+
+ swctl_unlock(ctl);
+ setbits_le32(ctl + DDRCTL_DFIMISC, DFIMISC_DFI_INIT_COMPLETE_EN);
+ swctl_lock(ctl);
+
+ phy_clkgate_set(grf, ch, 1);
+
+ /*
+ * Step 31 (LP4) / Step 35 (LP5):
+ * Switch dfi_init_complete source to DDR_GRF.
+ * This protects DDRCTL from the dfi_init_complete glitch that will
+ * occur when ctrl_dll_on is asserted (MDLL power-on dip).
+ * GRF_CHA/CHB_DDRPHY_CON0[5:4] = 0b11.
+ */
+ grf_dfi_init_comp_sel(grf, ch, 1);
+
+ /*
+ * Steps 32-33 (LP4) / Steps 36-37 (LP5):
+ * Turn on DDRPHY master DLL and wait for stable lock.
+ */
+ phy_mdll_lock(phy);
+
+ /*
+ * Step 34 (LP4) / Step 38 (LP5):
+ * Restore dfi_init_complete source to DDRCTL DFI output.
+ * GRF_CHA/CHB_DDRPHY_CON0[4] = 0.
+ */
+ grf_dfi_init_comp_sel(grf, ch, 0);
+
+ /*
+ * Step 35 (LP4) / Step 39 (LP5): DDRPHY scheduler and LP5 settings.
+ * ctrl_scheduler_en enables the PHY scheduler clock for normal op.
+ * LPDDR5 additionally requires: wck_enable=1, ctrl_dqs_drv_off=1
+ * (when write-link ECC is not in use), wdqs_oen_mode already set.
+ */
+ setbits_le32(phy + DDRPHY_LP_CON0, LP_CON0_CTRL_SCHEDULER_EN);
+ if (lpddr5)
+ setbits_le32(phy + DDRPHY_LP_CON0,
+ LP_CON0_WCK_ENABLE | LP_CON0_CTRL_DQS_DRV_OFF);
+
+ /*
+ * Step 36 (LP4) / Step 41 (LP5): Hardware training.
+ * phy_train_en=1 triggers the built-in training engine.
+ * Engine performs: write levelling → gate → read/write training.
+ * Poll phy_train_done=1, then clear phy_train_en.
+ */
+ ret = phy_train(phy);
+ if (ret)
+ return ret;
+
+ /*
+ * Step 37 (LP4) / Step 42 (LP5): Post-training settings.
+ * clkm_cg_en_sw=0, cal_vtc_en=1, dvfs_wr_train_en=1,
+ * toggle ctrl_resync to propagate DLL updates.
+ */
+ phy_post_training(phy);
+
+ /*
+ * LP5 step 42: set WCK driving policy before entering normal operation.
+ * Using WCK always-on disabled mode: WCK_MODE_APB=0, CAS_EN_APB=1.
+ * Both are reset defaults, but written explicitly per TRM §7.6.3 step 42
+ * (p.1074): "Please set WCK_MODE_APB before memory normal operation."
+ */
+ if (lpddr5) {
+ clrbits_le32(phy + DDRPHY_WCK2CKSYNC_CON0,
+ WCK2CKSYNC_CON0_WCK_MODE_MASK);
+ setbits_le32(phy + DDRPHY_SCHD_CMD_CON0,
+ SCHD_CMD_CON0_CAS_EN_APB);
+ }
+
+ /* Re-enable auto-refresh now that training is complete. */
+ clrbits_le32(ctl + DDRCTL_RFSHCTL0, RFSHCTL0_DIS_AUTO_REFRESH);
+
+ return 0;
+}
+
+/*
+ * sdram_init() — TPL entry point, called from arch/arm/mach-rockchip/sdram.c
+ *
+ * Initializes both DDR channels in parallel (sequential for now — parallel
+ * init would require separate PLL + GRF access which is more complex).
+ * Stores DRAM geometry in PMU1GRF OS_REG2/3 for SPL and Linux.
+ */
+int sdram_init(void)
+{
+ void __iomem *grf = (void __iomem *)(ulong)RK3576_DDR_GRF_BASE;
+ void __iomem *ctl0 = (void __iomem *)(ulong)RK3576_DDRCTL0_BASE;
+ void __iomem *ctl1 = (void __iomem *)(ulong)RK3576_DDRCTL1_BASE;
+ void __iomem *phy0 = (void __iomem *)(ulong)RK3576_DDRPHY0_BASE;
+ void __iomem *phy1 = (void __iomem *)(ulong)RK3576_DDRPHY1_BASE;
+ void __iomem *pmugrf = (void __iomem *)(ulong)RK3576_PMU1GRF_BASE;
+ const struct rk3576_sdram_params *params;
+ u32 sys_reg2 = 0, sys_reg3 = 0;
+ int ch, ret;
+
+ if (!ARRAY_SIZE(sdram_configs))
+ return -ENODATA;
+
+ /* Single config for Flipper One; auto-detection handled post-training */
+ params = &sdram_configs[0];
+
+ ret = ddr_init_channel(0, ctl0, phy0, grf, params);
+ if (ret)
+ return ret;
+
+ ret = ddr_init_channel(1, ctl1, phy1, grf, params);
+ if (ret)
+ return ret;
+
+ /* W10: store DRAM geometry in PMU1GRF OS_REG2/3 for SPL and Linux */
+ for (ch = 0; ch < params->base.num_channels; ch++)
+ sdram_org_config(¶ms->ch[ch].cap_info,
+ ¶ms->base, &sys_reg2, &sys_reg3, ch);
+ writel(sys_reg2, pmugrf + RK3576_PMUGRF_OS_REG2);
+ writel(sys_reg3, pmugrf + RK3576_PMUGRF_OS_REG3);
+
+ return 0;
+}
+
+#endif /* CONFIG_TPL_BUILD / CONFIG_XPL_BUILD */
+
+/* -----------------------------------------------------------------------
+ * SPL / U-Boot proper driver — reads DRAM size from PMU1GRF OS_REG2
+ * (written by TPL sdram_init() above)
+ * -----------------------------------------------------------------------
+ */
static int rk3576_dmc_get_info(struct udevice *dev, struct ram_info *info)
{
info->base = CFG_SYS_SDRAM_BASE;
- info->size = rockchip_sdram_size(PMU1GRF_BASE + OS_REG2_REG);
-
+ info->size = rockchip_sdram_size((phys_addr_t)(RK3576_PMU1GRF_BASE +
+ RK3576_PMUGRF_OS_REG2));
return 0;
}
@@ -28,8 +665,8 @@ static const struct udevice_id rk3576_dmc_ids[] = {
};
U_BOOT_DRIVER(rockchip_rk3576_dmc) = {
- .name = "rockchip_rk3576_dmc",
- .id = UCLASS_RAM,
- .of_match = rk3576_dmc_ids,
- .ops = &rk3576_dmc_ops,
+ .name = "rockchip_rk3576_dmc",
+ .id = UCLASS_RAM,
+ .of_match = rk3576_dmc_ids,
+ .ops = &rk3576_dmc_ops,
};
--
2.45.1.windows.1
More information about the U-Boot
mailing list