nb/intel/sandybridge/raminit: Add ECC support

Add ECC support for native raminit on SandyBridge/IvyBridge.

Change-Id: I1206746332c9939a78b67e7b48d3098bdef8a2ed
Depends-On: I5b7599746195cfa996a48320404a8dbe6820483a
Signed-off-by: Patrick Rudolph <siro@das-labor.org>
Signed-off-by: Jonathan A. Kollasch <jakllsch@kollasch.net>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/22215
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
diff --git a/src/northbridge/intel/sandybridge/Kconfig b/src/northbridge/intel/sandybridge/Kconfig
index 29a6db7..6b7520f 100644
--- a/src/northbridge/intel/sandybridge/Kconfig
+++ b/src/northbridge/intel/sandybridge/Kconfig
@@ -100,6 +100,12 @@
 	hex
 	default 0x0
 
+config RAMINIT_ENABLE_ECC
+	bool "Enable ECC if supported"
+	default y
+	help
+	  Enable ECC if supported by both, host and RAM.
+
 endif # USE_NATIVE_RAMINIT
 
 if !USE_NATIVE_RAMINIT
diff --git a/src/northbridge/intel/sandybridge/raminit.c b/src/northbridge/intel/sandybridge/raminit.c
index a938a49..e138756 100644
--- a/src/northbridge/intel/sandybridge/raminit.c
+++ b/src/northbridge/intel/sandybridge/raminit.c
@@ -102,6 +102,7 @@
 {
 	int dimms = 0, ch_dimms;
 	int channel, slot, spd_slot;
+	bool can_use_ecc = ctrl->ecc_supported;
 	dimm_info *dimm = &ctrl->info;
 
 	memset (ctrl->rankmap, 0, sizeof(ctrl->rankmap));
@@ -173,6 +174,9 @@
 
 			ctrl->channel_size_mb[channel] += dimm->dimm[channel][slot].size_mb;
 
+			if (!dimm->dimm[channel][slot].flags.is_ecc)
+				can_use_ecc = false;
+
 			ctrl->auto_self_refresh &= dimm->dimm[channel][slot].flags.asr;
 
 			ctrl->extended_temperature_range &=
@@ -204,6 +208,14 @@
 		}
 	}
 
+	if (ctrl->ecc_forced || CONFIG(RAMINIT_ENABLE_ECC))
+		ctrl->ecc_enabled = can_use_ecc;
+	if (ctrl->ecc_forced && !ctrl->ecc_enabled)
+		die("ECC mode forced but non-ECC DIMM installed!");
+	printk(BIOS_DEBUG, "ECC is %s\n", ctrl->ecc_enabled  ? "enabled" : "disabled");
+
+	ctrl->lanes = ctrl->ecc_enabled ? 9 : 8;
+
 	if (!dimms)
 		die("No DIMMs were found");
 }
diff --git a/src/northbridge/intel/sandybridge/raminit_common.c b/src/northbridge/intel/sandybridge/raminit_common.c
index 9642a55..51f3362 100644
--- a/src/northbridge/intel/sandybridge/raminit_common.c
+++ b/src/northbridge/intel/sandybridge/raminit_common.c
@@ -16,7 +16,6 @@
 #include "raminit_tables.h"
 #include "sandybridge.h"
 
-/* FIXME: no ECC support */
 /* FIXME: no support for 3-channel chipsets */
 
 /* length:      [1..4] */
@@ -309,12 +308,21 @@
 	}
 }
 
-void dram_dimm_set_mapping(ramctr_timing *ctrl)
+void dram_dimm_set_mapping(ramctr_timing *ctrl, int training)
 {
 	int channel;
+	u32 ecc;
+
+	if (ctrl->ecc_enabled)
+		ecc = training ? (1 << 24) : (3 << 24);
+	else
+		ecc = 0;
+
 	FOR_ALL_CHANNELS {
-		MCHBAR32(MAD_DIMM(channel)) = ctrl->mad_dimm[channel];
+		MCHBAR32(MAD_DIMM(channel)) = ctrl->mad_dimm[channel] | ecc;
 	}
+
+	//udelay(10); /* TODO: Might be needed for ECC configurations; so far works without. */
 }
 
 void dram_zones(ramctr_timing *ctrl, int training)
@@ -2120,13 +2128,13 @@
 				lanes_ok |= 1 << lane;
 		}
 		ctr++;
-		if (lanes_ok == ((1 << NUM_LANES) - 1))
+		if (lanes_ok == ((1 << ctrl->lanes) - 1))
 			break;
 	}
 
 	ctrl->timings[channel][slotrank] = saved_rt;
 
-	return lanes_ok != ((1 << NUM_LANES) - 1);
+	return lanes_ok != ((1 << ctrl->lanes) - 1);
 }
 
 static void fill_pattern5(ramctr_timing *ctrl, int channel, int patno)
@@ -3006,6 +3014,46 @@
 	return 0;
 }
 
