x86: Implement a cache for Memory Reference Code parameters

The memory reference code takes a very long time to 'train' its SDRAM
interface, around half a second. To avoid this delay on every boot we can
store the parameters from the last training sessions to speed up the next.

Add an implementation of this, storing the training data in CMOS RAM and
SPI flash.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/arch/x86/cpu/ivybridge/sdram.c b/arch/x86/cpu/ivybridge/sdram.c
index 9504735..4963448 100644
--- a/arch/x86/cpu/ivybridge/sdram.c
+++ b/arch/x86/cpu/ivybridge/sdram.c
@@ -14,12 +14,17 @@
 #include <errno.h>
 #include <fdtdec.h>
 #include <malloc.h>
+#include <net.h>
+#include <rtc.h>
+#include <spi.h>
+#include <spi_flash.h>
 #include <asm/processor.h>
 #include <asm/gpio.h>
 #include <asm/global_data.h>
 #include <asm/mtrr.h>
 #include <asm/pci.h>
 #include <asm/arch/me.h>
+#include <asm/arch/mrccache.h>
 #include <asm/arch/pei_data.h>
 #include <asm/arch/pch.h>
 #include <asm/post.h>
@@ -27,6 +32,10 @@
 
 DECLARE_GLOBAL_DATA_PTR;
 
