nb/intel/haswell: Allow using Broadwell MRC.bin

This is needed to support 9-series PCH-H (e.g. Z97) and Broadwell
non-ULT CPUs (for which more magic is required).

Tested on Asrock Z97 Extreme6: Boots, but ME has to be disabled so that
the system remains on after 30 seconds. Apparently, something Broadwell
MRC.bin does results in the ME being unhappy, as there is no such issue
when not using MRC.bin at all (native RAM init). S3 resume is working.

Change-Id: I7b33660099fa75c5ad46aeeda17b1215729f96c3
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/55496
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Nico Huber <nico.h@gmx.de>
Reviewed-by: Lean Sheng Tan <sheng.tan@9elements.com>
diff --git a/src/northbridge/intel/haswell/Kconfig b/src/northbridge/intel/haswell/Kconfig
index 06c9999..d1c9ec2 100644
--- a/src/northbridge/intel/haswell/Kconfig
+++ b/src/northbridge/intel/haswell/Kconfig
@@ -37,6 +37,18 @@
 	  binary is used meaning a jump is made from RW to the RO region
 	  and back to the RW region after the binary is done.
 
+config USE_BROADWELL_MRC
+	bool "Use Broadwell MRC.bin"
+	depends on !USE_NATIVE_RAMINIT
+	help
+	  Haswell MRC.bin has several limitations: it does not support
+	  Broadwell CPUs nor 9-series PCHs, it does not initialise PEG
+	  ports properly and it can't use more than one SPD file entry
+	  at the same time (which would be useful for memory overclock
+	  when using different DIMMs, without patching SPD EEPROMs). A
+	  workaround for some of these limitations is to use Broadwell
+	  MRC.bin instead.
+
 config VBOOT
 	select VBOOT_MUST_REQUEST_DISPLAY
 	select VBOOT_STARTS_IN_ROMSTAGE if !HASWELL_VBOOT_IN_BOOTBLOCK
diff --git a/src/northbridge/intel/haswell/Makefile.inc b/src/northbridge/intel/haswell/Makefile.inc
index df0b097..8da72dc 100644
--- a/src/northbridge/intel/haswell/Makefile.inc
+++ b/src/northbridge/intel/haswell/Makefile.inc
@@ -24,7 +24,13 @@
 subdirs-y  += native_raminit
 
 else
+ifeq ($(CONFIG_USE_BROADWELL_MRC),y)
+romstage-y += early_dmi.c early_pcie.c vcu_mailbox.c
+subdirs-y  += broadwell_mrc
+
+else
 subdirs-y  += haswell_mrc
 endif
+endif
 
 endif
