From 0966980e52286985fcd0fac6325bdd99f35ebcb8 Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Thu, 11 Apr 2024 17:25:07 +0200
Subject: [PATCH 30/51] haswell NRI: Initialise MPLL

Add code to initialise the MPLL (Memory PLL). The procedure is similar
to the one for Sandy/Ivy Bridge, but it is not worth factoring out.

Change-Id: I978c352de68f6d8cecc76f4ae3c12daaf4be9ed6
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
 .../intel/haswell/native_raminit/Makefile.mk  |   2 +
 .../intel/haswell/native_raminit/init_mpll.c  | 210 ++++++++++++++++++
 .../haswell/native_raminit/io_comp_control.c  |  22 ++
 .../haswell/native_raminit/raminit_main.c     |   3 +-
 .../haswell/native_raminit/raminit_native.h   |  11 +
 .../intel/haswell/registers/mchbar.h          |   3 +
 6 files changed, 250 insertions(+), 1 deletion(-)
 create mode 100644 src/northbridge/intel/haswell/native_raminit/init_mpll.c
 create mode 100644 src/northbridge/intel/haswell/native_raminit/io_comp_control.c

diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.mk b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
index ebf7abc6ec..c125d84f0b 100644
--- a/src/northbridge/intel/haswell/native_raminit/Makefile.mk
+++ b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
@@ -1,5 +1,7 @@
 ## SPDX-License-Identifier: GPL-2.0-or-later
 
+romstage-y += init_mpll.c
+romstage-y += io_comp_control.c
 romstage-y += raminit_main.c
 romstage-y += raminit_native.c
 romstage-y += spd_bitmunching.c
