From: Frieder Schrempf <[email protected]> Boards that support several DDR types probe them by calling ddr_init() with different configurations until one of them succeeds. The power up procedure at the beginning of ddr_init() assumes a DDRMIX that is still in its power-on state, which is only true for the first call.
Repeating the initialization after a failed run happens to work, as such a run never gets to the point where it configures the DDR device and leaves a trained state behind. Repeating it after a successful run does not: the DDRC is in normal operating mode and the PHY holds the results of the previous training run in registers that neither ddrphy_cfg[] nor the SRC resets cover. The next ddr_init() still reports a successful training, but the DRAM is not functional afterwards. This is reproducible on a Kontron OSM-S i.MX8MP with 4GB DDR: the 8GB configuration is tried first and succeeds, the following init with the 4GB configuration returns success and the first access to the DRAM freezes the SoC. Bring the DDRMIX back to a defined state before repeating the initialization. Stop the DRAM traffic and the periodic controller activity, park DfiClk on the bypass clock and power cycle the DDRMIX domain. Asserting the reset chain alone is not sufficient, only removing the power returns the PHY to its defaults. The power down sequence follows dram_enter_retention() in TF-A, which powers the domain down for retention suspend. This is implemented for i.MX8MM, i.MX8MN and i.MX8MP. The i.MX8MQ has a second DDR domain that would need its own handling, so it is skipped there and repeating ddr_init() behaves as before. Assisted-by: LLM Signed-off-by: Frieder Schrempf <[email protected]> --- arch/arm/include/asm/arch-imx8m/imx-regs.h | 10 ++ drivers/ddr/imx/imx8m/ddr_init.c | 172 +++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) diff --git a/arch/arm/include/asm/arch-imx8m/imx-regs.h b/arch/arm/include/asm/arch-imx8m/imx-regs.h index 95a8cc1c637..150cd0a0c92 100644 --- a/arch/arm/include/asm/arch-imx8m/imx-regs.h +++ b/arch/arm/include/asm/arch-imx8m/imx-regs.h @@ -130,10 +130,20 @@ #define SRC_SCR_M4C_NON_SCLR_RST_MASK BIT(0) #define SRC_DDR1_ENABLE_MASK 0x8F000000UL #define SRC_DDR2_ENABLE_MASK 0x8F000000UL +#define SRC_DDR1_RCR_SYS_RESET_MASK BIT(4) #define SRC_DDR1_RCR_PHY_PWROKIN_N_MASK BIT(3) #define SRC_DDR1_RCR_PHY_RESET_MASK BIT(2) #define SRC_DDR1_RCR_CORE_RESET_N_MASK BIT(1) #define SRC_DDR1_RCR_PRESET_N_MASK BIT(0) +/* + * All of the above, a set bit always means "reset asserted". Bit 5 + * (DDRC1_PHY_WRST) is left out, the power up procedure doesn't use it either. + */ +#define SRC_DDR1_RCR_ALL_RESET_MASK (SRC_DDR1_RCR_SYS_RESET_MASK | \ + SRC_DDR1_RCR_PHY_PWROKIN_N_MASK | \ + SRC_DDR1_RCR_PHY_RESET_MASK | \ + SRC_DDR1_RCR_CORE_RESET_N_MASK | \ + SRC_DDR1_RCR_PRESET_N_MASK) #define SNVS_LPSR 0x4c #define SNVS_LPLVDR 0x64 diff --git a/drivers/ddr/imx/imx8m/ddr_init.c b/drivers/ddr/imx/imx8m/ddr_init.c index 0706face14a..8e059249ef6 100644 --- a/drivers/ddr/imx/imx8m/ddr_init.c +++ b/drivers/ddr/imx/imx8m/ddr_init.c @@ -9,19 +9,39 @@ #include <asm/arch/ddr.h> #include <asm/arch/clock.h> #include <asm/arch/sys_proto.h> +#include <linux/delay.h> /* GPC registers for the DDRMIX (DDR1) power domain */ #ifdef CONFIG_IMX8MP #define GPC_PGC_CPU_MAPPING 0x1cc #define GPC_PGC_CPU_MAPPING_ALL 0x003fffff #define GPC_PU_PGC_SW_PUP_REQ 0x0d8 +#define GPC_PU_PGC_SW_PDN_REQ 0x0e4 +#define GPC_PU_PWRHSK 0x190 #define GPC_DDR1_SW_Pxx_REQ BIT(19) +#define GPC_DDRMIX_ADB400_SYNC BIT(11) +#define GPC_DDRMIX_ADB400_ACK BIT(27) +/* DDRMIX is PGC 31 and occupies 0xfc0 - 0xfff */ +#define GPC_PGC_DDR1_CTRL 0xfc0 #else +/* + * i.MX8MQ shares the registers used by the power up procedure, but has + * different ADB400 handshake bits. It doesn't use the DDRMIX reset, though. + */ #define GPC_PGC_CPU_MAPPING 0x0ec #define GPC_PGC_CPU_MAPPING_ALL 0x0000ffff #define GPC_PU_PGC_SW_PUP_REQ 0x0f8 +#define GPC_PU_PGC_SW_PDN_REQ 0x104 +#define GPC_PU_PWRHSK 0x1fc #define GPC_DDR1_SW_Pxx_REQ BIT(5) +#define GPC_DDRMIX_ADB400_SYNC BIT(2) +#define GPC_DDRMIX_ADB400_ACK BIT(20) +/* DDR1 is a PU type PGC and occupies 0xd40 - 0xd7f */ +#define GPC_PGC_DDR1_CTRL 0xd40 #endif +#define GPC_PGC_CTRL_PCR BIT(0) + +static bool ddrmix_initialized; static unsigned int g_cdd_rr_max[4]; static unsigned int g_cdd_rw_max[4]; @@ -323,6 +343,146 @@ void update_umctl2_rank_space_setting(unsigned int pstat_num) } } +/* + * Stop all DRAM traffic and the periodic controller activity, so that the + * DDRC and the PHY are idle when the resets below are asserted. + */ +static void ddrc_quiesce(void) +{ + unsigned int tmp; + + /* There is nothing to do if the DDRC is still held in reset */ + if (reg32_read(SRC_DDRC_RCR_ADDR) & SRC_DDR1_RCR_PRESET_N_MASK) + return; + + /* Block the AXI port from accepting further transactions */ + reg32_write(DDRC_PCTRL_0(0), 0x0); + + /* Disable auto-refresh, self-refresh, powerdown and dfi_dram_clk_disable */ + reg32_write(DDRC_RFSHCTL3(0), 0x1); + clrbits_le32(DDRC_PWRCTL(0), 0xb); + + /* + * Wait for the controller to fall back to normal operating mode, in + * case it was in self-refresh or powerdown. A previous init may have + * failed half way through, so this must not wait forever. + */ + tmp = 1000; + while (tmp-- && (reg32_read(DDRC_STAT(0)) & 0x3) != 0x1) + udelay(1); +} + +/* + * Assert the whole reset chain, including PwrOkIn, and hold it. Unlike on the + * initial power up, the PHY is running here, so it needs a PwrOkIn low phase of + * a defined length to clear its internal state. + */ +static void ddrmix_assert_resets(void) +{ + reg32_write(SRC_DDRC_RCR_ADDR, + SRC_DDR1_ENABLE_MASK | SRC_DDR1_RCR_ALL_RESET_MASK); + mdelay(1); +} + +static int poll_bits_clear(ulong reg, u32 mask, unsigned int timeout_us) +{ + do { + if (!(reg32_read(reg) & mask)) + return 0; + udelay(1); + } while (timeout_us--); + + return -ETIMEDOUT; +} + +/* + * Power cycle the DDRMIX power domain. Asserting the resets alone leaves parts + * of the PHY state intact, only removing the power actually returns it to the + * power-on defaults. + * + * The order of the steps below follows dram_enter_retention() in TF-A + * (plat/imx/imx8m/ddr/dram_retention.c), which powers the DDRMIX down for DRAM + * retention suspend. + */ +static void ddrmix_reset(void) +{ + /* + * pwrdnreqn_async adbm/adbs of ddr: request the ADB400 bridge to drain, + * wait for it to acknowledge and release the request again, which is + * also the reset value of GPC_PU_PWRHSK. + */ + clrbits_le32(GPC_BASE_ADDR + GPC_PU_PWRHSK, GPC_DDRMIX_ADB400_SYNC); + if (poll_bits_clear(GPC_BASE_ADDR + GPC_PU_PWRHSK, + GPC_DDRMIX_ADB400_ACK, 10000)) + printf("DDR: timeout on the DDRMIX ADB400 handshake\n"); + setbits_le32(GPC_BASE_ADDR + GPC_PU_PWRHSK, GPC_DDRMIX_ADB400_SYNC); + + /* Remove PowerOk and hold the DDRMIX in reset */ + ddrmix_assert_resets(); + + /* Stop the DDRMIX clock while the domain is powered down */ + clock_enable(CCGR_DDR1, false); + + /* + * The DDR1 domain must be mapped to the A53 domain, otherwise the + * requests below are ignored. Map all domains, just like the normal + * power up procedure further down does. + */ + reg32_write(GPC_BASE_ADDR + GPC_PGC_CPU_MAPPING, GPC_PGC_CPU_MAPPING_ALL); + + /* + * Enable the PHY isolation. Without PCR set, the requests below only run + * the isolation handshake and leave the domain powered. + */ + setbits_le32(GPC_BASE_ADDR + GPC_PGC_DDR1_CTRL, GPC_PGC_CTRL_PCR); + + setbits_le32(GPC_BASE_ADDR + GPC_PU_PGC_SW_PDN_REQ, GPC_DDR1_SW_Pxx_REQ); + if (poll_bits_clear(GPC_BASE_ADDR + GPC_PU_PGC_SW_PDN_REQ, + GPC_DDR1_SW_Pxx_REQ, 10000)) + printf("DDR: timeout powering down the DDRMIX\n"); + + udelay(100); + + setbits_le32(GPC_BASE_ADDR + GPC_PU_PGC_SW_PUP_REQ, GPC_DDR1_SW_Pxx_REQ); + if (poll_bits_clear(GPC_BASE_ADDR + GPC_PU_PGC_SW_PUP_REQ, + GPC_DDR1_SW_Pxx_REQ, 10000)) + printf("DDR: timeout powering up the DDRMIX\n"); + + /* Leave the power gating cell as we found it */ + clrbits_le32(GPC_BASE_ADDR + GPC_PGC_DDR1_CTRL, GPC_PGC_CTRL_PCR); + + clock_enable(CCGR_DDR1, true); +} + +/* + * The power up procedure in ddr_init() assumes a DDRMIX that is still in its + * power-on state. That is not the case if ddr_init() has already been run, for + * example by a board that probes several DDR configurations: the DDRC is in + * normal operating mode and the PHY holds the results of the last training run + * in registers that are not covered by ddrphy_cfg[]. Starting a new training + * run from there either fails outright or, worse, reports a successful training + * while leaving the DRAM completely non-functional. + * + * Return the DDRMIX to a defined state, so that the regular power up procedure + * can be applied again. + */ +static void ddrmix_cold_reset(void) +{ + debug("DDRINFO: cold reset the DDRMIX\n"); + + ddrc_quiesce(); + + /* + * Park DfiClk on the bypass clock. The PHY must not see the DRAM PLL + * being reprogrammed for the next run while it is still out of reset, + * and the DDRC APB clock has to keep running to be able to release the + * resets again. + */ + dram_enable_bypass(MHZ(100)); + + ddrmix_reset(); +} + int ddr_init(struct dram_timing_info *dram_timing) { unsigned int tmp, initial_drate, target_freq; @@ -330,6 +490,18 @@ int ddr_init(struct dram_timing_info *dram_timing) debug("DDRINFO: start DRAM init\n"); + /* + * Return the DDRMIX to a defined state if it has been initialized + * before. This is also needed if the previous init failed half way + * through, so the flag is set unconditionally below. + * + * This is not implemented for i.MX8MQ, which has a second DDR domain + * that would need its own handling. + */ + if (!IS_ENABLED(CONFIG_IMX8MQ) && ddrmix_initialized) + ddrmix_cold_reset(); + ddrmix_initialized = true; + /* Step1: Follow the power up procedure */ if (is_imx8mq()) { reg32_write(SRC_DDRC_RCR_ADDR + 0x04, 0x8F00000F); -- 2.55.0
