summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/include/asm/arch-imx8m/ddr.h7
-rw-r--r--drivers/ddr/imx/imx8m/Kconfig7
-rw-r--r--drivers/ddr/imx/imx8m/ddr_init.c72
3 files changed, 86 insertions, 0 deletions
diff --git a/arch/arm/include/asm/arch-imx8m/ddr.h b/arch/arm/include/asm/arch-imx8m/ddr.h
index 7a2a2d8edc..04c9c962cf 100644
--- a/arch/arm/include/asm/arch-imx8m/ddr.h
+++ b/arch/arm/include/asm/arch-imx8m/ddr.h
@@ -529,6 +529,8 @@ enum msg_response {
#define DDRC_SBRWDATA0(X) (DDRC_IPS_BASE_ADDR(X) + 0xf2c)
#define DDRC_SBRWDATA1(X) (DDRC_IPS_BASE_ADDR(X) + 0xf30)
#define DDRC_PDCH(X) (DDRC_IPS_BASE_ADDR(X) + 0xf34)
+#define DDRC_SBRSTART0(X) (DDRC_IPS_BASE_ADDR(X) + 0xf38)
+#define DDRC_SBRRANGE0(X) (DDRC_IPS_BASE_ADDR(X) + 0xf40)
#define DDRC_FREQ1_DERATEEN(X) (DDRC_IPS_BASE_ADDR(X) + 0x2020)
#define DDRC_FREQ1_DERATEINT(X) (DDRC_IPS_BASE_ADDR(X) + 0x2024)
@@ -708,6 +710,11 @@ int ddr_cfg_phy(struct dram_timing_info *timing_info);
void load_lpddr4_phy_pie(void);
void ddrphy_trained_csr_save(struct dram_cfg_param *param, unsigned int num);
void dram_config_save(struct dram_timing_info *info, unsigned long base);
+void board_dram_ecc_scrub(void);
+void ddrc_inline_ecc_scrub(unsigned int start_address,
+ unsigned int range_address);
+void ddrc_inline_ecc_scrub_end(unsigned int start_address,
+ unsigned int range_address);
/* utils function for ddr phy training */
int wait_ddrphy_training_complete(void);
diff --git a/drivers/ddr/imx/imx8m/Kconfig b/drivers/ddr/imx/imx8m/Kconfig
index 5bf61eb258..a5f5524fbe 100644
--- a/drivers/ddr/imx/imx8m/Kconfig
+++ b/drivers/ddr/imx/imx8m/Kconfig
@@ -29,4 +29,11 @@ config SAVED_DRAM_TIMING_BASE
info into memory for low power use. OCRAM_S is used for this
purpose on i.MX8MM.
default 0x180000
+
+config IMX8M_DRAM_INLINE_ECC
+ bool "imx8mp inline ECC"
+ depends on IMX8MP && IMX8M_LPDDR4
+ help
+ Select this config if you want to use inline ecc feature for
+ imx8mp-evk board.
endmenu
diff --git a/drivers/ddr/imx/imx8m/ddr_init.c b/drivers/ddr/imx/imx8m/ddr_init.c
index de302937d0..664966c41b 100644
--- a/drivers/ddr/imx/imx8m/ddr_init.c
+++ b/drivers/ddr/imx/imx8m/ddr_init.c
@@ -21,6 +21,76 @@ void ddr_cfg_umctl2(struct dram_cfg_param *ddrc_cfg, int num)
}
}
+#ifdef CONFIG_IMX8M_DRAM_INLINE_ECC
+void ddrc_inline_ecc_scrub(unsigned int start_address,
+ unsigned int range_address)
+{
+ unsigned int tmp;
+
+ /* Step1: Enable quasi-dynamic programming */
+ reg32_write(DDRC_SWCTL(0), 0x00000000);
+ /* Step2: Set ECCCFG1.ecc_parity_region_lock to 1 */
+ reg32setbit(DDRC_ECCCFG1(0), 0x4);
+ /* Step3: Block the AXI ports from taking the transaction */
+ reg32_write(DDRC_PCTRL_0(0), 0x0);
+ /* Step4: Set scrub start address */
+ reg32_write(DDRC_SBRSTART0(0), start_address);
+ /* Step5: Set scrub range address */
+ reg32_write(DDRC_SBRRANGE0(0), range_address);
+ /* Step6: Set scrub_mode to write */
+ reg32_write(DDRC_SBRCTL(0), 0x00000014);
+ /* Step7: Set the desired pattern through SBRWDATA0 registers */
+ reg32_write(DDRC_SBRWDATA0(0), 0x55aa55aa);
+ /* Step8: Enable the SBR by programming SBRCTL.scrub_en=1 */
+ reg32setbit(DDRC_SBRCTL(0), 0x0);
+ /* Step9: Poll SBRSTAT.scrub_done=1 */
+ tmp = reg32_read(DDRC_SBRSTAT(0));
+ while (tmp != 0x00000002)
+ tmp = reg32_read(DDRC_SBRSTAT(0)) & 0x2;
+ /* Step10: Poll SBRSTAT.scrub_busy=0 */
+ tmp = reg32_read(DDRC_SBRSTAT(0));
+ while (tmp != 0x0)
+ tmp = reg32_read(DDRC_SBRSTAT(0)) & 0x1;
+ /* Step11: Disable SBR by programming SBRCTL.scrub_en=0 */
+ clrbits_le32(DDRC_SBRCTL(0), 0x1);
+ /* Step12: Prepare for normal scrub operation(Read) and set scrub_interval*/
+ reg32_write(DDRC_SBRCTL(0), 0x100);
+ /* Step13: Enable the SBR by programming SBRCTL.scrub_en=1 */
+ reg32_write(DDRC_SBRCTL(0), 0x101);
+ /* Step14: Enable AXI ports by programming */
+ reg32_write(DDRC_PCTRL_0(0), 0x1);
+ /* Step15: Disable quasi-dynamic programming */
+ reg32_write(DDRC_SWCTL(0), 0x00000001);
+}
+
+void ddrc_inline_ecc_scrub_end(unsigned int start_address,
+ unsigned int range_address)
+{
+ /* Step1: Enable quasi-dynamic programming */
+ reg32_write(DDRC_SWCTL(0), 0x00000000);
+ /* Step2: Block the AXI ports from taking the transaction */
+ reg32_write(DDRC_PCTRL_0(0), 0x0);
+ /* Step3: Set scrub start address */
+ reg32_write(DDRC_SBRSTART0(0), start_address);
+ /* Step4: Set scrub range address */
+ reg32_write(DDRC_SBRRANGE0(0), range_address);
+ /* Step5: Disable SBR by programming SBRCTL.scrub_en=0 */
+ clrbits_le32(DDRC_SBRCTL(0), 0x1);
+ /* Step6: Prepare for normal scrub operation(Read) and set scrub_interval */
+ reg32_write(DDRC_SBRCTL(0), 0x100);
+ /* Step7: Enable the SBR by programming SBRCTL.scrub_en=1 */
+ reg32_write(DDRC_SBRCTL(0), 0x101);
+ /* Step8: Enable AXI ports by programming */
+ reg32_write(DDRC_PCTRL_0(0), 0x1);
+ /* Step9: Disable quasi-dynamic programming */
+ reg32_write(DDRC_SWCTL(0), 0x00000001);
+}
+#endif
+
+void __weak board_dram_ecc_scrub(void)
+{
+}
+
int ddr_init(struct dram_timing_info *dram_timing)
{
unsigned int tmp, initial_drate, target_freq;
@@ -169,6 +239,8 @@ int ddr_init(struct dram_timing_info *dram_timing)
reg32_write(DDRC_PCTRL_0(0), 0x00000001);
debug("DDRINFO: ddrmix config done\n");
+ board_dram_ecc_scrub();
+
/* save the dram timing config into memory */
dram_config_save(dram_timing, CONFIG_SAVED_DRAM_TIMING_BASE);