diff --git a/src/northbridge/intel/haswell/native_raminit/init_mpll.c b/src/northbridge/intel/haswell/native_raminit/init_mpll.c
new file mode 100644
index 0000000000..1f3f2c29a9
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/init_mpll.c
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <commonlib/bsd/clamp.h>
+#include <console/console.h>
+#include <delay.h>
+#include <device/pci_ops.h>
+#include <northbridge/intel/haswell/haswell.h>
+#include <types.h>
+
+#include "raminit_native.h"
+
+static uint32_t get_mem_multiplier(const struct sysinfo *ctrl)
+{
+	const uint32_t mult = NS2MHZ_DIV256 / (ctrl->tCK * ctrl->base_freq);
+
+	if (ctrl->base_freq == 100)
+		return clamp_u32(7, mult, 12);
+
+	if (ctrl->base_freq == 133)
+		return clamp_u32(3, mult, 10);
+
+	die("Unsupported base frequency\n");
+}
+
+static void normalize_tck(struct sysinfo *ctrl, const bool pll_ref100)
+{
+	/** TODO: Haswell supports up to DDR3-2600 **/
+	if (ctrl->tCK <= TCK_1200MHZ) {
+		ctrl->tCK = TCK_1200MHZ;
+		ctrl->base_freq = 133;
+		ctrl->mem_clock_mhz = 1200;
+
+	} else if (ctrl->tCK <= TCK_1100MHZ) {
+		ctrl->tCK = TCK_1100MHZ;
+		ctrl->base_freq = 100;
+		ctrl->mem_clock_mhz = 1100;
+
+	} else if (ctrl->tCK <= TCK_1066MHZ) {
+		ctrl->tCK = TCK_1066MHZ;
+		ctrl->base_freq = 133;
+		ctrl->mem_clock_mhz = 1066;
+
+	} else if (ctrl->tCK <= TCK_1000MHZ) {
+		ctrl->tCK = TCK_1000MHZ;
+		ctrl->base_freq = 100;
+		ctrl->mem_clock_mhz = 1000;
+
+	} else if (ctrl->tCK <= TCK_933MHZ) {
+		ctrl->tCK = TCK_933MHZ;
+		ctrl->base_freq = 133;
+		ctrl->mem_clock_mhz = 933;
+
+	} else if (ctrl->tCK <= TCK_900MHZ) {
+		ctrl->tCK = TCK_900MHZ;
+		ctrl->base_freq = 100;
+		ctrl->mem_clock_mhz = 900;
+
+	} else if (ctrl->tCK <= TCK_800MHZ) {
+		ctrl->tCK = TCK_800MHZ;
+		ctrl->base_freq = 133;
+		ctrl->mem_clock_mhz = 800;
+
+	} else if (ctrl->tCK <= TCK_700MHZ) {
+		ctrl->tCK = TCK_700MHZ;
+		ctrl->base_freq = 100;
+		ctrl->mem_clock_mhz = 700;
+
+	} else if (ctrl->tCK <= TCK_666MHZ) {
+		ctrl->tCK = TCK_666MHZ;
+		ctrl->base_freq = 133;
+		ctrl->mem_clock_mhz = 666;
+
+	} else if (ctrl->tCK <= TCK_533MHZ) {
+		ctrl->tCK = TCK_533MHZ;
+		ctrl->base_freq = 133;
+		ctrl->mem_clock_mhz = 533;
+
+	} else if (ctrl->tCK <= TCK_400MHZ) {
+		ctrl->tCK = TCK_400MHZ;
+		ctrl->base_freq = 133;
+		ctrl->mem_clock_mhz = 400;
+
+	} else {
+		ctrl->tCK = 0;
+		ctrl->base_freq = 1;
+		ctrl->mem_clock_mhz = 0;
+		return;
+	}
+	if (!pll_ref100 && ctrl->base_freq == 100) {
+		/* Skip unsupported frequency */
+		ctrl->tCK++;
+		normalize_tck(ctrl, pll_ref100);
+	}
+}
+
+#define MIN_CAS	4
+#define MAX_CAS	24
+
+static uint8_t find_compatible_cas(struct sysinfo *ctrl)
+{
+	printk(RAM_DEBUG, "With tCK %u, try CAS: ", ctrl->tCK);
+	const uint8_t cas_lower = MAX(MIN_CAS, DIV_ROUND_UP(ctrl->tAA, ctrl->tCK));
+	const uint8_t cas_upper = MIN(MAX_CAS, 19); /* JEDEC MR0 limit */
+
+	if (!(ctrl->cas_supported >> (cas_lower - MIN_CAS))) {
+		printk(RAM_DEBUG, "DIMMs do not support CAS >= %u\n", cas_lower);
+		ctrl->tCK++;
+		return 0;
+	}
+	for (uint8_t cas = cas_lower; cas <= cas_upper; cas++) {
+		printk(RAM_DEBUG, "%u ", cas);
+		if (ctrl->cas_supported & BIT(cas - MIN_CAS)) {
+			printk(RAM_DEBUG, "OK\n");
+			return cas;
+		}
+	}
+	return 0;
+}
+
+static enum raminit_status find_cas_tck(struct sysinfo *ctrl)
+{
+	/** TODO: Honor all possible PLL_REF100_CFG values **/
+	uint8_t pll_ref100 = (pci_read_config32(HOST_BRIDGE, CAPID0_B) >> 21) & 0x7;
+	printk(RAM_DEBUG, "PLL_REF100_CFG value: 0x%x\n", pll_ref100);
+	printk(RAM_DEBUG, "100MHz reference clock support: %s\n", pll_ref100 ? "yes" : "no");
+
+	uint8_t selected_cas;
+	while (true) {
+		/* Round tCK up so that it is a multiple of either 133 or 100 MHz */
+		normalize_tck(ctrl, pll_ref100);
+		if (!ctrl->tCK) {
+			printk(BIOS_ERR, "Couldn't find compatible clock / CAS settings\n");
+			return RAMINIT_STATUS_MPLL_INIT_FAILURE;
+		}
+		selected_cas = find_compatible_cas(ctrl);
+		if (selected_cas)
+			break;
+
+		ctrl->tCK++;
+	}
+	printk(BIOS_DEBUG, "Found compatible clock / CAS settings\n");
+	printk(BIOS_DEBUG, "Selected DRAM frequency: %u MHz\n", NS2MHZ_DIV256 / ctrl->tCK);
+	printk(BIOS_DEBUG, "Selected CAS latency   : %uT\n", selected_cas);
+	ctrl->multiplier = get_mem_multiplier(ctrl);
+	return RAMINIT_STATUS_SUCCESS;
+}
+
+enum raminit_status initialise_mpll(struct sysinfo *ctrl)
+{
+	if (ctrl->tCK > TCK_400MHZ) {
+		printk(BIOS_ERR, "tCK is too slow. Increasing to 400 MHz as last resort\n");
+		ctrl->tCK = TCK_400MHZ;
+	}
+	while (true) {
+		if (!ctrl->qclkps) {
+			const enum raminit_status status = find_cas_tck(ctrl);
+			if (status)
+				return status;
+		}
+
+		/*
+		 * Unlike previous generations, Haswell's MPLL won't shut down if the
+		 * requested frequency isn't supported. But we cannot reinitialize it.
+		 * Another different thing: MPLL registers are 4-bit instead of 8-bit.
+		 */
+
+		/** FIXME: Obtain current clock frequency if we want to skip this **/
+		//if (mchbar_read32(MC_BIOS_DATA) != 0)
+		//	break;
+
+		uint32_t mc_bios_req = ctrl->multiplier;
+		if (ctrl->base_freq == 100) {
+			/* Use 100 MHz reference clock */
+			mc_bios_req |= BIT(4);
+		}
+		mc_bios_req |= BIT(31);
+		printk(RAM_DEBUG, "MC_BIOS_REQ = 0x%08x\n", mc_bios_req);
+		printk(BIOS_DEBUG, "MPLL busy... ");
+		mchbar_write32(MC_BIOS_REQ, mc_bios_req);
+
+		for (unsigned int i = 0; i <= 5000; i++) {
+			if (!(mchbar_read32(MC_BIOS_REQ) & BIT(31))) {
+				printk(BIOS_DEBUG, "done in %u us\n", i);
+				break;
+			}
+			udelay(1);
+		}
+		if (mchbar_read32(MC_BIOS_REQ) & BIT(31))
+			printk(BIOS_DEBUG, "did not lock\n");
+
+		/* Verify locked frequency */
+		const uint32_t mc_bios_data = mchbar_read32(MC_BIOS_DATA);
+		printk(RAM_DEBUG, "MC_BIOS_DATA = 0x%08x\n", mc_bios_data);
+		if ((mc_bios_data & 0xf) >= ctrl->multiplier)
+			break;
+
+		printk(BIOS_DEBUG, "Retrying at a lower frequency\n\n");
+		ctrl->tCK++;
+	}
+	if (!ctrl->mem_clock_mhz) {
+		printk(BIOS_ERR, "Could not program MPLL frequency\n");
+		return RAMINIT_STATUS_MPLL_INIT_FAILURE;
+	}
+	printk(BIOS_DEBUG, "MPLL frequency is set to: %u MHz ", ctrl->mem_clock_mhz);
+	ctrl->mem_clock_fs = 1000000000 / ctrl->mem_clock_mhz;
+	printk(BIOS_DEBUG, "(period: %u femtoseconds)\n", ctrl->mem_clock_fs);
+	ctrl->qclkps = ctrl->mem_clock_fs / 2000;
+	printk(BIOS_DEBUG, "Quadrature clock period: %u picoseconds\n", ctrl->qclkps);
+	return wait_for_first_rcomp();
+}
diff --git a/src/northbridge/intel/haswell/native_raminit/io_comp_control.c b/src/northbridge/intel/haswell/native_raminit/io_comp_control.c
new file mode 100644
index 0000000000..d45b608dd3
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/io_comp_control.c
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <commonlib/bsd/clamp.h>
+#include <console/console.h>
+#include <northbridge/intel/haswell/haswell.h>
+#include <timer.h>
+#include <types.h>
+
+#include "raminit_native.h"
+
+enum raminit_status wait_for_first_rcomp(void)
+{
+	struct stopwatch timer;
+	stopwatch_init_msecs_expire(&timer, 2000);
+	do {
+		if (mchbar_read32(RCOMP_TIMER) & BIT(16))
+			return RAMINIT_STATUS_SUCCESS;
+
+	} while (!stopwatch_expired(&timer));
+	printk(BIOS_ERR, "Timed out waiting for RCOMP to complete\n");
+	return RAMINIT_STATUS_POLL_TIMEOUT;
+}
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_main.c b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
index 19ec5859ac..bf745e943f 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_main.c
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
@@ -19,7 +19,8 @@ struct task_entry {
 };
 
 static const struct task_entry cold_boot[] = {
-	{ collect_spd_info,           true, "PROCSPD",    },
+	{ collect_spd_info,                                       true, "PROCSPD",    },
+	{ initialise_mpll,                                        true, "INITMPLL",   },
 };
 
 /* Return a generic stepping value to make stepping checks simpler */
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_native.h b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
index 8078c9c386..15a1550424 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -24,6 +24,8 @@ enum raminit_status {
 	RAMINIT_STATUS_SUCCESS = 0,
 	RAMINIT_STATUS_NO_MEMORY_INSTALLED,
 	RAMINIT_STATUS_UNSUPPORTED_MEMORY,
+	RAMINIT_STATUS_MPLL_INIT_FAILURE,
+	RAMINIT_STATUS_POLL_TIMEOUT,
 	RAMINIT_STATUS_UNSPECIFIED_ERROR, /** TODO: Deprecated in favor of specific values **/
 };
 
