[coreboot-gerrit] Change in coreboot[master]: [WIP/UNTESTED]nb/intel/x4x: Implement both read and write training

Arthur Heymans (Code Review) gerrit at coreboot.org
Sat Nov 4 10:15:55 CET 2017


Arthur Heymans has uploaded this change for review. ( https://review.coreboot.org/22329


Change subject: [WIP/UNTESTED]nb/intel/x4x: Implement both read and write training
......................................................................

[WIP/UNTESTED]nb/intel/x4x: Implement both read and write training

Code looks bad and does weird things over ranks. Should probably do
things like gm45 and train one lane over all available ranks.

Change-Id: Iacdc63b91b4705d1a80437314bfe55385ea5b6c1
Signed-off-by: Arthur Heymans <arthur at aheymans.xyz>
---
M src/northbridge/intel/x4x/Makefile.inc
A src/northbridge/intel/x4x/dq_dqs.c
M src/northbridge/intel/x4x/raminit_ddr2.c
M src/northbridge/intel/x4x/x4x.h
4 files changed, 629 insertions(+), 5 deletions(-)



  git pull ssh://review.coreboot.org:29418/coreboot refs/changes/29/22329/1

diff --git a/src/northbridge/intel/x4x/Makefile.inc b/src/northbridge/intel/x4x/Makefile.inc
index 0e5a4a3..20db630 100644
--- a/src/northbridge/intel/x4x/Makefile.inc
+++ b/src/northbridge/intel/x4x/Makefile.inc
@@ -22,6 +22,7 @@
 romstage-y += ram_calc.c
 romstage-y += rcven.c
 romstage-y += raminit_tables.c
+romstage-y += dq_dqs.c
 
 ramstage-y += acpi.c
 ramstage-y += ram_calc.c
diff --git a/src/northbridge/intel/x4x/dq_dqs.c b/src/northbridge/intel/x4x/dq_dqs.c
new file mode 100644
index 0000000..6af88e0
--- /dev/null
+++ b/src/northbridge/intel/x4x/dq_dqs.c
@@ -0,0 +1,617 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2017 Arthur Heymans <arthur at aheymans.xyz>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <arch/io.h>
+#include <console/console.h>
+#include <stdint.h>
+#include <string.h>
+#include "x4x.h"
+#include "iomap.h"
+
+static void print_dll_setting(const struct dll_setting *dll_setting,
+			u8 default_verbose)
+{
+	u8 debug_level;
+	if (default_verbose)
+		debug_level = BIOS_DEBUG;
+	else
+		debug_level = RAM_DEBUG;
+
+	printk(debug_level, "%d.%d.%d.%d:%d.%d\n", dll_setting->coarse,
+		dll_setting->clk_delay, dll_setting->tap,
+		dll_setting->pi, dll_setting->db_en,
+		dll_setting->db_sel);
+}
+
+static void set_db(struct sysinfo *s, struct dll_setting *dq_dqs_setting)
+{
+	u8 db_tap, db_pi;
+
+	switch (s->selected_timings.mem_clk) {
+	default:
+	case MEM_CLOCK_800MHz:
+		db_tap = 0xa3;
+		db_pi = 0x32;
+		break;
+	case MEM_CLOCK_1066MHz:
+		db_tap = 0x82;
+		db_pi = 0x76;
+		break;
+	case MEM_CLOCK_1333MHz:
+		db_tap = 0xb3;
+		db_pi = 0x46;
+		break;
+	}
+
+	if (dq_dqs_setting->tap < (db_tap & 0xf)) {
+		dq_dqs_setting->db_en = 1;
+		dq_dqs_setting->db_sel = 1;
+	} else if ((dq_dqs_setting->tap == (db_tap & 0xf))
+			&& (dq_dqs_setting->pi < (db_pi & 0xf))) {
+		dq_dqs_setting->db_en = 1;
+		dq_dqs_setting->db_sel = 1;
+	} else if (dq_dqs_setting->tap < (db_tap >> 4)) {
+		dq_dqs_setting->db_en = 0;
+		dq_dqs_setting->db_sel = 0;
+	} else if ((dq_dqs_setting->tap == (db_tap >> 4))
+			&& (dq_dqs_setting->pi < (db_pi >> 4))) {
+		dq_dqs_setting->db_en = 0;
+		dq_dqs_setting->db_sel = 0;
+	} else {
+		dq_dqs_setting->db_en = 1;
+		dq_dqs_setting->db_sel = 0;
+	}
+}
+
+const static u8 max_tap[3] = {12, 10, 13};
+
+static int increment_dq_dqs(struct sysinfo *s, struct dll_setting *dq_dqs_setting)
+{
+	u8 max_tap_val = max_tap[s->selected_timings.mem_clk
+				- MEM_CLOCK_800MHz];
+	/* PI */
+	if (dq_dqs_setting->pi < 6) {
+		dq_dqs_setting->pi += 1;
+	} else if (dq_dqs_setting->tap < max_tap_val) {
+		dq_dqs_setting->pi = 0;
+		dq_dqs_setting->tap += 1;
+	} else if (dq_dqs_setting->clk_delay < 2) {
+		dq_dqs_setting->pi = 0;
+		dq_dqs_setting->tap = 0;
+		dq_dqs_setting->clk_delay += 1;
+	} else if (dq_dqs_setting->coarse < 1) {
+		dq_dqs_setting->pi = 0;
+		dq_dqs_setting->tap = 0;
+		dq_dqs_setting->clk_delay = 0;
+		dq_dqs_setting->coarse += 1;
+	} else {
+		return 1;
+	}
+	set_db(s, dq_dqs_setting);
+	return 0;
+}
+
+#define WT_PATTERN_SIZE 80
+
+static const u32 write_training_schedule[WT_PATTERN_SIZE] = {
+	0xffffffff, 0x00000000, 0xffffffff, 0x00000000,
+	0xffffffff, 0x00000000, 0xffffffff, 0x00000000,
+	0xffffffff, 0x00000000, 0xffffffff, 0x00000000,
+	0xffffffff, 0x00000000, 0xffffffff, 0x00000000,
+	0xefefefef, 0x10101010, 0xefefefef, 0x10101010,
+	0xefefefef, 0x10101010, 0xefefefef, 0x10101010,
+	0xefefefef, 0x10101010, 0xefefefef, 0x10101010,
+	0xefefefef, 0x10101010, 0xefefefef, 0x10101010,
+	0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010,
+	0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010,
+	0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010,
+	0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010,
+	0x03030303, 0x04040404, 0x09090909, 0x10101010,
+	0x21212121, 0x40404040, 0x81818181, 0x00000000,
+	0x03030303, 0x04040404, 0x09090909, 0x10101010,
+	0x21212121, 0x40404040, 0x81818181, 0x00000000,
+	0xfdfdfdfd, 0xfafafafa, 0xf7f7f7f7, 0xeeeeeeee,
+	0xdfdfdfdf, 0xbebebebe, 0x7f7f7f7f, 0xfefefefe,
+	0xfdfdfdfd, 0xfafafafa, 0xf7f7f7f7, 0xeeeeeeee,
+	0xdfdfdfdf, 0xbebebebe, 0x7f7f7f7f, 0xfefefefe,
+};
+
+static int test_dq_aligned(struct sysinfo *s, u32 address)
+{
+	u8 error_found = 0, lane, count;
+	u8 data[8];
+	u16 i;
+	u32 content;
+
+	for (i = 0; i < WT_PATTERN_SIZE; i++) {
+		for (count = 0; count < WT_PATTERN_SIZE; count++) {
+			if ((count % 16) == 0)
+				MCHBAR32(0xf90) = 1;
+			content = write_training_schedule[count];
+			write32((u32 *)address + 8 * count, content);
+			write32((u32 *)address + 8 * count + 4, content);
+		}
+
+		write32(&data[0], read32((u32 *)address + 8 * i));
+		write32(&data[4], read32((u32 *)address + 8 * i + 4));
+		for (lane = 0; lane < 8; lane++) {
+			if (data[lane] != (write_training_schedule[i] & 0xff))
+				error_found |= (1 << lane);
+		}
+		/* No need to continue now */
+		if (error_found == 0xff)
+			return 0xff;
+	}
+	return error_found;
+}
+
+enum training_modes {
+	SUCCEEDING = 0,
+	FAILING = 1
+};
+#define CONSISTENCY 10
+
+/*
+ * This function finds either failing or succeeding writes by increasing DQ.
+ * When it has found a failing or succeeding setting it will increase DQ
+ * another 10 times to make sure the result is consistent.
+ * This means that the middle between failing and succeeding writes is shifted
+ * by 9 steps, which need to be substracted later.
+ */
+static int find_dq_limit(struct sysinfo *s, u8 channel, u32 address,
+			struct dll_setting *dq_setting, u8 *dq_lim,
+			u8 direction)
+{
+	int status;
+	u8 lane_passes[8] = { };
+	u8 sample = 0xff;
+	u8 lane;
+	u8 lane_err;
+
+	for (lane = 0; lane < 8; lane++)
+		dqset(channel, lane, &dq_setting[lane]);
+
+	while(sample) {
+		status = 0;
+		/* TODO: This is probably more readable if done one lane at the time */
+		lane_err = test_dq_aligned(s, address);
+		lane_err ^= 0xff * direction;
+		for (lane = 0; lane < 8; lane++) {
+			if (lane_err & (1 << lane)) {
+				/* reuse function for DQ DLL settings */
+				status = increment_dq_dqs(s, &dq_setting[lane]);
+				dqset(channel, lane, &dq_setting[lane]);
+				dq_lim[lane]++;
+			} else if (lane_passes[lane] < CONSISTENCY) {
+				status = increment_dq_dqs(s, &dq_setting[lane]);
+				dqset(channel, lane, &dq_setting[lane]);
+				dq_lim[lane]++;
+				lane_passes[lane]++;
+			} else if (lane_passes[lane] == CONSISTENCY) {
+				sample &= ~(1 << lane);
+			}
+		}
+		if (status) {
+			if (direction == 0) {
+				printk(BIOS_ERR,
+					"Could not find good Write training settings\n");
+				return 1;
+			} else {
+				break;
+			}
+		}
+	}
+	return 0;
+}
+
+/*
+ * Increase DQ until writes succeed, then further increase DQ until it fails.
+ * Use the middle between this working lower limit and this failing upper
+ * limit.
+ * INVESTIGATE: why not taking most extreme values over each rank?
+ */
+int do_write_training(struct sysinfo *s)
+{
+	int i;
+	u8 channel, lane;
+	u32 address;
+	u8 dq_lower_r0[8];
+	u8 dq_upper_r0[8];
+	u8 dq_lower_r2[8];
+	u8 dq_upper_r2[8];
+	u8 dq_center[8];
+	struct dll_setting dq_setting[8];
+	u8 dq_average;
+	u32 dq_absolute;
+
+	printk(BIOS_DEBUG, "Starting DQ write training\n");
+
+	FOR_EACH_POPULATED_CHANNEL(s->dimms, channel) {
+		printk(BIOS_DEBUG, "Doing DQ write training on CH%d\n", channel);
+		address = 0x20000000 * channel;
+		for (i = 0; (i < RANKS_PER_CHANNEL) &&
+			     !RANK_IS_POPULATED(s->dimms, channel, i); i++)
+			address += 128 * MiB;
+
+		dq_average = 0;
+		dq_absolute = 0;
+		memset(dq_lower_r0, 0, sizeof(dq_lower_r0));
+		memset(dq_lower_r2, 0, sizeof(dq_lower_r2));
+
+		memset(dq_center, 0, sizeof(dq_center));
+		for (lane = 0; lane < 8; lane++) {
+			/* Start from DQS settings */
+			s->dq_settings[channel][lane] =
+				s->dqs_settings[channel][lane];
+			dq_setting[lane] = s->dqs_settings[channel][lane];
+		}
+
+		if (find_dq_limit(s, channel, address, dq_setting, dq_lower_r0,
+					SUCCEEDING)) {
+			printk(BIOS_CRIT,
+				"Could not find working lower limit DQ setting\n");
+			return -1;
+		}
+		printk(RAM_DEBUG, "Lower Limit for DQ on rank 0:\n");
+		for (lane = 0; lane < 8; lane++) {
+			printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",
+				channel, lane, dq_lower_r0[lane]);
+		}
+
+		memcpy(dq_upper_r0, dq_lower_r0, sizeof(dq_upper_r0));
+		if (find_dq_limit(s, channel, address, dq_setting, dq_upper_r0,
+					FAILING)) {
+			printk(BIOS_CRIT,
+				"Could not find failing upper limit DQ setting\n");
+			return -1;
+		}
+
+		printk(RAM_DEBUG, "Upper Limit for DQ on rank 0:\n");
+		for (lane = 0; lane < 8; lane++) {
+			printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",
+				channel, lane, dq_upper_r0[lane]);
+		}
+
+		if (RANK_IS_POPULATED(s->dimms, channel, 0)
+			&& RANK_IS_POPULATED(s->dimms, channel, 2)) {
+			address += 256 * MiB;
+
+			if (find_dq_limit(s, channel, address, dq_setting,
+						dq_lower_r2, SUCCEEDING)) {
+				printk(BIOS_CRIT, "Could not find working lower limit DQ setting\n");
+				return -1;
+			}
+			printk(RAM_DEBUG, "Lower Limit for DQ on rank 2:\n");
+			for (lane = 0; lane < 8; lane++) {
+				printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",
+					channel, lane, dq_lower_r2[lane]);
+			}
+
+			memcpy(dq_upper_r2, dq_lower_r2, sizeof(dq_upper_r2));
+			if (find_dq_limit(s, channel, address, dq_setting,
+						dq_upper_r2, FAILING)) {
+				printk(BIOS_CRIT, "Could not find failing upper limit DQ setting\n");
+				return -1;
+			}
+			printk(RAM_DEBUG, "Upper Limit for DQ on rank 2:\n");
+			for (lane = 0; lane < 8; lane++) {
+				printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",
+					channel, lane, dq_upper_r2[lane]);
+			}
+
+			for (lane = 0; lane < 8; lane++) {
+				dq_upper_r0[lane] = MIN(dq_upper_r0[lane],
+							dq_upper_r2[lane]);
+				dq_lower_r0[lane] = MAX(dq_lower_r0[lane],
+							dq_lower_r2[lane]);
+			}
+		} /* If Rank 2 is present */
+
+		for (lane = 0; lane < 8; lane++) {
+			dq_lower_r0[lane] -= CONSISTENCY - 1;
+			dq_upper_r0[lane] -= CONSISTENCY - 1;
+		}
+
+		for (lane = 0; lane < 8; lane++)
+			dq_center[lane] = (dq_upper_r0[lane]
+					+ dq_lower_r0[lane]) / 2;
+
+		printk(RAM_DEBUG, "Centered values for DQ DLL:\n");
+		for (lane = 0; lane < 8; lane++) {
+			printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",
+				channel, lane, dq_center[lane]);
+		}
+
+		/* Reset DQ DLL settings and increment with centered value*/
+		printk(BIOS_DEBUG, "Final DQ timings on CH%d\n", channel);
+		for (lane = 0; lane < 8; lane++) {
+			for (i = 0; i < dq_center[lane]; i++)
+				increment_dq_dqs(s, &s->dq_settings[channel][lane]);
+			printk(BIOS_DEBUG, "\tlane%d: ", lane);
+			print_dll_setting(&s->dq_settings[channel][lane], 1);
+			dqset(channel, lane, &s->dq_settings[channel][lane]);
+		}
+	}
+	printk(BIOS_DEBUG, "Done DQ write training\n");
+	return 0;
+}
+
+#define RT_PATTERN_SIZE 40
+
+static const u32 read_training_schedule[RT_PATTERN_SIZE] = {
+	0xffffffff, 0x00000000, 0xffffffff, 0x00000000,
+	0xffffffff, 0x00000000, 0xffffffff, 0x00000000,
+	0xefefefef, 0x10101010, 0xefefefef, 0x10101010,
+	0xefefefef, 0x10101010, 0xefefefef, 0x10101010,
+	0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010,
+	0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010,
+	0x03030303, 0x04040404, 0x09090909, 0x10101010,
+	0x21212121, 0x40404040, 0x81818181, 0x00000000,
+	0xfdfdfdfd, 0xfafafafa, 0xf7f7f7f7, 0xeeeeeeee,
+	0xdfdfdfdf, 0xbebebebe, 0x7f7f7f7f, 0xfefefefe
+};
+
+static void rt_set_dqs(u8 channel, u8 lane, u8 rank, struct rt_dqs_setting *dqs_setting)
+{
+	u8 saved_tap = MCHBAR16(0x540 + 0x400 * channel + lane * 4);
+	u8 saved_pi = MCHBAR16(0x542 + 0x400 * channel + lane * 4);
+	printk(RAM_SPEW, "RT DQS: ch%d, L%d, %d.%d\n", channel, lane,
+		dqs_setting->tap,
+		dqs_setting->pi);
+
+	saved_tap &= ~(0xf << (rank * 4));
+	saved_tap |= dqs_setting->tap << (rank * 4);
+	MCHBAR16(0x540 + 0x400 * channel + lane * 4) = saved_tap;
+
+	saved_pi &= ~(0x7 << (rank * 3));
+	saved_pi |= dqs_setting->pi << (rank * 4);
+	MCHBAR16(0x542 + 0x400 * channel + lane * 4) = saved_pi;
+}
+
+static int rt_increment_dqs(struct rt_dqs_setting *setting)
+{
+	if (setting->pi < 7) {
+		setting->pi++;
+	} else if (setting->tap < 14) {
+		setting->pi = 0;
+		setting->tap++;
+	} else {
+		return -1;
+	}
+	return 0;
+}
+
+static u8 test_dqs_aligned(struct sysinfo *s, u8 channel, u32 address)
+{
+	u8 error_lane = 0;
+	u8 data8[8];
+	int i, lane;
+
+	for (i = 0; i < RT_PATTERN_SIZE; i++) {
+		write32(&data8[0], read32((u32 *)address + i * 8));
+		write32(&data8[4], read32((u32 *)address + i * 8 + 4));
+		for (lane = 0; lane < 8; lane++) {
+			if (data8[lane] != (read_training_schedule[i] & 0xff))
+				error_lane |= (1 << lane);
+		}
+		/* No need to continue now */
+		if (error_lane == 0xff)
+			break;
+
+	}
+	return error_lane;
+}
+
+static int rt_find_dqs_limit(struct sysinfo *s, u8 channel, u32 address,
+			struct rt_dqs_setting *dqs_setting, u8 *dqs_lim,
+			u8 direction)
+{
+	int lane;
+	u8 sample = 0xff, lane_err;
+
+	for (lane = 0; lane < 8; lane++)
+		rt_set_dqs(channel, lane, 0,  &dqs_setting[lane]);
+
+	while(sample) {
+		/* TODO: This is probably more readable if done one lane at the time */
+		lane_err = test_dqs_aligned(s, channel, address);
+		lane_err ^= 0xff * direction;
+		for (lane = 0; lane < 8; lane++) {
+			/* Checking lanes that have already been done is a good idea */
+			/* since those can be found bad again. */
+			if (lane_err & (1 << lane)) {
+				if (rt_increment_dqs(&dqs_setting[lane])) {
+					if (direction == SUCCEEDING) {
+						printk(BIOS_CRIT,
+							"Could not find RT DQS setting\n");
+						return -1;
+					} else {
+						sample &= ~(1 << lane);
+						continue;
+					}
+				}
+				dqs_lim[lane]++;
+				rt_set_dqs(channel, lane, 0,
+					&dqs_setting[lane]);
+			} else {
+				sample &= ~(1 << lane);
+			}
+		}
+	}
+	return 0;
+}
+
+#define RT_LOOPS 3
+
+/*
+ * Increase DQS until read succeed, then further increase DQS until it fails.
+ * Use the middle between this working lower limit and this failing upper
+ * limit.
+ * To improve statistics this is done RT_LOOPS amount of timings, while
+ * additioning the centered values to some saved values. At the end the
+ * saved values are divided by RT_LOOPS.
+ *
+ * TO INVESTIGATE: vendor trains rank 0 and rank 2 and uses the most
+ * extreme results to compute the middle 'eye' value, which is then
+ * used for all ranks.
+ * Try performing the training and programming the result on each individual
+ * rank.
+ */
+int do_read_training(struct sysinfo *s)
+{
+	int loop, channel, i, lane, rank;
+	u32 address, content;
+	u8 dqs_lower_r0[8];
+	u8 dqs_upper_r0[8];
+	u8 dqs_lower_r2[8];
+	u8 dqs_upper_r2[8];
+	u8 dqs_center[8];
+	u16 saved_dqs[2][8] = { };
+
+	struct rt_dqs_setting dqs_setting[8];
+
+	printk(BIOS_DEBUG, "Starting DQS read training\n");
+	memset(s->rt_dqs, 0, sizeof(s->rt_dqs));
+
+	for (loop = 0; loop < RT_LOOPS; loop++) {
+		FOR_EACH_POPULATED_CHANNEL(s->dimms, channel) {
+			printk(BIOS_DEBUG, "Doing DQS write training on CH%d, loop%d\n",
+				channel, loop);
+			address = 0x20000000 * channel;
+			for (i = 0; !RANK_IS_POPULATED(s->dimms, channel, i)
+				     && i < RANKS_PER_CHANNEL; i++)
+				address += 128 * MiB;
+
+			/* Write pattern to strobe address*/
+			for (i = 0; i < RT_PATTERN_SIZE; i++) {
+				content = read_training_schedule[i];
+				write32((u32 *)address + 8 * i, content);
+				write32((u32 *)address + 8 * i + 4, content);
+			}
+
+			memset(dqs_lower_r0, 0, sizeof(dqs_lower_r0));
+			memset(dqs_lower_r2, 0, sizeof(dqs_lower_r2));
+			memset(dqs_setting, 0, sizeof(dqs_setting));
+
+			if (rt_find_dqs_limit(s, channel, address, dqs_setting,
+						dqs_lower_r0, SUCCEEDING)) {
+				printk(BIOS_CRIT,
+					"Could not find working lower limit DQS setting\n");
+				return -1;
+			}
+			printk(RAM_DEBUG, "Lower Limit for DQS on rank 0:\n");
+			for (lane = 0; lane < 8; lane++) {
+				printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",
+					channel, lane, dqs_lower_r0[lane]);
+			}
+
+			memcpy(dqs_upper_r0, dqs_lower_r0,
+				sizeof(dqs_upper_r0));
+			if (rt_find_dqs_limit(s, channel, address, dqs_setting,
+						dqs_upper_r0, FAILING)) {
+				printk(BIOS_CRIT,
+					"Could not find failing upper limit DQ setting\n");
+				return -1;
+			}
+
+			printk(RAM_DEBUG, "Upper Limit for DQS on rank 0:\n");
+			for (lane = 0; lane < 8; lane++) {
+				printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",
+				channel, lane, dqs_upper_r0[lane]);
+			}
+
+			if (RANK_IS_POPULATED(s->dimms, channel, 0)
+				&& RANK_IS_POPULATED(s->dimms, channel, 2)) {
+				address += 256 * MiB;
+
+				memset(dqs_setting, 0 , sizeof(dqs_setting));
+
+				if (rt_find_dqs_limit(s, channel, address,
+							dqs_setting,
+							dqs_lower_r2,
+							SUCCEEDING)) {
+					printk(BIOS_CRIT,
+						"Could not find working lower limit DQS setting\n");
+					return -1;
+				}
+				printk(RAM_DEBUG, "Lower Limit for DQS on rank 2:\n");
+				for (lane = 0; lane < 8; lane++) {
+					printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",
+						channel, lane, dqs_lower_r2[lane]);
+				}
+
+				memcpy(dqs_upper_r2, dqs_lower_r2,
+					sizeof(dqs_upper_r2));
+				if (rt_find_dqs_limit(s, channel, address,
+							dqs_setting,
+							dqs_upper_r2,
+							FAILING)) {
+					printk(BIOS_CRIT,
+						"Could not find failing upper limit DQS setting\n");
+					return -1;
+				}
+				printk(RAM_DEBUG,
+					"Upper Limit for DQS on rank 2:\n");
+				for (lane = 0; lane < 8; lane++) {
+					printk(RAM_DEBUG,
+						" ch%d, lane %d, #steps %d\n",
+						channel, lane, dqs_upper_r2[lane]);
+				}
+
+				for (lane = 0; lane < 8; lane++) {
+					dqs_upper_r0[lane] =
+						MIN(dqs_upper_r0[lane],
+							dqs_upper_r2[lane]);
+					dqs_lower_r0[lane] =
+						MAX(dqs_lower_r0[lane],
+							dqs_lower_r2[lane]);
+				}
+			} /* end If Rank 2 is present */
+
+			for (lane = 0; lane < 8; lane++) {
+				dqs_center[lane] = (dqs_upper_r0[lane]
+						+ dqs_lower_r0[lane]) / 2;
+				saved_dqs[channel][lane] += dqs_center[lane];
+			}
+		} /* END FOR_EACH_POPULATED_CHANNEL */
+	} /* end RT_LOOPS */
+
+	/* Set all ranks at the same value as the first rank */
+	FOR_EACH_POPULATED_CHANNEL(s->dimms, channel) {
+		printk(BIOS_DEBUG, "Final timings on CH%d\n", channel);
+		for (lane = 0; lane < 8; lane++) {
+			saved_dqs[channel][lane] /= RT_LOOPS;
+			while (saved_dqs[channel][lane]--) { /* check for overflow ? */
+				FOR_EACH_POPULATED_RANK_IN_CHANNEL(s->dimms,
+								channel, rank) {
+					rt_increment_dqs(&s->rt_dqs[channel][rank][lane]);
+				}
+			}
+			FOR_EACH_POPULATED_RANK_IN_CHANNEL(s->dimms, channel, rank)
+				rt_set_dqs(channel, lane, rank,
+					&s->rt_dqs[channel][rank][lane]);
+		}
+		FOR_EACH_POPULATED_RANK_IN_CHANNEL(s->dimms, channel, rank) {
+			for (lane = 0; lane < 8; lane++)
+				printk(BIOS_DEBUG, "\tr%dlane%d: %d.%d\n",
+					rank, lane,
+					s->rt_dqs[channel][rank][lane].tap,
+					s->rt_dqs[channel][rank][lane].pi);
+		}
+	}
+	printk(BIOS_DEBUG, "Done DQS read training\n");
+	return 0;
+}
diff --git a/src/northbridge/intel/x4x/raminit_ddr2.c b/src/northbridge/intel/x4x/raminit_ddr2.c
index 90912ad..7f54669 100644
--- a/src/northbridge/intel/x4x/raminit_ddr2.c
+++ b/src/northbridge/intel/x4x/raminit_ddr2.c
@@ -292,7 +292,7 @@
  * All finer DQ and DQS DLL settings are set to the same value
  * for each rank in a channel, while coarse is common.
  */