diff --git a/src/northbridge/intel/haswell/broadwell_mrc/Makefile.inc b/src/northbridge/intel/haswell/broadwell_mrc/Makefile.inc
new file mode 100644
index 0000000..bd6314c
--- /dev/null
+++ b/src/northbridge/intel/haswell/broadwell_mrc/Makefile.inc
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+romstage-y += raminit.c
+
+# We don't ship that, but booting without it is bound to fail
+cbfs-files-$(CONFIG_HAVE_MRC) += mrc.bin
+mrc.bin-file := $(call strip_quotes,$(CONFIG_MRC_FILE))
+mrc.bin-position := 0xfffa0000
+mrc.bin-type := mrc
diff --git a/src/northbridge/intel/haswell/broadwell_mrc/pei_data.h b/src/northbridge/intel/haswell/broadwell_mrc/pei_data.h
new file mode 100644
index 0000000..aee3083
--- /dev/null
+++ b/src/northbridge/intel/haswell/broadwell_mrc/pei_data.h
@@ -0,0 +1,249 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+#ifndef PEI_DATA_H
+#define PEI_DATA_H
+
+#include <types.h>
+
+#define PEI_VERSION 22
+
+#define ABI_X86 __attribute__((regparm(0)))
+
+typedef void ABI_X86 (*tx_byte_func)(unsigned char byte);
+
+enum board_type {
+	BOARD_TYPE_CRB_MOBILE = 0,	/* CRB Mobile */
+	BOARD_TYPE_CRB_DESKTOP,		/* CRB Desktop */
+	BOARD_TYPE_USER1,		/* SV mobile */
+	BOARD_TYPE_USER2,		/* SV desktop */
+	BOARD_TYPE_USER3,		/* SV server */
+	BOARD_TYPE_ULT,			/* ULT */
+	BOARD_TYPE_CRB_EMBDEDDED,	/* CRB Embedded */
+	BOARD_TYPE_UNKNOWN,
+};
+
+#define PEI_MAX_USB2_PORTS 14
+#define PEI_MAX_USB3_PORTS 6
+#define PEI_USB_OC_PIN_SKIP 8
+
+enum pei_usb2_port_location {
+	PEI_USB_PORT_BACK_PANEL = 0,
+	PEI_USB_PORT_FRONT_PANEL,
+	PEI_USB_PORT_DOCK,
+	PEI_USB_PORT_MINI_PCIE,
+	PEI_USB_PORT_FLEX,
+	PEI_USB_PORT_INTERNAL,
+	PEI_USB_PORT_SKIP,
+	PEI_USB_PORT_NGFF_DEVICE_DOWN,
+};
+
+struct pei_usb2_port_setting {
+	/*
+	 * Usb Port Length:
+	 * [16:4] = length in inches in octal format
+	 * [3:0]  = decimal point
+	 */
+	uint16_t length;
+	uint8_t enable;
+	uint8_t oc_pin;
+	uint8_t location;
+} __packed;
+
+struct pei_usb3_port_setting {
+	uint8_t enable;
+	uint8_t oc_pin;
+	/*
+	 * Set to 0 if trace length is > 5 inches
+	 * Set to 1 if trace length is <= 5 inches
+	 */
+	uint8_t fixed_eq;
+} __packed;
+
+#define PEI_DIMM_INFO_SERIAL_SIZE	5
+#define PEI_DIMM_INFO_PART_NUMBER_SIZE	19
+#define PEI_DIMM_INFO_TOTAL		8	/* Maximum num of dimm is 8 */
+
+/**
+ * This table is filled by the MRC blob and used to populate the mem_info
+ * struct, which is placed in CBMEM and then used to generate SMBIOS type
+ * 17 table(s)
+ *
+ * Values are specified according to the JEDEC SPD Standard.
+ */
+struct pei_dimm_info {
+	/*
+	 * Size of the module in MiB.
+	 */
+	uint32_t dimm_size;
+	/*
+	 * SMBIOS (not SPD) device type.
+	 *
+	 * See the smbios.h smbios_memory_device_type enum.
+	 */
+	uint16_t ddr_type;
+	uint16_t ddr_frequency;
+	uint8_t rank_per_dimm;
+	uint8_t channel_num;
+	uint8_t dimm_num;
+	uint8_t bank_locator;
+	/*
+	 * The last byte is '\0' for the end of string.
+	 *
+	 * Even though the SPD spec defines this field as a byte array the value
+	 * is passed directly to SMBIOS as a string, and thus must be printable
+	 * ASCII.
+	 */
+	uint8_t serial[PEI_DIMM_INFO_SERIAL_SIZE];
+	/*
+	 * The last byte is '\0' for the end of string
+	 *
+	 * Must contain only printable ASCII.
+	 */
+	uint8_t module_part_number[PEI_DIMM_INFO_PART_NUMBER_SIZE];
+	/*
+	 * SPD Manufacturer ID
+	 */
+	uint16_t mod_id;
+	/*
+	 * SPD Module Type.
+	 *
+	 * See spd.h for valid values.
+	 *
+	 * e.g., SPD_RDIMM, SPD_SODIMM, SPD_MICRO_DIMM
+	 */
+	uint8_t mod_type;
+	/*
+	 * SPD bus width.
+	 *
+	 * Bits 0 - 2 encode the primary bus width:
+	 *   0b000 = 8 bit width
+	 *   0b001 = 16 bit width
+	 *   0b010 = 32 bit width
+	 *   0b011 = 64 bit width
+	 *
+	 * Bits 3 - 4 encode the extension bits (ECC):
+	 *   0b00 = 0 extension bits
+	 *   0b01 = 8 bit of ECC
+	 *
+	 * e.g.,
+	 *   64 bit bus with 8 bits of ECC (72 bits total): 0b1011
+	 *   64 bit bus with 0 bits of ECC (64 bits total): 0b0011
+	 *
+	 * See the smbios.h smbios_memory_bus_width enum.
+	 */
+	uint8_t bus_width;
+} __packed;
+
+struct pei_memory_info {
+	uint8_t dimm_cnt;
+	struct pei_dimm_info dimm[PEI_DIMM_INFO_TOTAL];
+} __packed;
+
+struct pei_data {
+	uint32_t pei_version;
+
+	enum board_type board_type;
+	int boot_mode;
+	int ec_present;
+	int usbdebug;
+
+	/* Base addresses */
+	uint32_t pciexbar;
+	uint16_t smbusbar;
+	uint32_t xhcibar;
+	uint32_t ehcibar;
+	uint32_t gttbar;
+	uint32_t rcba;
+	uint32_t pmbase;
+	uint32_t gpiobase;
+	uint32_t temp_mmio_base;
+	uint32_t tseg_size;
+
+	/*
+	 * 0 = leave channel enabled
+	 * 1 = disable dimm 0 on channel
+	 * 2 = disable dimm 1 on channel
+	 * 3 = disable dimm 0+1 on channel
+	 */
+	int dimm_channel0_disabled;
+	int dimm_channel1_disabled;
+	/* Set to 0 for memory down */
+	uint8_t spd_addresses[4];
+	/* Enable 2x Refresh Mode */
+	int ddr_refresh_2x;
+	/* DQ pins are interleaved on board */
+	int dq_pins_interleaved;
+	/* Limit DDR3 frequency */
+	int max_ddr3_freq;
+	/* Disable self refresh */
+	int disable_self_refresh;
+	/* Disable cmd power/CKEPD */
+	int disable_cmd_pwr;
+
+	/* USB port configuration */
+	struct pei_usb2_port_setting usb2_ports[MAX_USB2_PORTS];
+	struct pei_usb3_port_setting usb3_ports[MAX_USB3_PORTS];
+
+	/*
+	 * USB3 board specific PHY tuning
+	 */
+
+	/* Valid range: 0x69 - 0x80 */
+	uint8_t usb3_txout_volt_dn_amp_adj[MAX_USB3_PORTS];
+	/* Valid range: 0x80 - 0x9c */
+	uint8_t usb3_txout_imp_sc_volt_amp_adj[MAX_USB3_PORTS];
+	/* Valid range: 0x39 - 0x80 */
+	uint8_t usb3_txout_de_emp_adj[MAX_USB3_PORTS];
+	/* Valid range: 0x3d - 0x4a */
+	uint8_t usb3_txout_imp_adj_volt_amp[MAX_USB3_PORTS];
+
+	/* Console output function */
+	tx_byte_func tx_byte;
+
+	/*
+	 * DIMM SPD data for memory down configurations
+	 * [CHANNEL][SLOT][SPD]
+	 */
+	uint8_t spd_data[2][2][512];
+
+	/*
+	 * LPDDR3 DQ byte map
+	 * [CHANNEL][ITERATION][2]
+	 *
+	 * Maps which PI clocks are used by what LPDDR DQ Bytes (from CPU side)
+	 * DQByteMap[0] - ClkDQByteMap:
+	 * - If clock is per rank, program to [0xFF, 0xFF]
+	 * - If clock is shared by 2 ranks, program to [0xFF, 0] or [0, 0xFF]
+	 * - If clock is shared by 2 ranks but does not go to all bytes,
+	 *   Entry[i] defines which DQ bytes Group i services
+	 * DQByteMap[1] - CmdNDQByteMap: [0] is CmdN/CAA and [1] is CmdN/CAB
+	 * DQByteMap[2] - CmdSDQByteMap: [0] is CmdS/CAA and [1] is CmdS/CAB
+	 * DQByteMap[3] - CkeDQByteMap : [0] is CKE /CAA and [1] is CKE /CAB
+	 *                For DDR, DQByteMap[3:1] = [0xFF, 0]
+	 * DQByteMap[4] - CtlDQByteMap : Always program to [0xFF, 0]
+	 *                since we have 1 CTL / rank
+	 * DQByteMap[5] - CmdVDQByteMap: Always program to [0xFF, 0]
+	 *                since we have 1 CA Vref
+	 */
+	uint8_t dq_map[2][6][2];
+
+	/*
+	 * LPDDR3 Map from CPU DQS pins to SDRAM DQS pins
+	 * [CHANNEL][MAX_BYTES]
+	 */
+	uint8_t dqs_map[2][8];
+
+	/* Data read from flash and passed into MRC */
+	const void *saved_data;
+	int saved_data_size;
+
+	/* Disable use of saved data (can be set by mainboard) */
+	int disable_saved_data;
+
+	/* Data from MRC that should be saved to flash */
+	void *data_to_save;
+	int data_to_save_size;
+	struct pei_memory_info meminfo;
+} __packed;
+
+#endif
diff --git a/src/northbridge/intel/haswell/broadwell_mrc/raminit.c b/src/northbridge/intel/haswell/broadwell_mrc/raminit.c
new file mode 100644
index 0000000..28422fb
--- /dev/null
+++ b/src/northbridge/intel/haswell/broadwell_mrc/raminit.c
@@ -0,0 +1,438 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <assert.h>
+#include <console/console.h>
+#include <console/streams.h>
+#include <console/usb.h>
+#include <string.h>
+#include <cbmem.h>
+#include <cbfs.h>
+#include <cf9_reset.h>
+#include <ip_checksum.h>
+#include <memory_info.h>
+#include <mrc_cache.h>
+#include <device/device.h>
+#include <device/pci_def.h>
+#include <device/pci_ops.h>
+#include <device/dram/ddr3.h>
+#include <northbridge/intel/haswell/chip.h>
+#include <northbridge/intel/haswell/haswell.h>
+#include <northbridge/intel/haswell/raminit.h>
+#include <smbios.h>
+#include <spd.h>
+#include <security/vboot/vboot_common.h>
+#include <commonlib/region.h>
+#include <southbridge/intel/lynxpoint/me.h>
+#include <southbridge/intel/lynxpoint/pch.h>
+#include <timestamp.h>
+#include <types.h>
+
+#include "pei_data.h"
+
+static void save_mrc_data(struct pei_data *pei_data)
+{
+	printk(BIOS_DEBUG, "MRC data at %p %d bytes\n", pei_data->data_to_save,
+	       pei_data->data_to_save_size);
+
+	if (pei_data->data_to_save != NULL && pei_data->data_to_save_size > 0)
+		mrc_cache_stash_data(MRC_TRAINING_DATA, 0,
+					pei_data->data_to_save,
+					pei_data->data_to_save_size);
+}
+
+static const char *const ecc_decoder[] = {
+	"inactive",
+	"active on IO",
+	"disabled on IO",
+	"active",
+};
+
+/*
+ * Dump in the log memory controller configuration as read from the memory
+ * controller registers.
+ */
+static void report_memory_config(void)
+{
+	int i;
+
+	const u32 addr_decoder_common = mchbar_read32(MAD_CHNL);
+
+	printk(BIOS_DEBUG, "memcfg DDR3 clock %d MHz\n",
+	       (mchbar_read32(MC_BIOS_DATA) * 13333 * 2 + 50) / 100);
+
+	printk(BIOS_DEBUG, "memcfg channel assignment: A: %d, B % d, C % d\n",
+	       (addr_decoder_common >> 0) & 3,
+	       (addr_decoder_common >> 2) & 3,
+	       (addr_decoder_common >> 4) & 3);
+
+	for (i = 0; i < NUM_CHANNELS; i++) {
+		const u32 ch_conf = mchbar_read32(MAD_DIMM(i));
+
+		printk(BIOS_DEBUG, "memcfg channel[%d] config (%8.8x):\n", i, ch_conf);
+		printk(BIOS_DEBUG, "   ECC %s\n", ecc_decoder[(ch_conf >> 24) & 3]);
+		printk(BIOS_DEBUG, "   enhanced interleave mode %s\n",
+		       ((ch_conf >> 22) & 1) ? "on" : "off");
+
+		printk(BIOS_DEBUG, "   rank interleave %s\n",
+		       ((ch_conf >> 21) & 1) ? "on" : "off");
+
+		printk(BIOS_DEBUG, "   DIMMA %d MB width %s %s rank%s\n",
+		       ((ch_conf >> 0) & 0xff) * 256,
+		       ((ch_conf >> 19) & 1) ? "x16" : "x8 or x32",
+		       ((ch_conf >> 17) & 1) ? "dual" : "single",
+		       ((ch_conf >> 16) & 1) ? "" : ", selected");
+
+		printk(BIOS_DEBUG, "   DIMMB %d MB width %s %s rank%s\n",
+		       ((ch_conf >> 8) & 0xff) * 256,
+		       ((ch_conf >> 20) & 1) ? "x16" : "x8 or x32",
+		       ((ch_conf >> 18) & 1) ? "dual" : "single",
+		       ((ch_conf >> 16) & 1) ? ", selected" : "");
+	}
+}
+
+typedef int ABI_X86 (*pei_wrapper_entry_t)(struct pei_data *pei_data);
+
+static void ABI_X86 send_to_console(unsigned char b)
+{
+	console_tx_byte(b);
+}
+
+/*
+ * Find PEI executable in coreboot filesystem and execute it.
+ */
+static void sdram_initialize(struct pei_data *pei_data)
+{
+	size_t mrc_size;
+	pei_wrapper_entry_t entry;
+	int ret;
+
+	/* Assume boot device is memory mapped. */
+	assert(CONFIG(BOOT_DEVICE_MEMORY_MAPPED));
+
+	pei_data->saved_data =
+		mrc_cache_current_mmap_leak(MRC_TRAINING_DATA, 0,
+					    &mrc_size);
+	if (pei_data->saved_data) {
+		/* MRC cache found */
+		pei_data->saved_data_size = mrc_size;
+	} else if (pei_data->boot_mode == ACPI_S3) {
+		/* Waking from S3 and no cache. */
+		printk(BIOS_DEBUG,
+		       "No MRC cache found in S3 resume path.\n");
+		post_code(POST_RESUME_FAILURE);
+		system_reset();
+	} else {
+		printk(BIOS_DEBUG, "No MRC cache found.\n");
+	}
+
+	/*
+	 * Do not use saved pei data.  Can be set by mainboard romstage
+	 * to force a full train of memory on every boot.
+	 */
+	if (pei_data->disable_saved_data) {
+		printk(BIOS_DEBUG, "Disabling PEI saved data by request\n");
+		pei_data->saved_data = NULL;
+		pei_data->saved_data_size = 0;
+	}
+
+	/* We don't care about leaking the mapping */
+	entry = cbfs_ro_map("mrc.bin", NULL);
+	if (entry == NULL)
+		die("mrc.bin not found!");
+
+	printk(BIOS_DEBUG, "Starting Memory Reference Code\n");
+
+	ret = entry(pei_data);
+	if (ret < 0)
+		die("pei_data version mismatch\n");
+
+	/* Print the MRC version after executing the UEFI PEI stage. */
+	u32 version = mchbar_read32(MRC_REVISION);
+	printk(BIOS_DEBUG, "MRC Version %u.%u.%u Build %u\n",
+		(version >> 24) & 0xff, (version >> 16) & 0xff,
+		(version >>  8) & 0xff, (version >>  0) & 0xff);
+
+	report_memory_config();
+}
+
+static uint8_t nb_get_ecc_type(const uint32_t capid0_a)
+{
+	return capid0_a & CAPID_ECCDIS ? MEMORY_ARRAY_ECC_NONE : MEMORY_ARRAY_ECC_SINGLE_BIT;
+}
+
+static uint16_t nb_slots_per_channel(const uint32_t capid0_a)
+{
+	return !(capid0_a & CAPID_DDPCD) + 1;
+}
+
+static uint16_t nb_number_of_channels(const uint32_t capid0_a)
+{
+	return !(capid0_a & CAPID_PDCD) + 1;
+}
+
+static uint32_t nb_max_chan_capacity_mib(const uint32_t capid0_a)
+{
+	uint32_t ddrsz;
+
+	/* Values from documentation, which assume two DIMMs per channel */
+	switch (CAPID_DDRSZ(capid0_a)) {
+	case 1:
+		ddrsz = 8192;
+		break;
+	case 2:
+		ddrsz = 2048;
+		break;
+	case 3:
+		ddrsz = 512;
+		break;
+	default:
+		ddrsz = 16384;
+		break;
+	}
+
+	/* Account for the maximum number of DIMMs per channel */
+	return (ddrsz / 2) * nb_slots_per_channel(capid0_a);
+}
+
+static void setup_sdram_meminfo(struct pei_data *pei_data)
+{
+	unsigned int dimm_cnt = 0;
+
+	struct memory_info *mem_info = cbmem_add(CBMEM_ID_MEMINFO, sizeof(*mem_info));
+	if (!mem_info)
+		die("Failed to add memory info to CBMEM.\n");
+
+	memset(mem_info, 0, sizeof(struct memory_info));
+
+	const u32 ddr_frequency = (mchbar_read32(MC_BIOS_DATA) * 13333 * 2 + 50) / 100;
+
+	for (unsigned int ch = 0; ch < NUM_CHANNELS; ch++) {
+		const u32 ch_conf = mchbar_read32(MAD_DIMM(ch));
+		for (unsigned int slot = 0; slot < NUM_SLOTS; slot++) {
+			const u32 dimm_size = ((ch_conf >> (slot * 8)) & 0xff) * 256;
+			if (dimm_size) {
+				struct dimm_info *dimm = &mem_info->dimm[dimm_cnt];
+				dimm->dimm_size = dimm_size;
+				dimm->ddr_type = MEMORY_TYPE_DDR3;
+				dimm->ddr_frequency = ddr_frequency;
+				dimm->rank_per_dimm = 1 + ((ch_conf >> (17 + slot)) & 1);
+				dimm->channel_num = ch;
+				dimm->dimm_num = slot;
+				dimm->bank_locator = ch * 2;
+				memcpy(dimm->serial,
+					&pei_data->spd_data[ch][slot][SPD_DIMM_SERIAL_NUM],
+					SPD_DIMM_SERIAL_LEN);
+				memcpy(dimm->module_part_number,
+					&pei_data->spd_data[ch][slot][SPD_DIMM_PART_NUM],
+					SPD_DIMM_PART_LEN);
+				dimm->mod_id =
+					(pei_data->spd_data[ch][slot][SPD_DIMM_MOD_ID2] << 8) |
+					(pei_data->spd_data[ch][slot][SPD_DIMM_MOD_ID1] & 0xff);
+				dimm->mod_type = SPD_DDR3_DIMM_TYPE_SO_DIMM;
+				dimm->bus_width = MEMORY_BUS_WIDTH_64;
+				dimm_cnt++;
+			}
+		}
+	}
+	mem_info->dimm_cnt = dimm_cnt;
+
+	const uint32_t capid0_a = pci_read_config32(HOST_BRIDGE, CAPID0_A);
+
+	const uint16_t channels = nb_number_of_channels(capid0_a);
+
+	mem_info->ecc_type = nb_get_ecc_type(capid0_a);
+	mem_info->max_capacity_mib = channels * nb_max_chan_capacity_mib(capid0_a);
+	mem_info->number_of_devices = channels * nb_slots_per_channel(capid0_a);
+}
+
+#include <device/smbus_host.h>
+#define SPD_LEN 256
+
+/* Copy SPD data for on-board memory */
+static void copy_spd(struct pei_data *pei_data, struct spd_info *spdi)
+{
+	if (!CONFIG(HAVE_SPD_IN_CBFS))
+		return;
+
+	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 not found.");
+
+	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)
+		die("Missing SPD data.");
+
+	/* MRC only uses index 0, but coreboot uses the other indices */
+	memcpy(pei_data->spd_data[0], spd_file + (spdi->spd_index * SPD_LEN), SPD_LEN);
+
+	for (size_t 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_LEN);
+	}
+}
+
+/*
+ * 0 = leave channel enabled
+ * 1 = disable dimm 0 on channel
+ * 2 = disable dimm 1 on channel
+ * 3 = disable dimm 0+1 on channel
+ */
+static int make_channel_disabled_mask(const struct pei_data *pd, int ch)
+{
+	return (!pd->spd_addresses[ch + ch] << 0) | (!pd->spd_addresses[ch + ch + 1] << 1);
+}
+
+static enum pei_usb2_port_location map_to_pei_usb2_location(const enum usb2_port_location loc)
+{
+	/* TODO: USB_PORT_NGFF_DEVICE_DOWN (not used by any board, though) */
+	static const enum pei_usb2_port_location map[] = {
+		[USB_PORT_SKIP]		= PEI_USB_PORT_SKIP,
+		[USB_PORT_BACK_PANEL]	= PEI_USB_PORT_BACK_PANEL,
+		[USB_PORT_FRONT_PANEL]	= PEI_USB_PORT_FRONT_PANEL,
+		[USB_PORT_DOCK]		= PEI_USB_PORT_DOCK,
+		[USB_PORT_MINI_PCIE]	= PEI_USB_PORT_MINI_PCIE,
+		[USB_PORT_FLEX]		= PEI_USB_PORT_FLEX,
+		[USB_PORT_INTERNAL]	= PEI_USB_PORT_INTERNAL,
+	};
+	return loc >= ARRAY_SIZE(map) ? PEI_USB_PORT_SKIP : map[loc];
+}
+
+static uint8_t map_to_pei_oc_pin(const uint8_t oc_pin)
+{
+	return oc_pin >= USB_OC_PIN_SKIP ? PEI_USB_OC_PIN_SKIP : oc_pin;
+}
+
+static bool early_init_native(int s3resume)
+{
+	printk(BIOS_DEBUG, "Starting native platform initialisation\n");
+
+	intel_early_me_init();
+	/** TODO: CPU replacement check must be skipped in warm boots and S3 resumes **/
+	const bool cpu_replaced = !s3resume && intel_early_me_cpu_replacement_check();
+
+	early_pch_init_native(s3resume);
+
+	if (!CONFIG(INTEL_LYNXPOINT_LP))
+		dmi_early_init();
+
+	return cpu_replaced;
+}
+
+void perform_raminit(const int s3resume)
+{
+	const struct northbridge_intel_haswell_config *cfg = config_of_soc();
+
+	struct pei_data pei_data = {
+		.pei_version		= PEI_VERSION,
+		.board_type		= get_pch_platform_type(),
+		.usbdebug		= CONFIG(USBDEBUG),
+		.pciexbar		= CONFIG_ECAM_MMCONF_BASE_ADDRESS,
+		.smbusbar		= CONFIG_FIXED_SMBUS_IO_BASE,
+		.ehcibar		= CONFIG_EHCI_BAR,
+		.xhcibar		= 0xd7000000,
+		.gttbar			= 0xe0000000,
+		.pmbase			= DEFAULT_PMBASE,
+		.gpiobase		= DEFAULT_GPIOBASE,
+		.tseg_size		= CONFIG_SMM_TSEG_SIZE,
+		.temp_mmio_base		= 0xfed08000,
+		.ec_present		= cfg->ec_present,
+		.dq_pins_interleaved	= cfg->dq_pins_interleaved,
+		.tx_byte		= send_to_console,
+		.ddr_refresh_2x		= CONFIG(ENABLE_DDR_2X_REFRESH),
+		.rcba			= CONFIG_FIXED_RCBA_MMIO_BASE,	/* Might be unused */
+	};
+
+	for (size_t i = 0; i < ARRAY_SIZE(mainboard_usb2_ports); i++) {
+		/* If a port is not enabled, skip it */
+		if (!mainboard_usb2_ports[i].enable) {
+			pei_data.usb2_ports[i].oc_pin	= PEI_USB_OC_PIN_SKIP;
+			pei_data.usb2_ports[i].location	= PEI_USB_PORT_SKIP;
+			continue;
+		}
+		const enum usb2_port_location loc = mainboard_usb2_ports[i].location;
+		const uint8_t oc_pin = mainboard_usb2_ports[i].oc_pin;
+		pei_data.usb2_ports[i].length	= mainboard_usb2_ports[i].length;
+		pei_data.usb2_ports[i].enable	= mainboard_usb2_ports[i].enable;
+		pei_data.usb2_ports[i].oc_pin	= map_to_pei_oc_pin(oc_pin);
+		pei_data.usb2_ports[i].location	= map_to_pei_usb2_location(loc);
+	}
+
+	for (size_t i = 0; i < ARRAY_SIZE(mainboard_usb3_ports); i++) {
+		const uint8_t oc_pin = mainboard_usb3_ports[i].oc_pin;
+		pei_data.usb3_ports[i].enable	= mainboard_usb3_ports[i].enable;
+		pei_data.usb3_ports[i].oc_pin	= map_to_pei_oc_pin(oc_pin);
+	}
+
+	/* Broadwell MRC uses ACPI values for boot_mode */
+	pei_data.boot_mode = s3resume ? ACPI_S3 : ACPI_S0;
+
+	/* Obtain the SPD addresses from mainboard code */
+	struct spd_info spdi = {0};
+	mb_get_spd_map(&spdi);
+
+	/*
+	 * Read the SPDs over SMBus in coreboot code so that the data can be used to
+	 * populate meminfo. MRC returns some data, but it seems to be incomplete.
+	 */
+	for (size_t i = 0; i < ARRAY_SIZE(spdi.addresses); i++) {
+		const uint8_t addr = spdi.addresses[i];
+		pei_data.spd_addresses[i] = addr == SPD_MEMORY_DOWN ? 0xff : addr << 1;
+		if (addr == SPD_MEMORY_DOWN)
+			continue;
+
+		if (i2c_eeprom_read(addr, 0, 256, pei_data.spd_data[i / 2][i % 2]) != 256) {
+			printk(BIOS_ERR, "0x%02x failed to read\n", addr);
+			memset(pei_data.spd_data[i / 2][i % 2], 0, 256);
+		}
+	}
+
+	/* Calculate unimplemented DIMM slots for each channel */
+	pei_data.dimm_channel0_disabled = make_channel_disabled_mask(&pei_data, 0);
+	pei_data.dimm_channel1_disabled = make_channel_disabled_mask(&pei_data, 1);
+
+	for (size_t i = 0; i < ARRAY_SIZE(spdi.addresses); i++)
+		pei_data.spd_addresses[i] = 0;
+
+	if (early_init_native(s3resume))
+		pei_data.disable_saved_data = true;
+
+	timestamp_add_now(TS_INITRAM_START);
+
+	copy_spd(&pei_data, &spdi);
+
+	sdram_initialize(&pei_data);
+
+	timestamp_add_now(TS_INITRAM_END);
+
+	if (intel_early_me_uma_size() > 0) {
+		/*
+		 * The 'other' success value is to report loss of memory
+		 * consistency to ME if warm boot was downgraded to cold.
+		 * However, we can't tell if MRC downgraded the bootmode.
+		 */
+		intel_early_me_init_done(ME_INIT_STATUS_SUCCESS_OTHER);
+	}
+
+	intel_early_me_status();
+
+	int cbmem_was_initted = !cbmem_recovery(s3resume);
+	if (s3resume && !cbmem_was_initted) {
+		/* Failed S3 resume, reset to come up cleanly */
+		printk(BIOS_CRIT, "Failed to recover CBMEM in S3 resume.\n");
+		system_reset();
+	}
+
+	/* Save data returned from MRC on non-S3 resumes. */
+	if (!s3resume)
+		save_mrc_data(&pei_data);
+
+	setup_sdram_meminfo(&pei_data);
+}
diff --git a/src/southbridge/intel/lynxpoint/Kconfig b/src/southbridge/intel/lynxpoint/Kconfig
index af346ab..0804298 100644
--- a/src/southbridge/intel/lynxpoint/Kconfig
+++ b/src/southbridge/intel/lynxpoint/Kconfig
@@ -45,6 +45,7 @@
 
 config EHCI_BAR
 	hex