+#define CMOS_OFFSET_MRC_SEED		152
+#define CMOS_OFFSET_MRC_SEED_S3		156
+#define CMOS_OFFSET_MRC_SEED_CHK	160
+
 /*
  * This function looks for the highest region of memory lower than 4GB which
  * has enough space for U-Boot where U-Boot is aligned on a page boundary.
@@ -80,6 +89,202 @@
 	}
 }
 
+static int get_mrc_entry(struct spi_flash **sfp, struct fmap_entry *entry)
+{
+	const void *blob = gd->fdt_blob;
+	int node, spi_node, mrc_node;
+	int upto;
+
+	/* Find the flash chip within the SPI controller node */
+	upto = 0;
+	spi_node = fdtdec_next_alias(blob, "spi", COMPAT_INTEL_ICH_SPI, &upto);
+	if (spi_node < 0)
+		return -ENOENT;
+	node = fdt_first_subnode(blob, spi_node);
+	if (node < 0)
+		return -ECHILD;
+
+	/* Find the place where we put the MRC cache */
+	mrc_node = fdt_subnode_offset(blob, node, "rw-mrc-cache");
+	if (mrc_node < 0)
+		return -EPERM;
+
+	if (fdtdec_read_fmap_entry(blob, mrc_node, "rm-mrc-cache", entry))
+		return -EINVAL;
+
+	if (sfp) {
+		*sfp = spi_flash_probe_fdt(blob, node, spi_node);
+		if (!*sfp)
+			return -EBADF;
+	}
+
+	return 0;
+}
+
+static int read_seed_from_cmos(struct pei_data *pei_data)
+{
+	u16 c1, c2, checksum, seed_checksum;
+
+	/*
+	 * Read scrambler seeds from CMOS RAM. We don't want to store them in
+	 * SPI flash since they change on every boot and that would wear down
+	 * the flash too much. So we store these in CMOS and the large MRC
+	 * data in SPI flash.
+	 */
+	pei_data->scrambler_seed = rtc_read32(CMOS_OFFSET_MRC_SEED);
+	debug("Read scrambler seed    0x%08x from CMOS 0x%02x\n",
+	      pei_data->scrambler_seed, CMOS_OFFSET_MRC_SEED);
+
+	pei_data->scrambler_seed_s3 = rtc_read32(CMOS_OFFSET_MRC_SEED_S3);
+	debug("Read S3 scrambler seed 0x%08x from CMOS 0x%02x\n",
+	      pei_data->scrambler_seed_s3, CMOS_OFFSET_MRC_SEED_S3);
+
+	/* Compute seed checksum and compare */
+	c1 = compute_ip_checksum((u8 *)&pei_data->scrambler_seed,
+				 sizeof(u32));
+	c2 = compute_ip_checksum((u8 *)&pei_data->scrambler_seed_s3,
+				 sizeof(u32));
+	checksum = add_ip_checksums(sizeof(u32), c1, c2);
+
+	seed_checksum = rtc_read8(CMOS_OFFSET_MRC_SEED_CHK);
+	seed_checksum |= rtc_read8(CMOS_OFFSET_MRC_SEED_CHK + 1) << 8;
+
+	if (checksum != seed_checksum) {
+		debug("%s: invalid seed checksum\n", __func__);
+		pei_data->scrambler_seed = 0;
+		pei_data->scrambler_seed_s3 = 0;
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int prepare_mrc_cache(struct pei_data *pei_data)
+{
+	struct mrc_data_container *mrc_cache;
+	struct fmap_entry entry;
+	int ret;
+
+	ret = read_seed_from_cmos(pei_data);
+	if (ret)
+		return ret;
+	ret = get_mrc_entry(NULL, &entry);
+	if (ret)
+		return ret;
+	mrc_cache = mrccache_find_current(&entry);
+	if (!mrc_cache)
+		return -ENOENT;
+
+	/*
+	 * TODO(sjg@chromium.org): Skip this for now as it causes boot
+	 * problems
+	 */
+	if (0) {
+		pei_data->mrc_input = mrc_cache->data;
+		pei_data->mrc_input_len = mrc_cache->data_size;
+	}
+	debug("%s: at %p, size %x checksum %04x\n", __func__,
+	      pei_data->mrc_input, pei_data->mrc_input_len,
+	      mrc_cache->checksum);
+
+	return 0;
+}
+
+static int build_mrc_data(struct mrc_data_container **datap)
+{
+	struct mrc_data_container *data;
+	int orig_len;
+	int output_len;
+
+	orig_len = gd->arch.mrc_output_len;
+	output_len = ALIGN(orig_len, 16);
+	data = malloc(output_len + sizeof(*data));
+	if (!data)
+		return -ENOMEM;
+	data->signature = MRC_DATA_SIGNATURE;
+	data->data_size = output_len;
+	data->reserved = 0;
+	memcpy(data->data, gd->arch.mrc_output, orig_len);
+
+	/* Zero the unused space in aligned buffer. */
+	if (output_len > orig_len)
+		memset(data->data + orig_len, 0, output_len - orig_len);
+
+	data->checksum = compute_ip_checksum(data->data, output_len);
+	*datap = data;
+
+	return 0;
+}
+
+static int write_seeds_to_cmos(struct pei_data *pei_data)
+{
+	u16 c1, c2, checksum;
+
+	/* Save the MRC seed values to CMOS */
+	rtc_write32(CMOS_OFFSET_MRC_SEED, pei_data->scrambler_seed);
+	debug("Save scrambler seed    0x%08x to CMOS 0x%02x\n",
+	      pei_data->scrambler_seed, CMOS_OFFSET_MRC_SEED);
+
+	rtc_write32(CMOS_OFFSET_MRC_SEED_S3, pei_data->scrambler_seed_s3);
+	debug("Save s3 scrambler seed 0x%08x to CMOS 0x%02x\n",
+	      pei_data->scrambler_seed_s3, CMOS_OFFSET_MRC_SEED_S3);
+
+	/* Save a simple checksum of the seed values */
+	c1 = compute_ip_checksum((u8 *)&pei_data->scrambler_seed,
+				 sizeof(u32));
+	c2 = compute_ip_checksum((u8 *)&pei_data->scrambler_seed_s3,
+				 sizeof(u32));
+	checksum = add_ip_checksums(sizeof(u32), c1, c2);
+
+	rtc_write8(CMOS_OFFSET_MRC_SEED_CHK, checksum & 0xff);
+	rtc_write8(CMOS_OFFSET_MRC_SEED_CHK + 1, (checksum >> 8) & 0xff);
+
+	return 0;
+}
+
+static int sdram_save_mrc_data(void)
+{
+	struct mrc_data_container *data;
+	struct fmap_entry entry;
+	struct spi_flash *sf;
+	int ret;
+
+	if (!gd->arch.mrc_output_len)
+		return 0;
+	debug("Saving %d bytes of MRC output data to SPI flash\n",
+	      gd->arch.mrc_output_len);
+
+	ret = get_mrc_entry(&sf, &entry);
+	if (ret)
+		goto err_entry;
+	ret = build_mrc_data(&data);
+	if (ret)
+		goto err_data;
+	ret = mrccache_update(sf, &entry, data);
+	if (!ret)
+		debug("Saved MRC data with checksum %04x\n", data->checksum);
+
+	free(data);
+err_data:
+	spi_flash_free(sf);
+err_entry:
+	if (ret)
+		debug("%s: Failed: %d\n", __func__, ret);
+	return ret;
+}
+
+/* Use this hook to save our SDRAM parameters */
+int misc_init_r(void)
+{
+	int ret;
+
+	ret = sdram_save_mrc_data();
+	if (ret)
+		printf("Unable to save MRC data: %d\n", ret);
+
+	return 0;
+}
+
 static const char *const ecc_decoder[] = {
 	"inactive",
 	"active on IO",
@@ -142,6 +347,11 @@
 #endif
 }
 
+static int recovery_mode_enabled(void)
+{
+	return false;
+}
+
 /**
  * Find the PEI executable in the ROM and execute it.
  *
@@ -166,6 +376,17 @@
 
 	debug("Starting UEFI PEI System Agent\n");
 
+	/*
+	 * Do not pass MRC data in for recovery mode boot,
+	 * Always pass it in for S3 resume.
+	 */
+	if (!recovery_mode_enabled() ||
+	    pei_data->boot_mode == PEI_BOOT_RESUME) {
+		ret = prepare_mrc_cache(pei_data);
+		if (ret)
+			debug("prepare_mrc_cache failed: %d\n", ret);
+	}
+
 	/* If MRC data is not found we cannot continue S3 resume. */
 	if (pei_data->boot_mode == PEI_BOOT_RESUME && !pei_data->mrc_input) {
 		debug("Giving up in sdram_initialize: No MRC data\n");
@@ -216,6 +437,8 @@
 	debug("System Agent Version %d.%d.%d Build %d\n",
 	      version >> 24 , (version >> 16) & 0xff,
 	      (version >> 8) & 0xff, version & 0xff);
+	debug("MCR output data length %#x at %p\n", pei_data->mrc_output_len,
+	      pei_data->mrc_output);
 
 	/*
 	 * Send ME init done for SandyBridge here.  This is done inside the
@@ -231,6 +454,36 @@
 	post_system_agent_init(pei_data);
 	report_memory_config();
 
+	/* S3 resume: don't save scrambler seed or MRC data */
+	if (pei_data->boot_mode != PEI_BOOT_RESUME) {
+		/*
+		 * This will be copied to SDRAM in reserve_arch(), then written
+		 * to SPI flash in sdram_save_mrc_data()
+		 */
+		gd->arch.mrc_output = (char *)pei_data->mrc_output;
+		gd->arch.mrc_output_len = pei_data->mrc_output_len;
+		ret = write_seeds_to_cmos(pei_data);
+		if (ret)
+			debug("Failed to write seeds to CMOS: %d\n", ret);
+	}
+
+	return 0;
+}
+
+int reserve_arch(void)
+{
+	u16 checksum;
+
+	checksum = compute_ip_checksum(gd->arch.mrc_output,
+				       gd->arch.mrc_output_len);
+	debug("Saving %d bytes for MRC output data, checksum %04x\n",
+	      gd->arch.mrc_output_len, checksum);
+	gd->start_addr_sp -= gd->arch.mrc_output_len;
+	memcpy((void *)gd->start_addr_sp, gd->arch.mrc_output,
+	       gd->arch.mrc_output_len);
+	gd->arch.mrc_output = (char *)gd->start_addr_sp;
+	gd->start_addr_sp &= ~0xf;
+
 	return 0;
 }