nb/intel/sandybridge/raminit: Add ECC detection support

Add support for detection ECC capability and forced ECC mode.
Print the ECC mode in verbose debugging mode.

Change-Id: I5b7599746195cfa996a48320404a8dbe6820483a
Signed-off-by: Patrick Rudolph <siro@das-labor.org>
Signed-off-by: Alexander Couzens <lynxis@fe80.eu>
Signed-off-by: Felix Held <felix-coreboot@felixheld.de>
Signed-off-by: Jonathan A. Kollasch <jakllsch@kollasch.net>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/22214
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
diff --git a/src/northbridge/intel/sandybridge/raminit.c b/src/northbridge/intel/sandybridge/raminit.c
index b096a11..a938a49 100644
--- a/src/northbridge/intel/sandybridge/raminit.c
+++ b/src/northbridge/intel/sandybridge/raminit.c
@@ -214,6 +214,23 @@
 	mrc_cache_stash_data(MRC_TRAINING_DATA, MRC_CACHE_VERSION, ctrl, sizeof(*ctrl));
 }
 
+static void reinit_ctrl(ramctr_timing *ctrl, int min_tck, const u32 cpuid)
+{
+	/* Reset internal state */
+	memset(ctrl, 0, sizeof(*ctrl));
+	ctrl->tCK = min_tck;
+
+	/* Get architecture */
+	ctrl->cpu = cpuid;
+
+	/* Get ECC support and mode */
+	ctrl->ecc_forced = get_host_ecc_forced();
+	ctrl->ecc_supported = ctrl->ecc_forced || get_host_ecc_cap();
+	printk(BIOS_DEBUG, "ECC supported: %s ECC forced: %s\n",
+			ctrl->ecc_supported ? "yes" : "no",
+			ctrl->ecc_forced ? "yes" : "no");
+}
+
 static void init_dram_ddr3(int min_tck, int s3resume, const u32 cpuid)
 {
 	int me_uma_size, cbmem_was_inited, fast_boot, err;
@@ -300,11 +317,10 @@
 	}
 	if (!fast_boot) {
 		/* Reset internal state */
-		memset(&ctrl, 0, sizeof(ctrl));
-		ctrl.tCK = min_tck;
+		reinit_ctrl(&ctrl, min_tck, cpuid);
 
-		/* Get architecture */
-		ctrl.cpu = cpuid;
+		printk(BIOS_INFO, "ECC RAM %s.\n", ctrl.ecc_forced ? "required" :
+			ctrl.ecc_supported ? "supported" : "unsupported");
 
 		/* Get DDR3 SPD data */
 		memset(spds, 0, sizeof(spds));
@@ -320,11 +336,7 @@
 		printram("Disable failing channel.\n");
 
 		/* Reset internal state */
-		memset(&ctrl, 0, sizeof(ctrl));
-		ctrl.tCK = min_tck;
-
-		/* Get architecture */
-		ctrl.cpu = cpuid;
+		reinit_ctrl(&ctrl, min_tck, cpuid);
 
 		/* Reset DDR3 frequency */
 		dram_find_spds_ddr3(spds, &ctrl);
diff --git a/src/northbridge/intel/sandybridge/raminit_common.c b/src/northbridge/intel/sandybridge/raminit_common.c
index 4ba5b59..9642a55 100644
--- a/src/northbridge/intel/sandybridge/raminit_common.c
+++ b/src/northbridge/intel/sandybridge/raminit_common.c
@@ -437,6 +437,32 @@
 		return cfg->pci_mmio_size;
 }
 
+/*
+ * Returns the ECC mode the NB is running at. It takes precedence over ECC capability.
+ * The ME/PCU/.. has the ability to change this.
+ * Return 0: ECC is optional
+ * Return 1: ECC is forced
+ */
+bool get_host_ecc_forced(void)
+{
+	/* read Capabilities A Register */
+	const u32 reg32 = pci_read_config32(HOST_BRIDGE, CAPID0_A);
+	return !!(reg32 & (1 << 24));
+}
+
+/*
+ * Returns the ECC capability.
+ * The ME/PCU/.. has the ability to change this.
+ * Return 0: ECC is disabled
+ * Return 1: ECC is possible
+ */
+bool get_host_ecc_cap(void)
+{
+	/* read Capabilities A Register */
+	const u32 reg32 = pci_read_config32(HOST_BRIDGE, CAPID0_A);
+	return !(reg32 & (1 << 25));
+}
+
 void dram_memorymap(ramctr_timing *ctrl, int me_uma_size)
 {
 	u32 reg, val, reclaim, tom, gfxstolen, gttsize;
diff --git a/src/northbridge/intel/sandybridge/raminit_common.h b/src/northbridge/intel/sandybridge/raminit_common.h
index fef4419..ea3d666 100644
--- a/src/northbridge/intel/sandybridge/raminit_common.h
+++ b/src/northbridge/intel/sandybridge/raminit_common.h
@@ -43,7 +43,7 @@
 /*
  * WARNING: Do not forget to increase MRC_CACHE_VERSION when the saved data is changed!
  */
-#define MRC_CACHE_VERSION 3
+#define MRC_CACHE_VERSION 4
 
 typedef struct odtmap_st {
 	u16 rttwr;
@@ -132,6 +132,8 @@
 	int pi_code_offset;
 	int pi_coding_threshold;
 
+	bool ecc_supported;
+	bool ecc_forced;
 	int edge_offset[3];
 	int timC_offset[3];
 
@@ -191,4 +193,7 @@
 void restore_timings(ramctr_timing *ctrl);
 int try_init_dram_ddr3(ramctr_timing *ctrl, int fast_boot, int s3resume, int me_uma_size);
 
+bool get_host_ecc_cap(void);
+bool get_host_ecc_forced(void);
+
 #endif