+void channel_scrub(ramctr_timing *ctrl)
+{
+	int channel, slotrank, row, rowsize;
+
+	FOR_ALL_POPULATED_CHANNELS FOR_ALL_POPULATED_RANKS {
+		rowsize = 1 << ctrl->info.dimm[channel][slotrank >> 1].row_bits;
+		for (row = 0; row < rowsize; row += 16) {
+
+			wait_for_iosav(channel);
+
+			/* DRAM command ACT */
+			MCHBAR32(IOSAV_n_SP_CMD_CTRL_ch(channel, 0)) = IOSAV_ACT;
+			MCHBAR32(IOSAV_n_SUBSEQ_CTRL_ch(channel, 0)) =
+				(MAX((ctrl->tFAW >> 2) + 1, ctrl->tRRD) << 10)
+				| 1 | (ctrl->tRCD << 16);
+			MCHBAR32(IOSAV_n_SP_CMD_ADDR_ch(channel, 0)) =
+				row | 0x00060000 | (slotrank << 24);
+			MCHBAR32(IOSAV_n_ADDR_UPDATE_ch(channel, 0)) = 0x00000241;
+
+			/* DRAM command WR */
+			MCHBAR32(IOSAV_n_SP_CMD_CTRL_ch(channel, 1)) = IOSAV_WR;
+			MCHBAR32(IOSAV_n_SUBSEQ_CTRL_ch(channel, 1)) = 0x08281081;
+			MCHBAR32(IOSAV_n_SP_CMD_ADDR_ch(channel, 1)) = row | (slotrank << 24);
+			MCHBAR32(IOSAV_n_ADDR_UPDATE_ch(channel, 1)) = 0x00000242;
+
+			/* DRAM command PRE */
+			MCHBAR32(IOSAV_n_SP_CMD_CTRL_ch(channel, 2)) = IOSAV_PRE;
+			MCHBAR32(IOSAV_n_SUBSEQ_CTRL_ch(channel, 2)) = 0x00280c01;
+			MCHBAR32(IOSAV_n_SP_CMD_ADDR_ch(channel, 2)) =
+				0x00060400 | (slotrank << 24);
+			MCHBAR32(IOSAV_n_ADDR_UPDATE_ch(channel, 2)) = 0x00000240;
+
+			/* execute command queue */
+			MCHBAR32(IOSAV_SEQ_CTL_ch(channel)) = IOSAV_RUN_ONCE(3);
+
+			wait_for_iosav(channel);
+		}
+	}
+}
+
 void set_scrambling_seed(ramctr_timing *ctrl)
 {
 	int channel;
diff --git a/src/northbridge/intel/sandybridge/raminit_common.h b/src/northbridge/intel/sandybridge/raminit_common.h
index ea3d666..93541b5 100644
--- a/src/northbridge/intel/sandybridge/raminit_common.h
+++ b/src/northbridge/intel/sandybridge/raminit_common.h
@@ -24,7 +24,7 @@
 #define NUM_CHANNELS	2
 #define NUM_SLOTRANKS	4
 #define NUM_SLOTS	2
-#define NUM_LANES	8
+#define NUM_LANES	9
 
 #define NO_RANKSEL		(~(1 << 16))
 #define IOSAV_MRS		(0x1f000)
@@ -43,7 +43,7 @@
 /*
  * WARNING: Do not forget to increase MRC_CACHE_VERSION when the saved data is changed!
  */
-#define MRC_CACHE_VERSION 4
+#define MRC_CACHE_VERSION 5
 
 typedef struct odtmap_st {
 	u16 rttwr;
@@ -134,6 +134,8 @@
 
 	bool ecc_supported;
 	bool ecc_forced;
+	bool ecc_enabled;
+	int lanes;	/* active lanes: 8 or 9 */
 	int edge_offset[3];
 	int timC_offset[3];
 
@@ -149,7 +151,7 @@
 
 #define SOUTHBRIDGE	PCI_DEV(0, 0x1f, 0)
 
-#define FOR_ALL_LANES for (lane = 0; lane < NUM_LANES; lane++)
+#define FOR_ALL_LANES for (lane = 0; lane < ctrl->lanes; lane++)
 #define FOR_ALL_CHANNELS for (channel = 0; channel < NUM_CHANNELS; channel++)
 #define FOR_ALL_POPULATED_RANKS for (slotrank = 0; slotrank < NUM_SLOTRANKS; slotrank++) if (ctrl->rankmap[channel] & (1 << slotrank))
 #define FOR_ALL_POPULATED_CHANNELS for (channel = 0; channel < NUM_CHANNELS; channel++) if (ctrl->rankmap[channel])
@@ -170,7 +172,7 @@
 void dram_xover(ramctr_timing *ctrl);
 void dram_timing_regs(ramctr_timing *ctrl);
 void dram_dimm_mapping(ramctr_timing *ctrl);
-void dram_dimm_set_mapping(ramctr_timing *ctrl);
+void dram_dimm_set_mapping(ramctr_timing *ctrl, int training);
 void dram_zones(ramctr_timing *ctrl, int training);
 unsigned int get_mem_min_tck(void);
 void dram_memorymap(ramctr_timing *ctrl, int me_uma_size);
@@ -193,6 +195,7 @@
 void restore_timings(ramctr_timing *ctrl);
 int try_init_dram_ddr3(ramctr_timing *ctrl, int fast_boot, int s3resume, int me_uma_size);
 
+void channel_scrub(ramctr_timing *ctrl);
 bool get_host_ecc_cap(void);
 bool get_host_ecc_forced(void);
 
diff --git a/src/northbridge/intel/sandybridge/raminit_native.c b/src/northbridge/intel/sandybridge/raminit_native.c
index d27914a..99c1a4c 100644
--- a/src/northbridge/intel/sandybridge/raminit_native.c
+++ b/src/northbridge/intel/sandybridge/raminit_native.c
@@ -537,7 +537,7 @@
 	MCHBAR32(MC_INIT_STATE_G) &= ~(1 << 5);
 
 	/* Set MAD-DIMM registers */
-	dram_dimm_set_mapping(ctrl);
+	dram_dimm_set_mapping(ctrl, 1);
 	printk(BIOS_DEBUG, "Done dimm mapping\n");
 
 	/* Zone config */
@@ -608,7 +608,13 @@
 		err = channel_test(ctrl);
 		if (err)
 			return err;
+
+		if (ctrl->ecc_enabled)
+			channel_scrub(ctrl);
 	}
 
+	/* Set MAD-DIMM registers */
+	dram_dimm_set_mapping(ctrl, 0);
+
 	return 0;
 }