haswell NRI: Collect SPD info

Collect SPD data from DIMMs and memory-down, and find the common
supported settings.

Original-Change-Id: I4e6a1408a638a463ecae37a447cfed1d6556e44a
Original-Signed-off-by: Angel Pons <th3fanbus@gmail.com>
Signed-off-by: Bill XIE <persmule@hardenedlinux.org>
Change-Id: I7948554eb02113bdca380222a11cfb322f9615f8
Reviewed-on: https://review.coreboot.org/c/coreboot/+/77049
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Martin L Roth <gaumless@gmail.com>
diff --git a/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c b/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c
new file mode 100644
index 0000000..2dab850
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <cbfs.h>
+#include <commonlib/bsd/clamp.h>
+#include <console/console.h>
+#include <device/dram/ddr3.h>
+#include <device/smbus_host.h>
+#include <northbridge/intel/haswell/haswell.h>
+#include <northbridge/intel/haswell/raminit.h>
+#include <string.h>
+#include <types.h>
+
+#include "raminit_native.h"
+
+static const uint8_t *get_spd_data_from_cbfs(struct spd_info *spdi)
+{
+	if (!CONFIG(HAVE_SPD_IN_CBFS))
+		return NULL;
+
+	printk(RAM_DEBUG, "SPD index %u\n", spdi->spd_index);
+
+	size_t spd_file_len;
+	uint8_t *spd_file = cbfs_map("spd.bin", &spd_file_len);
+
+	if (!spd_file) {
+		printk(BIOS_ERR, "SPD data not found in CBFS\n");
+		return NULL;
+	}
+
+	if (spd_file_len < ((spdi->spd_index + 1) * SPD_LEN)) {
+		printk(BIOS_ERR, "SPD index override to 0 - old hardware?\n");
+		spdi->spd_index = 0;
+	}
+
+	if (spd_file_len < SPD_LEN) {
+		printk(BIOS_ERR, "Invalid SPD data in CBFS\n");
+		return NULL;
+	}
+
+	return spd_file + (spdi->spd_index * SPD_LEN);
+}
+
+static void get_spd_for_dimm(struct raminit_dimm_info *const dimm, const uint8_t *cbfs_spd)
+{
+	if (dimm->spd_addr == SPD_MEMORY_DOWN) {
+		if (cbfs_spd) {
+			memcpy(dimm->raw_spd, cbfs_spd, SPD_LEN);
+			dimm->valid = true;
+			printk(RAM_DEBUG, "memory-down\n");
+			return;
+		} else {
+			printk(RAM_DEBUG, "memory-down but no CBFS SPD data, ignoring\n");
+			return;
+		}
+	}
+	printk(RAM_DEBUG, "slotted ");
+	const uint8_t spd_mem_type = smbus_read_byte(dimm->spd_addr, SPD_MEMORY_TYPE);
+	if (spd_mem_type != SPD_MEMORY_TYPE_SDRAM_DDR3) {
+		printk(RAM_DEBUG, "and not DDR3, ignoring\n");
+		return;
+	}
+	printk(RAM_DEBUG, "and DDR3\n");
+	if (i2c_eeprom_read(dimm->spd_addr, 0, SPD_LEN, dimm->raw_spd) != SPD_LEN) {
+		printk(BIOS_WARNING, "I2C block read failed, trying SMBus byte reads\n");
+		for (uint32_t i = 0; i < SPD_LEN; i++)
+			dimm->raw_spd[i] = smbus_read_byte(dimm->spd_addr, i);
+	}
+	dimm->valid = true;
+}
+
+static void get_spd_data(struct sysinfo *ctrl)
+{
+	struct spd_info spdi = {0};
+	mb_get_spd_map(&spdi);
+	const uint8_t *cbfs_spd = get_spd_data_from_cbfs(&spdi);
+	for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+		for (uint8_t slot = 0; slot < NUM_SLOTS; slot++) {
+			struct raminit_dimm_info *const dimm = &ctrl->dimms[channel][slot];
+			dimm->spd_addr = spdi.addresses[NUM_SLOTS * channel + slot];
+			if (!dimm->spd_addr)
+				continue;
+
+			printk(RAM_DEBUG, "CH%uS%u is ", channel, slot);
+			get_spd_for_dimm(dimm, cbfs_spd);
+		}
+	}
+}
+
+static void decode_spd(struct raminit_dimm_info *const dimm)
+{
+	/** TODO: Hook up somewhere, and handle lack of XMP data **/
+	const bool enable_xmp = false;
+	memset(&dimm->data, 0, sizeof(dimm->data));
+	if (enable_xmp)
+		spd_xmp_decode_ddr3(&dimm->data, dimm->raw_spd, DDR3_XMP_PROFILE_1);
+	else
+		spd_decode_ddr3(&dimm->data, dimm->raw_spd);
+
+	if (CONFIG(DEBUG_RAM_SETUP))
+		dram_print_spd_ddr3(&dimm->data);
+}
+
+static enum raminit_status find_common_spd_parameters(struct sysinfo *ctrl)
+{
+	ctrl->cas_supported = 0xffff;
+	ctrl->flags.raw = 0xffffffff;
+
+	ctrl->tCK  = 0;
+	ctrl->tAA  = 0;
+	ctrl->tWR  = 0;
+	ctrl->tRCD = 0;
+	ctrl->tRRD = 0;
+	ctrl->tRP  = 0;
+	ctrl->tRAS = 0;
+	ctrl->tRC  = 0;
+	ctrl->tRFC = 0;
+	ctrl->tWTR = 0;
+	ctrl->tRTP = 0;
+	ctrl->tFAW = 0;
+	ctrl->tCWL = 0;
+	ctrl->tCMD = 0;
+	ctrl->chanmap = 0;
+
+	bool yes_ecc = false;
+	bool not_ecc = false;
+
+	for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+		ctrl->dpc[channel] = 0;
+		ctrl->rankmap[channel] = 0;
+		ctrl->rank_mirrored[channel] = 0;
+		ctrl->channel_size_mb[channel] = 0;
+		for (uint8_t slot = 0; slot < NUM_SLOTS; slot++) {
+			struct raminit_dimm_info *const dimm = &ctrl->dimms[channel][slot];
+			if (!dimm->valid)
+				continue;
+
+			printk(RAM_DEBUG, "\nCH%uS%u SPD:\n", channel, slot);
+			decode_spd(dimm);
+
+			ctrl->chanmap |= BIT(channel);
+			ctrl->dpc[channel]++;
+			ctrl->channel_size_mb[channel] += dimm->data.size_mb;
+
+			/* The first rank of a populated slot is always present */
+			const uint8_t rank = slot + slot;
+			assert(dimm->data.ranks);
+			ctrl->rankmap[channel] |= (BIT(dimm->data.ranks) - 1) << rank;
+
+			if (dimm->data.flags.pins_mirrored)
+				ctrl->rank_mirrored[channel] |= BIT(rank + 1);
+
+			/* Find common settings */
+			ctrl->cas_supported &= dimm->data.cas_supported;
+			ctrl->flags.raw &= dimm->data.flags.raw;
+			ctrl->tCK  = MAX(ctrl->tCK,  dimm->data.tCK);
+			ctrl->tAA  = MAX(ctrl->tAA,  dimm->data.tAA);
+			ctrl->tWR  = MAX(ctrl->tWR,  dimm->data.tWR);
+			ctrl->tRCD = MAX(ctrl->tRCD, dimm->data.tRCD);
+			ctrl->tRRD = MAX(ctrl->tRRD, dimm->data.tRRD);
+			ctrl->tRP  = MAX(ctrl->tRP,  dimm->data.tRP);
+			ctrl->tRAS = MAX(ctrl->tRAS, dimm->data.tRAS);
+			ctrl->tRC  = MAX(ctrl->tRC,  dimm->data.tRC);
+			ctrl->tRFC = MAX(ctrl->tRFC, dimm->data.tRFC);
+			ctrl->tWTR = MAX(ctrl->tWTR, dimm->data.tWTR);
+			ctrl->tRTP = MAX(ctrl->tRTP, dimm->data.tRTP);
+			ctrl->tFAW = MAX(ctrl->tFAW, dimm->data.tFAW);
+			ctrl->tCWL = MAX(ctrl->tCWL, dimm->data.tCWL);
+			ctrl->tCMD = MAX(ctrl->tCMD, dimm->data.tCMD);
+
+			yes_ecc |=  dimm->data.flags.is_ecc;
+			not_ecc |= !dimm->data.flags.is_ecc;
+		}
+	}
+
+	if (!ctrl->chanmap) {
+		printk(BIOS_ERR, "No DIMMs were found\n");
+		return RAMINIT_STATUS_NO_MEMORY_INSTALLED;
+	}
+	if (!ctrl->cas_supported) {
+		printk(BIOS_ERR, "Could not resolve common CAS latency\n");
+		return RAMINIT_STATUS_UNSUPPORTED_MEMORY;
+	}
+	/** TODO: Properly handle ECC support and ECC forced **/
+	if (yes_ecc && not_ecc) {
+		/** TODO: Test if the ECC DIMMs can be operated as non-ECC DIMMs **/
+		printk(BIOS_ERR, "Both ECC and non-ECC DIMMs present, this is unsupported\n");
+		return RAMINIT_STATUS_UNSUPPORTED_MEMORY;
+	}
+	if (yes_ecc)
+		ctrl->lanes = NUM_LANES;
+	else
+		ctrl->lanes = NUM_LANES_NO_ECC;
+
+	ctrl->is_ecc = yes_ecc;
+
+	/** TODO: Complete LPDDR support **/
+	ctrl->lpddr = false;
+
+	return RAMINIT_STATUS_SUCCESS;
+}
+
+enum raminit_status collect_spd_info(struct sysinfo *ctrl)
+{
+	get_spd_data(ctrl);
+	return find_common_spd_parameters(ctrl);
+}