drivers: ddr: imx8mp: Add inline ECC feature support

the DRAM Controller in i.MX8MP will support a feature called "Inline ECC".
This is supported for all 3 supported DRAM technologies (LPDDR4, DDR4 and
DDR3L). When this feature is enabled by software, the DRAM Controller
reserves 12.5% of DRAM capacity for ECC information, and presents only
the non-ECC portion (lower 87.5% of the installed capacity of DRAM) to
the rest of the SoC.
The DRAM memory can be divided into 8 regions so that if a use case only
requires ECC protection on a subset of memory, then only that subset of
memory need support inline ECC. If this occurs, then there is no
performance penalty accessing the non-ECC-protected memory (no need to
access ECC for this portion of the memory map). This is all configured
with the DRAM Controller.

Signed-off-by: Sherry Sun <sherry.sun@nxp.com>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
This commit is contained in:
Sherry Sun 2020-01-20 11:13:14 +08:00 committed by Peng Fan
parent 1eb325af16
commit f3acb02386
3 changed files with 86 additions and 0 deletions

View file

@ -529,6 +529,8 @@ enum msg_response {
#define DDRC_SBRWDATA0(X) (DDRC_IPS_BASE_ADDR(X) + 0xf2c) #define DDRC_SBRWDATA0(X) (DDRC_IPS_BASE_ADDR(X) + 0xf2c)
#define DDRC_SBRWDATA1(X) (DDRC_IPS_BASE_ADDR(X) + 0xf30) #define DDRC_SBRWDATA1(X) (DDRC_IPS_BASE_ADDR(X) + 0xf30)
#define DDRC_PDCH(X) (DDRC_IPS_BASE_ADDR(X) + 0xf34) #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_DERATEEN(X) (DDRC_IPS_BASE_ADDR(X) + 0x2020)
#define DDRC_FREQ1_DERATEINT(X) (DDRC_IPS_BASE_ADDR(X) + 0x2024) #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 load_lpddr4_phy_pie(void);
void ddrphy_trained_csr_save(struct dram_cfg_param *param, unsigned int num); 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 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 */ /* utils function for ddr phy training */
int wait_ddrphy_training_complete(void); int wait_ddrphy_training_complete(void);

View file

@ -29,4 +29,11 @@ config SAVED_DRAM_TIMING_BASE
info into memory for low power use. OCRAM_S is used for this info into memory for low power use. OCRAM_S is used for this
purpose on i.MX8MM. purpose on i.MX8MM.
default 0x180000 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 endmenu

View file

@ -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) int ddr_init(struct dram_timing_info *dram_timing)
{ {
unsigned int tmp, initial_drate, target_freq; 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); reg32_write(DDRC_PCTRL_0(0), 0x00000001);
debug("DDRINFO: ddrmix config done\n"); debug("DDRINFO: ddrmix config done\n");
board_dram_ecc_scrub();
/* save the dram timing config into memory */ /* save the dram timing config into memory */
dram_config_save(dram_timing, CONFIG_SAVED_DRAM_TIMING_BASE); dram_config_save(dram_timing, CONFIG_SAVED_DRAM_TIMING_BASE);