From: Viorel Suman <viorel.su...@nxp.com> Implement Synopsys LPDDR PHY QuickBoot flow.
Signed-off-by: Viorel Suman <viorel.su...@nxp.com> Signed-off-by: Peng Fan <peng....@nxp.com> --- arch/arm/include/asm/arch-imx9/ddr.h | 7 ++- drivers/ddr/imx/imx9/ddr_init.c | 9 +++- drivers/ddr/imx/phy/Kconfig | 6 +++ drivers/ddr/imx/phy/Makefile | 1 + drivers/ddr/imx/phy/ddrphy_qb.c | 100 +++++++++++++++++++++++++++++++++++ drivers/ddr/imx/phy/helper.c | 39 +++++++++++++- 6 files changed, 158 insertions(+), 4 deletions(-) diff --git a/arch/arm/include/asm/arch-imx9/ddr.h b/arch/arm/include/asm/arch-imx9/ddr.h index 4c092509111c66c09cd006711131db107dbf17e3..6e5a3e3b6793d0171c2fbe6415821968ac1d3ba4 100644 --- a/arch/arm/include/asm/arch-imx9/ddr.h +++ b/arch/arm/include/asm/arch-imx9/ddr.h @@ -101,7 +101,7 @@ struct dram_timing_info { extern struct dram_timing_info dram_timing; -#if defined(CONFIG_IMX_SNPS_DDR_PHY_QB_GEN) +#if (defined(CONFIG_IMX_SNPS_DDR_PHY_QB_GEN) || defined(CONFIG_IMX_SNPS_DDR_PHY_QB)) #define DDRPHY_QB_FSP_SIZE 3 #define DDRPHY_QB_CSR_SIZE 1792 #define DDRPHY_QB_FLAG_2D BIT(0) /* =1 if First boot used 2D training, =0 otherwise */ @@ -118,8 +118,13 @@ struct ddrphy_qb_state { extern struct ddrphy_qb_state qb_state; +#if defined(CONFIG_IMX_SNPS_DDR_PHY_QB_GEN) int ddrphy_qb_save(void); #endif +#if defined(CONFIG_IMX_SNPS_DDR_PHY_QB) +int ddr_cfg_phy_qb(struct dram_timing_info *timing_info, int fsp_id); +#endif +#endif void ddr_load_train_firmware(enum fw_type type); int ddr_init(struct dram_timing_info *timing_info); diff --git a/drivers/ddr/imx/imx9/ddr_init.c b/drivers/ddr/imx/imx9/ddr_init.c index 758a4049139228198a86449bdf2f9106c0773040..7d40e199ad9900c51c689c530999dd83dc983144 100644 --- a/drivers/ddr/imx/imx9/ddr_init.c +++ b/drivers/ddr/imx/imx9/ddr_init.c @@ -375,6 +375,12 @@ int ddr_init(struct dram_timing_info *dram_timing) /* default to the frequency point 0 clock */ ddrphy_init_set_dfi_clk(initial_drate); +#if defined(CONFIG_IMX_SNPS_DDR_PHY_QB) + /* Configure PHY in QuickBoot mode */ + ret = ddr_cfg_phy_qb(dram_timing, 0); + if (ret) + return ret; +#else /* * Start PHY initialization and training by * accessing relevant PUB registers @@ -391,6 +397,7 @@ int ddr_init(struct dram_timing_info *dram_timing) #if defined(CONFIG_IMX_SNPS_DDR_PHY_QB_GEN) ddrphy_qb_save(); +#endif #endif /* save the ddr PHY trained CSR in memory for low power use */ ddrphy_trained_csr_save(dram_timing->ddrphy_trained_csr, @@ -400,7 +407,7 @@ int ddr_init(struct dram_timing_info *dram_timing) update_umctl2_rank_space_setting(dram_timing, dram_timing->fsp_msg_num - 1); - /* rogram the ddrc registers */ + /* program the ddrc registers */ debug("DDRINFO: ddrc config start\n"); ddrc_config(dram_timing); debug("DDRINFO: ddrc config done\n"); diff --git a/drivers/ddr/imx/phy/Kconfig b/drivers/ddr/imx/phy/Kconfig index 28a9eca0788f96355e8459427a92832446be0e02..b0554172af3654da50d8ed929c382ef54506464b 100644 --- a/drivers/ddr/imx/phy/Kconfig +++ b/drivers/ddr/imx/phy/Kconfig @@ -8,3 +8,9 @@ config IMX_SNPS_DDR_PHY_QB_GEN depends on IMX_SNPS_DDR_PHY help Select the DDR PHY training data generation for QuickBoot support on i.MX9 SOC. + +config IMX_SNPS_DDR_PHY_QB + bool "i.MX Synopsys DDR PHY QuickBoot mode" + depends on IMX_SNPS_DDR_PHY && !IMX_SNPS_DDR_PHY_QB_GEN + help + Select the DDR PHY QuickBoot mode on i.MX9 SOC. diff --git a/drivers/ddr/imx/phy/Makefile b/drivers/ddr/imx/phy/Makefile index d82842759e72d104aba41110b9c6e01b997d78fb..2310e69ab5c3036a2903465f4c4e95a79bf0b2c5 100644 --- a/drivers/ddr/imx/phy/Makefile +++ b/drivers/ddr/imx/phy/Makefile @@ -7,4 +7,5 @@ ifdef CONFIG_XPL_BUILD obj-$(CONFIG_IMX_SNPS_DDR_PHY) += helper.o ddrphy_utils.o ddrphy_train.o obj-$(CONFIG_IMX_SNPS_DDR_PHY_QB_GEN) += ddrphy_qb_gen.o +obj-$(CONFIG_IMX_SNPS_DDR_PHY_QB) += ddrphy_qb.o endif diff --git a/drivers/ddr/imx/phy/ddrphy_qb.c b/drivers/ddr/imx/phy/ddrphy_qb.c new file mode 100644 index 0000000000000000000000000000000000000000..04a6520be5464ac26e30a516265b1b6993eec38c --- /dev/null +++ b/drivers/ddr/imx/phy/ddrphy_qb.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2025 NXP + */ + +#include <asm/arch/ddr.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/types.h> + +struct ddrphy_qb_state qb_state; + +static void ddrphy_w(u32 addr, u32 offset, u32 value) +{ + u32 val = dwc_ddrphy_apb_rd(addr); + bool hight = (offset % 2); + + val &= (hight ? 0x00FF : 0xFF00); + val |= value << (hight ? 8 : 0); + + dwc_ddrphy_apb_wr(addr, val); +} + +static int ddrphy_qb_restore(struct dram_timing_info *info, int fsp_id) +{ + int i; + u32 to_addr; + struct dram_fsp_msg *fsp_msg; + struct dram_cfg_param *dram_cfg; + + fsp_msg = &info->fsp_msg[fsp_id]; + for (i = 0; i < fsp_msg->fsp_cfg_num; i++) { + dram_cfg = &fsp_msg->fsp_cfg[i]; + dwc_ddrphy_apb_wr(dram_cfg->reg, dram_cfg->val); + } + + /* enable the ddrphy apb */ + dwc_ddrphy_apb_wr(0xd0000, 0x00); + dwc_ddrphy_apb_wr(0x54008, 0x01); /* SequenceCtrl = 0x1 (DevInit Only)*/ + ddrphy_w(0x5400c, 0x19, 0x1); /* Lp4Quickboot = 0x1 */ + + /* Adjust MR14_xy if pstate=0 and 2D training data collected during training phase */ + if (fsp_id == 0 && (qb_state.flags & DDRPHY_QB_FLAG_2D)) { + ddrphy_w(0x5401c, 0x39, qb_state.fsp[0] >> 8); /* TrainedVREFDQ_A0 -> MR14_A0 */ + ddrphy_w(0x54022, 0x45, qb_state.fsp[1] & 0xFF); /* TrainedVREFDQ_A1 -> MR14_A1 */ + ddrphy_w(0x54036, 0x6c, qb_state.fsp[2] & 0xFF); /* TrainedVREFDQ_B0 -> MR14_B0 */ + ddrphy_w(0x5403c, 0x78, qb_state.fsp[2] >> 8); /* TrainedVREFDQ_B1 -> MR14_B1 */ + } + + /* save CSRs to address starting with 0x54800 */ + for (i = 0, to_addr = 0x54800; i < DDRPHY_QB_CSR_SIZE; i++, to_addr++) + dwc_ddrphy_apb_wr(to_addr, qb_state.csr[i]); + + /* disable the ddrphy apb */ + dwc_ddrphy_apb_wr(0xd0000, 0x01); + + return 0; +} + +int ddr_cfg_phy_qb(struct dram_timing_info *dram_timing, int fsp_id) +{ + int ret; + + /* MemReset Toggle */ + dwc_ddrphy_apb_wr(0xd0000, 0x0); + dwc_ddrphy_apb_wr(0x20060, 0x3); + dwc_ddrphy_apb_wr(0x2008F, 0x1); + + /* load the dram quick boot firmware image */ + dwc_ddrphy_apb_wr(0xd0000, 0x0); + ddr_load_train_firmware(FW_1D_IMAGE); + ddrphy_qb_restore(dram_timing, fsp_id); + + /* excute the firmware */ + dwc_ddrphy_apb_wr(0xd0000, 0x0); + dwc_ddrphy_apb_wr(0xc0080, 0x3); + dwc_ddrphy_apb_wr(0xd0031, 0x1); + dwc_ddrphy_apb_wr(0xd0000, 0x1); + dwc_ddrphy_apb_wr(0xd0099, 0x9); + dwc_ddrphy_apb_wr(0xd0099, 0x1); + dwc_ddrphy_apb_wr(0xd0099, 0x0); + + /* Wait for the quick boot firmware to complete */ + ret = wait_ddrphy_training_complete(); + if (ret) + return ret; + + /* Halt the microcontroller. */ + dwc_ddrphy_apb_wr(0xd0099, 0x1); + dwc_ddrphy_apb_wr(0xd0000, 0x0); + + get_trained_CDD(0); + + /* step I Configure PHY for hardware control */ + dwc_ddrphy_apb_wr(0xd00e7, 0x400); + dwc_ddrphy_apb_wr(0xc0080, 0x2); + dwc_ddrphy_apb_wr(0xd0000, 0x1); + + return 0; +} diff --git a/drivers/ddr/imx/phy/helper.c b/drivers/ddr/imx/phy/helper.c index b0dfc3a0b4f3f8cbbefc27b122b360e64d6cd6a2..877cc4fb42d72222cb889f0edb1182265b853d76 100644 --- a/drivers/ddr/imx/phy/helper.c +++ b/drivers/ddr/imx/phy/helper.c @@ -13,6 +13,9 @@ #include <asm/arch/ddr.h> #include <asm/arch/ddr.h> #include <asm/sections.h> +#if defined(CONFIG_IMX_SNPS_DDR_PHY_QB) +#include <u-boot/crc.h> +#endif DECLARE_GLOBAL_DATA_PTR; @@ -30,7 +33,12 @@ binman_sym_declare(ulong, ddr_1d_imem_fw, size); binman_sym_declare(ulong, ddr_1d_dmem_fw, image_pos); binman_sym_declare(ulong, ddr_1d_dmem_fw, size); -#if !IS_ENABLED(CONFIG_IMX8M_DDR3L) +#if IS_ENABLED(CONFIG_IMX_SNPS_DDR_PHY_QB) +binman_sym_declare(ulong, ddr_qb_state, image_pos); +binman_sym_declare(ulong, ddr_qb_state, size); +#endif + +#if !IS_ENABLED(CONFIG_IMX8M_DDR3L) && !IS_ENABLED(CONFIG_IMX_SNPS_DDR_PHY_QB) binman_sym_declare(ulong, ddr_2d_imem_fw, image_pos); binman_sym_declare(ulong, ddr_2d_imem_fw, size); @@ -42,13 +50,21 @@ binman_sym_declare(ulong, ddr_2d_dmem_fw, size); void ddr_load_train_firmware(enum fw_type type) { u32 tmp32, i; - u32 error = 0; unsigned long pr_to32, pr_from32; uint32_t fw_offset = type ? IMEM_2D_OFFSET : 0; unsigned long imem_start = (unsigned long)_end + fw_offset; unsigned long dmem_start; unsigned long imem_len = IMEM_LEN, dmem_len = DMEM_LEN; static enum fw_type last_type = -1; +#if defined(CONFIG_IMX_SNPS_DDR_PHY_QB) + unsigned long qbst_start; + unsigned long qbst_len = sizeof(struct ddrphy_qb_state); + int size; + u32 *to32 = (u32 *)&qb_state; + u32 crc; +#else + u32 error = 0; +#endif /* If FW doesn't change, we can save the loading. */ if (last_type == type) @@ -65,6 +81,9 @@ void ddr_load_train_firmware(enum fw_type type) #endif dmem_start = imem_start + imem_len; +#if IS_ENABLED(CONFIG_IMX_SNPS_DDR_PHY_QB) + qbst_start = dmem_start + dmem_len; +#endif if (BINMAN_SYMS_OK) { switch (type) { @@ -73,6 +92,10 @@ void ddr_load_train_firmware(enum fw_type type) imem_len = binman_sym(ulong, ddr_1d_imem_fw, size); dmem_start = binman_sym(ulong, ddr_1d_dmem_fw, image_pos); dmem_len = binman_sym(ulong, ddr_1d_dmem_fw, size); +#if IS_ENABLED(CONFIG_IMX_SNPS_DDR_PHY_QB) + qbst_start = binman_sym(ulong, ddr_qb_state, image_pos); + qbst_len = binman_sym(ulong, ddr_qb_state, size); +#endif break; case FW_2D_IMAGE: #if !IS_ENABLED(CONFIG_IMX8M_DDR3L) @@ -111,6 +134,17 @@ void ddr_load_train_firmware(enum fw_type type) i += 4; } +#if IS_ENABLED(CONFIG_IMX_SNPS_DDR_PHY_QB) + size = qbst_len / sizeof(*to32); + pr_from32 = qbst_start; + for (i = 0; i < size; i += 1, pr_from32 += 4, to32 += 1) + (*to32) = readl(pr_from32); + + crc = crc32(0, (void *)&qb_state.flags, DDRPHY_QB_STATE_SIZE); + if (crc != qb_state.crc) + log_err("DDRPHY TD CRC error FW BIN -> U-Boot: fw=0x%08x, uboot=0x%08x\n", + qb_state.crc, crc); +#else debug("check ddr_pmu_train_imem code\n"); pr_from32 = imem_start; pr_to32 = IMEM_OFFSET_ADDR; @@ -154,6 +188,7 @@ void ddr_load_train_firmware(enum fw_type type) printf("check ddr_pmu_train_dmem code fail=%d", error); else debug("check ddr_pmu_train_dmem code pass\n"); +#endif } void ddrphy_trained_csr_save(struct dram_cfg_param *ddrphy_csr, -- 2.35.3