nb/intel/sandybridge: Standardize MRC vs. native SPD mapping API

Changes both MRC and native raminit code path to get SPD mapping
from one place.

Boards with all memory socketed specify their mappings in a
devicetree setting introduced in commit 5709e03613b3
("nb/intel/sandybridge: Migrate MRC settings to devicetree") back in
May 2019 but remains unused as of this patch. This setting
will now hold raw SMBus addresses, and MRC raminit gets code to
translate them into a representation MRC expects.

Boards with soldered down memory (specifically with HAVE_SPD_IN_CBFS
in their board Kconfig), with or without socketed memory, specify
their layouts in mb_get_spd_map() as used by Haswell boards, where
they access hardware GPIO straps to select which SPD data to use.

This harmonizes the way boards specify their SPD layouts across
Haswell/SNB/IVB boards whether using MRC or native raminit. Going
forward they only need to specify the layout in one place. (Going
forward the devicetree setting should be backported to Haswell,
once we get native raminit working there.)

With this, northbridge code is now fully responsible for loading
all SPD data, be it from CBFS or SMBus.

To avoid breakage, transition will happen in stages:

1. This patch gets all the code in, and implements weak stubs that
maintain existing code and data flow (i.e. mainboards still populate
final SPD layout data). At this point devicetree already uses new
representation, but is still unused meaning no breakage.

2. Follow-up patch(es) remove mainboard_get_spd() from mainboards, and
replace it with mb_get_spd_map() or devicetree values (as appropriate)
with converted SPD info. The "weak" mainboard_get_spd() with new logic
takes over. Boards go Haswell Style at this point. Boards with MRC
raminit also lose code to fill in SPD data, allowing new data to take
hold.

3. Clean-up patch removes the weak functions and public prototypes re
mainboard_get_spd(), making it internal to northbridge. Changeover is
complete.

Change-Id: I1a75279d981e46505930a9ce1aae894ccc4e1f24
Signed-off-by: Keith Hui <buurin@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/76965
Reviewed-by: Martin L Roth <gaumless@gmail.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/src/northbridge/intel/sandybridge/chip.h b/src/northbridge/intel/sandybridge/chip.h
index 99d9763..82e58f6 100644
--- a/src/northbridge/intel/sandybridge/chip.h
+++ b/src/northbridge/intel/sandybridge/chip.h
@@ -45,7 +45,7 @@
 
 	/* Data for RAM init */
 
-	/* DIMM SPD address. Use 8bit notation where BIT0 is always zero. */
+	/* DIMM SPD address. */
 	u8 spd_addresses[4];
 
 	/* PEI data for RAM init and early silicon init */
diff --git a/src/northbridge/intel/sandybridge/raminit.c b/src/northbridge/intel/sandybridge/raminit.c
index 82931da..2575a9c 100644
--- a/src/northbridge/intel/sandybridge/raminit.c
+++ b/src/northbridge/intel/sandybridge/raminit.c
@@ -4,7 +4,10 @@
 #include <commonlib/region.h>
 #include <cf9_reset.h>
 #include <string.h>
+#include <cbfs.h>
 #include <arch/cpu.h>
+#include <device/device.h>
+#include <device/dram/ddr3.h>
 #include <device/mmio.h>
 #include <device/pci_ops.h>
 #include <device/smbus_host.h>
@@ -16,9 +19,11 @@
 #include <cpu/x86/msr.h>
 #include <types.h>
 
+#include "raminit.h"
 #include "raminit_native.h"
 #include "raminit_common.h"
 #include "sandybridge.h"
+#include "chip.h"
 
 /* FIXME: no support for 3-channel chipsets */
 
@@ -139,14 +144,59 @@
 {
 	int j;
 	if (id_only) {
-		for (j = 117; j < 128; j++)
+		for (j = SPD_DIMM_MOD_ID1; j < 128; j++)
 			(*spd)[j] = smbus_read_byte(addr, j);
 	} else {
-		for (j = 0; j < 256; j++)
+		for (j = 0; j < SPD_SIZE_MAX_DDR3; j++)
 			(*spd)[j] = smbus_read_byte(addr, j);
 	}
 }
 
