From c24b26594bfab47a8709ed7fb5cb77307fb73a53 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/20] 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 055c666eee..299c44a6b0 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/bsd/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 b4e8c7de5a..5242b16f28 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  */
@@ -514,6 +534,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

