driver/ddr/fsl: Add built-in memory test for DDR4 driver

Add built-in memory test to catch errors after DDR is initialized, before
any other transactions. To enable this test, define CONFIG_FSL_DDR_BIST.
An environmental variable "ddr_bist" is checked before starting test.
It takes a while (several seconds) depending on system memory size.

Signed-off-by: York Sun <yorksun@freescale.com>
diff --git a/README b/README
index d33f3b5..f5a719d 100644
--- a/README
+++ b/README
@@ -4884,6 +4884,9 @@
 - CONFIG_FSL_DDR_SYNC_REFRESH
 		Enable sync of refresh for multiple controllers.
 
+- CONFIG_FSL_DDR_BIST
+		Enable built-in memory test for Freescale DDR controllers.
+
 - CONFIG_SYS_83XX_DDR_USES_CS0
 		Only for 83xx systems. If specified, then DDR should
 		be configured using CS0 and CS1 instead of CS2 and CS3.
diff --git a/drivers/ddr/fsl/fsl_ddr_gen4.c b/drivers/ddr/fsl/fsl_ddr_gen4.c
index d9fce7d..1d67983 100644
--- a/drivers/ddr/fsl/fsl_ddr_gen4.c
+++ b/drivers/ddr/fsl/fsl_ddr_gen4.c
@@ -36,6 +36,13 @@
 	defined(CONFIG_SYS_FSL_ERRATUM_A008514)
 	u32 *eddrtqcr1;
 #endif
+#ifdef CONFIG_FSL_DDR_BIST
+	u32 mtcr, err_detect, err_sbe;
+	u32 cs0_bnds, cs1_bnds, cs2_bnds, cs3_bnds, cs0_config;
+#endif
+#ifdef CONFIG_FSL_DDR_BIST
+	char buffer[CONFIG_SYS_CBSIZE];
+#endif
 
 	switch (ctrl_num) {
 	case 0:
@@ -309,4 +316,70 @@
 		ddr_out32(&ddr->sdram_cfg_2, temp_sdram_cfg);
 	}
 #endif
+
+#ifdef CONFIG_FSL_DDR_BIST
+#define BIST_PATTERN1	0xFFFFFFFF
+#define BIST_PATTERN2	0x0
+#define BIST_CR		0x80010000
+#define BIST_CR_EN	0x80000000
+#define BIST_CR_STAT	0x00000001
+#define CTLR_INTLV_MASK	0x20000000
+	/* Perform build-in test on memory. Three-way interleaving is not yet
+	 * supported by this code. */
+	if (getenv_f("ddr_bist", buffer, CONFIG_SYS_CBSIZE) >= 0) {
+		puts("Running BIST test. This will take a while...");
+		cs0_config = ddr_in32(&ddr->cs0_config);
+		if (cs0_config & CTLR_INTLV_MASK) {
+			cs0_bnds = ddr_in32(&cs0_bnds);
+			cs1_bnds = ddr_in32(&cs1_bnds);
+			cs2_bnds = ddr_in32(&cs2_bnds);
+			cs3_bnds = ddr_in32(&cs3_bnds);
+			/* set bnds to non-interleaving */
+			ddr_out32(&cs0_bnds, (cs0_bnds & 0xfffefffe) >> 1);
+			ddr_out32(&cs1_bnds, (cs1_bnds & 0xfffefffe) >> 1);
+			ddr_out32(&cs2_bnds, (cs2_bnds & 0xfffefffe) >> 1);
+			ddr_out32(&cs3_bnds, (cs3_bnds & 0xfffefffe) >> 1);
+		}
+		ddr_out32(&ddr->mtp1, BIST_PATTERN1);
+		ddr_out32(&ddr->mtp2, BIST_PATTERN1);
+		ddr_out32(&ddr->mtp3, BIST_PATTERN2);
+		ddr_out32(&ddr->mtp4, BIST_PATTERN2);
+		ddr_out32(&ddr->mtp5, BIST_PATTERN1);
+		ddr_out32(&ddr->mtp6, BIST_PATTERN1);
+		ddr_out32(&ddr->mtp7, BIST_PATTERN2);
+		ddr_out32(&ddr->mtp8, BIST_PATTERN2);
+		ddr_out32(&ddr->mtp9, BIST_PATTERN1);
+		ddr_out32(&ddr->mtp10, BIST_PATTERN2);
+		mtcr = BIST_CR;
+		ddr_out32(&ddr->mtcr, mtcr);
+		timeout = 100;
+		while (timeout > 0 && (mtcr & BIST_CR_EN)) {
+			mdelay(1000);
+			timeout--;
+			mtcr = ddr_in32(&ddr->mtcr);
+		}
+		if (timeout <= 0)
+			puts("Timeout\n");
+		else
+			puts("Done\n");
+		err_detect = ddr_in32(&ddr->err_detect);
+		err_sbe = ddr_in32(&ddr->err_sbe);
+		if (mtcr & BIST_CR_STAT) {
+			printf("BIST test failed on controller %d.\n",
+			       ctrl_num);
+		}
+		if (err_detect || (err_sbe & 0xffff)) {
+			printf("ECC error detected on controller %d.\n",
+			       ctrl_num);
+		}
+
+		if (cs0_config & CTLR_INTLV_MASK) {
+			/* restore bnds registers */
+			ddr_out32(&cs0_bnds, cs0_bnds);
+			ddr_out32(&cs1_bnds, cs1_bnds);
+			ddr_out32(&cs2_bnds, cs2_bnds);
+			ddr_out32(&cs3_bnds, cs3_bnds);
+		}
+	}
+#endif
 }