-static void dqsset(u8 ch, u8 lane, const struct dll_setting *setting)
+void dqsset(u8 ch, u8 lane, const struct dll_setting *setting)
 {
 	int rank;
 
@@ -319,7 +319,7 @@
 	}
 }
 
-static void dqset(u8 ch, u8 lane, const struct dll_setting *setting)
+void dqset(u8 ch, u8 lane, const struct dll_setting *setting)
 {
 	int rank;
 	MCHBAR32(0x400 * ch + 0x5fc) = (MCHBAR32(0x400 * ch + 0x5fc)
@@ -1687,9 +1687,11 @@
 
 	// XXX tRD
 
-	// XXX Write training
-
-	// XXX Read training
+	if (!fast_boot) {
+		if (s->selected_timings.mem_clk > MEM_CLOCK_667MHz)
+			do_write_training(s);
+		do_read_training(s);
+	}
 
 	// DRADRB
 	dradrb_ddr2(s);
diff --git a/src/northbridge/intel/x4x/x4x.h b/src/northbridge/intel/x4x/x4x.h
index eaec7c6..cd803ea 100644
--- a/src/northbridge/intel/x4x/x4x.h
+++ b/src/northbridge/intel/x4x/x4x.h
@@ -358,6 +358,10 @@
 void rcven(struct sysinfo *s);
 u32 fsb2mhz(u32 speed);
 u32 ddr2mhz(u32 speed);
+void dqsset(u8 ch, u8 lane, const struct dll_setting *setting);
+void dqset(u8 ch, u8 lane, const struct dll_setting *setting);
+int do_write_training(struct sysinfo *s);
+int do_read_training(struct sysinfo *s);
 
 extern const struct dll_setting default_ddr2_667_ctrl[7];
 extern const struct dll_setting default_ddr2_800_ctrl[7];

-- 
To view, visit https://review.coreboot.org/22329
To unsubscribe, visit https://review.coreboot.org/settings

Gerrit-Project: coreboot
Gerrit-Branch: master
Gerrit-MessageType: newchange
Gerrit-Change-Id: Iacdc63b91b4705d1a80437314bfe55385ea5b6c1
Gerrit-Change-Number: 22329
Gerrit-PatchSet: 1
Gerrit-Owner: Arthur Heymans <arthur at aheymans.xyz>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.coreboot.org/pipermail/coreboot-gerrit/attachments/20171104/6b4c2922/attachment-0001.html>


More information about the coreboot-gerrit mailing list