rebase haswell nri patches to coreboot/default

scrap the haswell tree. handle mrc- and nri-based haswell
configurations from the same default coreboot tree

this means that it will now be possible to use it on the
dell 9020 sff/mt ports

only txtmode configs were available before, on libremrc
setups for w541/t440p. because the grub payload was added,
i've now added the corebootfb configs.

the libremrc configs have been re-done, by re-copying the
mrc ones and then turning off mrc, instead enabling nri

Signed-off-by: Leah Rowe <leah@libreboot.org>
This commit is contained in:
Leah Rowe
2024-04-07 23:34:17 +01:00
parent 6da91df6b9
commit a43d2e60e8
38 changed files with 1776 additions and 4039 deletions
@@ -0,0 +1,348 @@
From f3c5218c6d7e211c9e8425e5e71cda76a7de1fad Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sat, 7 May 2022 14:36:10 +0200
Subject: [PATCH 01/15] 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..2faa183724
--- /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/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..7e96c08938
--- /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/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 b06330c4eb..09545422c0 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_main.c
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
@@ -20,7 +20,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 1a0793947e..a54581abc7 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -23,6 +23,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 **/
};
@@ -82,10 +84,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.2
@@ -0,0 +1,249 @@
From e344871e519441d1f7bc6891ca1eda911e3e4bf5 Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sat, 7 May 2022 16:29:55 +0200
Subject: [PATCH 02/15] haswell NRI: Post-process selected timings
Once the MPLL has been initialised, convert the timings from the SPD to
be in DCLKs, which is what the hardware expects. In addition, calculate
the values for tREFI and tXP.
Change-Id: Id02caf858f75b9e08016762b3aefda282b274386
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
.../intel/haswell/native_raminit/Makefile.mk | 1 +
.../haswell/native_raminit/lookup_timings.c | 62 +++++++++++
.../haswell/native_raminit/raminit_main.c | 1 +
.../haswell/native_raminit/raminit_native.h | 8 ++
.../haswell/native_raminit/spd_bitmunching.c | 100 ++++++++++++++++++
5 files changed, 172 insertions(+)
create mode 100644 src/northbridge/intel/haswell/native_raminit/lookup_timings.c
diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.mk b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
index c125d84f0b..2769e0bbb4 100644
--- a/src/northbridge/intel/haswell/native_raminit/Makefile.mk
+++ b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
@@ -1,5 +1,6 @@
## SPDX-License-Identifier: GPL-2.0-or-later
+romstage-y += lookup_timings.c
romstage-y += init_mpll.c
romstage-y += io_comp_control.c
romstage-y += raminit_main.c
diff --git a/src/northbridge/intel/haswell/native_raminit/lookup_timings.c b/src/northbridge/intel/haswell/native_raminit/lookup_timings.c
new file mode 100644
index 0000000000..038686c844
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/lookup_timings.c
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <commonlib/clamp.h>
+#include <types.h>
+
+#include "raminit_native.h"
+
+struct timing_lookup {
+ uint32_t clock;
+ uint32_t value;
+};
+
+static uint32_t lookup_timing(
+ const uint32_t mem_clock_mhz,
+ const struct timing_lookup *const lookup,
+ const size_t length)
+{
+ /* Fall back to the last index */
+ size_t i;
+ for (i = 0; i < length - 1; i++) {
+ /* Account for imprecise frequency values */
+ if ((mem_clock_mhz - 5) <= lookup[i].clock)
+ break;
+ }
+ return lookup[i].value;
+}
+
+static const uint32_t fmax = UINT32_MAX;
+
+uint8_t get_tCWL(const uint32_t mem_clock_mhz)
+{
+ const struct timing_lookup lut[] = {
+ { 400, 5 },
+ { 533, 6 },
+ { 666, 7 },
+ { 800, 8 },
+ { 933, 9 },
+ { 1066, 10 },
+ { 1200, 11 },
+ { fmax, 12 },
+ };
+ return lookup_timing(mem_clock_mhz, lut, ARRAY_SIZE(lut));
+}
+
+/* tREFI = 7800 ns * DDR MHz */
+uint32_t get_tREFI(const uint32_t mem_clock_mhz)
+{
+ return (mem_clock_mhz * 7800) / 1000;
+}
+
+uint32_t get_tXP(const uint32_t mem_clock_mhz)
+{
+ const struct timing_lookup lut[] = {
+ { 400, 3 },
+ { 666, 4 },
+ { 800, 5 },
+ { 933, 6 },
+ { 1066, 7 },
+ { fmax, 8 },
+ };
+ return lookup_timing(mem_clock_mhz, lut, ARRAY_SIZE(lut));
+}
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_main.c b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
index 09545422c0..5f2be980d4 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_main.c
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
@@ -22,6 +22,7 @@ struct task_entry {
static const struct task_entry cold_boot[] = {
{ collect_spd_info, true, "PROCSPD", },
{ initialise_mpll, true, "INITMPLL", },
+ { convert_timings, true, "CONVTIM", },
};
/* 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 a54581abc7..01e5ed1bd6 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -78,6 +78,9 @@ struct sysinfo {
uint32_t tCWL;
uint32_t tCMD;
+ uint32_t tREFI;
+ uint32_t tXP;
+
uint8_t lanes; /* 8 or 9 */
uint8_t chanmap;
uint8_t dpc[NUM_CHANNELS]; /* DIMMs per channel */
@@ -96,7 +99,12 @@ 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 convert_timings(struct sysinfo *ctrl);
enum raminit_status wait_for_first_rcomp(void);
+uint8_t get_tCWL(uint32_t mem_clock_mhz);
+uint32_t get_tREFI(uint32_t mem_clock_mhz);
+uint32_t get_tXP(uint32_t mem_clock_mhz);
+
#endif
diff --git a/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c b/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c
index 2dab8504c4..9ba695fb64 100644
--- a/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c
+++ b/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c
@@ -204,3 +204,103 @@ enum raminit_status collect_spd_info(struct sysinfo *ctrl)
get_spd_data(ctrl);
return find_common_spd_parameters(ctrl);
}
+
+#define MIN_CWL 5
+#define MAX_CWL 12
+
+/* Except for tCK, hardware expects all timing values in DCLKs, not nanoseconds */
+enum raminit_status convert_timings(struct sysinfo *ctrl)
+{
+ /*
+ * Obtain all required timing values, in DCLKs.
+ */
+
+ /* Convert primary timings from nanoseconds to DCLKs */
+ ctrl->tAA = DIV_ROUND_UP(ctrl->tAA, ctrl->tCK);
+ ctrl->tWR = DIV_ROUND_UP(ctrl->tWR, ctrl->tCK);
+ ctrl->tRCD = DIV_ROUND_UP(ctrl->tRCD, ctrl->tCK);
+ ctrl->tRRD = DIV_ROUND_UP(ctrl->tRRD, ctrl->tCK);
+ ctrl->tRP = DIV_ROUND_UP(ctrl->tRP, ctrl->tCK);
+ ctrl->tRAS = DIV_ROUND_UP(ctrl->tRAS, ctrl->tCK);
+ ctrl->tRC = DIV_ROUND_UP(ctrl->tRC, ctrl->tCK);
+ ctrl->tRFC = DIV_ROUND_UP(ctrl->tRFC, ctrl->tCK);
+ ctrl->tWTR = DIV_ROUND_UP(ctrl->tWTR, ctrl->tCK);
+ ctrl->tRTP = DIV_ROUND_UP(ctrl->tRTP, ctrl->tCK);
+ ctrl->tFAW = DIV_ROUND_UP(ctrl->tFAW, ctrl->tCK);
+ ctrl->tCWL = DIV_ROUND_UP(ctrl->tCWL, ctrl->tCK);
+ ctrl->tCMD = DIV_ROUND_UP(ctrl->tCMD, ctrl->tCK);
+
+ /* Constrain primary timings to hardware limits */
+ /** TODO: complain when clamping? **/
+ ctrl->tAA = clamp_u32(4, ctrl->tAA, 24);
+ ctrl->tWR = clamp_u32(5, ctrl->tWR, 16);
+ ctrl->tRCD = clamp_u32(4, ctrl->tRCD, 20);
+ ctrl->tRRD = clamp_u32(4, ctrl->tRRD, 65535);
+ ctrl->tRP = clamp_u32(4, ctrl->tRP, 15);
+ ctrl->tRAS = clamp_u32(10, ctrl->tRAS, 40);
+ ctrl->tRC = clamp_u32(1, ctrl->tRC, 4095);
+ ctrl->tRFC = clamp_u32(1, ctrl->tRFC, 511);
+ ctrl->tWTR = clamp_u32(4, ctrl->tWTR, 10);
+ ctrl->tRTP = clamp_u32(4, ctrl->tRTP, 15);
+ ctrl->tFAW = clamp_u32(10, ctrl->tFAW, 54);
+
+ /** TODO: Honor tREFI from XMP **/
+ ctrl->tREFI = get_tREFI(ctrl->mem_clock_mhz);
+ ctrl->tXP = get_tXP(ctrl->mem_clock_mhz);
+
+ /*
+ * Check some values, and adjust them if necessary.
+ */
+
+ /* If tWR cannot be written into DDR3 MR0, adjust it */
+ switch (ctrl->tWR) {
+ case 9:
+ case 11:
+ case 13:
+ case 15:
+ ctrl->tWR++;
+ }
+
+ /* If tCWL is not supported or unspecified, look up a reasonable default */
+ if (ctrl->tCWL < MIN_CWL || ctrl->tCWL > MAX_CWL)
+ ctrl->tCWL = get_tCWL(ctrl->mem_clock_mhz);
+
+ /* This is needed to support ODT properly on 2DPC */
+ if (ctrl->tAA - ctrl->tCWL > 4)
+ ctrl->tCWL = ctrl->tAA - 4;
+
+ /* If tCMD is invalid, use a guesstimate default */
+ if (!ctrl->tCMD) {
+ ctrl->tCMD = MAX(ctrl->dpc[0], ctrl->dpc[1]);
+ printk(RAM_DEBUG, "tCMD was zero, picking a guesstimate value\n");
+ }
+ ctrl->tCMD = clamp_u32(1, ctrl->tCMD, 3);
+
+ /*
+ * Print final timings.
+ */
+
+ /* tCK is special */
+ printk(BIOS_DEBUG, "Selected tCK : %u ns\n", ctrl->tCK / 256);
+
+ /* Primary timings */
+ printk(BIOS_DEBUG, "Selected tAA : %uT\n", ctrl->tAA);
+ printk(BIOS_DEBUG, "Selected tWR : %uT\n", ctrl->tWR);
+ printk(BIOS_DEBUG, "Selected tRCD : %uT\n", ctrl->tRCD);
+ printk(BIOS_DEBUG, "Selected tRRD : %uT\n", ctrl->tRRD);
+ printk(BIOS_DEBUG, "Selected tRP : %uT\n", ctrl->tRP);
+ printk(BIOS_DEBUG, "Selected tRAS : %uT\n", ctrl->tRAS);
+ printk(BIOS_DEBUG, "Selected tRC : %uT\n", ctrl->tRC);
+ printk(BIOS_DEBUG, "Selected tRFC : %uT\n", ctrl->tRFC);
+ printk(BIOS_DEBUG, "Selected tWTR : %uT\n", ctrl->tWTR);
+ printk(BIOS_DEBUG, "Selected tRTP : %uT\n", ctrl->tRTP);
+ printk(BIOS_DEBUG, "Selected tFAW : %uT\n", ctrl->tFAW);
+ printk(BIOS_DEBUG, "Selected tCWL : %uT\n", ctrl->tCWL);
+ printk(BIOS_DEBUG, "Selected tCMD : %uT\n", ctrl->tCMD);
+
+ /* Derived timings */
+ printk(BIOS_DEBUG, "Selected tREFI : %uT\n", ctrl->tREFI);
+ printk(BIOS_DEBUG, "Selected tXP : %uT\n", ctrl->tXP);
+
+ return RAMINIT_STATUS_SUCCESS;
+}
--
2.39.2
@@ -0,0 +1,541 @@
From 136a9f660bda9cb515cbbe1e859bc68e7aa04d92 Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sat, 7 May 2022 20:59:58 +0200
Subject: [PATCH 04/15] haswell NRI: Add timings/refresh programming
Program the registers with timing and refresh parameters.
Change-Id: Id2ea339d2c9ea8b56c71d6e88ec76949653ff5c2
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
.../haswell/native_raminit/lookup_timings.c | 102 ++++++++
.../haswell/native_raminit/raminit_native.h | 14 ++
.../haswell/native_raminit/reg_structs.h | 93 +++++++
.../haswell/native_raminit/timings_refresh.c | 233 +++++++++++++++++-
.../intel/haswell/registers/mchbar.h | 12 +
5 files changed, 452 insertions(+), 2 deletions(-)
diff --git a/src/northbridge/intel/haswell/native_raminit/lookup_timings.c b/src/northbridge/intel/haswell/native_raminit/lookup_timings.c
index 038686c844..afe2c615d2 100644
--- a/src/northbridge/intel/haswell/native_raminit/lookup_timings.c
+++ b/src/northbridge/intel/haswell/native_raminit/lookup_timings.c
@@ -60,3 +60,105 @@ uint32_t get_tXP(const uint32_t mem_clock_mhz)
};
return lookup_timing(mem_clock_mhz, lut, ARRAY_SIZE(lut));
}
+
+static uint32_t get_lpddr_tCKE(const uint32_t mem_clock_mhz)
+{
+ const struct timing_lookup lut[] = {
+ { 533, 4 },
+ { 666, 5 },
+ { fmax, 6 },
+ };
+ return lookup_timing(mem_clock_mhz, lut, ARRAY_SIZE(lut));
+}
+
+static uint32_t get_ddr_tCKE(const uint32_t mem_clock_mhz)
+{
+ const struct timing_lookup lut[] = {
+ { 533, 3 },
+ { 800, 4 },
+ { 933, 5 },
+ { 1200, 6 },
+ { fmax, 7 },
+ };
+ return lookup_timing(mem_clock_mhz, lut, ARRAY_SIZE(lut));
+}
+
+uint32_t get_tCKE(const uint32_t mem_clock_mhz, const bool lpddr)
+{
+ return lpddr ? get_lpddr_tCKE(mem_clock_mhz) : get_ddr_tCKE(mem_clock_mhz);
+}
+
+uint32_t get_tXPDLL(const uint32_t mem_clock_mhz)
+{
+ const struct timing_lookup lut[] = {
+ { 400, 10 },
+ { 533, 13 },
+ { 666, 16 },
+ { 800, 20 },
+ { 933, 23 },
+ { 1066, 26 },
+ { 1200, 29 },
+ { fmax, 32 },
+ };
+ return lookup_timing(mem_clock_mhz, lut, ARRAY_SIZE(lut));
+}
+
+uint32_t get_tAONPD(const uint32_t mem_clock_mhz)
+{
+ const struct timing_lookup lut[] = {
+ { 400, 4 },
+ { 533, 5 },
+ { 666, 6 },
+ { 800, 7 }, /* SNB had 8 */
+ { 933, 8 },
+ { 1066, 10 },
+ { 1200, 11 },
+ { fmax, 12 },
+ };
+ return lookup_timing(mem_clock_mhz, lut, ARRAY_SIZE(lut));
+}
+
+uint32_t get_tMOD(const uint32_t mem_clock_mhz)
+{
+ const struct timing_lookup lut[] = {
+ { 800, 12 },
+ { 933, 14 },
+ { 1066, 16 },
+ { 1200, 18 },
+ { fmax, 20 },
+ };
+ return lookup_timing(mem_clock_mhz, lut, ARRAY_SIZE(lut));
+}
+
+uint32_t get_tXS_offset(const uint32_t mem_clock_mhz)
+{
+ return DIV_ROUND_UP(mem_clock_mhz, 100);
+}
+
+static uint32_t get_lpddr_tZQOPER(const uint32_t mem_clock_mhz)
+{
+ return (mem_clock_mhz * 360) / 1000;
+}
+
+static uint32_t get_ddr_tZQOPER(const uint32_t mem_clock_mhz)
+{
+ const struct timing_lookup lut[] = {
+ { 800, 256 },
+ { 933, 299 },
+ { 1066, 342 },
+ { 1200, 384 },
+ { fmax, 427 },
+ };
+ return lookup_timing(mem_clock_mhz, lut, ARRAY_SIZE(lut));
+}
+
+/* tZQOPER defines the period required for ZQCL after SR exit */
+uint32_t get_tZQOPER(const uint32_t mem_clock_mhz, const bool lpddr)
+{
+ return lpddr ? get_lpddr_tZQOPER(mem_clock_mhz) : get_ddr_tZQOPER(mem_clock_mhz);
+}
+
+uint32_t get_tZQCS(const uint32_t mem_clock_mhz, const bool lpddr)
+{
+ return DIV_ROUND_UP(get_tZQOPER(mem_clock_mhz, lpddr), 4);
+}
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_native.h b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
index aa86b9aa39..cd1f2eb2a5 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -155,6 +155,12 @@ struct sysinfo {
uint8_t cke_cmd_pi_code[NUM_CHANNELS][NUM_GROUPS];
uint8_t cmd_north_pi_code[NUM_CHANNELS][NUM_GROUPS];
uint8_t cmd_south_pi_code[NUM_CHANNELS][NUM_GROUPS];
+
+ union tc_bank_reg tc_bank[NUM_CHANNELS];
+ union tc_bank_rank_a_reg tc_bankrank_a[NUM_CHANNELS];
+ union tc_bank_rank_b_reg tc_bankrank_b[NUM_CHANNELS];
+ union tc_bank_rank_c_reg tc_bankrank_c[NUM_CHANNELS];
+ union tc_bank_rank_d_reg tc_bankrank_d[NUM_CHANNELS];
};
static inline bool is_hsw_ult(void)
@@ -200,6 +206,14 @@ enum raminit_status configure_mc(struct sysinfo *ctrl);
void configure_timings(struct sysinfo *ctrl);
void configure_refresh(struct sysinfo *ctrl);
+uint32_t get_tCKE(uint32_t mem_clock_mhz, bool lpddr);
+uint32_t get_tXPDLL(uint32_t mem_clock_mhz);
+uint32_t get_tAONPD(uint32_t mem_clock_mhz);
+uint32_t get_tMOD(uint32_t mem_clock_mhz);
+uint32_t get_tXS_offset(uint32_t mem_clock_mhz);
+uint32_t get_tZQOPER(uint32_t mem_clock_mhz, bool lpddr);
+uint32_t get_tZQCS(uint32_t mem_clock_mhz, bool lpddr);
+
enum raminit_status wait_for_first_rcomp(void);
uint8_t get_rx_bias(const struct sysinfo *ctrl);
diff --git a/src/northbridge/intel/haswell/native_raminit/reg_structs.h b/src/northbridge/intel/haswell/native_raminit/reg_structs.h
index d11cda4b3d..70487e1640 100644
--- a/src/northbridge/intel/haswell/native_raminit/reg_structs.h
+++ b/src/northbridge/intel/haswell/native_raminit/reg_structs.h
@@ -335,6 +335,99 @@ union mcscheds_cbit_reg {
uint32_t raw;
};
+union tc_bank_reg {
+ struct __packed {
+ uint32_t tRCD : 5; // Bits 4:0
+ uint32_t tRP : 5; // Bits 9:5
+ uint32_t tRAS : 6; // Bits 15:10
+ uint32_t tRDPRE : 4; // Bits 19:16
+ uint32_t tWRPRE : 6; // Bits 25:20
+ uint32_t tRRD : 4; // Bits 29:26
+ uint32_t tRPab_ext : 2; // Bits 31:30
+ };
+ uint32_t raw;
+};
+
+union tc_bank_rank_a_reg {
+ struct __packed {
+ uint32_t tCKE : 4; // Bits 3:0
+ uint32_t tFAW : 8; // Bits 11:4
+ uint32_t tRDRD_sr : 3; // Bits 14:12
+ uint32_t tRDRD_dr : 4; // Bits 18:15
+ uint32_t tRDRD_dd : 4; // Bits 22:19
+ uint32_t tRDPDEN : 5; // Bits 27:23
+ uint32_t : 1; // Bits 28:28
+ uint32_t cmd_3st_dis : 1; // Bits 29:29
+ uint32_t cmd_stretch : 2; // Bits 31:30
+ };
+ uint32_t raw;
+};
+
+union tc_bank_rank_b_reg {
+ struct __packed {
+ uint32_t tWRRD_sr : 6; // Bits 5:0
+ uint32_t tWRRD_dr : 4; // Bits 9:6
+ uint32_t tWRRD_dd : 4; // Bits 13:10
+ uint32_t tWRWR_sr : 3; // Bits 16:14
+ uint32_t tWRWR_dr : 4; // Bits 20:17
+ uint32_t tWRWR_dd : 4; // Bits 24:21
+ uint32_t tWRPDEN : 6; // Bits 30:25
+ uint32_t dec_wrd : 1; // Bits 31:31
+ };
+ uint32_t raw;
+};
+
+union tc_bank_rank_c_reg {
+ struct __packed {
+ uint32_t tXPDLL : 6; // Bits 5:0
+ uint32_t tXP : 4; // Bits 9:6
+ uint32_t tAONPD : 4; // Bits 13:10
+ uint32_t tRDWR_sr : 5; // Bits 18:14
+ uint32_t tRDWR_dr : 5; // Bits 23:19
+ uint32_t tRDWR_dd : 5; // Bits 28:24
+ uint32_t : 3; // Bits 31:29
+ };
+ uint32_t raw;
+};
+
+/* NOTE: Non-ULT only implements the lower 21 bits (odt_write_delay is 2 bits) */
+union tc_bank_rank_d_reg {
+ struct __packed {
+ uint32_t tAA : 5; // Bits 4:0
+ uint32_t tCWL : 5; // Bits 9:5
+ uint32_t tCPDED : 2; // Bits 11:10
+ uint32_t tPRPDEN : 2; // Bits 13:12
+ uint32_t odt_read_delay : 3; // Bits 16:14
+ uint32_t odt_read_duration : 2; // Bits 18:17
+ uint32_t odt_write_duration : 3; // Bits 21:19
+ uint32_t odt_write_delay : 3; // Bits 24:22
+ uint32_t odt_always_rank_0 : 1; // Bits 25:25
+ uint32_t cmd_delay : 2; // Bits 27:26
+ uint32_t : 4; // Bits 31:28
+ };
+ uint32_t raw;
+};
+
+union tc_rftp_reg {
+ struct __packed {
+ uint32_t tREFI : 16; // Bits 15:0
+ uint32_t tRFC : 9; // Bits 24:16
+ uint32_t tREFIx9 : 7; // Bits 31:25
+ };
+ uint32_t raw;
+};
+
+union tc_srftp_reg {
+ struct __packed {
+ uint32_t tXSDLL : 12; // Bits 11:0
+ uint32_t tXS_offset : 4; // Bits 15:12
+ uint32_t tZQOPER : 10; // Bits 25:16
+ uint32_t : 2; // Bits 27:26
+ uint32_t tMOD : 4; // Bits 31:28
+ };
+ uint32_t raw;
+};
+
union mcmain_command_rate_limit_reg {
struct __packed {
uint32_t enable_cmd_limit : 1; // Bits 0:0
diff --git a/src/northbridge/intel/haswell/native_raminit/timings_refresh.c b/src/northbridge/intel/haswell/native_raminit/timings_refresh.c
index a9d960f31b..20a05b359b 100644
--- a/src/northbridge/intel/haswell/native_raminit/timings_refresh.c
+++ b/src/northbridge/intel/haswell/native_raminit/timings_refresh.c
@@ -1,13 +1,242 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
+#include <assert.h>
+#include <commonlib/clamp.h>
+#include <console/console.h>
+#include <delay.h>
+#include <device/pci_ops.h>
+#include <northbridge/intel/haswell/haswell.h>
+
#include "raminit_native.h"
+#define BL 8 /* Burst length */
+#define tCCD 4
+#define tRPRE 1
+#define tWPRE 1
+#define tDLLK 512
+
+static bool is_sodimm(const enum spd_dimm_type_ddr3 type)
+{
+ return type == SPD_DDR3_DIMM_TYPE_SO_DIMM || type == SPD_DDR3_DIMM_TYPE_72B_SO_UDIMM;
+}
+
+static uint8_t get_odt_stretch(const struct sysinfo *const ctrl)
+{
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ /* Only stretch with 2 DIMMs per channel */
+ if (ctrl->dpc[channel] != 2)
+ continue;
+
+ const struct raminit_dimm_info *dimms = ctrl->dimms[channel];
+
+ /* Only stretch when using SO-DIMMs */
+ if (!is_sodimm(dimms[0].data.dimm_type) || !is_sodimm(dimms[1].data.dimm_type))
+ continue;
+
+ /* Only stretch with mismatched card types */
+ if (dimms[0].data.reference_card == dimms[1].data.reference_card)
+ continue;
+
+ /* Stretch if one SO-DIMM is card F */
+ for (uint8_t slot = 0; slot < NUM_SLOTS; slot++) {
+ if (dimms[slot].data.reference_card == 5)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static union tc_bank_reg make_tc_bank(struct sysinfo *const ctrl)
+{
+ return (union tc_bank_reg) {
+ .tRCD = ctrl->tRCD,
+ .tRP = ctrl->tRP,
+ .tRAS = ctrl->tRAS,
+ .tRDPRE = ctrl->tRTP,
+ .tWRPRE = 4 + ctrl->tCWL + ctrl->tWR,
+ .tRRD = ctrl->tRRD,
+ .tRPab_ext = 0, /** TODO: For LPDDR, this is ctrl->tRPab - ctrl->tRP **/
+ };
+}
+
+static union tc_bank_rank_a_reg make_tc_bankrank_a(struct sysinfo *ctrl, uint8_t odt_stretch)
+{
+ /* Use 3N mode for DDR during training, but always use 1N mode for LPDDR */
+ const uint32_t tCMD = ctrl->lpddr ? 0 : 3;
+ const uint32_t tRDRD_drdd = BL / 2 + 1 + tRPRE + odt_stretch + !!ctrl->lpddr;
+
+ return (union tc_bank_rank_a_reg) {
+ .tCKE = get_tCKE(ctrl->mem_clock_mhz, ctrl->lpddr),
+ .tFAW = ctrl->tFAW,
+ .tRDRD_sr = tCCD,
+ .tRDRD_dr = tRDRD_drdd,
+ .tRDRD_dd = tRDRD_drdd,
+ .tRDPDEN = ctrl->tAA + BL / 2 + 1,
+ .cmd_3st_dis = 1, /* Disable command tri-state before training */
+ .cmd_stretch = tCMD,
+ };
+}
+
+static union tc_bank_rank_b_reg make_tc_bankrank_b(struct sysinfo *const ctrl)
+{
+ const uint8_t tWRRD_drdd = ctrl->tCWL - ctrl->tAA + BL / 2 + 2 + tRPRE;
+ const uint8_t tWRWR_drdd = BL / 2 + 2 + tWPRE;
+
+ return (union tc_bank_rank_b_reg) {
+ .tWRRD_sr = tCCD + ctrl->tCWL + ctrl->tWTR + 2,
+ .tWRRD_dr = ctrl->lpddr ? 8 : tWRRD_drdd,
+ .tWRRD_dd = ctrl->lpddr ? 8 : tWRRD_drdd,
+ .tWRWR_sr = tCCD,
+ .tWRWR_dr = tWRWR_drdd,
+ .tWRWR_dd = tWRWR_drdd,
+ .tWRPDEN = ctrl->tWR + ctrl->tCWL + BL / 2,
+ .dec_wrd = ctrl->tCWL >= 6,
+ };
+}
+
+static uint32_t get_tRDWR_sr(const struct sysinfo *ctrl)
+{
+ if (ctrl->lpddr) {
+ const uint32_t tdqsck_max = DIV_ROUND_UP(5500, ctrl->qclkps * 2);
+ return ctrl->tAA - ctrl->tCWL + tCCD + tWPRE + tdqsck_max + 1;
+ } else {
+ const bool fast_clock = ctrl->mem_clock_mhz > 666;
+ return ctrl->tAA - ctrl->tCWL + tCCD + tWPRE + 2 + fast_clock;
+ }
+}
+
+static union tc_bank_rank_c_reg make_tc_bankrank_c(struct sysinfo *ctrl, uint8_t odt_stretch)
+{
+ const uint32_t tRDWR_sr = get_tRDWR_sr(ctrl);
+ const uint32_t tRDWR_drdd = tRDWR_sr + odt_stretch;
+
+ return (union tc_bank_rank_c_reg) {
+ .tXPDLL = get_tXPDLL(ctrl->mem_clock_mhz),
+ .tXP = MAX(ctrl->tXP, 7), /* Use a higher tXP for training */
+ .tAONPD = get_tAONPD(ctrl->mem_clock_mhz),
+ .tRDWR_sr = tRDWR_sr,
+ .tRDWR_dr = tRDWR_drdd,
+ .tRDWR_dd = tRDWR_drdd,
+ };
+}
+
+static union tc_bank_rank_d_reg make_tc_bankrank_d(struct sysinfo *ctrl, uint8_t odt_stretch)
+{
+ const uint32_t odt_rd_delay = ctrl->tAA - ctrl->tCWL;
+ if (!ctrl->lpddr) {
+ return (union tc_bank_rank_d_reg) {
+ .tAA = ctrl->tAA,
+ .tCWL = ctrl->tCWL,
+ .tCPDED = 1,
+ .tPRPDEN = 1,
+ .odt_read_delay = odt_rd_delay,
+ .odt_read_duration = odt_stretch,
+ };
+ }
+
+ /* tCWL has 1 extra clock because of tDQSS, subtract it here */
+ const uint32_t tCWL_lpddr = ctrl->tCWL - 1;
+ const uint32_t odt_wr_delay = tCWL_lpddr + DIV_ROUND_UP(3500, ctrl->qclkps * 2);
+ const uint32_t odt_wr_duration = DIV_ROUND_UP(3500 - 1750, ctrl->qclkps * 2) + 1;
+
+ return (union tc_bank_rank_d_reg) {
+ .tAA = ctrl->tAA,
+ .tCWL = tCWL_lpddr,
+ .tCPDED = 2, /* Required by JEDEC LPDDR3 spec */
+ .tPRPDEN = 1,
+ .odt_read_delay = odt_rd_delay,
+ .odt_read_duration = odt_stretch,
+ .odt_write_delay = odt_wr_delay,
+ .odt_write_duration = odt_wr_duration,
+ .odt_always_rank_0 = ctrl->lpddr_dram_odt
+ };
+}
+
+/* ZQCS period values, in (tREFI * 128) units */
+#define ZQCS_PERIOD_DDR3 128 /* tREFI * 128 = 7.8 us * 128 = 1ms */
+#define ZQCS_PERIOD_LPDDR3 256 /* tREFI * 128 = 3.9 us * 128 = 0.5ms */
+
+static uint32_t make_tc_zqcal(const struct sysinfo *const ctrl)
+{
+ const uint32_t zqcs_period = ctrl->lpddr ? ZQCS_PERIOD_LPDDR3 : ZQCS_PERIOD_DDR3;
+ const uint32_t tZQCS = get_tZQCS(ctrl->mem_clock_mhz, ctrl->lpddr);
+ return tZQCS << (is_hsw_ult() ? 10 : 8) | zqcs_period;
+}
+
+static union tc_rftp_reg make_tc_rftp(const struct sysinfo *const ctrl)
+{
+ /*
+ * The tREFIx9 field should be programmed to minimum of 8.9 * tREFI (to allow
+ * for possible delays from ZQ or isoc) and tRASmax (70us) divided by 1024.
+ */
+ return (union tc_rftp_reg) {
+ .tREFI = ctrl->tREFI,
+ .tRFC = ctrl->tRFC,
+ .tREFIx9 = ctrl->tREFI * 89 / 10240,
+ };
+}
+
+static union tc_srftp_reg make_tc_srftp(const struct sysinfo *const ctrl)
+{
+ return (union tc_srftp_reg) {
+ .tXSDLL = tDLLK,
+ .tXS_offset = get_tXS_offset(ctrl->mem_clock_mhz),
+ .tZQOPER = get_tZQOPER(ctrl->mem_clock_mhz, ctrl->lpddr),
+ .tMOD = get_tMOD(ctrl->mem_clock_mhz) - 8,
+ };
+}
+
void configure_timings(struct sysinfo *ctrl)
{
- /** TODO: Stub **/
+ if (ctrl->lpddr)
+ die("%s: Missing support for LPDDR\n");
+
+ const uint8_t odt_stretch = get_odt_stretch(ctrl);
+ const union tc_bank_reg tc_bank = make_tc_bank(ctrl);
+ const union tc_bank_rank_a_reg tc_bank_rank_a = make_tc_bankrank_a(ctrl, odt_stretch);
+ const union tc_bank_rank_b_reg tc_bank_rank_b = make_tc_bankrank_b(ctrl);
+ const union tc_bank_rank_c_reg tc_bank_rank_c = make_tc_bankrank_c(ctrl, odt_stretch);
+ const union tc_bank_rank_d_reg tc_bank_rank_d = make_tc_bankrank_d(ctrl, odt_stretch);
+
+ const uint8_t wr_delay = tc_bank_rank_b.dec_wrd + 1;
+ uint8_t sc_wr_add_delay = 0;
+ sc_wr_add_delay |= wr_delay << 0;
+ sc_wr_add_delay |= wr_delay << 2;
+ sc_wr_add_delay |= wr_delay << 4;
+ sc_wr_add_delay |= wr_delay << 6;
+
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ ctrl->tc_bank[channel] = tc_bank;
+ ctrl->tc_bankrank_a[channel] = tc_bank_rank_a;
+ ctrl->tc_bankrank_b[channel] = tc_bank_rank_b;
+ ctrl->tc_bankrank_c[channel] = tc_bank_rank_c;
+ ctrl->tc_bankrank_d[channel] = tc_bank_rank_d;
+
+ mchbar_write32(TC_BANK_ch(channel), ctrl->tc_bank[channel].raw);
+ mchbar_write32(TC_BANK_RANK_A_ch(channel), ctrl->tc_bankrank_a[channel].raw);
+ mchbar_write32(TC_BANK_RANK_B_ch(channel), ctrl->tc_bankrank_b[channel].raw);
+ mchbar_write32(TC_BANK_RANK_C_ch(channel), ctrl->tc_bankrank_c[channel].raw);
+ mchbar_write32(TC_BANK_RANK_D_ch(channel), ctrl->tc_bankrank_d[channel].raw);
+ mchbar_write8(SC_WR_ADD_DELAY_ch(channel), sc_wr_add_delay);
+ }
}
void configure_refresh(struct sysinfo *ctrl)
{
- /** TODO: Stub **/
+ const union tc_srftp_reg tc_srftp = make_tc_srftp(ctrl);
+ const union tc_rftp_reg tc_rftp = make_tc_rftp(ctrl);
+ const uint32_t tc_zqcal = make_tc_zqcal(ctrl);
+
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ mchbar_setbits32(TC_RFP_ch(channel), 0xff);
+ mchbar_write32(TC_RFTP_ch(channel), tc_rftp.raw);
+ mchbar_write32(TC_SRFTP_ch(channel), tc_srftp.raw);
+ mchbar_write32(TC_ZQCAL_ch(channel), tc_zqcal);
+ }
}
diff --git a/src/northbridge/intel/haswell/registers/mchbar.h b/src/northbridge/intel/haswell/registers/mchbar.h
index 4c3f399b5d..2acc5cbbc8 100644
--- a/src/northbridge/intel/haswell/registers/mchbar.h
+++ b/src/northbridge/intel/haswell/registers/mchbar.h
@@ -86,9 +86,21 @@
#define DDR_COMP_VSSHI_CONTROL 0x3a24
/* MCMAIN per-channel */
+#define TC_BANK_ch(ch) _MCMAIN_C(0x4000, ch)
+#define TC_BANK_RANK_A_ch(ch) _MCMAIN_C(0x4004, ch)
+#define TC_BANK_RANK_B_ch(ch) _MCMAIN_C(0x4008, ch)
+#define TC_BANK_RANK_C_ch(ch) _MCMAIN_C(0x400c, ch)
#define COMMAND_RATE_LIMIT_ch(ch) _MCMAIN_C(0x4010, ch)
+#define TC_BANK_RANK_D_ch(ch) _MCMAIN_C(0x4014, ch)
+#define SC_ROUNDT_LAT_ch(ch) _MCMAIN_C(0x4024, ch)
+#define SC_WR_ADD_DELAY_ch(ch) _MCMAIN_C(0x40d0, ch)
+
+#define TC_ZQCAL_ch(ch) _MCMAIN_C(0x4290, ch)
+#define TC_RFP_ch(ch) _MCMAIN_C(0x4294, ch)
+#define TC_RFTP_ch(ch) _MCMAIN_C(0x4298, ch)
#define MC_INIT_STATE_ch(ch) _MCMAIN_C(0x42a0, ch)
+#define TC_SRFTP_ch(ch) _MCMAIN_C(0x42a4, ch)
/* MCMAIN broadcast */
#define MCSCHEDS_CBIT 0x4c20
--
2.39.2
@@ -0,0 +1,263 @@
From f2f47233a17045d1fb2338ca62ce0b107fbdf765 Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sat, 7 May 2022 21:24:50 +0200
Subject: [PATCH 05/15] haswell NRI: Program memory map
This is very similar to Sandy/Ivy Bridge, except that there's several
registers to program in GDXCBAR. One of these GDXCBAR registers has a
lock bit that must be set in order for the memory controller to allow
normal access to DRAM. And it took me four months to realize this one
bit was the only reason why native raminit did not work.
Change-Id: I3af73a018a7ba948701a542e661e7fefd57591fe
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
.../intel/haswell/native_raminit/Makefile.mk | 1 +
.../intel/haswell/native_raminit/memory_map.c | 183 ++++++++++++++++++
.../haswell/native_raminit/raminit_main.c | 1 +
.../haswell/native_raminit/raminit_native.h | 1 +
.../intel/haswell/registers/host_bridge.h | 2 +
5 files changed, 188 insertions(+)
create mode 100644 src/northbridge/intel/haswell/native_raminit/memory_map.c
diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.mk b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
index fc55277a65..37d527e972 100644
--- a/src/northbridge/intel/haswell/native_raminit/Makefile.mk
+++ b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
@@ -4,6 +4,7 @@ romstage-y += configure_mc.c
romstage-y += lookup_timings.c
romstage-y += init_mpll.c
romstage-y += io_comp_control.c
+romstage-y += memory_map.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/memory_map.c b/src/northbridge/intel/haswell/native_raminit/memory_map.c
new file mode 100644
index 0000000000..e3aded2b37
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/memory_map.c
@@ -0,0 +1,183 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <device/pci_ops.h>
+#include <northbridge/intel/haswell/haswell.h>
+#include <southbridge/intel/lynxpoint/me.h>
+#include <types.h>
+
+#include "raminit_native.h"
+
+/* GDXCBAR */
+#define MPCOHTRK_GDXC_MOT_ADDRESS_LO 0x10
+#define MPCOHTRK_GDXC_MOT_ADDRESS_HI 0x14
+#define MPCOHTRK_GDXC_MOT_REGION 0x18
+
+#define MPCOHTRK_GDXC_OCLA_ADDRESS_LO 0x20
+#define MPCOHTRK_GDXC_OCLA_ADDRESS_HI 0x24
+#define MPCOHTRK_GDXC_OCLA_REGION 0x28
+
+/* This lock bit made me lose what little sanity I had left. - Angel Pons */
+#define MPCOHTRK_GDXC_OCLA_ADDRESS_HI_LOCK BIT(2)
+
+static inline uint32_t gdxcbar_read32(const uintptr_t offset)
+{
+ return read32p((mchbar_read32(GDXCBAR) & ~1) + offset);
+}
+
+static inline void gdxcbar_write32(const uintptr_t offset, const uint32_t value)
+{
+ write32p((mchbar_read32(GDXCBAR) & ~1) + offset, value);
+}
+
+static inline void gdxcbar_clrsetbits32(const uintptr_t offset, uint32_t clear, uint32_t set)
+{
+ const uintptr_t address = (mchbar_read32(GDXCBAR) & ~1) + offset;
+ clrsetbits32((void *)address, clear, set);
+}
+
+#define gdxcbar_setbits32(offset, set) gdxcbar_clrsetbits32(offset, 0, set)
+#define gdxcbar_clrbits32(offset, clear) gdxcbar_clrsetbits32(offset, clear, 0)
+
+/* All values stored in here (except the bool) are specified in MiB */
+struct memory_map_data {
+ uint32_t dpr_size;
+ uint32_t tseg_size;
+ uint32_t gtt_size;
+ uint32_t gms_size;
+ uint32_t me_stolen_size;
+ uint32_t mmio_size;
+ uint32_t touud;
+ uint32_t remaplimit;
+ uint32_t remapbase;
+ uint32_t tom;
+ uint32_t tom_minus_me;
+ uint32_t tolud;
+ uint32_t bdsm_base;
+ uint32_t gtt_base;
+ uint32_t tseg_base;
+ bool reclaim_possible;
+};
+
+static void compute_memory_map(struct memory_map_data *map)
+{
+ map->tom_minus_me = map->tom - map->me_stolen_size;
+
+ /*
+ * MMIO size will actually be slightly smaller than computed,
+ * but matches what MRC does and is more MTRR-friendly given
+ * that TSEG is treated as WB, but SMRR makes TSEG UC anyway.
+ */
+ const uint32_t mmio_size = MIN(map->tom_minus_me, 4096) / 2;
+ map->gtt_base = ALIGN_DOWN(mmio_size, map->tseg_size);
+ map->tseg_base = map->gtt_base - map->tseg_size;
+ map->bdsm_base = map->gtt_base + map->gtt_size;
+ map->tolud = map->bdsm_base + map->gms_size;
+ map->reclaim_possible = map->tom_minus_me > map->tolud;
+
+ if (map->reclaim_possible) {
+ map->remapbase = MAX(4096, map->tom_minus_me);
+ map->touud = MIN(4096, map->tom_minus_me) + map->remapbase - map->tolud;
+ map->remaplimit = map->touud - 1;
+ } else {
+ map->remapbase = 0;
+ map->remaplimit = 0;
+ map->touud = map->tom_minus_me;
+ }
+}
+
+static void display_memory_map(const struct memory_map_data *map)
+{
+ if (!CONFIG(DEBUG_RAM_SETUP))
+ return;
+
+ printk(BIOS_DEBUG, "============ MEMORY MAP ============\n");
+ printk(BIOS_DEBUG, "\n");
+ printk(BIOS_DEBUG, "dpr_size = %u MiB\n", map->dpr_size);
+ printk(BIOS_DEBUG, "tseg_size = %u MiB\n", map->tseg_size);
+ printk(BIOS_DEBUG, "gtt_size = %u MiB\n", map->gtt_size);
+ printk(BIOS_DEBUG, "gms_size = %u MiB\n", map->gms_size);
+ printk(BIOS_DEBUG, "me_stolen_size = %u MiB\n", map->me_stolen_size);
+ printk(BIOS_DEBUG, "\n");
+ printk(BIOS_DEBUG, "touud = %u MiB\n", map->touud);
+ printk(BIOS_DEBUG, "remaplimit = %u MiB\n", map->remaplimit);
+ printk(BIOS_DEBUG, "remapbase = %u MiB\n", map->remapbase);
+ printk(BIOS_DEBUG, "tom = %u MiB\n", map->tom);
+ printk(BIOS_DEBUG, "tom_minus_me = %u MiB\n", map->tom_minus_me);
+ printk(BIOS_DEBUG, "tolud = %u MiB\n", map->tolud);
+ printk(BIOS_DEBUG, "bdsm_base = %u MiB\n", map->bdsm_base);
+ printk(BIOS_DEBUG, "gtt_base = %u MiB\n", map->gtt_base);
+ printk(BIOS_DEBUG, "tseg_base = %u MiB\n", map->tseg_base);
+ printk(BIOS_DEBUG, "\n");
+ printk(BIOS_DEBUG, "reclaim_possible = %s\n", map->reclaim_possible ? "Yes" : "No");
+}
+
+static void map_write_reg64(const uint16_t reg, const uint64_t size)
+{
+ const uint64_t value = size << 20;
+ pci_write_config32(HOST_BRIDGE, reg + 4, value >> 32);
+ pci_write_config32(HOST_BRIDGE, reg + 0, value >> 0);
+}
+
+static void map_write_reg32(const uint16_t reg, const uint32_t size)
+{
+ const uint32_t value = size << 20;
+ pci_write_config32(HOST_BRIDGE, reg, value);
+}
+
+static void program_memory_map(const struct memory_map_data *map)
+{
+ map_write_reg64(TOUUD, map->touud);
+ map_write_reg64(TOM, map->tom);
+ if (map->reclaim_possible) {
+ map_write_reg64(REMAPBASE, map->remapbase);
+ map_write_reg64(REMAPLIMIT, map->remaplimit);
+ }
+ if (map->me_stolen_size) {
+ map_write_reg64(MESEG_LIMIT, 0x80000 - map->me_stolen_size);
+ map_write_reg64(MESEG_BASE, map->tom_minus_me);
+ pci_or_config32(HOST_BRIDGE, MESEG_LIMIT, ME_STLEN_EN);
+ }
+ map_write_reg32(TOLUD, map->tolud);
+ map_write_reg32(BDSM, map->bdsm_base);
+ map_write_reg32(BGSM, map->gtt_base);
+ map_write_reg32(TSEG, map->tseg_base);
+
+ const uint32_t dpr_reg = map->tseg_base << 20 | map->dpr_size << 4;
+ pci_write_config32(HOST_BRIDGE, DPR, dpr_reg);
+
+ const uint16_t gfx_stolen_size = GGC_IGD_MEM_IN_32MB_UNITS(map->gms_size / 32);
+ const uint16_t ggc = map->gtt_size << 8 | gfx_stolen_size;
+ pci_write_config16(HOST_BRIDGE, GGC, ggc);
+
+ /** TODO: Do not hardcode these? GDXC has weird alignment requirements, though. **/
+ gdxcbar_write32(MPCOHTRK_GDXC_MOT_ADDRESS_LO, 0);
+ gdxcbar_write32(MPCOHTRK_GDXC_MOT_ADDRESS_HI, 0);
+ gdxcbar_write32(MPCOHTRK_GDXC_MOT_REGION, 0);
+
+ gdxcbar_write32(MPCOHTRK_GDXC_OCLA_ADDRESS_LO, 0);
+ gdxcbar_write32(MPCOHTRK_GDXC_OCLA_ADDRESS_HI, 0);
+ gdxcbar_write32(MPCOHTRK_GDXC_OCLA_REGION, 0);
+
+ gdxcbar_setbits32(MPCOHTRK_GDXC_OCLA_ADDRESS_HI, MPCOHTRK_GDXC_OCLA_ADDRESS_HI_LOCK);
+}
+
+enum raminit_status configure_memory_map(struct sysinfo *ctrl)
+{
+ struct memory_map_data memory_map = {
+ .tom = ctrl->channel_size_mb[0] + ctrl->channel_size_mb[1],
+ .dpr_size = CONFIG_INTEL_TXT_DPR_SIZE,
+ .tseg_size = CONFIG_SMM_TSEG_SIZE >> 20,
+ .me_stolen_size = intel_early_me_uma_size(),
+ };
+ /** FIXME: MRC hardcodes iGPU parameters, but we should not **/
+ const bool igpu_on = pci_read_config32(HOST_BRIDGE, DEVEN) & DEVEN_D2EN;
+ if (CONFIG(ONBOARD_VGA_IS_PRIMARY) || igpu_on) {
+ memory_map.gtt_size = 2;
+ memory_map.gms_size = 64;
+ pci_or_config32(HOST_BRIDGE, DEVEN, DEVEN_D2EN);
+ }
+ compute_memory_map(&memory_map);
+ display_memory_map(&memory_map);
+ program_memory_map(&memory_map);
+ return 0;
+}
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_main.c b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
index 3a773cfa19..136a8ba989 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_main.c
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
@@ -24,6 +24,7 @@ static const struct task_entry cold_boot[] = {
{ initialise_mpll, true, "INITMPLL", },
{ convert_timings, true, "CONVTIM", },
{ configure_mc, true, "CONFMC", },
+ { configure_memory_map, true, "MEMMAP", },
};
/* 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 cd1f2eb2a5..4763b25e8d 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -202,6 +202,7 @@ enum raminit_status collect_spd_info(struct sysinfo *ctrl);
enum raminit_status initialise_mpll(struct sysinfo *ctrl);
enum raminit_status convert_timings(struct sysinfo *ctrl);
enum raminit_status configure_mc(struct sysinfo *ctrl);
+enum raminit_status configure_memory_map(struct sysinfo *ctrl);
void configure_timings(struct sysinfo *ctrl);
void configure_refresh(struct sysinfo *ctrl);
diff --git a/src/northbridge/intel/haswell/registers/host_bridge.h b/src/northbridge/intel/haswell/registers/host_bridge.h
index 1ee0ab2890..0228cf6bb9 100644
--- a/src/northbridge/intel/haswell/registers/host_bridge.h
+++ b/src/northbridge/intel/haswell/registers/host_bridge.h
@@ -34,6 +34,8 @@
#define MESEG_BASE 0x70 /* Management Engine Base */
#define MESEG_LIMIT 0x78 /* Management Engine Limit */
+#define MELCK (1 << 10) /* ME Range Lock */
+#define ME_STLEN_EN (1 << 11) /* ME Stolen Memory Enable */
#define PAM0 0x80
#define PAM1 0x81
--
2.39.2
@@ -0,0 +1,384 @@
From 1992c1119e36e7cdbfb676dc167157b0df22f7a4 Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sat, 7 May 2022 23:12:18 +0200
Subject: [PATCH 07/15] haswell NRI: Add pre-training steps
Implement pre-training steps, which consist of enabling ECC I/O and
filling the WDB (Write Data Buffer, stores test patterns) through a
magic LDAT port.
Change-Id: Ie2e09e3b218c4569ed8de5c5e1b05d491032e0f1
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
.../intel/haswell/native_raminit/Makefile.mk | 1 +
.../haswell/native_raminit/raminit_main.c | 34 ++++
.../haswell/native_raminit/raminit_native.h | 24 +++
.../haswell/native_raminit/reg_structs.h | 45 +++++
.../intel/haswell/native_raminit/setup_wdb.c | 159 ++++++++++++++++++
.../intel/haswell/registers/mchbar.h | 9 +
6 files changed, 272 insertions(+)
create mode 100644 src/northbridge/intel/haswell/native_raminit/setup_wdb.c
diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.mk b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
index e9212df9e6..8d7d4e4db0 100644
--- a/src/northbridge/intel/haswell/native_raminit/Makefile.mk
+++ b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
@@ -10,5 +10,6 @@ romstage-y += memory_map.c
romstage-y += raminit_main.c
romstage-y += raminit_native.c
romstage-y += reut.c
+romstage-y += setup_wdb.c
romstage-y += spd_bitmunching.c
romstage-y += timings_refresh.c
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_main.c b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
index 73ff180b8c..5e4674957d 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_main.c
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
@@ -13,6 +13,39 @@
#include "raminit_native.h"
+static enum raminit_status pre_training(struct sysinfo *ctrl)
+{
+ /* Skip on S3 resume */
+ if (ctrl->bootmode == BOOTMODE_S3)
+ return RAMINIT_STATUS_SUCCESS;
+
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ for (uint8_t slot = 0; slot < NUM_SLOTS; slot++) {
+ if (!rank_in_ch(ctrl, slot + slot, channel))
+ continue;
+
+ printk(RAM_DEBUG, "C%uS%u:\n", channel, slot);
+ printk(RAM_DEBUG, "\tMR0: 0x%04x\n", ctrl->mr0[channel][slot]);
+ printk(RAM_DEBUG, "\tMR1: 0x%04x\n", ctrl->mr1[channel][slot]);
+ printk(RAM_DEBUG, "\tMR2: 0x%04x\n", ctrl->mr2[channel][slot]);
+ printk(RAM_DEBUG, "\tMR3: 0x%04x\n", ctrl->mr3[channel][slot]);
+ printk(RAM_DEBUG, "\n");
+ }
+ if (ctrl->is_ecc) {
+ union mad_dimm_reg mad_dimm = {
+ .raw = mchbar_read32(MAD_DIMM(channel)),
+ };
+ /* Enable ECC I/O */
+ mad_dimm.ecc_mode = 1;
+ mchbar_write32(MAD_DIMM(channel), mad_dimm.raw);
+ /* Wait 4 usec after enabling the ECC I/O, needed by HW */
+ udelay(4);
+ }
+ }
+ setup_wdb(ctrl);
+ return RAMINIT_STATUS_SUCCESS;
+}
+
struct task_entry {
enum raminit_status (*task)(struct sysinfo *);
bool is_enabled;
@@ -26,6 +59,7 @@ static const struct task_entry cold_boot[] = {
{ configure_mc, true, "CONFMC", },
{ configure_memory_map, true, "MEMMAP", },
{ do_jedec_init, true, "JEDECINIT", },
+ { pre_training, true, "PRETRAIN", },
};
/* 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 e3cf4254a0..f29c2ec366 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -35,6 +35,13 @@
#define RTTNOM_MASK (BIT(9) | BIT(6) | BIT(2))
+#define BASIC_VA_PAT_SPREAD_8 0x01010101
+
+#define WDB_CACHE_LINE_SIZE 8
+
+#define NUM_WDB_CL_MUX_SEEDS 3
+#define NUM_CADB_MUX_SEEDS 3
+
/* ZQ calibration types */
enum {
ZQ_INIT, /* DDR3: ZQCL with tZQinit, LPDDR3: ZQ Init with tZQinit */
@@ -318,6 +325,23 @@ void reut_issue_mrs_all(
enum raminit_status reut_issue_zq(struct sysinfo *ctrl, uint8_t chanmask, uint8_t zq_type);
+void write_wdb_fixed_pat(
+ const struct sysinfo *ctrl,
+ const uint8_t patterns[],
+ const uint8_t pat_mask[],
+ uint8_t spread,
+ uint16_t start);
+
+void write_wdb_va_pat(
+ const struct sysinfo *ctrl,
+ uint32_t agg_mask,
+ uint32_t vic_mask,
+ uint8_t vic_rot,
+ uint16_t start);
+
+void program_wdb_lfsr(const struct sysinfo *ctrl, bool cleanup);
+void setup_wdb(const struct sysinfo *ctrl);
+
uint8_t get_rx_bias(const struct sysinfo *ctrl);
uint8_t get_tCWL(uint32_t mem_clock_mhz);
diff --git a/src/northbridge/intel/haswell/native_raminit/reg_structs.h b/src/northbridge/intel/haswell/native_raminit/reg_structs.h
index 9929f617fe..7aa8d8c8b2 100644
--- a/src/northbridge/intel/haswell/native_raminit/reg_structs.h
+++ b/src/northbridge/intel/haswell/native_raminit/reg_structs.h
@@ -335,6 +335,18 @@ union mcscheds_cbit_reg {
uint32_t raw;
};
+union reut_pat_cl_mux_lmn_reg {
+ struct __packed {
+ uint32_t l_data_select : 1; // Bits 0:0
+ uint32_t en_sweep_freq : 1; // Bits 1:1
+ uint32_t : 6; // Bits 7:2
+ uint32_t l_counter : 8; // Bits 15:8
+ uint32_t m_counter : 8; // Bits 23:16
+ uint32_t n_counter : 8; // Bits 31:24
+ };
+ uint32_t raw;
+};
+
union reut_pat_cadb_prog_reg {
struct __packed {
uint32_t addr : 16; // Bits 15:0
@@ -439,6 +451,39 @@ union reut_misc_odt_ctrl_reg {
uint32_t raw;
};
+union ldat_pdat_reg {
+ struct __packed {
+ uint32_t fast_addr : 12; // Bits 11:0
+ uint32_t : 4; // Bits 15:12
+ uint32_t addr_en : 1; // Bits 16:16
+ uint32_t seq_en : 1; // Bits 17:17
+ uint32_t pol_0 : 1; // Bits 18:18
+ uint32_t pol_1 : 1; // Bits 19:19
+ uint32_t cmd_a : 4; // Bits 23:20
+ uint32_t cmd_b : 4; // Bits 27:24
+ uint32_t cmd_c : 4; // Bits 31:28
+ };
+ uint32_t raw;
+};
+
+union ldat_sdat_reg {
+ struct __packed {
+ uint32_t bank_sel : 4; // Bits 3:0
+ uint32_t : 1; // Bits 4:4
+ uint32_t array_sel : 5; // Bits 9:5
+ uint32_t cmp : 1; // Bits 10:10
+ uint32_t replicate : 1; // Bits 11:11
+ uint32_t dword : 4; // Bits 15:12
+ uint32_t mode : 2; // Bits 17:16
+ uint32_t mpmap : 6; // Bits 23:18
+ uint32_t mpb_offset : 4; // Bits 27:24
+ uint32_t stage_en : 1; // Bits 28:28
+ uint32_t shadow : 2; // Bits 30:29
+ uint32_t : 1; // Bits 31:31
+ };
+ uint32_t raw;
+};
+
union mcscheds_dft_misc_reg {
struct __packed {
uint32_t wdar : 1; // Bits 0:0
diff --git a/src/northbridge/intel/haswell/native_raminit/setup_wdb.c b/src/northbridge/intel/haswell/native_raminit/setup_wdb.c
new file mode 100644
index 0000000000..ec37c48415
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/setup_wdb.c
@@ -0,0 +1,159 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <northbridge/intel/haswell/haswell.h>
+#include <types.h>
+
+#include "raminit_native.h"
+
+static void ldat_write_cacheline(
+ const struct sysinfo *const ctrl,
+ const uint8_t chunk,
+ const uint16_t start,
+ const uint64_t data)
+{
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ /*
+ * Do not do a 64-bit write here. The register is not aligned
+ * to a 64-bit boundary, which could potentially cause issues.
+ */
+ mchbar_write32(QCLK_ch_LDAT_DATA_IN_x(channel, 0), data & UINT32_MAX);
+ mchbar_write32(QCLK_ch_LDAT_DATA_IN_x(channel, 1), data >> 32);
+ /*
+ * Set REPLICATE = 0 as you don't want to replicate the data.
+ * Set BANK_SEL to the chunk you want to write the 64 bits to.
+ * Set ARRAY_SEL = 0 (the MC WDB) and MODE = 1.
+ */
+ const union ldat_sdat_reg ldat_sdat = {
+ .bank_sel = chunk,
+ .mode = 1,
+ };
+ mchbar_write32(QCLK_ch_LDAT_SDAT(channel), ldat_sdat.raw);
+ /*
+ * Finally, write the PDAT register indicating which cacheline
+ * of the WDB you want to write to by setting FAST_ADDR field
+ * to one of the 64 cache lines. Also set CMD_B in the PDAT
+ * register to 4'b1000, indicating that this is a LDAT write.
+ */
+ const union ldat_pdat_reg ldat_pdat = {
+ .fast_addr = MIN(start, 0xfff),
+ .cmd_b = 8,
+ };
+ mchbar_write32(QCLK_ch_LDAT_PDAT(channel), ldat_pdat.raw);
+ }
+}
+
+static void clear_ldat_mode(const struct sysinfo *const ctrl)
+{
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++)
+ mchbar_write32(QCLK_ch_LDAT_SDAT(channel), 0);
+}
+
+void write_wdb_fixed_pat(
+ const struct sysinfo *const ctrl,
+ const uint8_t patterns[],
+ const uint8_t pat_mask[],
+ const uint8_t spread,
+ const uint16_t start)
+{
+ for (uint8_t chunk = 0; chunk < WDB_CACHE_LINE_SIZE; chunk++) {
+ uint64_t data = 0;
+ for (uint8_t b = 0; b < 64; b++) {
+ const uint8_t beff = b % spread;
+ const uint8_t burst = patterns[pat_mask[beff]];
+ if (burst & BIT(chunk))
+ data |= 1ULL << b;
+ }
+ ldat_write_cacheline(ctrl, chunk, start, data);
+ }
+ clear_ldat_mode(ctrl);
+}
+
+static inline uint32_t rol_u32(const uint32_t val)
+{
+ return (val << 1) | ((val >> 31) & 1);
+}
+
+void write_wdb_va_pat(
+ const struct sysinfo *const ctrl,
+ const uint32_t agg_mask,
+ const uint32_t vic_mask,
+ const uint8_t vic_rot,
+ const uint16_t start)
+{
+ static const uint8_t va_mask_to_compressed[4] = {0xaa, 0xc0, 0xcc, 0xf0};
+ uint32_t v_mask = vic_mask;
+ uint32_t a_mask = agg_mask;
+ for (uint8_t v = 0; v < vic_rot; v++) {
+ uint8_t compressed[32] = {0};
+ /* Iterate through all 32 bits and create a compressed version of cacheline */
+ for (uint8_t b = 0; b < ARRAY_SIZE(compressed); b++) {
+ const uint8_t vic = !!(v_mask & BIT(b));
+ const uint8_t agg = !!(a_mask & BIT(b));
+ const uint8_t index = !vic << 1 | agg << 0;
+ compressed[b] = va_mask_to_compressed[index];
+ }
+ for (uint8_t chunk = 0; chunk < WDB_CACHE_LINE_SIZE; chunk++) {
+ uint32_t data = 0;
+ for (uint8_t b = 0; b < ARRAY_SIZE(compressed); b++)
+ data |= !!(compressed[b] & BIT(chunk)) << b;
+
+ const uint64_t data64 = (uint64_t)data << 32 | data;
+ ldat_write_cacheline(ctrl, chunk, start + v, data64);
+ }
+ v_mask = rol_u32(v_mask);
+ a_mask = rol_u32(a_mask);
+ }
+ clear_ldat_mode(ctrl);
+}
+
+void program_wdb_lfsr(const struct sysinfo *ctrl, const bool cleanup)
+{
+ /* Cleanup LFSR seeds are sequential */
+ const uint32_t cleanup_seeds[NUM_WDB_CL_MUX_SEEDS] = { 0xaaaaaa, 0xcccccc, 0xf0f0f0 };
+ const uint32_t regular_seeds[NUM_WDB_CL_MUX_SEEDS] = { 0xa10ca1, 0xef0d08, 0xad0a1e };
+ const uint32_t *seeds = cleanup ? cleanup_seeds : regular_seeds;
+
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ for (uint8_t i = 0; i < NUM_WDB_CL_MUX_SEEDS; i++) {
+ mchbar_write32(REUT_ch_PAT_WDB_CL_MUX_RD_x(channel, i), seeds[i]);
+ mchbar_write32(REUT_ch_PAT_WDB_CL_MUX_WR_x(channel, i), seeds[i]);
+ }
+ }
+}
+
+void setup_wdb(const struct sysinfo *ctrl)
+{
+ const uint32_t amask[9] = {
+ 0x86186186, 0x18618618, 0x30c30c30,
+ 0xa28a28a2, 0x8a28a28a, 0x14514514,
+ 0x28a28a28, 0x92492492, 0x24924924,
+ };
+ const uint32_t vmask = 0x41041041;
+
+ /* Fill first 8 entries with simple 2-LFSR VA pattern */
+ write_wdb_va_pat(ctrl, 0, BASIC_VA_PAT_SPREAD_8, 8, 0);
+
+ /* Fill next 54 entries with 3-LFSR VA pattern */
+ for (uint8_t a = 0; a < ARRAY_SIZE(amask); a++)
+ write_wdb_va_pat(ctrl, amask[a], vmask, 6, 8 + a * 6);
+
+ program_wdb_lfsr(ctrl, false);
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ const union reut_pat_cl_mux_lmn_reg wdb_cl_mux_lmn = {
+ .en_sweep_freq = 1,
+ .l_counter = 1,
+ .m_counter = 1,
+ .n_counter = 10,
+ };
+ mchbar_write32(REUT_ch_PAT_WDB_CL_MUX_LMN(channel), wdb_cl_mux_lmn.raw);
+ }
+}
diff --git a/src/northbridge/intel/haswell/registers/mchbar.h b/src/northbridge/intel/haswell/registers/mchbar.h
index 4fc78a7f43..f8408e51a0 100644
--- a/src/northbridge/intel/haswell/registers/mchbar.h
+++ b/src/northbridge/intel/haswell/registers/mchbar.h
@@ -94,6 +94,11 @@
#define TC_BANK_RANK_D_ch(ch) _MCMAIN_C(0x4014, ch)
#define SC_ROUNDT_LAT_ch(ch) _MCMAIN_C(0x4024, ch)
+#define REUT_ch_PAT_WDB_CL_MUX_WR_x(ch, x) _MCMAIN_C_X(0x4048, ch, x) /* x in 0 .. 2 */
+#define REUT_ch_PAT_WDB_CL_MUX_RD_x(ch, x) _MCMAIN_C_X(0x4054, ch, x) /* x in 0 .. 2 */
+
+#define REUT_ch_PAT_WDB_CL_MUX_LMN(ch) _MCMAIN_C(0x4078, ch)
+
#define SC_WR_ADD_DELAY_ch(ch) _MCMAIN_C(0x40d0, ch)
#define REUT_ch_MISC_CKE_CTRL(ch) _MCMAIN_C(0x4190, ch)
@@ -110,6 +115,10 @@
#define MC_INIT_STATE_ch(ch) _MCMAIN_C(0x42a0, ch)
#define TC_SRFTP_ch(ch) _MCMAIN_C(0x42a4, ch)
+#define QCLK_ch_LDAT_PDAT(ch) _MCMAIN_C(0x42d0, ch)
+#define QCLK_ch_LDAT_SDAT(ch) _MCMAIN_C(0x42d4, ch)
+#define QCLK_ch_LDAT_DATA_IN_x(ch, x) _MCMAIN_C_X(0x42dc, ch, x) /* x in 0 .. 1 */
+
#define REUT_GLOBAL_ERR 0x4804
#define REUT_ch_SEQ_CFG(ch) (0x48a8 + 8 * (ch))
--
2.39.2
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,222 @@
From 3cd11e7e31e0416ead9782213d582aefc6461e94 Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sun, 8 May 2022 00:56:00 +0200
Subject: [PATCH 09/15] haswell NRI: Add range tracking library
Implement a small library used to keep track of passing ranges. This
will be used by 1D training algorithms when margining some parameter.
Change-Id: I8718e85165160afd7c0c8e730b5ce6c9c00f8a60
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
.../intel/haswell/native_raminit/Makefile.mk | 1 +
.../intel/haswell/native_raminit/ranges.c | 109 ++++++++++++++++++
.../intel/haswell/native_raminit/ranges.h | 68 +++++++++++
3 files changed, 178 insertions(+)
create mode 100644 src/northbridge/intel/haswell/native_raminit/ranges.c
create mode 100644 src/northbridge/intel/haswell/native_raminit/ranges.h
diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.mk b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
index 6e1b365602..2da950771d 100644
--- a/src/northbridge/intel/haswell/native_raminit/Makefile.mk
+++ b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
@@ -9,6 +9,7 @@ romstage-y += io_comp_control.c
romstage-y += memory_map.c
romstage-y += raminit_main.c
romstage-y += raminit_native.c
+romstage-y += ranges.c
romstage-y += reut.c
romstage-y += setup_wdb.c
romstage-y += spd_bitmunching.c
diff --git a/src/northbridge/intel/haswell/native_raminit/ranges.c b/src/northbridge/intel/haswell/native_raminit/ranges.c
new file mode 100644
index 0000000000..cdebc1fa66
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/ranges.c
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <types.h>
+
+#include "ranges.h"
+
+void linear_record_pass(
+ struct linear_train_data *const data,
+ const bool pass,
+ const int32_t value,
+ const int32_t start,
+ const int32_t step)
+{
+ /* If this is the first time, initialize all values */
+ if (value == start) {
+ /*
+ * If value passed, create a zero-length region for the current value,
+ * which may be extended as long as the successive values are passing.
+ *
+ * Otherwise, create a zero-length range for the preceding value. This
+ * range cannot be extended by other passing values, which is desired.
+ */
+ data->current.start = start - (pass ? 0 : step);
+ data->current.end = data->current.start;
+ data->largest = data->current;
+ } else if (pass) {
+ /* If this pass is not contiguous, it belongs to a new region */
+ if (data->current.end != (value - step))
+ data->current.start = value;
+
+ /* Update end of current region */
+ data->current.end = value;
+
+ /* Update largest region */
+ if (range_width(data->current) > range_width(data->largest))
+ data->largest = data->current;
+ }
+}
+
+void phase_record_pass(
+ struct phase_train_data *const data,
+ const bool pass,
+ const int32_t value,
+ const int32_t start,
+ const int32_t step)
+{
+ /* If this is the first time, initialize all values */
+ if (value == start) {
+ /*
+ * If value passed, create a zero-length region for the current value,
+ * which may be extended as long as the successive values are passing.
+ *
+ * Otherwise, create a zero-length range for the preceding value. This
+ * range cannot be extended by other passing values, which is desired.
+ */
+ data->current.start = start - (pass ? 0 : step);
+ data->current.end = data->current.start;
+ data->largest = data->current;
+ data->initial = data->current;
+ return;
+ }
+ if (!pass)
+ return;
+
+ /* Update initial region */
+ if (data->initial.end == (value - step))
+ data->initial.end = value;
+
+ /* If this pass is not contiguous, it belongs to a new region */
+ if (data->current.end != (value - step))
+ data->current.start = value;
+
+ /* Update end of current region */
+ data->current.end = value;
+
+ /* Update largest region */
+ if (range_width(data->current) > range_width(data->largest))
+ data->largest = data->current;
+}
+
+void phase_append_initial_to_current(
+ struct phase_train_data *const data,
+ const int32_t start,
+ const int32_t step)
+{
+ /* If initial region is valid and does not overlap, append it */
+ if (data->initial.start == start && data->initial.end != data->current.end)
+ data->current.end += step + range_width(data->initial);
+
+ /* Update largest region */
+ if (range_width(data->current) > range_width(data->largest))
+ data->largest = data->current;
+}
+
+void phase_append_current_to_initial(
+ struct phase_train_data *const data,
+ const int32_t start,
+ const int32_t step)
+{
+ /* If initial region is valid and does not overlap, append it */
+ if (data->initial.start == start && data->initial.end != data->current.end) {
+ data->initial.start -= (step + range_width(data->current));
+ data->current = data->initial;
+ }
+
+ /* Update largest region */
+ if (range_width(data->current) > range_width(data->largest))
+ data->largest = data->current;
+}
diff --git a/src/northbridge/intel/haswell/native_raminit/ranges.h b/src/northbridge/intel/haswell/native_raminit/ranges.h
new file mode 100644
index 0000000000..235392df96
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/ranges.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef HASWELL_RAMINIT_RANGES_H
+#define HASWELL_RAMINIT_RANGES_H
+
+#include <types.h>
+
+/*
+ * Many algorithms shmoo some parameter to determine the largest passing
+ * range. Provide a common implementation to avoid redundant boilerplate.
+ */
+struct passing_range {
+ int32_t start;
+ int32_t end;
+};
+
+/* Structure for linear parameters, such as roundtrip delays */
+struct linear_train_data {
+ struct passing_range current;
+ struct passing_range largest;
+};
+
+/*
+ * Phase ranges are "circular": the first and last indices are contiguous.
+ * To correctly determine the largest passing range, one has to combine
+ * the initial range and the current range when processing the last index.
+ */
+struct phase_train_data {
+ struct passing_range initial;
+ struct passing_range current;
+ struct passing_range largest;
+};
+
+static inline int32_t range_width(const struct passing_range range)
+{
+ return range.end - range.start;
+}
+
+static inline int32_t range_center(const struct passing_range range)
+{
+ return range.start + range_width(range) / 2;
+}
+
+void linear_record_pass(
+ struct linear_train_data *data,
+ bool pass,
+ int32_t value,
+ int32_t start,
+ int32_t step);
+
+void phase_record_pass(
+ struct phase_train_data *data,
+ bool pass,
+ int32_t value,
+ int32_t start,
+ int32_t step);
+
+void phase_append_initial_to_current(
+ struct phase_train_data *data,
+ int32_t start,
+ int32_t step);
+
+void phase_append_current_to_initial(
+ struct phase_train_data *data,
+ int32_t start,
+ int32_t step);
+
+#endif
--
2.39.2
@@ -0,0 +1,294 @@
From 9d416f00d53bda9f074b9f843fef4a6d4537202a Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sun, 8 May 2022 01:11:03 +0200
Subject: [PATCH 10/15] haswell NRI: Add library to change margins
Implement a library to change Rx/Tx margins. It will be expanded later.
Change-Id: I0b55aba428d8b4d4e16d2fbdec57235ce3ce8adf
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
.../intel/haswell/native_raminit/Makefile.mk | 1 +
.../haswell/native_raminit/change_margin.c | 154 ++++++++++++++++++
.../haswell/native_raminit/raminit_native.h | 50 ++++++
.../intel/haswell/registers/mchbar.h | 9 +
4 files changed, 214 insertions(+)
create mode 100644 src/northbridge/intel/haswell/native_raminit/change_margin.c
diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.mk b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
index 2da950771d..ebe9e9b762 100644
--- a/src/northbridge/intel/haswell/native_raminit/Makefile.mk
+++ b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
@@ -1,5 +1,6 @@
## SPDX-License-Identifier: GPL-2.0-or-later
+romstage-y += change_margin.c
romstage-y += configure_mc.c
romstage-y += ddr3.c
romstage-y += jedec_reset.c
diff --git a/src/northbridge/intel/haswell/native_raminit/change_margin.c b/src/northbridge/intel/haswell/native_raminit/change_margin.c
new file mode 100644
index 0000000000..12da59580f
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/change_margin.c
@@ -0,0 +1,154 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <commonlib/clamp.h>
+#include <console/console.h>
+#include <delay.h>
+#include <northbridge/intel/haswell/haswell.h>
+#include <timer.h>
+
+#include "raminit_native.h"
+
+void update_rxt(
+ struct sysinfo *ctrl,
+ const uint8_t channel,
+ const uint8_t rank,
+ const uint8_t byte,
+ const enum rxt_subfield subfield,
+ const int32_t value)
+{
+ union ddr_data_rx_train_rank_reg rxt = {
+ .rcven = ctrl->rcven[channel][rank][byte],
+ .dqs_p = ctrl->rxdqsp[channel][rank][byte],
+ .rx_eq = ctrl->rx_eq[channel][rank][byte],
+ .dqs_n = ctrl->rxdqsn[channel][rank][byte],
+ .vref = ctrl->rxvref[channel][rank][byte],
+ };
+ int32_t new_value;
+ switch (subfield) {
+ case RXT_RCVEN:
+ new_value = clamp_s32(0, value, 511);
+ rxt.rcven = new_value;
+ break;
+ case RXT_RXDQS_P:
+ new_value = clamp_s32(0, value, 63);
+ rxt.dqs_p = new_value;
+ break;
+ case RXT_RX_EQ:
+ new_value = clamp_s32(0, value, 31);
+ rxt.rx_eq = new_value;
+ break;
+ case RXT_RXDQS_N:
+ new_value = clamp_s32(0, value, 63);
+ rxt.dqs_n = new_value;
+ break;
+ case RXT_RX_VREF:
+ new_value = clamp_s32(-32, value, 31);
+ rxt.vref = new_value;
+ break;
+ case RXT_RXDQS_BOTH:
+ new_value = clamp_s32(0, value, 63);
+ rxt.dqs_p = new_value;
+ rxt.dqs_n = new_value;
+ break;
+ case RXT_RESTORE:
+ new_value = value;
+ break;
+ default:
+ die("%s: Unhandled subfield index %u\n", __func__, subfield);
+ }
+
+ if (new_value != value) {
+ printk(BIOS_ERR, "%s: Overflow for subfield %u: %d ---> %d\n",
+ __func__, subfield, value, new_value);
+ }
+ mchbar_write32(RX_TRAIN_ch_r_b(channel, rank, byte), rxt.raw);
+ download_regfile(ctrl, channel, false, rank, REG_FILE_USE_RANK, byte, true, false);
+}
+
+void update_txt(
+ struct sysinfo *ctrl,
+ const uint8_t channel,
+ const uint8_t rank,
+ const uint8_t byte,
+ const enum txt_subfield subfield,
+ const int32_t value)
+{
+ union ddr_data_tx_train_rank_reg txt = {
+ .dq_delay = ctrl->tx_dq[channel][rank][byte],
+ .dqs_delay = ctrl->txdqs[channel][rank][byte],
+ .tx_eq = ctrl->tx_eq[channel][rank][byte],
+ };
+ int32_t new_value;
+ switch (subfield) {
+ case TXT_TX_DQ:
+ new_value = clamp_s32(0, value, 511);
+ txt.dq_delay = new_value;
+ break;
+ case TXT_TXDQS:
+ new_value = clamp_s32(0, value, 511);
+ txt.dqs_delay = new_value;
+ break;
+ case TXT_TX_EQ:
+ new_value = clamp_s32(0, value, 63);
+ txt.tx_eq = new_value;
+ break;
+ case TXT_DQDQS_OFF:
+ new_value = value;
+ txt.dqs_delay += new_value;
+ txt.dq_delay += new_value;
+ break;
+ case TXT_RESTORE:
+ new_value = value;
+ break;
+ default:
+ die("%s: Unhandled subfield index %u\n", __func__, subfield);
+ }
+ if (new_value != value) {
+ printk(BIOS_ERR, "%s: Overflow for subfield %u: %d ---> %d\n",
+ __func__, subfield, value, new_value);
+ }
+ mchbar_write32(TX_TRAIN_ch_r_b(channel, rank, byte), txt.raw);
+ download_regfile(ctrl, channel, false, rank, REG_FILE_USE_RANK, byte, false, true);
+}
+
+void download_regfile(
+ struct sysinfo *ctrl,
+ const uint8_t channel,
+ const bool multicast,
+ const uint8_t rank,
+ const enum regfile_mode regfile,
+ const uint8_t byte,
+ const bool read_rf_rd,
+ const bool read_rf_wr)
+{
+ union reut_seq_base_addr_reg reut_seq_base_addr;
+ switch (regfile) {
+ case REG_FILE_USE_START:
+ reut_seq_base_addr.raw = mchbar_read64(REUT_ch_SEQ_ADDR_START(channel));
+ break;
+ case REG_FILE_USE_CURRENT:
+ reut_seq_base_addr.raw = mchbar_read64(REUT_ch_SEQ_ADDR_CURRENT(channel));
+ break;
+ case REG_FILE_USE_RANK:
+ reut_seq_base_addr.raw = 0;
+ if (rank >= NUM_SLOTRANKS)
+ die("%s: bad rank %u\n", __func__, rank);
+ break;
+ default:
+ die("%s: Invalid regfile param %u\n", __func__, regfile);
+ }
+ uint8_t phys_rank = rank;
+ if (reut_seq_base_addr.raw != 0) {
+ /* Map REUT logical rank to physical rank */
+ const uint32_t log_to_phys = mchbar_read32(REUT_ch_RANK_LOG_TO_PHYS(channel));
+ phys_rank = log_to_phys >> (reut_seq_base_addr.rank_addr * 4) & 0x3;
+ }
+ uint32_t reg = multicast ? DDR_DATA_ch_CONTROL_0(channel) : DQ_CONTROL_0(channel, byte);
+ union ddr_data_control_0_reg ddr_data_control_0 = {
+ .raw = mchbar_read32(reg),
+ };
+ ddr_data_control_0.read_rf_rd = read_rf_rd;
+ ddr_data_control_0.read_rf_wr = read_rf_wr;
+ ddr_data_control_0.read_rf_rank = phys_rank;
+ mchbar_write32(reg, ddr_data_control_0.raw);
+}
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_native.h b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
index 56df36ca8d..7c1a786780 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -117,6 +117,30 @@ enum test_stop {
ALSOE = 3, /* Stop on all lanes error */
};
+enum rxt_subfield {
+ RXT_RCVEN = 0,
+ RXT_RXDQS_P = 1,
+ RXT_RX_EQ = 2,
+ RXT_RXDQS_N = 3,
+ RXT_RX_VREF = 4,
+ RXT_RXDQS_BOTH = 5,
+ RXT_RESTORE = 255,
+};
+
+enum txt_subfield {
+ TXT_TX_DQ = 0,
+ TXT_TXDQS = 1,
+ TXT_TX_EQ = 2,
+ TXT_DQDQS_OFF = 3,
+ TXT_RESTORE = 255,
+};
+
+enum regfile_mode {
+ REG_FILE_USE_RANK, /* Used when changing parameters for each rank */
+ REG_FILE_USE_START, /* Used when changing parameters before the test */
+ REG_FILE_USE_CURRENT, /* Used when changing parameters after the test */
+};
+
struct wdb_pat {
uint32_t start_ptr; /* Starting pointer in WDB */
uint32_t stop_ptr; /* Stopping pointer in WDB */
@@ -452,6 +476,32 @@ uint8_t select_reut_ranks(struct sysinfo *ctrl, uint8_t channel, uint8_t rankmas
void run_mpr_io_test(bool clear_errors);
uint8_t run_io_test(struct sysinfo *ctrl, uint8_t chanmask, uint8_t dq_pat, bool clear_errors);
+void update_rxt(
+ struct sysinfo *ctrl,
+ uint8_t channel,
+ uint8_t rank,
+ uint8_t byte,
+ enum rxt_subfield subfield,
+ int32_t value);
+
+void update_txt(
+ struct sysinfo *ctrl,
+ uint8_t channel,
+ uint8_t rank,
+ uint8_t byte,
+ enum txt_subfield subfield,
+ int32_t value);
+
+void download_regfile(
+ struct sysinfo *ctrl,
+ uint8_t channel,
+ bool multicast,
+ uint8_t rank,
+ enum regfile_mode regfile,
+ uint8_t byte,
+ bool read_rf_rd,
+ bool read_rf_wr);
+
uint8_t get_rx_bias(const struct sysinfo *ctrl);
uint8_t get_tCWL(uint32_t mem_clock_mhz);
diff --git a/src/northbridge/intel/haswell/registers/mchbar.h b/src/northbridge/intel/haswell/registers/mchbar.h
index 817a9f8bf8..a81559bb1e 100644
--- a/src/northbridge/intel/haswell/registers/mchbar.h
+++ b/src/northbridge/intel/haswell/registers/mchbar.h
@@ -15,7 +15,11 @@
/* Register definitions */
/* DDR DATA per-channel per-bytelane */
+#define RX_TRAIN_ch_r_b(ch, rank, byte) _DDRIO_C_R_B(0x0000, ch, rank, byte)
+#define TX_TRAIN_ch_r_b(ch, rank, byte) _DDRIO_C_R_B(0x0020, ch, rank, byte)
+
#define DQ_CONTROL_2(ch, byte) _DDRIO_C_R_B(0x0064, ch, 0, byte)
+#define DQ_CONTROL_0(ch, byte) _DDRIO_C_R_B(0x0074, ch, 0, byte)
/* DDR CKE per-channel */
#define DDR_CKE_ch_CMD_COMP_OFFSET(ch) _DDRIO_C_R_B(0x1204, ch, 0, 0)
@@ -38,6 +42,9 @@
#define DDR_SCRAMBLE_ch(ch) (0x2000 + 4 * (ch))
#define DDR_SCRAM_MISC_CONTROL 0x2008
+/* DDR DATA per-channel multicast */
+#define DDR_DATA_ch_CONTROL_0(ch) _DDRIO_C_R_B(0x3074, ch, 0, 0)
+
/* DDR CMDN/CMDS per-channel (writes go to both CMDN and CMDS fubs) */
#define DDR_CMD_ch_COMP_OFFSET(ch) _DDRIO_C_R_B(0x3204, ch, 0, 0)
#define DDR_CMD_ch_PI_CODING(ch) _DDRIO_C_R_B(0x3208, ch, 0, 0)
@@ -147,6 +154,8 @@
#define REUT_ch_SEQ_ADDR_WRAP(ch) (0x48e8 + 8 * (ch))
+#define REUT_ch_SEQ_ADDR_CURRENT(ch) (0x48f8 + 8 * (ch))
+
#define REUT_ch_SEQ_MISC_CTL(ch) (0x4908 + 4 * (ch))
#define REUT_ch_SEQ_ADDR_INC_CTL(ch) (0x4910 + 8 * (ch))
--
2.39.2
@@ -0,0 +1,708 @@
From 770d4ea91297f76038e9351f4611def640df2137 Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sun, 8 May 2022 00:05:41 +0200
Subject: [PATCH 11/15] haswell NRI: Add RcvEn training
Implement the RcvEn (Receive Enable) calibration procedure.
Change-Id: Ifbfa520f3e0486c56d0988ce67af2ddb9cf29888
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
.../intel/haswell/native_raminit/Makefile.mk | 1 +
.../haswell/native_raminit/raminit_main.c | 1 +
.../haswell/native_raminit/raminit_native.h | 14 +
.../haswell/native_raminit/reg_structs.h | 13 +
.../native_raminit/train_receive_enable.c | 561 ++++++++++++++++++
.../intel/haswell/registers/mchbar.h | 3 +
6 files changed, 593 insertions(+)
create mode 100644 src/northbridge/intel/haswell/native_raminit/train_receive_enable.c
diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.mk b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
index ebe9e9b762..e2fbfb4211 100644
--- a/src/northbridge/intel/haswell/native_raminit/Makefile.mk
+++ b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
@@ -16,3 +16,4 @@ romstage-y += setup_wdb.c
romstage-y += spd_bitmunching.c
romstage-y += testing_io.c
romstage-y += timings_refresh.c
+romstage-y += train_receive_enable.c
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_main.c b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
index 5e4674957d..7d444659c3 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_main.c
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
@@ -60,6 +60,7 @@ static const struct task_entry cold_boot[] = {
{ configure_memory_map, true, "MEMMAP", },
{ do_jedec_init, true, "JEDECINIT", },
{ pre_training, true, "PRETRAIN", },
+ { train_receive_enable, true, "RCVET", },
};
/* 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 7c1a786780..a36ebfacd1 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -42,6 +42,9 @@
#define NUM_WDB_CL_MUX_SEEDS 3
#define NUM_CADB_MUX_SEEDS 3
+/* Specified in PI ticks. 64 PI ticks == 1 qclk */
+#define tDQSCK_DRIFT 64
+
/* ZQ calibration types */
enum {
ZQ_INIT, /* DDR3: ZQCL with tZQinit, LPDDR3: ZQ Init with tZQinit */
@@ -188,6 +191,7 @@ enum raminit_status {
RAMINIT_STATUS_MPLL_INIT_FAILURE,
RAMINIT_STATUS_POLL_TIMEOUT,
RAMINIT_STATUS_REUT_ERROR,
+ RAMINIT_STATUS_RCVEN_FAILURE,
RAMINIT_STATUS_UNSPECIFIED_ERROR, /** TODO: Deprecated in favor of specific values **/
};
@@ -270,6 +274,10 @@ struct sysinfo {
union ddr_data_vref_adjust_reg dimm_vref;
+ uint8_t io_latency[NUM_CHANNELS][NUM_SLOTRANKS];
+ uint8_t rt_latency[NUM_CHANNELS][NUM_SLOTRANKS];
+ uint32_t rt_io_comp[NUM_CHANNELS];
+
uint32_t data_offset_train[NUM_CHANNELS][NUM_LANES];
uint32_t data_offset_comp[NUM_CHANNELS][NUM_LANES];
@@ -344,6 +352,11 @@ static inline void clear_data_offset_train_all(struct sysinfo *ctrl)
memset(ctrl->data_offset_train, 0, sizeof(ctrl->data_offset_train));
}
+static inline uint32_t get_data_train_feedback(const uint8_t channel, const uint8_t byte)
+{
+ return mchbar_read32(DDR_DATA_TRAIN_FEEDBACK(channel, byte));
+}
+
/* Number of ticks to wait in units of 69.841279 ns (citation needed) */
static inline void tick_delay(const uint32_t delay)
{
@@ -401,6 +414,7 @@ enum raminit_status convert_timings(struct sysinfo *ctrl);
enum raminit_status configure_mc(struct sysinfo *ctrl);
enum raminit_status configure_memory_map(struct sysinfo *ctrl);
enum raminit_status do_jedec_init(struct sysinfo *ctrl);
+enum raminit_status train_receive_enable(struct sysinfo *ctrl);
void configure_timings(struct sysinfo *ctrl);
void configure_refresh(struct sysinfo *ctrl);
diff --git a/src/northbridge/intel/haswell/native_raminit/reg_structs.h b/src/northbridge/intel/haswell/native_raminit/reg_structs.h
index b943259b91..b099f4bb82 100644
--- a/src/northbridge/intel/haswell/native_raminit/reg_structs.h
+++ b/src/northbridge/intel/haswell/native_raminit/reg_structs.h
@@ -297,6 +297,19 @@ union ddr_scram_misc_control_reg {
uint32_t raw;
};
+union sc_io_latency_reg {
+ struct __packed {
+ uint32_t iolat_rank0 : 4; // Bits 3:0
+ uint32_t iolat_rank1 : 4; // Bits 7:4
+ uint32_t iolat_rank2 : 4; // Bits 11:8
+ uint32_t iolat_rank3 : 4; // Bits 15:12
+ uint32_t rt_iocomp : 6; // Bits 21:16
+ uint32_t : 9; // Bits 30:22
+ uint32_t dis_rt_clk_gate : 1; // Bits 31:31
+ };
+ uint32_t raw;
+};
+
union mcscheds_cbit_reg {
struct __packed {
uint32_t dis_opp_cas : 1; // Bits 0:0
diff --git a/src/northbridge/intel/haswell/native_raminit/train_receive_enable.c b/src/northbridge/intel/haswell/native_raminit/train_receive_enable.c
new file mode 100644
index 0000000000..576c6bc21e
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/train_receive_enable.c
@@ -0,0 +1,561 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <console/console.h>
+#include <northbridge/intel/haswell/haswell.h>
+#include <types.h>
+
+#include "raminit_native.h"
+#include "ranges.h"
+
+#define RCVEN_PLOT RAM_DEBUG
+
+static enum raminit_status change_rcven_timing(struct sysinfo *ctrl, const uint8_t channel)
+{
+ int16_t max_rcven = -4096;
+ int16_t min_rcven = 4096;
+ int16_t max_rcven_rank[NUM_SLOTRANKS];
+ int16_t min_rcven_rank[NUM_SLOTRANKS];
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ max_rcven_rank[rank] = max_rcven;
+ min_rcven_rank[rank] = min_rcven;
+ }
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ int16_t new_rcven = ctrl->rcven[channel][rank][byte];
+ new_rcven -= ctrl->io_latency[channel][rank] * 64;
+ if (max_rcven_rank[rank] < new_rcven)
+ max_rcven_rank[rank] = new_rcven;
+
+ if (min_rcven_rank[rank] > new_rcven)
+ min_rcven_rank[rank] = new_rcven;
+ }
+ if (max_rcven < max_rcven_rank[rank])
+ max_rcven = max_rcven_rank[rank];
+
+ if (min_rcven > min_rcven_rank[rank])
+ min_rcven = min_rcven_rank[rank];
+ }
+
+ /*
+ * Determine how far we are from the ideal center point for RcvEn timing.
+ * (PiIdeal - AveRcvEn) / 64 is the ideal number of cycles we should have
+ * for IO latency. command training will reduce this by 64, so plan for
+ * that now in the ideal value. Round to closest integer.
+ */
+ const int16_t rre_pi_ideal = 256 + 64;
+ const int16_t pi_reserve = 64;
+ const int16_t rcven_center = (max_rcven + min_rcven) / 2;
+ const int8_t iolat_target = DIV_ROUND_CLOSEST(rre_pi_ideal - rcven_center, 64);
+
+ int8_t io_g_offset = 0;
+ int8_t io_lat[NUM_SLOTRANKS] = { 0 };
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ io_lat[rank] = iolat_target;
+
+ /* Check for RcvEn underflow/overflow */
+ const int16_t rcven_lower = 64 * io_lat[rank] + min_rcven_rank[rank];
+ if (rcven_lower < pi_reserve)
+ io_lat[rank] += DIV_ROUND_UP(pi_reserve - rcven_lower, 64);
+
+ const int16_t rcven_upper = 64 * io_lat[rank] + max_rcven_rank[rank];
+ if (rcven_upper > 511 - pi_reserve)
+ io_lat[rank] -= DIV_ROUND_UP(rcven_upper - (511 - pi_reserve), 64);
+
+ /* Check for IO latency over/underflow */
+ if (io_lat[rank] - io_g_offset > 14)
+ io_g_offset = io_lat[rank] - 14;
+
+ if (io_lat[rank] - io_g_offset < 1)
+ io_g_offset = io_lat[rank] - 1;
+
+ const int8_t cycle_offset = io_lat[rank] - ctrl->io_latency[channel][rank];
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ ctrl->rcven[channel][rank][byte] += 64 * cycle_offset;
+ update_rxt(ctrl, channel, rank, byte, RXT_RESTORE, 0);
+ }
+ }
+
+ /* Calculate new IO comp latency */
+ union sc_io_latency_reg sc_io_lat = {
+ .raw = mchbar_read32(SC_IO_LATENCY_ch(channel)),
+ };
+
+ /* Check if we are underflowing or overflowing this field */
+ if (io_g_offset < 0 && sc_io_lat.rt_iocomp < -io_g_offset) {
+ printk(BIOS_ERR, "%s: IO COMP underflow\n", __func__);
+ printk(BIOS_ERR, "io_g_offset: %d\n", io_g_offset);
+ printk(BIOS_ERR, "rt_iocomp: %u\n", sc_io_lat.rt_iocomp);
+ return RAMINIT_STATUS_RCVEN_FAILURE;
+ }
+ if (io_g_offset > 0 && io_g_offset > 0x3f - sc_io_lat.rt_iocomp) {
+ printk(BIOS_ERR, "%s: IO COMP overflow\n", __func__);
+ printk(BIOS_ERR, "io_g_offset: %d\n", io_g_offset);
+ printk(BIOS_ERR, "rt_iocomp: %u\n", sc_io_lat.rt_iocomp);
+ return RAMINIT_STATUS_RCVEN_FAILURE;
+ }
+ sc_io_lat.rt_iocomp += io_g_offset;
+ ctrl->rt_io_comp[channel] = sc_io_lat.rt_iocomp;
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ if (ctrl->rankmap[channel] & BIT(rank))
+ ctrl->io_latency[channel][rank] = io_lat[rank] - io_g_offset;
+
+ const uint8_t shift = rank * 4;
+ sc_io_lat.raw &= ~(0xf << shift);
+ sc_io_lat.raw |= ctrl->io_latency[channel][rank] << shift;
+ }
+ mchbar_write32(SC_IO_LATENCY_ch(channel), sc_io_lat.raw);
+ return RAMINIT_STATUS_SUCCESS;
+}
+
+#define RL_START (256 + 24)
+#define RL_STOP (384 + 24)
+#define RL_STEP 8
+
+#define RE_NUM_SAMPLES 6
+
+static enum raminit_status verify_high_region(const int32_t center, const int32_t lwidth)
+{
+ if (center > RL_STOP) {
+ /* Check if center of high was found where it should be */
+ printk(BIOS_ERR, "RcvEn: Center of high (%d) higher than expected\n", center);
+ return RAMINIT_STATUS_RCVEN_FAILURE;
+ }
+ if (lwidth <= 32) {
+ /* Check if width is large enough */
+ printk(BIOS_ERR, "RcvEn: Width of high region (%d) too small\n", lwidth);
+ return RAMINIT_STATUS_RCVEN_FAILURE;
+ }
+ if (lwidth >= 96) {
+ /* Since we're calibrating a phase, a too large region is a problem */
+ printk(BIOS_ERR, "RcvEn: Width of high region (%d) too large\n", lwidth);
+ return RAMINIT_STATUS_RCVEN_FAILURE;
+ }
+ return RAMINIT_STATUS_SUCCESS;
+}
+
+static void program_io_latency(struct sysinfo *ctrl, const uint8_t channel, const uint8_t rank)
+{
+ const uint8_t shift = rank * 4;
+ const uint8_t iolat = ctrl->io_latency[channel][rank];
+ mchbar_clrsetbits32(SC_IO_LATENCY_ch(channel), 0xf << shift, iolat << shift);
+}
+
+static void program_rl_delays(struct sysinfo *ctrl, const uint8_t rank, const uint16_t rl_delay)
+{
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ update_rxt(ctrl, channel, rank, byte, RXT_RCVEN, rl_delay);
+ }
+}
+
+static bool sample_dqs(const uint8_t channel, const uint8_t byte)
+{
+ return (get_data_train_feedback(channel, byte) & 0x1ff) >= BIT(RE_NUM_SAMPLES - 1);
+}
+
+enum raminit_status train_receive_enable(struct sysinfo *ctrl)
+{
+ const struct reut_box reut_addr = {
+ .col = {
+ .start = 0,
+ .stop = 1023,
+ .inc_rate = 0,
+ .inc_val = 1,
+ },
+ };
+ const struct wdb_pat wdb_pattern = {
+ .start_ptr = 0,
+ .stop_ptr = 9,
+ .inc_rate = 32,
+ .dq_pattern = BASIC_VA,
+ };
+
+ const uint16_t bytemask = BIT(ctrl->lanes) - 1;
+ const uint8_t fine_step = 1;
+
+ const uint8_t rt_delta = is_hsw_ult() ? 4 : 2;
+ const uint8_t rt_io_comp = 21 + rt_delta;
+ const uint8_t rt_latency = 16 + rt_delta;
+ setup_io_test(
+ ctrl,
+ ctrl->chanmap,
+ PAT_RD,
+ 2,
+ RE_NUM_SAMPLES + 1,
+ &reut_addr,
+ 0,
+ &wdb_pattern,
+ 0,
+ 8);
+
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ union ddr_data_control_2_reg data_control_2 = {
+ .raw = ctrl->dq_control_2[channel][byte],
+ };
+ data_control_2.force_rx_on = 1;
+ mchbar_write32(DQ_CONTROL_2(channel, byte), data_control_2.raw);
+ }
+ union ddr_data_control_0_reg data_control_0 = {
+ .raw = ctrl->dq_control_0[channel],
+ };
+ if (ctrl->lpddr) {
+ /**
+ * W/A for b4618574 - @todo: remove for HSW ULT C0
+ * Can't have force_odt_on together with leaker, disable LPDDR
+ * mode during this training step. lpddr_mode is restored
+ * at the end of this function from the host structure.
+ */
+ data_control_0.lpddr_mode = 0;
+ mchbar_write32(DDR_DATA_ch_CONTROL_0(channel), data_control_0.raw);
+ }
+ data_control_0.force_odt_on = 1;
+ data_control_0.rl_training_mode = 1;
+ mchbar_write32(DDR_DATA_ch_CONTROL_0(channel), data_control_0.raw);
+ mchbar_write32(SC_IO_LATENCY_ch(channel), (union sc_io_latency_reg) {
+ .rt_iocomp = rt_io_comp,
+ }.raw);
+ }
+ enum raminit_status status = RAMINIT_STATUS_SUCCESS;
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ if (!does_rank_exist(ctrl, rank))
+ continue;
+
+ /*
+ * Set initial roundtrip latency values. Assume -4 QCLK for worst board
+ * layout. This is calculated as HW_ROUNDT_LAT_DEFAULT_VALUE plus:
+ *
+ * DDR3: Default + (2 * tAA) + 4 QCLK + PI_CLK + N-mode value * 2
+ * LPDDR3: Default + (2 * tAA) + 4 QCLK + PI_CLK + tDQSCK_max
+ *
+ * N-mode is 3 during training mode. Both channels use the same timings.
+ */
+ /** TODO: differs for LPDDR **/
+ const uint32_t tmp = MAX(ctrl->multiplier, 4) + 5 + 2 * ctrl->tAA;
+ const uint32_t initial_rt_latency = MIN(rt_latency + tmp, 0x3f);
+
+ uint8_t chanmask = 0;
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ chanmask |= select_reut_ranks(ctrl, channel, BIT(rank));
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ ctrl->io_latency[channel][rank] = 0;
+ mchbar_write8(SC_ROUNDT_LAT_ch(channel) + rank, initial_rt_latency);
+ ctrl->rt_latency[channel][rank] = initial_rt_latency;
+ }
+
+ printk(BIOS_DEBUG, "Rank %u\n", rank);
+ printk(BIOS_DEBUG, "Steps 1 and 2: Find middle of high region\n");
+ printk(RCVEN_PLOT, "Byte");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(RCVEN_PLOT, "\t");
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ printk(RCVEN_PLOT, "%u ", byte);
+ }
+ printk(RCVEN_PLOT, "\nRcvEn\n");
+ struct phase_train_data region_data[NUM_CHANNELS][NUM_LANES] = { 0 };
+ for (uint16_t rl_delay = RL_START; rl_delay < RL_STOP; rl_delay += RL_STEP) {
+ printk(RCVEN_PLOT, " % 3d", rl_delay);
+ program_rl_delays(ctrl, rank, rl_delay);
+ run_io_test(ctrl, chanmask, BASIC_VA, true);
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(RCVEN_PLOT, "\t");
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ const bool high = sample_dqs(channel, byte);
+ printk(RCVEN_PLOT, high ? ". " : "# ");
+ phase_record_pass(
+ &region_data[channel][byte],
+ high,
+ rl_delay,
+ RL_START,
+ RL_STEP);
+ }
+ }
+ printk(RCVEN_PLOT, "\n");
+ }
+ printk(RCVEN_PLOT, "\n");
+ printk(BIOS_DEBUG, "Update RcvEn timing to be in the center of high region\n");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(BIOS_DEBUG, "C%u.R%u: \tLeft\tRight\tWidth\tCenter\n",
+ channel, rank);
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ struct phase_train_data *const curr_data =
+ &region_data[channel][byte];
+ phase_append_current_to_initial(curr_data, RL_START, RL_STEP);
+ const int32_t lwidth = range_width(curr_data->largest);
+ const int32_t center = range_center(curr_data->largest);
+ printk(BIOS_DEBUG, " B%u: \t%d\t%d\t%d\t%d\n",
+ byte,
+ curr_data->largest.start,
+ curr_data->largest.end,
+ lwidth,
+ center);
+
+ status = verify_high_region(center, lwidth);
+ if (status) {
+ printk(BIOS_ERR,
+ "RcvEn problems on channel %u, byte %u\n",
+ channel, byte);
+ goto clean_up;
+ }
+ ctrl->rcven[channel][rank][byte] = center;
+ update_rxt(ctrl, channel, rank, byte, RXT_RESTORE, 0);
+ }
+ printk(BIOS_DEBUG, "\n");
+ }
+
+ printk(BIOS_DEBUG, "Step 3: Quarter preamble - Walk backwards\n");
+ printk(RCVEN_PLOT, "Byte");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(RCVEN_PLOT, "\t");
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ printk(RCVEN_PLOT, "%u ", byte);
+ }
+ printk(RCVEN_PLOT, "\nIOLAT\n");
+ bool done = false;
+ while (!done) {
+ run_io_test(ctrl, chanmask, BASIC_VA, true);
+ done = true;
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(RCVEN_PLOT, " %2u\t", ctrl->io_latency[channel][rank]);
+ uint16_t highs = 0;
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ const bool high = sample_dqs(channel, byte);
+ printk(RCVEN_PLOT, high ? "H " : "L ");
+ if (high)
+ highs |= BIT(byte);
+ }
+ if (!highs)
+ continue;
+
+ done = false;
+
+ /* If all bytes sample high, adjust timing globally */
+ if (highs == bytemask && ctrl->io_latency[channel][rank] < 14) {
+ ctrl->io_latency[channel][rank] += 2;
+ ctrl->io_latency[channel][rank] %= 16;
+ program_io_latency(ctrl, channel, rank);
+ continue;
+ }
+
+ /* Otherwise, adjust individual bytes */
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ if (!(highs & BIT(byte)))
+ continue;
+
+ if (ctrl->rcven[channel][rank][byte] < 128) {
+ printk(BIOS_ERR,
+ "RcvEn underflow: walking backwards\n");
+ printk(BIOS_ERR,
+ "For channel %u, rank %u, byte %u\n",
+ channel, rank, byte);
+ status = RAMINIT_STATUS_RCVEN_FAILURE;
+ goto clean_up;
+ }
+ ctrl->rcven[channel][rank][byte] -= 128;
+ update_rxt(ctrl, channel, rank, byte, RXT_RESTORE, 0);
+ }
+ }
+ printk(RCVEN_PLOT, "\n");
+ }
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(BIOS_DEBUG, "\nC%u: Preamble\n", channel);
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ printk(BIOS_DEBUG,
+ " B%u: %u\n", byte, ctrl->rcven[channel][rank][byte]);
+ }
+ }
+ printk(BIOS_DEBUG, "\n");
+
+ printk(BIOS_DEBUG, "Step 4: Add 1 qclk\n");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ ctrl->rcven[channel][rank][byte] += 64;
+ update_rxt(ctrl, channel, rank, byte, RXT_RESTORE, 0);
+ }
+ }
+ printk(BIOS_DEBUG, "\n");
+
+ printk(BIOS_DEBUG, "Step 5: Walk forward to find rising edge\n");
+ printk(RCVEN_PLOT, "Byte");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(RCVEN_PLOT, "\t");
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ printk(RCVEN_PLOT, "%u ", byte);
+ }
+ printk(RCVEN_PLOT, "\n inc\n");
+ uint16_t ch_result[NUM_CHANNELS] = { 0 };
+ uint8_t inc_preamble[NUM_CHANNELS][NUM_LANES] = { 0 };
+ for (uint8_t inc = 0; inc < 64; inc += fine_step) {
+ printk(RCVEN_PLOT, " %2u\t", inc);
+ run_io_test(ctrl, chanmask, BASIC_VA, true);
+ done = true;
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ if (ch_result[channel] & BIT(byte)) {
+ /* Skip bytes that are already done */
+ printk(RCVEN_PLOT, ". ");
+ continue;
+ }
+ const bool pass = sample_dqs(channel, byte);
+ printk(RCVEN_PLOT, pass ? ". " : "# ");
+ if (pass) {
+ ch_result[channel] |= BIT(byte);
+ continue;
+ }
+ ctrl->rcven[channel][rank][byte] += fine_step;
+ update_rxt(ctrl, channel, rank, byte, RXT_RESTORE, 0);
+ inc_preamble[channel][byte] = inc;
+ }
+ printk(RCVEN_PLOT, "\t");
+ if (ch_result[channel] != bytemask)
+ done = false;
+ }
+ printk(RCVEN_PLOT, "\n");
+ if (done)
+ break;
+ }
+ printk(BIOS_DEBUG, "\n");
+ if (!done) {
+ printk(BIOS_ERR, "Error: Preamble edge not found for all bytes\n");
+ printk(BIOS_ERR, "The final RcvEn results are as follows:\n");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(BIOS_ERR, "Channel %u Rank %u: preamble\n",
+ channel, rank);
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ printk(BIOS_ERR, " Byte %u: %u%s\n", byte,
+ ctrl->rcven[channel][rank][byte],
+ (ch_result[channel] ^ bytemask) & BIT(byte)
+ ? ""
+ : " *** Check this byte! ***");
+ }
+ }
+ status = RAMINIT_STATUS_RCVEN_FAILURE;
+ goto clean_up;
+ }
+
+ printk(BIOS_DEBUG, "Step 6: center on preamble and clean up rank\n");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(BIOS_DEBUG, "C%u: Preamble increment\n", channel);
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ /*
+ * For Traditional, pull in RcvEn by 64. For ULT, take the DQS
+ * drift into account to the specified guardband: tDQSCK_DRIFT.
+ */
+ ctrl->rcven[channel][rank][byte] -= tDQSCK_DRIFT;
+ update_rxt(ctrl, channel, rank, byte, RXT_RESTORE, 0);
+ printk(BIOS_DEBUG, " B%u: %u %u\n", byte,
+ ctrl->rcven[channel][rank][byte],
+ inc_preamble[channel][byte]);
+ }
+ printk(BIOS_DEBUG, "\n");
+ }
+ printk(BIOS_DEBUG, "\n");
+ }
+
+clean_up:
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ if (ctrl->lpddr) {
+ /**
+ * W/A for b4618574 - @todo: remove for HSW ULT C0
+ * Can't have force_odt_on together with leaker, disable LPDDR mode for
+ * this training step. This write will disable force_odt_on while still
+ * keeping LPDDR mode disabled. Second write will restore LPDDR mode.
+ */
+ union ddr_data_control_0_reg data_control_0 = {
+ .raw = ctrl->dq_control_0[channel],
+ };
+ data_control_0.lpddr_mode = 0;
+ mchbar_write32(DDR_DATA_ch_CONTROL_0(channel), data_control_0.raw);
+ }
+ mchbar_write32(DDR_DATA_ch_CONTROL_0(channel), ctrl->dq_control_0[channel]);
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ mchbar_write32(DQ_CONTROL_2(channel, byte),
+ ctrl->dq_control_2[channel][byte]);
+ }
+ }
+ io_reset();
+ if (status)
+ return status;
+
+ printk(BIOS_DEBUG, "Step 7: Sync IO latency across all ranks\n");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ status = change_rcven_timing(ctrl, channel);
+ if (status)
+ return status;
+ }
+ printk(BIOS_DEBUG, "\nFinal Receive Enable and IO latency settings:\n");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ const union sc_io_latency_reg sc_io_latency = {
+ .raw = mchbar_read32(SC_IO_LATENCY_ch(channel)),
+ };
+ printk(BIOS_DEBUG, " C%u.R%u: IOLAT = %u rt_iocomp = %u\n", channel,
+ rank, ctrl->io_latency[channel][rank], sc_io_latency.rt_iocomp);
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ printk(BIOS_DEBUG, " B%u: %u\n", byte,
+ ctrl->rcven[channel][rank][byte]);
+ }
+ printk(BIOS_DEBUG, "\n");
+ }
+ }
+ return status;
+}
diff --git a/src/northbridge/intel/haswell/registers/mchbar.h b/src/northbridge/intel/haswell/registers/mchbar.h
index a81559bb1e..9172d4f2b0 100644
--- a/src/northbridge/intel/haswell/registers/mchbar.h
+++ b/src/northbridge/intel/haswell/registers/mchbar.h
@@ -18,6 +18,8 @@
#define RX_TRAIN_ch_r_b(ch, rank, byte) _DDRIO_C_R_B(0x0000, ch, rank, byte)
#define TX_TRAIN_ch_r_b(ch, rank, byte) _DDRIO_C_R_B(0x0020, ch, rank, byte)
+#define DDR_DATA_TRAIN_FEEDBACK(ch, byte) _DDRIO_C_R_B(0x0054, ch, 0, byte)
+
#define DQ_CONTROL_2(ch, byte) _DDRIO_C_R_B(0x0064, ch, 0, byte)
#define DQ_CONTROL_0(ch, byte) _DDRIO_C_R_B(0x0074, ch, 0, byte)
@@ -100,6 +102,7 @@
#define COMMAND_RATE_LIMIT_ch(ch) _MCMAIN_C(0x4010, ch)
#define TC_BANK_RANK_D_ch(ch) _MCMAIN_C(0x4014, ch)
#define SC_ROUNDT_LAT_ch(ch) _MCMAIN_C(0x4024, ch)
+#define SC_IO_LATENCY_ch(ch) _MCMAIN_C(0x4028, ch)
#define REUT_ch_PAT_WDB_CL_MUX_CFG(ch) _MCMAIN_C(0x4040, ch)
--
2.39.2
@@ -0,0 +1,272 @@
From be7b1c72cff3403ecfd746255fccb80c8cee6403 Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sun, 8 May 2022 11:58:59 +0200
Subject: [PATCH 12/15] haswell NRI: Add function to change margins
Implement a function to change margin parameters. Haswell provides a
register to apply an offset to margin parameters during training, so
make use of it. There are other margin parameters that have not been
implemented yet, as they are not needed for now and special handling
is needed to provide offset training functionality.
Change-Id: I5392380e13de3c44e77b7bc9f3b819e2661d1e2d
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
.../haswell/native_raminit/change_margin.c | 136 ++++++++++++++++++
.../haswell/native_raminit/raminit_native.h | 39 +++++
.../haswell/native_raminit/reg_structs.h | 12 ++
.../intel/haswell/registers/mchbar.h | 1 +
4 files changed, 188 insertions(+)
diff --git a/src/northbridge/intel/haswell/native_raminit/change_margin.c b/src/northbridge/intel/haswell/native_raminit/change_margin.c
index 12da59580f..4ba9cfa5c6 100644
--- a/src/northbridge/intel/haswell/native_raminit/change_margin.c
+++ b/src/northbridge/intel/haswell/native_raminit/change_margin.c
@@ -1,5 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
+#include <assert.h>
#include <commonlib/clamp.h>
#include <console/console.h>
#include <delay.h>
@@ -152,3 +153,138 @@ void download_regfile(
ddr_data_control_0.read_rf_rank = phys_rank;
mchbar_write32(reg, ddr_data_control_0.raw);
}
+
+static void update_data_offset_train(
+ struct sysinfo *ctrl,
+ const uint8_t param,
+ const uint8_t en_multicast,
+ const uint8_t channel_in,
+ const uint8_t rank,
+ const uint8_t byte_in,
+ const bool update_ctrl,
+ const enum regfile_mode regfile,
+ const uint32_t value)
+{
+ bool is_rd = false;
+ bool is_wr = false;
+ switch (param) {
+ case RdT:
+ case RdV:
+ case RcvEna:
+ is_rd = true;
+ break;
+ case WrT:
+ case WrDqsT:
+ is_wr = true;
+ break;
+ default:
+ die("%s: Invalid margin parameter %u\n", __func__, param);
+ }
+ if (en_multicast) {
+ mchbar_write32(DDR_DATA_OFFSET_TRAIN, value);
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ download_regfile(ctrl, channel, true, rank, regfile, 0, is_rd, is_wr);
+ if (update_ctrl) {
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ ctrl->data_offset_train[channel][byte] = value;
+ }
+ }
+ } else {
+ mchbar_write32(DDR_DATA_OFFSET_TRAIN_ch_b(channel_in, byte_in), value);
+ download_regfile(ctrl, channel_in, false, rank, regfile, byte_in, is_rd, is_wr);
+ if (update_ctrl)
+ ctrl->data_offset_train[channel_in][byte_in] = value;
+ }
+}
+
+static uint32_t get_max_margin(const enum margin_parameter param)
+{
+ switch (param) {
+ case RcvEna:
+ case RdT:
+ case WrT:
+ case WrDqsT:
+ return MAX_POSSIBLE_TIME;
+ case RdV:
+ return MAX_POSSIBLE_VREF;
+ default:
+ die("%s: Invalid margin parameter %u\n", __func__, param);
+ }
+}
+
+void change_margin(
+ struct sysinfo *ctrl,
+ const enum margin_parameter param,
+ const int32_t value0,
+ const bool en_multicast,
+ const uint8_t channel,
+ const uint8_t rank,
+ const uint8_t byte,
+ const bool update_ctrl,
+ const enum regfile_mode regfile)
+{
+ /** FIXME: Remove this **/
+ if (rank == 0xff)
+ die("%s: rank is 0xff\n", __func__);
+
+ if (!en_multicast && !does_ch_exist(ctrl, channel))
+ die("%s: Tried to change margin of empty channel %u\n", __func__, channel);
+
+ const uint32_t max_value = get_max_margin(param);
+ const int32_t v0 = clamp_s32(-max_value, value0, max_value);
+
+ union ddr_data_offset_train_reg ddr_data_offset_train = {
+ .raw = en_multicast ? 0 : ctrl->data_offset_train[channel][byte],
+ };
+ bool update_offset_train = false;
+ switch (param) {
+ case RcvEna:
+ ddr_data_offset_train.rcven = v0;
+ update_offset_train = true;
+ break;
+ case RdT:
+ ddr_data_offset_train.rx_dqs = v0;
+ update_offset_train = true;
+ break;
+ case WrT:
+ ddr_data_offset_train.tx_dq = v0;
+ update_offset_train = true;
+ break;
+ case WrDqsT:
+ ddr_data_offset_train.tx_dqs = v0;
+ update_offset_train = true;
+ break;
+ case RdV:
+ ddr_data_offset_train.vref = v0;
+ update_offset_train = true;
+ break;
+ default:
+ die("%s: Invalid margin parameter %u\n", __func__, param);
+ }
+ if (update_offset_train) {
+ update_data_offset_train(
+ ctrl,
+ param,
+ en_multicast,
+ channel,
+ rank,
+ byte,
+ update_ctrl,
+ regfile,
+ ddr_data_offset_train.raw);
+ }
+}
+
+void change_1d_margin_multicast(
+ struct sysinfo *ctrl,
+ const enum margin_parameter param,
+ const int32_t value0,
+ const uint8_t rank,
+ const bool update_ctrl,
+ const enum regfile_mode regfile)
+{
+ change_margin(ctrl, param, value0, true, 0, rank, 0, update_ctrl, regfile);
+}
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_native.h b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
index a36ebfacd1..500fc28909 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -35,6 +35,18 @@
#define RTTNOM_MASK (BIT(9) | BIT(6) | BIT(2))
+/* Margin parameter limits */
+#define MAX_POSSIBLE_TIME 31
+#define MAX_POSSIBLE_VREF 54
+
+#define MAX_POSSIBLE_BOTH MAX_POSSIBLE_VREF
+
+#define MIN_TIME (-MAX_POSSIBLE_TIME)
+#define MAX_TIME (MAX_POSSIBLE_TIME)
+
+#define MIN_VREF (-MAX_POSSIBLE_VREF)
+#define MAX_VREF (MAX_POSSIBLE_VREF)
+
#define BASIC_VA_PAT_SPREAD_8 0x01010101
#define WDB_CACHE_LINE_SIZE 8
@@ -45,6 +57,14 @@
/* Specified in PI ticks. 64 PI ticks == 1 qclk */
#define tDQSCK_DRIFT 64
+enum margin_parameter {
+ RcvEna,
+ RdT,
+ WrT,
+ WrDqsT,
+ RdV,
+};
+
/* ZQ calibration types */
enum {
ZQ_INIT, /* DDR3: ZQCL with tZQinit, LPDDR3: ZQ Init with tZQinit */
@@ -516,6 +536,25 @@ void download_regfile(
bool read_rf_rd,
bool read_rf_wr);
+void change_margin(
+ struct sysinfo *ctrl,
+ const enum margin_parameter param,
+ const int32_t value0,
+ const bool en_multicast,
+ const uint8_t channel,
+ const uint8_t rank,
+ const uint8_t byte,
+ const bool update_ctrl,
+ const enum regfile_mode regfile);
+
+void change_1d_margin_multicast(
+ struct sysinfo *ctrl,
+ const enum margin_parameter param,
+ const int32_t value0,
+ const uint8_t rank,
+ const bool update_ctrl,
+ const enum regfile_mode regfile);
+
uint8_t get_rx_bias(const struct sysinfo *ctrl);
uint8_t get_tCWL(uint32_t mem_clock_mhz);
diff --git a/src/northbridge/intel/haswell/native_raminit/reg_structs.h b/src/northbridge/intel/haswell/native_raminit/reg_structs.h
index b099f4bb82..a0e36ed082 100644
--- a/src/northbridge/intel/haswell/native_raminit/reg_structs.h
+++ b/src/northbridge/intel/haswell/native_raminit/reg_structs.h
@@ -25,6 +25,18 @@ union ddr_data_tx_train_rank_reg {
uint32_t raw;
};
+union ddr_data_offset_train_reg {
+ struct __packed {
+ int32_t rcven : 6; // Bits 5:0
+ int32_t rx_dqs : 6; // Bits 11:6
+ int32_t tx_dq : 6; // Bits 17:12
+ int32_t tx_dqs : 6; // Bits 23:18
+ int32_t vref : 7; // Bits 30:24
+ int32_t : 1; // Bits 31:31
+ };
+ uint32_t raw;
+};
+
union ddr_data_control_0_reg {
struct __packed {
uint32_t rx_training_mode : 1; // Bits 0:0
diff --git a/src/northbridge/intel/haswell/registers/mchbar.h b/src/northbridge/intel/haswell/registers/mchbar.h
index 9172d4f2b0..0acafbc826 100644
--- a/src/northbridge/intel/haswell/registers/mchbar.h
+++ b/src/northbridge/intel/haswell/registers/mchbar.h
@@ -21,6 +21,7 @@
#define DDR_DATA_TRAIN_FEEDBACK(ch, byte) _DDRIO_C_R_B(0x0054, ch, 0, byte)
#define DQ_CONTROL_2(ch, byte) _DDRIO_C_R_B(0x0064, ch, 0, byte)
+#define DDR_DATA_OFFSET_TRAIN_ch_b(ch, byte) _DDRIO_C_R_B(0x0070, ch, 0, byte)
#define DQ_CONTROL_0(ch, byte) _DDRIO_C_R_B(0x0074, ch, 0, byte)
/* DDR CKE per-channel */
--
2.39.2
@@ -0,0 +1,331 @@
From ec3dd2a40834e363a5ddaef5e8093e1baa1af56e Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sun, 8 May 2022 11:35:49 +0200
Subject: [PATCH 13/15] haswell NRI: Add read MPR training
Implement read training using DDR3 MPR (Multi-Purpose Register).
Change-Id: Id17cb2c4c399ac9bcc937b595b58f863c152461b
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
.../intel/haswell/native_raminit/Makefile.mk | 1 +
.../haswell/native_raminit/raminit_main.c | 1 +
.../haswell/native_raminit/raminit_native.h | 4 +
.../haswell/native_raminit/train_read_mpr.c | 240 ++++++++++++++++++
.../intel/haswell/registers/mchbar.h | 2 +-
5 files changed, 247 insertions(+), 1 deletion(-)
create mode 100644 src/northbridge/intel/haswell/native_raminit/train_read_mpr.c
diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.mk b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
index e2fbfb4211..c442be0728 100644
--- a/src/northbridge/intel/haswell/native_raminit/Makefile.mk
+++ b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
@@ -16,4 +16,5 @@ romstage-y += setup_wdb.c
romstage-y += spd_bitmunching.c
romstage-y += testing_io.c
romstage-y += timings_refresh.c
+romstage-y += train_read_mpr.c
romstage-y += train_receive_enable.c
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_main.c b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
index 7d444659c3..264d1468f5 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_main.c
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
@@ -61,6 +61,7 @@ static const struct task_entry cold_boot[] = {
{ do_jedec_init, true, "JEDECINIT", },
{ pre_training, true, "PRETRAIN", },
{ train_receive_enable, true, "RCVET", },
+ { train_read_mpr, true, "RDMPRT", },
};
/* 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 500fc28909..a7551ad63c 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -27,6 +27,8 @@
/* Always use 12 legs for emphasis (not trained) */
#define TXEQFULLDRV (3 << 4)
+#define LOOPCOUNT_INFINITE 0xff
+
/* DDR3 mode register bits */
#define MR0_DLL_RESET BIT(8)
@@ -212,6 +214,7 @@ enum raminit_status {
RAMINIT_STATUS_POLL_TIMEOUT,
RAMINIT_STATUS_REUT_ERROR,
RAMINIT_STATUS_RCVEN_FAILURE,
+ RAMINIT_STATUS_RMPR_FAILURE,
RAMINIT_STATUS_UNSPECIFIED_ERROR, /** TODO: Deprecated in favor of specific values **/
};
@@ -435,6 +438,7 @@ enum raminit_status configure_mc(struct sysinfo *ctrl);
enum raminit_status configure_memory_map(struct sysinfo *ctrl);
enum raminit_status do_jedec_init(struct sysinfo *ctrl);
enum raminit_status train_receive_enable(struct sysinfo *ctrl);
+enum raminit_status train_read_mpr(struct sysinfo *ctrl);
void configure_timings(struct sysinfo *ctrl);
void configure_refresh(struct sysinfo *ctrl);
diff --git a/src/northbridge/intel/haswell/native_raminit/train_read_mpr.c b/src/northbridge/intel/haswell/native_raminit/train_read_mpr.c
new file mode 100644
index 0000000000..0225e1a384
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/train_read_mpr.c
@@ -0,0 +1,240 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <commonlib/clamp.h>
+#include <console/console.h>
+#include <delay.h>
+#include <northbridge/intel/haswell/haswell.h>
+#include <types.h>
+
+#include "raminit_native.h"
+#include "ranges.h"
+
+#define RMPR_START (-32)
+#define RMPR_STOP (32)
+#define RMPR_STEP 1
+
+#define RMPR_MIN_WIDTH 12
+
+#define RMPR_PLOT RAM_DEBUG
+
+/*
+ * Clear rx_training_mode. For LPDDR, we first need to disable odt_samp_extend_en,
+ * then disable rx_training_mode, and finally re-enable odt_samp_extend_en.
+ */
+static void clear_rx_training_mode(struct sysinfo *ctrl, const uint8_t channel)
+{
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ mchbar_write32(DQ_CONTROL_2(channel, byte), ctrl->dq_control_2[channel][byte]);
+
+ if (ctrl->lpddr) {
+ union ddr_data_control_0_reg data_control_0 = {
+ .raw = mchbar_read32(DDR_DATA_ch_CONTROL_0(channel)),
+ };
+ data_control_0.odt_samp_extend_en = 0;
+ mchbar_write32(DDR_DATA_ch_CONTROL_0(channel), data_control_0.raw);
+ tick_delay(1);
+ data_control_0.rx_training_mode = 0;
+ mchbar_write32(DDR_DATA_ch_CONTROL_0(channel), data_control_0.raw);
+ tick_delay(1);
+ }
+ mchbar_write32(DDR_DATA_ch_CONTROL_0(channel), ctrl->dq_control_0[channel]);
+}
+
+static void set_rxdqs_edges_to_midpoint(struct sysinfo *ctrl)
+{
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ update_rxt(ctrl, channel, rank, byte, RXT_RXDQS_BOTH, 32);
+ }
+ }
+}
+
+static void enter_mpr_train_ddr_mode(struct sysinfo *ctrl, const uint8_t rank)
+{
+ /* Program MR3 and mask RAS/WE to prevent scheduler from issuing non-read commands */
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ if (!ctrl->lpddr)
+ reut_issue_mrs(ctrl, channel, BIT(rank), 3, 1 << 2);
+
+ union reut_misc_odt_ctrl_reg reut_misc_odt_ctrl = {
+ .raw = mchbar_read32(REUT_ch_MISC_ODT_CTRL(channel)),
+ };
+ reut_misc_odt_ctrl.mpr_train_ddr_on = 1;
+ mchbar_write32(REUT_ch_MISC_ODT_CTRL(channel), reut_misc_odt_ctrl.raw);
+ }
+}
+
+static void leave_mpr_train_ddr_mode(struct sysinfo *ctrl, const uint8_t rank)
+{
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ /*
+ * The mpr_train_ddr_on bit will force a special command.
+ * Therefore, clear it before issuing the MRS command.
+ */
+ union reut_misc_odt_ctrl_reg reut_misc_odt_ctrl = {
+ .raw = mchbar_read32(REUT_ch_MISC_ODT_CTRL(channel)),
+ };
+ reut_misc_odt_ctrl.mpr_train_ddr_on = 0;
+ mchbar_write32(REUT_ch_MISC_ODT_CTRL(channel), reut_misc_odt_ctrl.raw);
+ if (!ctrl->lpddr)
+ reut_issue_mrs(ctrl, channel, BIT(rank), 3, 0 << 2);
+ }
+}
+
+enum raminit_status train_read_mpr(struct sysinfo *ctrl)
+{
+ set_rxdqs_edges_to_midpoint(ctrl);
+ clear_data_offset_train_all(ctrl);
+ setup_io_test_mpr(ctrl, ctrl->chanmap, LOOPCOUNT_INFINITE, NSOE);
+ enum raminit_status status = RAMINIT_STATUS_SUCCESS;
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ if (!does_rank_exist(ctrl, rank))
+ continue;
+
+ printk(BIOS_DEBUG, "Rank %u\n", rank);
+ printk(RMPR_PLOT, "Channel");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(RMPR_PLOT, "\t%u\t\t", channel);
+ }
+ printk(RMPR_PLOT, "\nByte");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(RMPR_PLOT, "\t");
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ printk(RMPR_PLOT, "%u ", byte);
+ }
+ enter_mpr_train_ddr_mode(ctrl, rank);
+ struct linear_train_data region_data[NUM_CHANNELS][NUM_LANES] = { 0 };
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++)
+ select_reut_ranks(ctrl, channel, BIT(rank));
+
+ printk(RMPR_PLOT, "\nDqsDelay\n");
+ int8_t dqs_delay;
+ for (dqs_delay = RMPR_START; dqs_delay < RMPR_STOP; dqs_delay += RMPR_STEP) {
+ printk(RMPR_PLOT, "% 5d", dqs_delay);
+ const enum regfile_mode regfile = REG_FILE_USE_START;
+ change_1d_margin_multicast(ctrl, RdT, dqs_delay, 0, false, regfile);
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ union ddr_data_control_2_reg data_control_2 = {
+ .raw = ctrl->dq_control_2[channel][byte],
+ };
+ data_control_2.force_bias_on = 1;
+ data_control_2.force_rx_on = 1;
+ data_control_2.leaker_comp = 0;
+ mchbar_write32(DQ_CONTROL_2(channel, byte),
+ data_control_2.raw);
+ }
+ union ddr_data_control_0_reg data_control_0 = {
+ .raw = ctrl->dq_control_0[channel],
+ };
+ data_control_0.rx_training_mode = 1;
+ data_control_0.force_odt_on = !ctrl->lpddr;
+ data_control_0.en_read_preamble = 0;
+ data_control_0.odt_samp_extend_en = ctrl->lpddr;
+ const uint32_t reg_offset = DDR_DATA_ch_CONTROL_0(channel);
+ mchbar_write32(reg_offset, data_control_0.raw);
+ }
+ run_mpr_io_test(false);
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(RMPR_PLOT, "\t");
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ uint32_t fb = get_data_train_feedback(channel, byte);
+ const bool pass = fb == 1;
+ printk(RMPR_PLOT, pass ? ". " : "# ");
+ linear_record_pass(
+ &region_data[channel][byte],
+ pass,
+ dqs_delay,
+ RMPR_START,
+ RMPR_STEP);
+ }
+ }
+ printk(RMPR_PLOT, "\n");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ clear_rx_training_mode(ctrl, channel);
+ }
+ io_reset();
+ }
+ printk(RMPR_PLOT, "\n");
+ leave_mpr_train_ddr_mode(ctrl, rank);
+ clear_data_offset_train_all(ctrl);
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(BIOS_DEBUG, "C%u.R%u: \tLeft\tRight\tWidth\tCenter\tRxDqsPN\n",
+ channel, rank);
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ struct linear_train_data *data = &region_data[channel][byte];
+ const int32_t lwidth = range_width(data->largest);
+ if (lwidth <= RMPR_MIN_WIDTH) {
+ printk(BIOS_ERR,
+ "Bad eye (lwidth %d <= min %d) for byte %u\n",
+ lwidth, RMPR_MIN_WIDTH, byte);
+ status = RAMINIT_STATUS_RMPR_FAILURE;
+ }
+ /*
+ * The MPR center may not be ideal on certain platforms for
+ * unknown reasons. If so, adjust it with a magical number.
+ * For Haswell, the magical number is zero. Hell knows why.
+ */
+ const int32_t center = range_center(data->largest);
+ ctrl->rxdqsp[channel][rank][byte] = center - RMPR_START;
+ ctrl->rxdqsn[channel][rank][byte] = center - RMPR_START;
+ printk(BIOS_DEBUG, " B%u: \t%d\t%d\t%d\t%d\t%u\n", byte,
+ data->largest.start, data->largest.end, lwidth,
+ center, ctrl->rxdqsp[channel][rank][byte]);
+ }
+ printk(BIOS_DEBUG, "\n");
+ }
+ }
+
+ /*
+ * Now program the DQS center values on populated ranks. data is taken from
+ * the host struct. We need to do it after all ranks are trained, because we
+ * need to keep the same DQS value on all ranks during the training procedure.
+ */
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ update_rxt(ctrl, channel, rank, byte, RXT_RESTORE, 0);
+ }
+ }
+ change_1d_margin_multicast(ctrl, RdT, 0, 0, false, REG_FILE_USE_CURRENT);
+ io_reset();
+ return status;
+}
diff --git a/src/northbridge/intel/haswell/registers/mchbar.h b/src/northbridge/intel/haswell/registers/mchbar.h
index 0acafbc826..6a31d3a32c 100644
--- a/src/northbridge/intel/haswell/registers/mchbar.h
+++ b/src/northbridge/intel/haswell/registers/mchbar.h
@@ -122,7 +122,7 @@
#define REUT_ch_ERR_DATA_MASK(ch) _MCMAIN_C(0x40d8, ch)
#define REUT_ch_MISC_CKE_CTRL(ch) _MCMAIN_C(0x4190, ch)
-
+#define REUT_ch_MISC_ODT_CTRL(ch) _MCMAIN_C(0x4194, ch)
#define REUT_ch_MISC_PAT_CADB_CTRL(ch) _MCMAIN_C(0x4198, ch)
#define REUT_ch_PAT_CADB_MRS(ch) _MCMAIN_C(0x419c, ch)
#define REUT_ch_PAT_CADB_MUX_CTRL(ch) _MCMAIN_C(0x41a0, ch)
--
2.39.2
@@ -0,0 +1,688 @@
From f091b29ba192afe0eb90a737613ec0735c876830 Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sun, 8 May 2022 12:56:04 +0200
Subject: [PATCH 14/15] haswell NRI: Add write leveling
Implement JEDEC write leveling, which is done in two steps. The first
step uses the JEDEC procedure to do "fine" write leveling, i.e. align
the DQS phase to the clock signal. The second step performs a regular
read-write test to correct "coarse" cycle errors.
Change-Id: I27678523fe22c38173a688e2a4751c259a20f009
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
.../intel/haswell/native_raminit/Makefile.mk | 1 +
.../haswell/native_raminit/raminit_main.c | 1 +
.../haswell/native_raminit/raminit_native.h | 10 +
.../train_jedec_write_leveling.c | 580 ++++++++++++++++++
.../intel/haswell/registers/mchbar.h | 2 +
5 files changed, 594 insertions(+)
create mode 100644 src/northbridge/intel/haswell/native_raminit/train_jedec_write_leveling.c
diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.mk b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
index c442be0728..40c2f5e014 100644
--- a/src/northbridge/intel/haswell/native_raminit/Makefile.mk
+++ b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
@@ -16,5 +16,6 @@ romstage-y += setup_wdb.c
romstage-y += spd_bitmunching.c
romstage-y += testing_io.c
romstage-y += timings_refresh.c
+romstage-y += train_jedec_write_leveling.c
romstage-y += train_read_mpr.c
romstage-y += train_receive_enable.c
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_main.c b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
index 264d1468f5..1ff23be615 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_main.c
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
@@ -62,6 +62,7 @@ static const struct task_entry cold_boot[] = {
{ pre_training, true, "PRETRAIN", },
{ train_receive_enable, true, "RCVET", },
{ train_read_mpr, true, "RDMPRT", },
+ { train_jedec_write_leveling, true, "JWRL", },
};
/* 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 a7551ad63c..666b233c45 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -59,6 +59,9 @@
/* Specified in PI ticks. 64 PI ticks == 1 qclk */
#define tDQSCK_DRIFT 64
+/* Maximum additional latency */
+#define MAX_ADD_DELAY 2
+
enum margin_parameter {
RcvEna,
RdT,
@@ -215,6 +218,7 @@ enum raminit_status {
RAMINIT_STATUS_REUT_ERROR,
RAMINIT_STATUS_RCVEN_FAILURE,
RAMINIT_STATUS_RMPR_FAILURE,
+ RAMINIT_STATUS_JWRL_FAILURE,
RAMINIT_STATUS_UNSPECIFIED_ERROR, /** TODO: Deprecated in favor of specific values **/
};
@@ -380,6 +384,11 @@ static inline uint32_t get_data_train_feedback(const uint8_t channel, const uint
return mchbar_read32(DDR_DATA_TRAIN_FEEDBACK(channel, byte));
}
+static inline uint16_t get_byte_group_errors(const uint8_t channel)
+{
+ return mchbar_read32(4 + REUT_ch_ERR_MISC_STATUS(channel)) & 0x1ff;
+}
+
/* Number of ticks to wait in units of 69.841279 ns (citation needed) */
static inline void tick_delay(const uint32_t delay)
{
@@ -439,6 +448,7 @@ enum raminit_status configure_memory_map(struct sysinfo *ctrl);
enum raminit_status do_jedec_init(struct sysinfo *ctrl);
enum raminit_status train_receive_enable(struct sysinfo *ctrl);
enum raminit_status train_read_mpr(struct sysinfo *ctrl);
+enum raminit_status train_jedec_write_leveling(struct sysinfo *ctrl);
void configure_timings(struct sysinfo *ctrl);
void configure_refresh(struct sysinfo *ctrl);
diff --git a/src/northbridge/intel/haswell/native_raminit/train_jedec_write_leveling.c b/src/northbridge/intel/haswell/native_raminit/train_jedec_write_leveling.c
new file mode 100644
index 0000000000..1ba28a3bd4
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/train_jedec_write_leveling.c
@@ -0,0 +1,580 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <assert.h>
+#include <console/console.h>
+#include <delay.h>
+#include <northbridge/intel/haswell/haswell.h>
+#include <types.h>
+
+#include "raminit_native.h"
+#include "ranges.h"
+
+#define JWLC_PLOT RAM_DEBUG
+#define JWRL_PLOT RAM_DEBUG
+
+static void reset_dram_dll(struct sysinfo *ctrl, const uint8_t channel, const uint8_t rank)
+{
+ reut_issue_mrs(ctrl, channel, BIT(rank), 0, ctrl->mr0[channel][rank] | MR0_DLL_RESET);
+}
+
+static void program_wdb_pattern(struct sysinfo *ctrl, const bool invert)
+{
+ /* Pattern to keep DQ-DQS simple but detect any failures. Same as NHM/WSM. */
+ const uint8_t pat[4][2] = {
+ { 0x00, 0xff },
+ { 0xff, 0x00 },
+ { 0xc3, 0x3c },
+ { 0x3c, 0xc3 },
+ };
+ const uint8_t pmask[2][8] = {
+ { 0, 0, 1, 1, 1, 1, 0, 0 },
+ { 1, 1, 0, 0, 0, 0, 1, 1 },
+ };
+ for (uint8_t s = 0; s < ARRAY_SIZE(pat); s++)
+ write_wdb_fixed_pat(ctrl, pat[s], pmask[invert], ARRAY_SIZE(pmask[invert]), s);
+}
+
+static int16_t set_add_delay(uint32_t *add_delay, uint8_t rank, int8_t target_off)
+{
+ const uint8_t shift = rank * 2;
+ if (target_off > MAX_ADD_DELAY) {
+ *add_delay &= ~(3 << shift);
+ *add_delay |= MAX_ADD_DELAY << shift;
+ return 128 * (target_off - MAX_ADD_DELAY);
+ } else if (target_off < 0) {
+ *add_delay &= ~(3 << shift);
+ *add_delay |= 0 << shift;
+ return 128 * target_off;
+ } else {
+ *add_delay &= ~(3 << shift);
+ *add_delay |= target_off << shift;
+ return 0;
+ }
+}
+
+static enum raminit_status train_jedec_write_leveling_cleanup(struct sysinfo *ctrl)
+{
+ const struct reut_box reut_addr = {
+ .col = {
+ .start = 0,
+ .stop = 1023,
+ .inc_val = 1,
+ },
+ };
+ const struct wdb_pat wdb_pattern = {
+ .start_ptr = 0,
+ .stop_ptr = 3,
+ .inc_rate = 1,
+ .dq_pattern = BASIC_VA,
+ };
+ const int8_t offsets[] = { 0, 1, -1, 2, 3 };
+ const int8_t dq_offsets[] = { 0, -10, 10, -5, 5, -15, 15 };
+ const uint8_t dq_offset_max = ARRAY_SIZE(dq_offsets);
+
+ /* Set LFSR seeds to be sequential */
+ program_wdb_lfsr(ctrl, true);
+ setup_io_test(
+ ctrl,
+ ctrl->chanmap,
+ PAT_WR_RD,
+ 2,
+ 4,
+ &reut_addr,
+ NSOE,
+ &wdb_pattern,
+ 0,
+ 0);
+
+ const union reut_pat_wdb_cl_mux_cfg_reg reut_wdb_cl_mux_cfg = {
+ .mux_0_control = REUT_MUX_BTBUFFER,
+ .mux_1_control = REUT_MUX_BTBUFFER,
+ .mux_2_control = REUT_MUX_BTBUFFER,
+ .ecc_data_source_sel = 1,
+ };
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ mchbar_write32(REUT_ch_PAT_WDB_CL_MUX_CFG(channel), reut_wdb_cl_mux_cfg.raw);
+ }
+
+ int8_t byte_off[NUM_CHANNELS][NUM_LANES] = { 0 };
+ uint32_t add_delay[NUM_CHANNELS] = { 0 };
+ enum raminit_status status = RAMINIT_STATUS_SUCCESS;
+ bool invert = false;
+ const uint16_t valid_byte_mask = BIT(ctrl->lanes) - 1;
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ uint8_t chanmask = 0;
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++)
+ chanmask |= select_reut_ranks(ctrl, channel, BIT(rank));
+
+ if (!chanmask)
+ continue;
+
+ printk(BIOS_DEBUG, "Rank %u\n", rank);
+ printk(JWLC_PLOT, "Channel");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(JWLC_PLOT, "\t\t%u\t", channel);
+ }
+ printk(JWLC_PLOT, "\nByte\t");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(JWLC_PLOT, "\t");
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ printk(JWLC_PLOT, "%u ", byte);
+ }
+ printk(JWLC_PLOT, "\nDelay DqOffset");
+ bool done = false;
+ int8_t byte_sum[NUM_CHANNELS] = { 0 };
+ uint16_t byte_pass[NUM_CHANNELS] = { 0 };
+ for (uint8_t off = 0; off < ARRAY_SIZE(offsets); off++) {
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ const int16_t global_byte_off =
+ set_add_delay(&add_delay[channel], rank, offsets[off]);
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ update_txt(ctrl, channel, rank, byte, TXT_DQDQS_OFF,
+ global_byte_off);
+ }
+ mchbar_write32(SC_WR_ADD_DELAY_ch(channel),
+ add_delay[channel]);
+ }
+ /* Reset FIFOs and DRAM DLL (Micron workaround) */
+ if (!ctrl->lpddr) {
+ io_reset();
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ reset_dram_dll(ctrl, channel, rank);
+ }
+ udelay(1);
+ }
+ for (uint8_t dq_offset = 0; dq_offset < dq_offset_max; dq_offset++) {
+ printk(JWLC_PLOT, "\n% 3d\t% 3d",
+ offsets[off], dq_offsets[dq_offset]);
+ change_1d_margin_multicast(
+ ctrl,
+ WrT,
+ dq_offsets[dq_offset],
+ rank,
+ false,
+ REG_FILE_USE_RANK);
+
+ /*
+ * Re-program the WDB pattern. Change the pattern
+ * for the next test to avoid false pass issues.
+ */
+ program_wdb_pattern(ctrl, invert);
+ invert = !invert;
+ run_io_test(ctrl, chanmask, BASIC_VA, true);
+ done = true;
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(JWLC_PLOT, "\t");
+ uint16_t result = get_byte_group_errors(channel);
+ result &= valid_byte_mask;
+
+ /* Skip bytes that have failed or already passed */
+ const uint16_t skip_me = result | byte_pass[channel];
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ const bool pass = result & BIT(byte);
+ printk(JWLC_PLOT, pass ? "# " : ". ");
+ if (skip_me & BIT(byte))
+ continue;
+
+ byte_pass[channel] |= BIT(byte);
+ byte_off[channel][byte] = offsets[off];
+ byte_sum[channel] += offsets[off];
+ }
+ if (byte_pass[channel] != valid_byte_mask)
+ done = false;
+ }
+ if (done)
+ break;
+ }
+ if (done)
+ break;
+ }
+ printk(BIOS_DEBUG, "\n\n");
+ if (!done) {
+ printk(BIOS_ERR, "JWLC: Could not find a pass for all bytes\n");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(BIOS_ERR, "Channel %u, rank %u fail:", channel, rank);
+ const uint16_t passing_mask = byte_pass[channel];
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ if (BIT(byte) & passing_mask)
+ continue;
+
+ printk(BIOS_ERR, " %u", byte);
+ }
+ printk(BIOS_ERR, "\n");
+ }
+ status = RAMINIT_STATUS_JWRL_FAILURE;
+ break;
+ }
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ /* Refine target offset to make sure it works for all bytes */
+ int8_t target_off = DIV_ROUND_CLOSEST(byte_sum[channel], ctrl->lanes);
+ int16_t global_byte_off = 0;
+ uint8_t all_good_loops = 0;
+ bool all_good = 0;
+ while (!all_good) {
+ global_byte_off =
+ set_add_delay(&add_delay[channel], rank, target_off);
+ all_good = true;
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ int16_t local_offset;
+ local_offset = byte_off[channel][byte] - target_off;
+ local_offset = local_offset * 128 + global_byte_off;
+ const uint16_t tx_dq = ctrl->tx_dq[channel][rank][byte];
+ if (tx_dq + local_offset >= (512 - 64)) {
+ all_good = false;
+ all_good_loops++;
+ target_off++;
+ break;
+ }
+ const uint16_t txdqs = ctrl->tx_dq[channel][rank][byte];
+ if (txdqs + local_offset < 96) {
+ all_good = false;
+ all_good_loops++;
+ target_off--;
+ break;
+ }
+ }
+ /* Avoid an infinite loop */
+ if (all_good_loops > 3)
+ break;
+ }
+ if (!all_good) {
+ printk(BIOS_ERR, "JWLC: Target offset refining failed\n");
+ status = RAMINIT_STATUS_JWRL_FAILURE;
+ break;
+ }
+ printk(BIOS_DEBUG, "C%u.R%u: Offset\tFinalEdge\n", channel, rank);
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ int16_t local_offset;
+ local_offset = byte_off[channel][byte] - target_off;
+ local_offset = local_offset * 128 + global_byte_off;
+ ctrl->tx_dq[channel][rank][byte] += local_offset;
+ ctrl->txdqs[channel][rank][byte] += local_offset;
+ update_txt(ctrl, channel, rank, byte, TXT_RESTORE, 0);
+ printk(BIOS_DEBUG, " B%u: %d\t%d\n", byte, local_offset,
+ ctrl->txdqs[channel][rank][byte]);
+ }
+ mchbar_write32(SC_WR_ADD_DELAY_ch(channel), add_delay[channel]);
+ if (!ctrl->lpddr) {
+ reset_dram_dll(ctrl, channel, rank);
+ udelay(1);
+ }
+ printk(BIOS_DEBUG, "\n");
+ }
+ printk(BIOS_DEBUG, "\n");
+ }
+
+ /* Restore WDB after test */
+ write_wdb_va_pat(ctrl, 0, BASIC_VA_PAT_SPREAD_8, 8, 0);
+ program_wdb_lfsr(ctrl, false);
+ mchbar_write32(DDR_DATA_OFFSET_TRAIN, 0);
+
+ /** TODO: Do full JEDEC init instead? **/
+ io_reset();
+ return status;
+}
+
+static enum raminit_status verify_wl_width(const int32_t lwidth)
+{
+ if (lwidth <= 32) {
+ /* Check if width is valid */
+ printk(BIOS_ERR, "WrLevel: Width region (%d) too small\n", lwidth);
+ return RAMINIT_STATUS_JWRL_FAILURE;
+ }
+ if (lwidth >= 96) {
+ /* Since we're calibrating a phase, a too large region is a problem */
+ printk(BIOS_ERR, "WrLevel: Width region (%d) too large\n", lwidth);
+ return RAMINIT_STATUS_JWRL_FAILURE;
+ }
+ return 0;
+}
+
+enum raminit_status train_jedec_write_leveling(struct sysinfo *ctrl)
+{
+ /*
+ * Enabling WL mode causes DQS to toggle for 1024 QCLK.
+ * Wait for this to stop. Round up to nearest microsecond.
+ */
+ const bool wl_long_delay = ctrl->lpddr;
+ const uint32_t dqs_toggle_time = wl_long_delay ? 2048 : 1024;
+ const uint32_t wait_time_us = DIV_ROUND_UP(ctrl->qclkps * dqs_toggle_time, 1000 * 1000);
+
+ const uint16_t wl_start = 192;
+ const uint16_t wl_stop = 192 + 128;
+ const uint16_t wl_step = 2;
+
+ /* Do not use cached MR values */
+ const bool save_restore_mrs = ctrl->restore_mrs;
+ ctrl->restore_mrs = 0;
+
+ /* Propagate delay values (without a write command) */
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ /* Propagate delay values from rank 0 to prevent assertion failures in RTL */
+ union ddr_data_control_0_reg data_control_0 = {
+ .raw = ctrl->dq_control_0[channel],
+ };
+ data_control_0.read_rf_rd = 0;
+ data_control_0.read_rf_wr = 1;
+ data_control_0.read_rf_rank = 0;
+ mchbar_write32(DDR_DATA_ch_CONTROL_0(channel), data_control_0.raw);
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ union ddr_data_control_2_reg data_control_2 = {
+ .raw = ctrl->dq_control_2[channel][byte],
+ };
+ data_control_2.force_bias_on = 1;
+ data_control_2.force_rx_on = 0;
+ data_control_2.wl_long_delay = wl_long_delay;
+ mchbar_write32(DQ_CONTROL_2(channel, byte), data_control_2.raw);
+ }
+ }
+
+ if (ctrl->lpddr)
+ die("%s: Missing LPDDR support\n", __func__);
+
+ if (!ctrl->lpddr)
+ ddr3_program_mr1(ctrl, 0, 1);
+
+ enum raminit_status status = RAMINIT_STATUS_SUCCESS;
+ struct phase_train_data region_data[NUM_CHANNELS][NUM_LANES] = { 0 };
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ if (!does_rank_exist(ctrl, rank))
+ continue;
+
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ /** TODO: Differs for LPDDR **/
+ uint16_t mr1reg = ctrl->mr1[channel][rank];
+ mr1reg &= ~MR1_QOFF_ENABLE;
+ mr1reg |= MR1_WL_ENABLE;
+ if (is_hsw_ult()) {
+ mr1reg &= ~RTTNOM_MASK;
+ mr1reg |= encode_ddr3_rttnom(120);
+ } else if (ctrl->dpc[channel] == 2) {
+ mr1reg &= ~RTTNOM_MASK;
+ mr1reg |= encode_ddr3_rttnom(60);
+ }
+ reut_issue_mrs(ctrl, channel, BIT(rank), 1, mr1reg);
+
+ /* Assert ODT for myself */
+ uint8_t odt_matrix = BIT(rank);
+ if (ctrl->dpc[channel] == 2) {
+ /* Assert ODT for non-target DIMM */
+ const uint8_t other_dimm = ((rank + 2) / 2) & 1;
+ odt_matrix |= BIT(2 * other_dimm);
+ }
+
+ union reut_misc_odt_ctrl_reg reut_misc_odt_ctrl = {
+ .raw = 0,
+ };
+ if (ctrl->lpddr) {
+ /* Only one ODT pin for ULT */
+ reut_misc_odt_ctrl.odt_on = 1;
+ reut_misc_odt_ctrl.odt_override = 1;
+ } else if (!is_hsw_ult()) {
+ reut_misc_odt_ctrl.odt_on = odt_matrix;
+ reut_misc_odt_ctrl.odt_override = 0xf;
+ }
+ mchbar_write32(REUT_ch_MISC_ODT_CTRL(channel), reut_misc_odt_ctrl.raw);
+ }
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ /*
+ * Enable write leveling mode in DDR and propagate delay
+ * values (without a write command). Stay in WL mode.
+ */
+ union ddr_data_control_0_reg data_control_0 = {
+ .raw = ctrl->dq_control_0[channel],
+ };
+ data_control_0.wl_training_mode = 1;
+ data_control_0.tx_pi_on = 1;
+ data_control_0.read_rf_rd = 0;
+ data_control_0.read_rf_wr = 1;
+ data_control_0.read_rf_rank = rank;
+ mchbar_write32(DDR_DATA_ch_CONTROL_0(channel), data_control_0.raw);
+ }
+ printk(BIOS_DEBUG, "\nRank %u\n", rank);
+ printk(JWRL_PLOT, "Channel\t");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(JWRL_PLOT, "%u", channel);
+ if (channel > 0)
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ printk(JWRL_PLOT, "\t");
+ }
+ printk(JWRL_PLOT, "\nByte");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ printk(JWRL_PLOT, "\t%u", byte);
+ }
+ printk(JWRL_PLOT, "\nWlDelay");
+ for (uint16_t wl_delay = wl_start; wl_delay < wl_stop; wl_delay += wl_step) {
+ printk(JWRL_PLOT, "\n %3u:", wl_delay);
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ update_txt(ctrl, channel, rank, byte, TXT_TXDQS,
+ wl_delay);
+ }
+ }
+ /* Wait for the first burst to finish */
+ if (wl_delay == wl_start)
+ udelay(wait_time_us);
+
+ io_reset();
+ udelay(wait_time_us);
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ const uint32_t feedback =
+ get_data_train_feedback(channel, byte);
+ const bool pass = (feedback & 0x1ff) >= 16;
+ printk(JWRL_PLOT, "\t%c%u", pass ? '.' : '#', feedback);
+ phase_record_pass(
+ &region_data[channel][byte],
+ pass,
+ wl_delay,
+ wl_start,
+ wl_step);
+ }
+ }
+ }
+ printk(JWRL_PLOT, "\n");
+ printk(BIOS_DEBUG, "\n\tInitSt\tInitEn\tCurrSt\tCurrEn\tLargSt\tLargEn\n");
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ printk(BIOS_DEBUG, "C%u\n", channel);
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ struct phase_train_data *data = &region_data[channel][byte];
+
+ phase_append_initial_to_current(data, wl_start, wl_step);
+ printk(BIOS_DEBUG, " B%u:\t%d\t%d\t%d\t%d\t%d\t%d\n",
+ byte,
+ data->initial.start,
+ data->initial.end,
+ data->current.start,
+ data->current.end,
+ data->largest.start,
+ data->largest.end);
+ }
+ }
+
+ /*
+ * Clean up after test. Very coarsely adjust for
+ * any cycle errors. Program values for TxDQS.
+ */
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ /* Clear ODT before MRS (JEDEC spec) */
+ mchbar_write32(REUT_ch_MISC_ODT_CTRL(channel), 0);
+
+ /** TODO: Differs for LPDDR **/
+ const uint16_t mr1reg = ctrl->mr1[channel][rank] | MR1_QOFF_ENABLE;
+ reut_issue_mrs(ctrl, channel, BIT(rank), 1, mr1reg);
+
+ printk(BIOS_DEBUG, "\nC%u.R%u: LftEdge Width\n", channel, rank);
+ const bool rank_x16 = ctrl->dimms[channel][rank / 2].data.width == 16;
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ struct phase_train_data *data = &region_data[channel][byte];
+ const int32_t lwidth = range_width(data->largest);
+ int32_t tx_start = data->largest.start;
+ printk(BIOS_DEBUG, " B%u: %d\t%d\n", byte, tx_start, lwidth);
+ status = verify_wl_width(lwidth);
+ if (status) {
+ printk(BIOS_ERR,
+ "WrLevel problems on channel %u, byte %u\n",
+ channel, byte);
+ goto clean_up;
+ }
+
+ /* Align byte pairs if DIMM is x16 */
+ if (rank_x16 && (byte & 1)) {
+ const struct phase_train_data *const ref_data =
+ &region_data[channel][byte - 1];
+
+ if (tx_start > ref_data->largest.start + 64)
+ tx_start -= 128;
+
+ if (tx_start < ref_data->largest.start - 64)
+ tx_start += 128;
+ }
+
+ /* Fix for b4618067 - need to add 1 QCLK to DQS PI */
+ if (is_hsw_ult())
+ tx_start += 64;
+
+ assert(tx_start >= 0);
+ ctrl->txdqs[channel][rank][byte] = tx_start;
+ ctrl->tx_dq[channel][rank][byte] = tx_start + 32;
+ update_txt(ctrl, channel, rank, byte, TXT_RESTORE, 0);
+ }
+ }
+ printk(BIOS_DEBUG, "\n");
+ }
+
+clean_up:
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ mchbar_write32(DDR_DATA_ch_CONTROL_0(channel), ctrl->dq_control_0[channel]);
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ mchbar_write32(DQ_CONTROL_2(channel, byte),
+ ctrl->dq_control_2[channel][byte]);
+ }
+ }
+ if (!ctrl->lpddr)
+ ddr3_program_mr1(ctrl, 0, 0);
+
+ ctrl->restore_mrs = save_restore_mrs;
+
+ if (status)
+ return status;
+
+ /** TODO: If this step fails and dec_wrd is set, clear it and try again **/
+ return train_jedec_write_leveling_cleanup(ctrl);
+}
diff --git a/src/northbridge/intel/haswell/registers/mchbar.h b/src/northbridge/intel/haswell/registers/mchbar.h
index 6a31d3a32c..7c0b5a49de 100644
--- a/src/northbridge/intel/haswell/registers/mchbar.h
+++ b/src/northbridge/intel/haswell/registers/mchbar.h
@@ -121,6 +121,8 @@
#define REUT_ch_ERR_DATA_MASK(ch) _MCMAIN_C(0x40d8, ch)
+#define REUT_ch_ERR_MISC_STATUS(ch) _MCMAIN_C(0x40e8, ch)
+
#define REUT_ch_MISC_CKE_CTRL(ch) _MCMAIN_C(0x4190, ch)
#define REUT_ch_MISC_ODT_CTRL(ch) _MCMAIN_C(0x4194, ch)
#define REUT_ch_MISC_PAT_CADB_CTRL(ch) _MCMAIN_C(0x4198, ch)
--
2.39.2
@@ -0,0 +1,570 @@
From 94230b02f22d871af5de5c83e2efc6204394475e Mon Sep 17 00:00:00 2001
From: Angel Pons <th3fanbus@gmail.com>
Date: Sun, 8 May 2022 14:29:05 +0200
Subject: [PATCH 15/15] haswell NRI: Add final raminit steps
Implement the remaining raminit steps. Although many training steps are
missing, this is enough to boot on the Asrock B85M Pro4.
Change-Id: I94f3b65f0218d4da4fda4d84592dfd91f77f8f21
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
---
src/northbridge/intel/haswell/Kconfig | 4 +-
.../intel/haswell/native_raminit/Makefile.mk | 1 +
.../haswell/native_raminit/activate_mc.c | 388 ++++++++++++++++++
.../haswell/native_raminit/raminit_main.c | 5 +-
.../haswell/native_raminit/raminit_native.c | 5 +-
.../haswell/native_raminit/raminit_native.h | 2 +
.../haswell/native_raminit/reg_structs.h | 12 +
.../intel/haswell/registers/mchbar.h | 7 +
8 files changed, 416 insertions(+), 8 deletions(-)
create mode 100644 src/northbridge/intel/haswell/native_raminit/activate_mc.c
diff --git a/src/northbridge/intel/haswell/Kconfig b/src/northbridge/intel/haswell/Kconfig
index 4b83a25bc1..c6ab27184e 100644
--- a/src/northbridge/intel/haswell/Kconfig
+++ b/src/northbridge/intel/haswell/Kconfig
@@ -11,12 +11,12 @@ config NORTHBRIDGE_INTEL_HASWELL
if NORTHBRIDGE_INTEL_HASWELL
config USE_NATIVE_RAMINIT
- bool "[NOT WORKING] Use native raminit"
+ bool "[NOT COMPLETE] Use native raminit"
default n
select HAVE_DEBUG_RAM_SETUP
help
Select if you want to use coreboot implementation of raminit rather than
- MRC.bin. Currently incomplete and does not boot.
+ MRC.bin. Currently incomplete and does not support S3 resume.
config HASWELL_VBOOT_IN_BOOTBLOCK
depends on VBOOT
diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.mk b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
index 40c2f5e014..d97da72890 100644
--- a/src/northbridge/intel/haswell/native_raminit/Makefile.mk
+++ b/src/northbridge/intel/haswell/native_raminit/Makefile.mk
@@ -1,5 +1,6 @@
## SPDX-License-Identifier: GPL-2.0-or-later
+romstage-y += activate_mc.c
romstage-y += change_margin.c
romstage-y += configure_mc.c
romstage-y += ddr3.c
diff --git a/src/northbridge/intel/haswell/native_raminit/activate_mc.c b/src/northbridge/intel/haswell/native_raminit/activate_mc.c
new file mode 100644
index 0000000000..78a7ad27ef
--- /dev/null
+++ b/src/northbridge/intel/haswell/native_raminit/activate_mc.c
@@ -0,0 +1,388 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <console/console.h>
+#include <delay.h>
+#include <device/pci_ops.h>
+#include <northbridge/intel/haswell/haswell.h>
+#include <timer.h>
+#include <types.h>
+
+#include "raminit_native.h"
+
+static void update_internal_clocks_on(struct sysinfo *ctrl)
+{
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ bool clocks_on = false;
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ const union ddr_data_control_1_reg data_control_1 = {
+ .raw = ctrl->dq_control_1[channel][byte],
+ };
+ const int8_t o_on = data_control_1.odt_delay;
+ const int8_t s_on = data_control_1.sense_amp_delay;
+ const int8_t o_off = data_control_1.odt_duration;
+ const int8_t s_off = data_control_1.sense_amp_duration;
+ if (o_on + o_off >= 7 || s_on + s_off >= 7) {
+ clocks_on = true;
+ break;
+ }
+ }
+ union ddr_data_control_0_reg data_control_0 = {
+ .raw = ctrl->dq_control_0[channel],
+ };
+ data_control_0.internal_clocks_on = clocks_on;
+ ctrl->dq_control_0[channel] = data_control_0.raw;
+ mchbar_write32(DDR_DATA_ch_CONTROL_0(channel), data_control_0.raw);
+ }
+}
+
+/* Switch off unused segments of the SDLL to save power */
+static void update_sdll_length(struct sysinfo *ctrl)
+{
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ uint8_t max_pi = 0;
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ const uint8_t rx_dqs_p = ctrl->rxdqsp[channel][rank][byte];
+ const uint8_t rx_dqs_n = ctrl->rxdqsn[channel][rank][byte];
+ max_pi = MAX(max_pi, MAX(rx_dqs_p, rx_dqs_n));
+ }
+ /* Update SDLL length for power savings */
+ union ddr_data_control_1_reg data_control_1 = {
+ .raw = ctrl->dq_control_1[channel][byte],
+ };
+ /* Calculate which segments to turn off */
+ data_control_1.sdll_segment_disable = (7 - (max_pi >> 3)) & ~1;
+ ctrl->dq_control_1[channel][byte] = data_control_1.raw;
+ mchbar_write32(DQ_CONTROL_1(channel, byte), data_control_1.raw);
+ }
+ }
+}
+
+static void set_rx_clk_stg_num(struct sysinfo *ctrl, const uint8_t channel)
+{
+ const uint8_t rcven_drift = ctrl->lpddr ? DIV_ROUND_UP(tDQSCK_DRIFT, ctrl->qclkps) : 1;
+ uint8_t max_rcven = 0;
+ for (uint8_t rank = 0; rank < NUM_SLOTRANKS; rank++) {
+ if (!rank_in_ch(ctrl, rank, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++)
+ max_rcven = MAX(max_rcven, ctrl->rcven[channel][rank][byte] / 64);
+ }
+ const union ddr_data_control_1_reg ddr_data_control_1 = {
+ .raw = ctrl->dq_control_1[channel][0],
+ };
+ const bool lpddr_long_odt = ddr_data_control_1.lpddr_long_odt_en;
+ const uint8_t rcven_turnoff = max_rcven + 18 + 2 * rcven_drift + lpddr_long_odt;
+ const union ddr_data_control_0_reg ddr_data_control_0 = {
+ .raw = ctrl->dq_control_0[channel],
+ };
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ union ddr_data_control_2_reg ddr_data_control_2 = {
+ .raw = ctrl->dq_control_2[channel][byte],
+ };
+ if (ddr_data_control_0.odt_samp_extend_en) {
+ if (ddr_data_control_2.rx_clk_stg_num < rcven_turnoff)
+ ddr_data_control_2.rx_clk_stg_num = rcven_turnoff;
+ } else {
+ const int8_t o_on = ddr_data_control_1.odt_delay;
+ const int8_t o_off = ddr_data_control_1.odt_duration;
+ ddr_data_control_2.rx_clk_stg_num = MAX(17, o_on + o_off + 14);
+ }
+ ctrl->dq_control_2[channel][byte] = ddr_data_control_2.raw;
+ mchbar_write32(DQ_CONTROL_2(channel, byte), ddr_data_control_2.raw);
+ }
+}
+
+#define SELF_REFRESH_IDLE_COUNT 0x200
+
+static void enter_sr(void)
+{
+ mchbar_write32(PM_SREF_CONFIG, SELF_REFRESH_IDLE_COUNT | BIT(16));
+ udelay(1);
+}
+
+enum power_down_mode {
+ PDM_NO_PD = 0,
+ PDM_APD = 1,
+ PDM_PPD = 2,
+ PDM_PPD_DLL_OFF = 6,
+};
+
+static void power_down_config(struct sysinfo *ctrl)
+{
+ const enum power_down_mode pd_mode = ctrl->lpddr ? PDM_PPD : PDM_PPD_DLL_OFF;
+ mchbar_write32(PM_PDWN_CONFIG, pd_mode << 12 | 0x40);
+}
+
+static void train_power_modes_post(struct sysinfo *ctrl)
+{
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ /* Adjust tCPDED and tPRPDEN */
+ if (ctrl->mem_clock_mhz >= 933)
+ ctrl->tc_bankrank_d[channel].tCPDED = 2;
+
+ if (ctrl->mem_clock_mhz >= 1066)
+ ctrl->tc_bankrank_d[channel].tPRPDEN = 2;
+
+ mchbar_write32(TC_BANK_RANK_D_ch(channel), ctrl->tc_bankrank_d[channel].raw);
+ }
+ power_down_config(ctrl);
+ mchbar_write32(MCDECS_CBIT, BIT(30)); /* dis_msg_clk_gate */
+}
+
+static uint8_t compute_burst_end_odt_delay(const struct sysinfo *const ctrl)
+{
+ /* Must be disabled for LPDDR */
+ if (ctrl->lpddr)
+ return 0;
+
+ const uint8_t beod = MIN(7, DIV_ROUND_CLOSEST(14300 * 20 / 100, ctrl->qclkps));
+ if (beod < 3)
+ return 0;
+
+ if (beod < 4)
+ return 4;
+
+ return beod;
+}
+
+static void program_burst_end_odt_delay(struct sysinfo *ctrl)
+{
+ /* Program burst_end_odt_delay - it should be zero during training steps */
+ const uint8_t beod = compute_burst_end_odt_delay(ctrl);
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ for (uint8_t byte = 0; byte < ctrl->lanes; byte++) {
+ union ddr_data_control_1_reg ddr_data_control_1 = {
+ .raw = ctrl->dq_control_1[channel][byte],
+ };
+ ddr_data_control_1.burst_end_odt_delay = beod;
+ ctrl->dq_control_1[channel][byte] = ddr_data_control_1.raw;
+ mchbar_write32(DQ_CONTROL_1(channel, byte), ddr_data_control_1.raw);
+ }
+ }
+}
+
+/*
+ * Return a random value to use for scrambler seeds. Try to use RDRAND
+ * first and fall back to hardcoded values if RDRAND does not succeed.
+ */
+static uint16_t get_random_number(const uint8_t channel)
+{
+ /* The RDRAND instruction is only available 100k cycles after reset */
+ for (size_t i = 0; i < 100000; i++) {
+ uint32_t status;
+ uint32_t random;
+ /** TODO: Clean up asm **/
+ __asm__ __volatile__(
+ "\n\t .byte 0x0F, 0xC7, 0xF0"
+ "\n\t movl %%eax, %0"
+ "\n\t pushf"
+ "\n\t pop %%eax"
+ "\n\t movl %%eax, %1"
+ : "=m"(random),
+ "=m"(status)
+ : /* No inputs */
+ : "eax", "cc");
+
+ /* Only consider non-zero random values as valid */
+ if (status & 1 && random)
+ return random;
+ }
+
+ /* https://xkcd.com/221 */
+ if (channel)
+ return 0x28f4;
+ else
+ return 0x893e;
+}
+
+/* Work around "error: 'typeof' applied to a bit-field" */
+static inline uint32_t max(const uint32_t a, const uint32_t b)
+{
+ return MAX(a, b);
+}
+
+enum raminit_status activate_mc(struct sysinfo *ctrl)
+{
+ const bool enable_scrambling = true;
+ const bool enable_cmd_tristate = true;
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ if (enable_scrambling && ctrl->stepping < STEPPING_C0) {
+ /* Make sure tRDRD_(sr, dr, dd) are at least 6 for scrambler W/A */
+ union tc_bank_rank_a_reg tc_bank_rank_a = {
+ .raw = mchbar_read32(TC_BANK_RANK_A_ch(channel)),
+ };
+ tc_bank_rank_a.tRDRD_sr = max(tc_bank_rank_a.tRDRD_sr, 6);
+ tc_bank_rank_a.tRDRD_dr = max(tc_bank_rank_a.tRDRD_dr, 6);
+ tc_bank_rank_a.tRDRD_dd = max(tc_bank_rank_a.tRDRD_dd, 6);
+ mchbar_write32(TC_BANK_RANK_A_ch(channel), tc_bank_rank_a.raw);
+ }
+ if (enable_scrambling) {
+ const union ddr_scramble_reg ddr_scramble = {
+ .scram_key = get_random_number(channel),
+ .scram_en = 1,
+ };
+ mchbar_write32(DDR_SCRAMBLE_ch(channel), ddr_scramble.raw);
+ }
+ if (ctrl->tCMD == 1) {
+ /* If we are in 1N mode, enable and set command rate limit to 3 */
+ union mcmain_command_rate_limit_reg cmd_rate_limit = {
+ .raw = mchbar_read32(COMMAND_RATE_LIMIT_ch(channel)),
+ };
+ cmd_rate_limit.enable_cmd_limit = 1;
+ cmd_rate_limit.cmd_rate_limit = 3;
+ mchbar_write32(COMMAND_RATE_LIMIT_ch(channel), cmd_rate_limit.raw);
+ }
+ if (enable_cmd_tristate) {
+ /* Enable command tri-state at the end of training */
+ union tc_bank_rank_a_reg tc_bank_rank_a = {
+ .raw = mchbar_read32(TC_BANK_RANK_A_ch(channel)),
+ };
+ tc_bank_rank_a.cmd_3st_dis = 0;
+ mchbar_write32(TC_BANK_RANK_A_ch(channel), tc_bank_rank_a.raw);
+ }
+ /* Set MC to normal mode and clean the ODT and CKE */
+ mchbar_write32(REUT_ch_SEQ_CFG(channel), REUT_MODE_NOP << 12);
+ /* Set again the rank occupancy */
+ mchbar_write8(MC_INIT_STATE_ch(channel), ctrl->rankmap[channel]);
+ if (ctrl->is_ecc) {
+ /* Enable ECC I/O and logic */
+ union mad_dimm_reg mad_dimm = {
+ .raw = mchbar_read32(MAD_DIMM(channel)),
+ };
+ mad_dimm.ecc_mode = 3;
+ mchbar_write32(MAD_DIMM(channel), mad_dimm.raw);
+ }
+ }
+
+ if (!is_hsw_ult())
+ update_internal_clocks_on(ctrl);
+
+ update_sdll_length(ctrl);
+
+ program_burst_end_odt_delay(ctrl);
+
+ if (is_hsw_ult()) {
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ if (!does_ch_exist(ctrl, channel))
+ continue;
+
+ set_rx_clk_stg_num(ctrl, channel);
+ }
+ /** TODO: Program DDRPL_CR_DDR_TX_DELAY if Memory Trace is enabled **/
+ }
+
+ /* Enable periodic COMP */
+ mchbar_write32(M_COMP, (union pcu_comp_reg) {
+ .comp_interval = COMP_INT,
+ }.raw);
+
+ /* Enable the power mode before PCU starts working */
+ train_power_modes_post(ctrl);
+
+ /* Set idle timer and self refresh enable bits */
+ enter_sr();
+
+ /** FIXME: Do not hardcode power weights and RAPL settings **/
+ mchbar_write32(0x5888, 0x00000d0d);
+ mchbar_write32(0x5884, 0x00000004); /* 58.2 pJ */
+
+ mchbar_write32(0x58e0, 0);
+ mchbar_write32(0x58e4, 0);
+
+ mchbar_write32(0x5890, 0xffff);
+ mchbar_write32(0x5894, 0xffff);
+ mchbar_write32(0x5898, 0xffff);
+ mchbar_write32(0x589c, 0xffff);
+ mchbar_write32(0x58d0, 0xffff);
+ mchbar_write32(0x58d4, 0xffff);
+ mchbar_write32(0x58d8, 0xffff);
+ mchbar_write32(0x58dc, 0xffff);
+
+ /* Overwrite thermal parameters */
+ for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
+ mchbar_write32(_MCMAIN_C(0x42ec, channel), 0x0000000f);
+ mchbar_write32(_MCMAIN_C(0x42f0, channel), 0x00000009);
+ mchbar_write32(_MCMAIN_C(0x42f4, channel), 0x00000093);
+ mchbar_write32(_MCMAIN_C(0x42f8, channel), 0x00000087);
+ mchbar_write32(_MCMAIN_C(0x42fc, channel), 0x000000de);
+
+ /** TODO: Differs for LPDDR **/
+ mchbar_write32(PM_THRT_CKE_MIN_ch(channel), 0x30);
+ }
+ mchbar_write32(PCU_DDR_PTM_CTL, 0x40);
+ return RAMINIT_STATUS_SUCCESS;
+}
+
+static void mc_lockdown(void)
+{
+ /* Lock memory controller registers */
+ mchbar_write32(MC_LOCK, 0x8f);
+
+ /* MPCOHTRK_GDXC_OCLA_ADDRESS_HI_LOCK is set when programming the memory map */
+
+ /* Lock memory map registers */
+ pci_or_config16(HOST_BRIDGE, GGC, 1 << 0);
+ pci_or_config32(HOST_BRIDGE, DPR, 1 << 0);
+ pci_or_config32(HOST_BRIDGE, MESEG_LIMIT, 1 << 10);
+ pci_or_config32(HOST_BRIDGE, REMAPBASE, 1 << 0);
+ pci_or_config32(HOST_BRIDGE, REMAPLIMIT, 1 << 0);
+ pci_or_config32(HOST_BRIDGE, TOM, 1 << 0);
+ pci_or_config32(HOST_BRIDGE, TOUUD, 1 << 0);
+ pci_or_config32(HOST_BRIDGE, BDSM, 1 << 0);
+ pci_or_config32(HOST_BRIDGE, BGSM, 1 << 0);
+ pci_or_config32(HOST_BRIDGE, TOLUD, 1 << 0);
+}
+
+enum raminit_status raminit_done(struct sysinfo *ctrl)
+{
+ union mc_init_state_g_reg mc_init_state_g = {
+ .raw = mchbar_read32(MC_INIT_STATE_G),
+ };
+ mc_init_state_g.refresh_enable = 1;
+ mc_init_state_g.pu_mrc_done = 1;
+ mc_init_state_g.mrc_done = 1;
+ mchbar_write32(MC_INIT_STATE_G, mc_init_state_g.raw);
+
+ /* Lock the memory controller to enable normal operation */
+ mc_lockdown();
+
+ /* Poll for mc_init_done_ack to make sure memory initialization is complete */
+ printk(BIOS_DEBUG, "Waiting for mc_init_done acknowledgement... ");
+
+ struct stopwatch timer;
+ stopwatch_init_msecs_expire(&timer, 2000);
+ do {
+ mc_init_state_g.raw = mchbar_read32(MC_INIT_STATE_G);
+
+ /* DRAM will NOT work without the acknowledgement. There is no hope. */
+ if (stopwatch_expired(&timer))
+ die("\nTimed out waiting for mc_init_done acknowledgement\n");
+
+ } while (mc_init_state_g.mc_init_done_ack == 0);
+ printk(BIOS_DEBUG, "DONE!\n");
+
+ /* Provide some data for the graphics driver. Yes, it's hardcoded. */
+ mchbar_write32(SSKPD + 0, 0x05a2404f);
+ mchbar_write32(SSKPD + 4, 0x140000a0);
+ return RAMINIT_STATUS_SUCCESS;
+}
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_main.c b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
index 1ff23be615..3a65fb01fb 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_main.c
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_main.c
@@ -63,6 +63,8 @@ static const struct task_entry cold_boot[] = {
{ train_receive_enable, true, "RCVET", },
{ train_read_mpr, true, "RDMPRT", },
{ train_jedec_write_leveling, true, "JWRL", },
+ { activate_mc, true, "ACTIVATE", },
+ { raminit_done, true, "RAMINITEND", },
};
/* Return a generic stepping value to make stepping checks simpler */
@@ -143,7 +145,4 @@ void raminit_main(const enum raminit_boot_mode bootmode)
if (status != RAMINIT_STATUS_SUCCESS)
die("Memory initialization was met with utmost failure and misery\n");
-
- /** TODO: Implement the required magic **/
- die("NATIVE RAMINIT: More Magic (tm) required.\n");
}
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_native.c b/src/northbridge/intel/haswell/native_raminit/raminit_native.c
index ffb5e825f0..6bf7e7cdfb 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.c
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.c
@@ -200,8 +200,6 @@ void perform_raminit(const int s3resume)
else
me_status = ME_INIT_STATUS_SUCCESS;
- /** TODO: Remove this once raminit is implemented **/
- me_status = ME_INIT_STATUS_ERROR;
intel_early_me_init_done(me_status);
}
@@ -215,7 +213,8 @@ void perform_raminit(const int s3resume)
}
/* Save training data on non-S3 resumes */
- if (!s3resume)
+ /** TODO: Enable this once training data is populated **/
+ if (0 && !s3resume)
save_mrc_data(&md);
/** TODO: setup_sdram_meminfo **/
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_native.h b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
index 666b233c45..98e39cb76e 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -449,6 +449,8 @@ enum raminit_status do_jedec_init(struct sysinfo *ctrl);
enum raminit_status train_receive_enable(struct sysinfo *ctrl);
enum raminit_status train_read_mpr(struct sysinfo *ctrl);
enum raminit_status train_jedec_write_leveling(struct sysinfo *ctrl);
+enum raminit_status activate_mc(struct sysinfo *ctrl);
+enum raminit_status raminit_done(struct sysinfo *ctrl);
void configure_timings(struct sysinfo *ctrl);
void configure_refresh(struct sysinfo *ctrl);
diff --git a/src/northbridge/intel/haswell/native_raminit/reg_structs.h b/src/northbridge/intel/haswell/native_raminit/reg_structs.h
index a0e36ed082..0d9aaa1f7c 100644
--- a/src/northbridge/intel/haswell/native_raminit/reg_structs.h
+++ b/src/northbridge/intel/haswell/native_raminit/reg_structs.h
@@ -294,6 +294,18 @@ union ddr_cke_ctl_controls_reg {
uint32_t raw;
};
+union ddr_scramble_reg {
+ struct __packed {
+ uint32_t scram_en : 1; // Bits 0:0
+ uint32_t scram_key : 16; // Bits 16:1
+ uint32_t clk_gate_ab : 2; // Bits 18:17
+ uint32_t clk_gate_c : 2; // Bits 20:19
+ uint32_t en_dbi_ab : 1; // Bits 21:21
+ uint32_t : 10; // Bits 31:17
+ };
+ uint32_t raw;
+};
+
union ddr_scram_misc_control_reg {
struct __packed {
uint32_t wl_wake_cycles : 2; // Bits 1:0
diff --git a/src/northbridge/intel/haswell/registers/mchbar.h b/src/northbridge/intel/haswell/registers/mchbar.h
index 7c0b5a49de..49a215aa71 100644
--- a/src/northbridge/intel/haswell/registers/mchbar.h
+++ b/src/northbridge/intel/haswell/registers/mchbar.h
@@ -20,6 +20,7 @@
#define DDR_DATA_TRAIN_FEEDBACK(ch, byte) _DDRIO_C_R_B(0x0054, ch, 0, byte)
+#define DQ_CONTROL_1(ch, byte) _DDRIO_C_R_B(0x0060, ch, 0, byte)
#define DQ_CONTROL_2(ch, byte) _DDRIO_C_R_B(0x0064, ch, 0, byte)
#define DDR_DATA_OFFSET_TRAIN_ch_b(ch, byte) _DDRIO_C_R_B(0x0070, ch, 0, byte)
#define DQ_CONTROL_0(ch, byte) _DDRIO_C_R_B(0x0074, ch, 0, byte)
@@ -147,6 +148,8 @@
#define QCLK_ch_LDAT_SDAT(ch) _MCMAIN_C(0x42d4, ch)
#define QCLK_ch_LDAT_DATA_IN_x(ch, x) _MCMAIN_C_X(0x42dc, ch, x) /* x in 0 .. 1 */
+#define PM_THRT_CKE_MIN_ch(ch) _MCMAIN_C(0x4328, ch)
+
#define REUT_GLOBAL_CTL 0x4800
#define REUT_GLOBAL_ERR 0x4804
@@ -175,6 +178,8 @@
#define MCSCHEDS_DFT_MISC 0x4c30
+#define PM_PDWN_CONFIG 0x4cb0
+
#define REUT_ERR_DATA_STATUS 0x4ce0
#define REUT_MISC_CKE_CTRL 0x4d90
@@ -186,8 +191,10 @@
#define MAD_CHNL 0x5000 /* Address Decoder Channel Configuration */
#define MAD_DIMM(ch) (0x5004 + (ch) * 4)
#define MAD_ZR 0x5014
+#define MCDECS_CBIT 0x501c
#define MC_INIT_STATE_G 0x5030
#define MRC_REVISION 0x5034 /* MRC Revision */
+#define PM_SREF_CONFIG 0x5060
#define RCOMP_TIMER 0x5084
--
2.39.2
@@ -0,0 +1,109 @@
From 0310d8b3fcf8f305f655dacc3322de60bdc2adc4 Mon Sep 17 00:00:00 2001
From: Leah Rowe <info@minifree.org>
Date: Mon, 8 Apr 2024 00:38:18 +0100
Subject: [PATCH 1/1] haswell NRI: include commonlib/bsd/clamp.h
the location of clamp.h changed. these sources were rebased
from an older coreboot revision. update to match new code.
Signed-off-by: Leah Rowe <info@minifree.org>
---
src/northbridge/intel/haswell/native_raminit/change_margin.c | 2 +-
src/northbridge/intel/haswell/native_raminit/configure_mc.c | 2 +-
src/northbridge/intel/haswell/native_raminit/init_mpll.c | 2 +-
src/northbridge/intel/haswell/native_raminit/io_comp_control.c | 2 +-
src/northbridge/intel/haswell/native_raminit/lookup_timings.c | 2 +-
src/northbridge/intel/haswell/native_raminit/timings_refresh.c | 2 +-
src/northbridge/intel/haswell/native_raminit/train_read_mpr.c | 2 +-
7 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/northbridge/intel/haswell/native_raminit/change_margin.c b/src/northbridge/intel/haswell/native_raminit/change_margin.c
index 4ba9cfa5c6..299c44a6b0 100644
--- a/src/northbridge/intel/haswell/native_raminit/change_margin.c
+++ b/src/northbridge/intel/haswell/native_raminit/change_margin.c
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <assert.h>
-#include <commonlib/clamp.h>
+#include <commonlib/bsd/clamp.h>
#include <console/console.h>
#include <delay.h>
#include <northbridge/intel/haswell/haswell.h>
diff --git a/src/northbridge/intel/haswell/native_raminit/configure_mc.c b/src/northbridge/intel/haswell/native_raminit/configure_mc.c
index 2a667b075b..003fa0e941 100644
--- a/src/northbridge/intel/haswell/native_raminit/configure_mc.c
+++ b/src/northbridge/intel/haswell/native_raminit/configure_mc.c
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <assert.h>
-#include <commonlib/clamp.h>
+#include <commonlib/bsd/clamp.h>
#include <console/console.h>
#include <delay.h>
#include <lib.h>
diff --git a/src/northbridge/intel/haswell/native_raminit/init_mpll.c b/src/northbridge/intel/haswell/native_raminit/init_mpll.c
index 2faa183724..1f3f2c29a9 100644
--- a/src/northbridge/intel/haswell/native_raminit/init_mpll.c
+++ b/src/northbridge/intel/haswell/native_raminit/init_mpll.c
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
-#include <commonlib/clamp.h>
+#include <commonlib/bsd/clamp.h>
#include <console/console.h>
#include <delay.h>
#include <device/pci_ops.h>
diff --git a/src/northbridge/intel/haswell/native_raminit/io_comp_control.c b/src/northbridge/intel/haswell/native_raminit/io_comp_control.c
index ad8c848e57..8a55fd81b2 100644
--- a/src/northbridge/intel/haswell/native_raminit/io_comp_control.c
+++ b/src/northbridge/intel/haswell/native_raminit/io_comp_control.c
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
-#include <commonlib/clamp.h>
+#include <commonlib/bsd/clamp.h>
#include <console/console.h>
#include <northbridge/intel/haswell/haswell.h>
#include <timer.h>
diff --git a/src/northbridge/intel/haswell/native_raminit/lookup_timings.c b/src/northbridge/intel/haswell/native_raminit/lookup_timings.c
index afe2c615d2..b8d6c1ef40 100644
--- a/src/northbridge/intel/haswell/native_raminit/lookup_timings.c
+++ b/src/northbridge/intel/haswell/native_raminit/lookup_timings.c
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
-#include <commonlib/clamp.h>
+#include <commonlib/bsd/clamp.h>
#include <types.h>
#include "raminit_native.h"
diff --git a/src/northbridge/intel/haswell/native_raminit/timings_refresh.c b/src/northbridge/intel/haswell/native_raminit/timings_refresh.c
index 20a05b359b..820bb336b0 100644
--- a/src/northbridge/intel/haswell/native_raminit/timings_refresh.c
+++ b/src/northbridge/intel/haswell/native_raminit/timings_refresh.c
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <assert.h>
-#include <commonlib/clamp.h>
+#include <commonlib/bsd/clamp.h>
#include <console/console.h>
#include <delay.h>
#include <device/pci_ops.h>
diff --git a/src/northbridge/intel/haswell/native_raminit/train_read_mpr.c b/src/northbridge/intel/haswell/native_raminit/train_read_mpr.c
index 0225e1a384..86f79942d3 100644
--- a/src/northbridge/intel/haswell/native_raminit/train_read_mpr.c
+++ b/src/northbridge/intel/haswell/native_raminit/train_read_mpr.c
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
-#include <commonlib/clamp.h>
+#include <commonlib/bsd/clamp.h>
#include <console/console.h>
#include <delay.h>
#include <northbridge/intel/haswell/haswell.h>
--
2.39.2
@@ -0,0 +1,81 @@
From 2728c87b7f792aa8e4db29d4dc3d2d2f71f6dafd Mon Sep 17 00:00:00 2001
From: Leah Rowe <info@minifree.org>
Date: Mon, 8 Apr 2024 00:54:44 +0100
Subject: [PATCH 1/1] haswell NRI: mitigate compiler warnings
Random -Werror warnings. I'd rather patch these up
than disable warnings-as-errors in the code.
Signed-off-by: Leah Rowe <info@minifree.org>
---
.../intel/haswell/native_raminit/raminit_native.h | 1 +
src/northbridge/intel/haswell/native_raminit/reut.c | 5 +++--
src/northbridge/intel/haswell/native_raminit/testing_io.c | 2 +-
.../intel/haswell/native_raminit/timings_refresh.c | 2 +-
4 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_native.h b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
index 98e39cb76e..76a74d6569 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h
@@ -397,6 +397,7 @@ static inline void tick_delay(const uint32_t delay)
/* Just perform reads to a random register */
for (uint32_t start = 0; start <= delay; start++)
junk = mchbar_read32(REUT_ERR_DATA_STATUS);
+ (void)junk; /* mitigate -Werror=unused-but-set-variable */
}
/*
diff --git a/src/northbridge/intel/haswell/native_raminit/reut.c b/src/northbridge/intel/haswell/native_raminit/reut.c
index c55cdd9c7e..c861528a92 100644
--- a/src/northbridge/intel/haswell/native_raminit/reut.c
+++ b/src/northbridge/intel/haswell/native_raminit/reut.c
@@ -153,7 +153,7 @@ enum raminit_status reut_issue_zq(struct sysinfo *ctrl, uint8_t chanmask, uint8_
{
/** TODO: Issuing ZQ commands differs for LPDDR **/
if (ctrl->lpddr)
- die("%s: LPDDR not yet supported in ZQ calibration\n");
+ die("LPDDR not yet supported in ZQ calibration\n");
uint8_t opcode; /* NOTE: Only used for LPDDR */
uint16_t zq = 0;
@@ -173,8 +173,9 @@ enum raminit_status reut_issue_zq(struct sysinfo *ctrl, uint8_t chanmask, uint8_
opcode = 0xc3;
break;
default:
- die("%s: ZQ type %u is invalid\n", zq_type);
+ die("ZQ type %u is invalid\n", zq_type);
}
+ (void)opcode; /* Mitigate -Werror=unused-but-set-variable */
/* ZQCS on single-channel needs a longer delay */
const uint8_t delay = zq_type == ZQ_SHORT && (!ctrl->dpc[0] || !ctrl->dpc[1]) ? 7 : 1;
diff --git a/src/northbridge/intel/haswell/native_raminit/testing_io.c b/src/northbridge/intel/haswell/native_raminit/testing_io.c
index 7716fc4285..63ae59c33e 100644
--- a/src/northbridge/intel/haswell/native_raminit/testing_io.c
+++ b/src/northbridge/intel/haswell/native_raminit/testing_io.c
@@ -221,7 +221,7 @@ void setup_io_test(
const uint8_t subseq_wait)
{
if (!chanmask)
- die("\n%s: invalid chanmask\n", __func__, chanmask);
+ die("\n%d: invalid chanmask\n", chanmask);
/*
* Prepare variables needed for both channels.
diff --git a/src/northbridge/intel/haswell/native_raminit/timings_refresh.c b/src/northbridge/intel/haswell/native_raminit/timings_refresh.c
index 820bb336b0..e5d8088912 100644
--- a/src/northbridge/intel/haswell/native_raminit/timings_refresh.c
+++ b/src/northbridge/intel/haswell/native_raminit/timings_refresh.c
@@ -189,7 +189,7 @@ static union tc_srftp_reg make_tc_srftp(const struct sysinfo *const ctrl)
void configure_timings(struct sysinfo *ctrl)
{
if (ctrl->lpddr)
- die("%s: Missing support for LPDDR\n");
+ die("Missing support for LPDDR\n");
const uint8_t odt_stretch = get_odt_stretch(ctrl);
const union tc_bank_reg tc_bank = make_tc_bank(ctrl);
--
2.39.2