+/* Temporary stub */
+__weak void mb_get_spd_map(struct spd_info *spdi) {}
+
+__weak void mainboard_get_spd(spd_raw_data *spd, bool id_only)
+{
+	const struct northbridge_intel_sandybridge_config *cfg = config_of_soc();
+	unsigned int i;
+
+	if (CONFIG(HAVE_SPD_IN_CBFS)) {
+		struct spd_info spdi = {0};
+
+		mb_get_spd_map(&spdi);
+
+		size_t spd_file_len;
+		uint8_t *spd_file = cbfs_map("spd.bin", &spd_file_len);
+
+		printk(BIOS_DEBUG, "SPD index %d\n", spdi.spd_index);
+
+		/* SPD file sanity check */
+		if (!spd_file)
+			die("SPD data %s!", "not found");
+
+		if (spd_file_len < ((spdi.spd_index + 1) * SPD_SIZE_MAX_DDR3))
+			die("SPD data %s!", "incomplete");
+
+		/*
+		 * Copy SPD data specified by spd_info.spd_index to all slots marked as
+		 * SPD_MEMORY_DOWN.
+		 *
+		 * Read SPD data from slots with a real SMBus address.
+		 */
+		for (i = 0; i < ARRAY_SIZE(spdi.addresses); i++) {
+			if (spdi.addresses[i] == SPD_MEMORY_DOWN)
+				memcpy(&spd[i], spd_file + (spdi.spd_index * SPD_SIZE_MAX_DDR3), SPD_SIZE_MAX_DDR3);
+			else if (spdi.addresses[i] != 0)
+				read_spd(&spd[i], spdi.addresses[i], id_only);
+		}
+	} else {
+		for (i = 0; i < ARRAY_SIZE(cfg->spd_addresses); i++) {
+			if (cfg->spd_addresses[i] != 0)
+				read_spd(&spd[i], cfg->spd_addresses[i], id_only);
+		}
+	} /* CONFIG(HAVE_SPD_IN_CBFS) */
+}
+
 static void dram_find_spds_ddr3(spd_raw_data *spd, ramctr_timing *ctrl)
 {
 	int dimms = 0, ch_dimms;
diff --git a/src/northbridge/intel/sandybridge/raminit.h b/src/northbridge/intel/sandybridge/raminit.h
index fa9fa98..8e87fdf 100644
--- a/src/northbridge/intel/sandybridge/raminit.h
+++ b/src/northbridge/intel/sandybridge/raminit.h
@@ -11,4 +11,33 @@
 
 void mainboard_fill_pei_data(struct pei_data *pei_data);
 
+/*
+ * SPD information API adopted from nb/intel/haswell.
+ *
+ * This applies to both MRC and native raminit, only for boards with
+ * CONFIG(HAVE_SPD_IN_CBFS).
+ *
+ * spd_info.addresses is an array of 4 bytes representing the SMBus addresses
+ * of the SPD EEPROM of (respectively) Channel 0 Slot 0, C0S1, C1S0, C1S1.
+ * Boards with onboard memory for the slot without actual SPD EEPROM enter
+ * SPD_MEMORY_DOWN in that position and enter in spd_info.spd_index a 0-based index into
+ * spd.bin file in CBFS, which is a concatenation of 256-byte SPD data blobs.
+ *
+ * Only one set of SPD data is supported.
+ */
+
+#define SPD_MEMORY_DOWN 0xFF
+
+struct spd_info {
+	uint8_t addresses[4];
+	unsigned int spd_index;
+};
+
+/*
+ * Mainboard callback to fill in the SPD addresses.
+ *
+ * @param spdi Pointer to spd_info struct to be populated by mainboard.
+ */
+void mb_get_spd_map(struct spd_info *spdi);
+
 #endif /* RAMINIT_H */
diff --git a/src/northbridge/intel/sandybridge/raminit_mrc.c b/src/northbridge/intel/sandybridge/raminit_mrc.c
index 69baf44..de41ae5 100644
--- a/src/northbridge/intel/sandybridge/raminit_mrc.c
+++ b/src/northbridge/intel/sandybridge/raminit_mrc.c
@@ -264,13 +264,7 @@
 
 static void devicetree_fill_pei_data(struct pei_data *pei_data)
 {
-	const struct northbridge_intel_sandybridge_config *cfg;
-
-	const struct device *dev = pcidev_on_root(0, 0);
-	if (!dev || !dev->chip_info)
-		return;
-
-	cfg = dev->chip_info;
+	const struct northbridge_intel_sandybridge_config *cfg = config_of_soc();
 
 	switch (cfg->max_mem_clock_mhz) {
 	/* MRC only supports fixed numbers of frequencies */
@@ -292,7 +286,15 @@
 
 	}
 
-	memcpy(pei_data->spd_addresses, cfg->spd_addresses, sizeof(pei_data->spd_addresses));
+	/*
+	 * SPD addresses are listed in devicetree as actual addresses,
+	 * and for MRC need to be shifted left so bit 0 is always zero.
+	 */
+	if (!CONFIG(HAVE_SPD_IN_CBFS)) {
+		for (unsigned int i = 0; i < ARRAY_SIZE(cfg->spd_addresses); i++) {
+			pei_data->spd_addresses[i] = cfg->spd_addresses[i] << 1;
+		}
+	}
 	memcpy(pei_data->ts_addresses,  cfg->ts_addresses,  sizeof(pei_data->ts_addresses));
 
 	pei_data->ec_present     = cfg->ec_present;
@@ -310,6 +312,49 @@
 	pei_data->usb3.xhci_streams        = cfg->usb3.xhci_streams;
 }
 
+/* Temporary stub */
+__weak void mb_get_spd_map(struct spd_info *spdi) {}
+
+static void spd_fill_pei_data(struct pei_data *pei_data)
+{
+	struct spd_info spdi = {0};
+	unsigned int i, have_memory_down;
+
+	mb_get_spd_map(&spdi);
+
+	for (i = 0; i < ARRAY_SIZE(spdi.addresses); i++) {
+		if (spdi.addresses[i] == SPD_MEMORY_DOWN) {
+			pei_data->spd_addresses[i] = 0;
+			have_memory_down = 1;
+		} else {
+			/* MRC expects left-aligned SMBus addresses. */
+			pei_data->spd_addresses[i] = spdi.addresses[i] << 1;
+		}
+	}
+	/* Copy SPD data from CBFS for on-board memory */
+	if (have_memory_down) {
+		printk(BIOS_DEBUG, "SPD index %d\n", spdi.spd_index);
+
+		size_t spd_file_len;
+		uint8_t *spd_file = cbfs_map("spd.bin", &spd_file_len);
+
+		if (!spd_file)
+			die("SPD data %s!", "not found");
+
+		if (spd_file_len < ((spdi.spd_index + 1) * SPD_SIZE_MAX_DDR3))
+			die("SPD data %s!", "incomplete");
+
+		/* MRC only uses index 0... */
+		memcpy(pei_data->spd_data[0], spd_file + (spdi.spd_index * SPD_SIZE_MAX_DDR3), SPD_SIZE_MAX_DDR3);
+
+		/* but coreboot uses the other indices */
+		for (i = 1; i < ARRAY_SIZE(spdi.addresses); i++) {
+			if (spdi.addresses[i] == SPD_MEMORY_DOWN)
+				memcpy(pei_data->spd_data[i], pei_data->spd_data[0], SPD_SIZE_MAX_DDR3);
+		}
+	}
+}
+
 static void disable_p2p(void)
 {
 	/* Disable PCI-to-PCI bridge early to prevent probing by MRC */
@@ -337,6 +382,8 @@
 	northbridge_fill_pei_data(&pei_data);
 	southbridge_fill_pei_data(&pei_data);
 	devicetree_fill_pei_data(&pei_data);
+	if (CONFIG(HAVE_SPD_IN_CBFS))
+		spd_fill_pei_data(&pei_data);
 	mainboard_fill_pei_data(&pei_data);
 
 	post_code(0x3a);