@@ -83,10 +85,19 @@ struct sysinfo {
 	uint8_t rankmap[NUM_CHANNELS];
 	uint8_t rank_mirrored[NUM_CHANNELS];
 	uint32_t channel_size_mb[NUM_CHANNELS];
+
+	uint8_t base_freq;		/* Memory base frequency, either 100 or 133 MHz */
+	uint32_t multiplier;
+	uint32_t mem_clock_mhz;
+	uint32_t mem_clock_fs;		/* Memory clock period in femtoseconds */
+	uint32_t qclkps;		/* Quadrature clock period in picoseconds */
 };
 
 void raminit_main(enum raminit_boot_mode bootmode);
 
 enum raminit_status collect_spd_info(struct sysinfo *ctrl);
+enum raminit_status initialise_mpll(struct sysinfo *ctrl);
+
+enum raminit_status wait_for_first_rcomp(void);
 
 #endif
diff --git a/src/northbridge/intel/haswell/registers/mchbar.h b/src/northbridge/intel/haswell/registers/mchbar.h
index 5610e7089a..45f8174995 100644
--- a/src/northbridge/intel/haswell/registers/mchbar.h
+++ b/src/northbridge/intel/haswell/registers/mchbar.h
@@ -13,6 +13,8 @@
 #define MC_INIT_STATE_G		0x5030
 #define MRC_REVISION		0x5034 /* MRC Revision */
 
+#define RCOMP_TIMER		0x5084
+
 #define MC_LOCK			0x50fc /* Memory Controller Lock register */
 
 #define GFXVTBAR		0x5400 /* Base address for IGD */
@@ -61,6 +63,7 @@
 
 #define BIOS_RESET_CPL		0x5da8 /* 8-bit */
 
+#define MC_BIOS_REQ		0x5e00 /* Memory frequency request register */
 #define MC_BIOS_DATA		0x5e04 /* Miscellaneous information for BIOS */
 #define SAPMCTL			0x5f00
 
-- 
2.39.5