+	default 0xd8000000 if USE_BROADWELL_MRC
 	default 0xe8000000
 
 config SERIRQ_CONTINUOUS_MODE
diff --git a/src/southbridge/intel/lynxpoint/Makefile.inc b/src/southbridge/intel/lynxpoint/Makefile.inc
index bba89f4..5088d8b 100644
--- a/src/southbridge/intel/lynxpoint/Makefile.inc
+++ b/src/southbridge/intel/lynxpoint/Makefile.inc
@@ -34,10 +34,14 @@
 bootblock-y += early_pch.c
 romstage-y += early_usb.c early_me.c me_status.c early_pch.c
 romstage-y += pmutil.c
+romstage-y += iobp.c
 
 romstage-$(CONFIG_USE_NATIVE_RAMINIT) += early_pch_native.c early_usb_native.c iobp.c thermal.c
 subdirs-$(CONFIG_USE_NATIVE_RAMINIT) += hsio
 
+romstage-$(CONFIG_USE_BROADWELL_MRC) += early_pch_native.c early_usb_native.c iobp.c thermal.c
+subdirs-$(CONFIG_USE_BROADWELL_MRC) += hsio
+
 ifeq ($(CONFIG_INTEL_LYNXPOINT_LP),y)
 romstage-y += lp_gpio.c
 ramstage-y += lp_gpio.c
diff --git a/src/southbridge/intel/lynxpoint/bootblock.c b/src/southbridge/intel/lynxpoint/bootblock.c
index 495871c..5bdebef 100644
--- a/src/southbridge/intel/lynxpoint/bootblock.c
+++ b/src/southbridge/intel/lynxpoint/bootblock.c
@@ -52,6 +52,21 @@
 	pch_enable_lpc();
 	mainboard_config_superio();
 
+	/* Broadwell MRC.bin uses HPET, but does not enable it beforehand */
+	if (CONFIG(USE_BROADWELL_MRC)) {
+		/* Set HPET address and enable it */
+		RCBA32_AND_OR(HPTC, ~3, 1 << 7);
+
+		/*
+		 * Reading the register back guarantees that the write is
+		 * done before we use the configured base address below.
+		 */
+		(void)RCBA32(HPTC);
+
+		/* Enable HPET to start counter */
+		setbits32((void *)0xfed00000 + 0x10, 1 << 0);
+	}
+
 	if (CONFIG(SERIALIO_UART_CONSOLE))
 		uart_bootblock_init();
 }