aboutsummaryrefslogtreecommitdiff
path: root/src/northbridge/intel/x4x/rcven.c
diff options
context:
space:
mode:
authorArthur Heymans <arthur@aheymans.xyz>2017-03-07 20:48:14 +0100
committerArthur Heymans <arthur@aheymans.xyz>2017-08-20 13:36:03 +0000
commit6d7a8c1125d17781fe2354eb316df247c82df741 (patch)
tree4b3522a81a6cc4ad9f11319301023aab80aa282e /src/northbridge/intel/x4x/rcven.c
parente464ccd116fe51137d9068c5db2edd7275ae8c9d (diff)
nb/intel/x4x/raminit: Rework receive enable calibration
Moves receive enable calibration to a separate file to lighten raminit.c a bit. Receive enable calibration is quite similar to gm45 so it reuses some of its function names. The functional changes are: * the minimum coarse is now reset for each channel; * on the second fine search for DQS high, TAP overflow is handled by increasing medium; * start coarse at CAS + 1 instead of CAS - 1. Other Intel northbridges do the same and the results are more in line with register dumps from vendor bios. These might improve stability. TESTED on ga-g41m-es2l Change-Id: I0c970455e609d3ce96a262cbf110336a2079da4d Signed-off-by: Arthur Heymans <arthur@aheymans.xyz> Reviewed-on: https://review.coreboot.org/18692 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Nico Huber <nico.h@gmx.de>
Diffstat (limited to 'src/northbridge/intel/x4x/rcven.c')
-rw-r--r--src/northbridge/intel/x4x/rcven.c375
1 files changed, 375 insertions, 0 deletions
diff --git a/src/northbridge/intel/x4x/rcven.c b/src/northbridge/intel/x4x/rcven.c
new file mode 100644
index 0000000000..23f8d52c3c
--- /dev/null
+++ b/src/northbridge/intel/x4x/rcven.c
@@ -0,0 +1,375 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2015 Damien Zammit <damien@zamaudio.com>
+ * Copyright (C) 2017 Arthur Heymans <arthur@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 <delay.h>
+#include "iomap.h"
+#include "x4x.h"
+
+#define MAX_COARSE 15
+#define DQS_HIGH 1
+#define DQS_LOW 0
+
+#define RESET_CNTL(channel) (0x5d8 + channel * 0x400)
+
+struct rec_timing {
+ u8 medium;
+ u8 coarse;
+ u8 pi;
+ u8 tap;
+};
+
+static inline void barrier(void)
+{
+ asm volatile("mfence":::);
+}
+
+static u8 sampledqs(u32 addr, u8 lane, u8 channel)
+{
+ volatile u32 strobe;
+ u32 sample_offset = 0x400 * channel + 0x561 + lane * 4;
+
+ /* Reset the DQS probe */
+ MCHBAR8(RESET_CNTL(channel)) &= ~0x2;
+ udelay(2);
+ MCHBAR8(RESET_CNTL(channel)) |= 0x2;
+ udelay(2);
+ barrier();
+ strobe = read32((u32 *)addr);
+ barrier();
+ return (MCHBAR8(sample_offset) >> 6) & 1;
+}
+
+static void program_timing(const struct rec_timing *timing, u8 channel,
+ u8 lane)
+{
+ u32 reg32;
+ u16 reg16;
+ u8 reg8;
+
+ printk(RAM_SPEW, " Programming timings:"
+ "Coarse: %d, Medium: %d, TAP: %d, PI: %d\n",
+ timing->coarse, timing->medium, timing->tap, timing->pi);
+
+ reg32 = MCHBAR32(0x400 * channel + 0x248);
+ reg32 &= ~0xf0000;
+ reg32 |= timing->coarse << 16;
+ MCHBAR32(0x400 * channel + 0x248) = reg32;
+
+ reg16 = MCHBAR16(0x400 * channel + 0x58c);
+ reg16 &= ~(3 << (lane * 2));
+ reg16 |= timing->medium << (lane * 2);
+ MCHBAR16(0x400 * channel + 0x58c) = reg16;
+
+ reg8 = MCHBAR8(0x400 * channel + 0x560 + lane * 4);
+ reg8 &= ~0x7f;
+ reg8 |= timing->tap;
+ reg8 |= timing->pi << 4;
+ MCHBAR8(0x400 * channel + 0x560 + lane * 4) = reg8;
+}
+
+static int increase_medium(struct rec_timing *timing)
+{
+ if (timing->medium < 3) {
+ timing->medium++;
+ } else if (timing->coarse < MAX_COARSE) {
+ timing->medium = 0;
+ timing->coarse++;
+ } else {
+ printk(BIOS_ERR, "Cannot increase medium any further.\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int decrease_medium(struct rec_timing *timing)
+{
+ if (timing->medium > 0) {
+ timing->medium--;
+ } else if (timing->coarse > 0) {
+ timing->medium = 3;
+ timing->coarse--;
+ } else {
+ printk(BIOS_ERR, "Cannot lower medium any further.\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int increase_tap(struct rec_timing *timing)
+{
+ if (timing->tap == 14) {
+ if (increase_medium(timing))
+ return -1;
+ timing->tap = 0;
+ } else {
+ timing->tap++;
+ }
+ return 0;
+}
+
+static int decrease_tap(struct rec_timing *timing)
+{
+ if (timing->tap > 0) {
+ timing->tap--;
+ } else {
+ if (decrease_medium(timing))
+ return -1;
+ timing->tap = 14;
+ }
+ return 0;
+}
+
+static int decr_coarse_low(u8 channel, u8 lane, u32 addr,
+ struct rec_timing *timing)
+{
+ printk(BIOS_DEBUG,
+ " Decreasing coarse until high to low transition is found\n");
+ while (sampledqs(addr, lane, channel) != DQS_LOW) {
+ if (timing->coarse == 0) {
+ printk(BIOS_CRIT,
+ "Couldn't find DQS-high 0 indicator, halt\n");
+ return -1;
+ }
+ timing->coarse--;
+ program_timing(timing, channel, lane);
+ }
+ printk(BIOS_DEBUG, " DQS low at coarse=%d medium=%d\n",
+ timing->coarse, timing->medium);
+ return 0;
+}
+
+static int fine_search_dqs_high(u8 channel, u8 lane, u32 addr,
+ struct rec_timing *timing)
+{
+ printk(BIOS_DEBUG,
+ " Increasing TAP until low to high transition is found\n");
+ /*
+ * We use a do while loop since it happens that the strobe read
+ * is inconsistent, with the strobe already high. The current
+ * code flow results in failure later when finding the preamble,
+ * at which DQS needs to be high is often not the case if TAP was
+ * not increased at least once here. Work around this by incrementing
+ * TAP at least once to guarantee searching for preamble start at
+ * DQS high.
+ * This seems to be the result of hysteresis on some settings, where
+ * the DQS probe is influenced by its previous value.
+ */
+ if (sampledqs(addr, lane, channel) == DQS_HIGH) {
+ printk(BIOS_WARNING,
+ "DQS already HIGH... DQS probe is inconsistent!\n"
+ "Continuing....\n");
+ }
+ do {
+ if (increase_tap(timing)) {
+ printk(BIOS_CRIT,
+ "Could not find DQS-high on fine search.\n");
+ return -1;
+ }
+ program_timing(timing, channel, lane);
+ } while (sampledqs(addr, lane, channel) != DQS_HIGH);
+
+ printk(BIOS_DEBUG, " DQS high at coarse=%d medium=%d tap:%d\n",
+ timing->coarse, timing->medium, timing->tap);
+ return 0;
+}
+
+static int find_dqs_low(u8 channel, u8 lane, u32 addr,
+ struct rec_timing *timing)
+{
+ /* Look for DQS low, using quarter steps. */
+ printk(BIOS_DEBUG, " Increasing medium until DQS LOW is found\n");
+ while (sampledqs(addr, lane, channel) != DQS_LOW) {
+ if (increase_medium(timing)) {
+ printk(BIOS_CRIT,
+ "Coarse > 15: DQS tuning failed, halt\n");
+ return -1;
+ }
+ program_timing(timing, channel, lane);
+ }
+ printk(BIOS_DEBUG, " DQS low at coarse=%d medium=%d\n",
+ timing->coarse, timing->medium);
+ return 0;
+}
+static int find_dqs_high(u8 channel, u8 lane, u32 addr,
+ struct rec_timing *timing)
+{
+ /* Look for DQS high, using quarter steps. */
+ printk(BIOS_DEBUG, " Increasing medium until DQS HIGH is found\n");
+ while (sampledqs(addr, lane, channel) != DQS_HIGH) {
+ if (increase_medium(timing)) {
+ printk(BIOS_CRIT,
+ "Coarse > 16: DQS tuning failed, halt\n");
+ return -1;
+ }
+ program_timing(timing, channel, lane);
+ }
+ printk(BIOS_DEBUG, " DQS high at coarse=%d medium=%d\n",
+ timing->coarse, timing->medium);
+ return 0;
+}
+
+static int find_dqs_edge_lowhigh(u8 channel, u8 lane,
+ u32 addr, struct rec_timing *timing)
+{
+ /* Medium search for DQS high. */
+ if (find_dqs_high(channel, lane, addr, timing))
+ return -1;
+
+ /* Go back and perform finer search. */
+ if (decrease_medium(timing))
+ return -1;
+ program_timing(timing, channel, lane);
+ if (fine_search_dqs_high(channel, lane, addr, timing) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int find_preamble(u8 channel, u8 lane, u32 addr,
+ struct rec_timing *timing)
+{
+ /* Add a quarter step */
+ if (increase_medium(timing))
+ return -1;
+ program_timing(timing, channel, lane);
+ /* Verify we are at high */
+ if (sampledqs(addr, lane, channel) != DQS_HIGH) {
+ printk(BIOS_CRIT, "Not at DQS high, d'oh\n");
+ return -1;
+ }
+
+ /* Decrease coarse until LOW is found */
+ if (decr_coarse_low(channel, lane, addr, timing))
+ return -1;
+ return 0;
+}
+
+static int calibrate_receive_enable(u8 channel, u8 lane,
+ u32 addr, struct rec_timing *timing)
+{
+ program_timing(timing, channel, lane);
+ /* Set receive enable bit */
+ MCHBAR16(0x400 * channel + 0x588) = (MCHBAR16(0x400 * channel + 0x588)
+ & ~(3 << (lane * 2))) | (1 << (lane * 2));
+
+ if (find_dqs_low(channel, lane, addr, timing))
+ return -1;
+
+ /* Advance a little further. */
+ if (increase_medium(timing)) {
+ /* A finer search could be implemented */
+ printk(BIOS_WARNING, "Cannot increase medium further");
+ return -1;
+ }
+ program_timing(timing, channel, lane);
+
+ if (find_dqs_edge_lowhigh(channel, lane, addr, timing))
+ return -1;
+
+ /* Go back on fine search */
+ if (decrease_tap(timing))
+ return -1;
+ timing->pi = 3;
+ program_timing(timing, channel, lane);
+
+ if (find_preamble(channel, lane, addr, timing))
+ return -1;
+
+ if (find_dqs_edge_lowhigh(channel, lane, addr, timing))
+ return -1;
+ if (decrease_tap(timing))
+ return -1;
+ timing->pi = 7;
+ program_timing(timing, channel, lane);
+
+ /* Unset receive enable bit */
+ MCHBAR16(0x400 * channel + 0x588) = MCHBAR16(0x400 * channel + 0x588) &
+ ~(3 << (lane * 2));
+ return 0;
+}
+
+void rcven(const struct sysinfo *s)
+{
+ int i;
+ u8 channel, lane, reg8;
+ u32 addr;
+ struct rec_timing timing[8];
+ u8 mincoarse;
+
+ MCHBAR8(0x5d8) = MCHBAR8(0x5d8) & ~0xc;
+ MCHBAR8(0x9d8) = MCHBAR8(0x9d8) & ~0xc;
+ MCHBAR8(0x5dc) = MCHBAR8(0x5dc) & ~0x80;
+ FOR_EACH_POPULATED_CHANNEL(s->dimms, channel) {
+ addr = (channel << 29);
+ mincoarse = 0xff;
+ for (i = 0; i < RANKS_PER_CHANNEL &&
+ !RANK_IS_POPULATED(s->dimms, channel, i); i++)
+ addr += 128 * MiB;
+ for (lane = 0; lane < 8; lane++) {
+ printk(BIOS_DEBUG, "Channel %d, Lane %d addr=0x%08x\n",
+ channel, lane, addr);
+ timing[lane].coarse = (s->selected_timings.CAS + 1);
+ switch (lane) {
+ default:
+ case 0:
+ case 1:
+ timing[lane].medium = 0;
+ break;
+ case 2:
+ case 3:
+ timing[lane].medium = 1;
+ break;
+ case 4:
+ case 5:
+ timing[lane].medium = 2;
+ break;
+ case 6:
+ case 7:
+ timing[lane].medium = 3;
+ break;
+ }
+ timing[lane].tap = 0;
+ timing[lane].pi = 0;
+
+ if (calibrate_receive_enable(channel, lane, addr,
+ &timing[lane]))
+ die("Receive enable calibration failed\n");
+ if (mincoarse > timing[lane].coarse)
+ mincoarse = timing[lane].coarse;
+ }
+ printk(BIOS_DEBUG, "Found min coarse value = %d\n", mincoarse);
+ printk(BIOS_DEBUG, "Receive enable, final timings:\n");
+ /* Normalise coarse */
+ for (lane = 0; lane < 8; lane++) {
+ if (timing[lane].coarse == 0)
+ reg8 = 0;
+ else
+ reg8 = timing[lane].coarse - mincoarse;
+ printk(BIOS_DEBUG, "ch %d lane %d: coarse offset: %d;"
+ "medium: %d; tap: %d\n",
+ channel, lane, reg8, timing[lane].medium,
+ timing[lane].tap);
+ MCHBAR16(0x400 * channel + 0x5fa) &=
+ ~(3 << (lane * 2)) | (reg8 << (lane * 2));
+ }
+ /* simply use timing[0] to program mincoarse */
+ timing[0].coarse = mincoarse;
+ program_timing(&timing[0], channel, 0);
+ }
+}