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 <[email protected]> --- 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

