diff options
author | Arthur Heymans <arthur@aheymans.xyz> | 2017-03-07 20:48:14 +0100 |
---|---|---|
committer | Arthur Heymans <arthur@aheymans.xyz> | 2017-08-20 13:36:03 +0000 |
commit | 6d7a8c1125d17781fe2354eb316df247c82df741 (patch) | |
tree | 4b3522a81a6cc4ad9f11319301023aab80aa282e /src/northbridge/intel/x4x/rcven.c | |
parent | e464ccd116fe51137d9068c5db2edd7275ae8c9d (diff) | |
download | coreboot-6d7a8c1125d17781fe2354eb316df247c82df741.tar.xz |
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.c | 375 |
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); + } +} |