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@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@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];