Arthur Heymans has uploaded a new change for review. ( https://review.coreboot.org/19878 )
Change subject: [WIP]nb/intel/x4x/raminit: Add write leveling ......................................................................
[WIP]nb/intel/x4x/raminit: Add write leveling
Does not work!!
Change-Id: Ibfbaa235bc4eb08e9345321b851e880390a624e8 Signed-off-by: Arthur Heymans arthur@aheymans.xyz --- M src/northbridge/intel/x4x/Makefile.inc A src/northbridge/intel/x4x/dq_dqsl_dll.c M src/northbridge/intel/x4x/raminit_ddr23.c M src/northbridge/intel/x4x/raminit_tables.c M src/northbridge/intel/x4x/spd_ddr3_decode.c 5 files changed, 507 insertions(+), 2 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/78/19878/1
diff --git a/src/northbridge/intel/x4x/Makefile.inc b/src/northbridge/intel/x4x/Makefile.inc index 8b59dbf..72f2c0e 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 += spd_ddr2_decode.c romstage-y += spd_ddr3_decode.c +romstage-y += write_level.c romstage-y += raminit_tables.c
ramstage-y += acpi.c diff --git a/src/northbridge/intel/x4x/dq_dqsl_dll.c b/src/northbridge/intel/x4x/dq_dqsl_dll.c new file mode 100644 index 0000000..a1dea68 --- /dev/null +++ b/src/northbridge/intel/x4x/dq_dqsl_dll.c @@ -0,0 +1,439 @@ +/* + * 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 <stdint.h> +#include <string.h> +#include <console/console.h> +#include <arch/io.h> +#include <halt.h> +#include <delay.h> +#include "x4x.h" +#include "iomap.h" + +static void do_write_level(struct sysinfo *s, u8 channel, u8 config, u8 rank1, + u8 rank0, int wl_enable) +{ + u32 emrs1; + + /* Is shifted by bits 2 later so u8 can be used to reduce size */ + const static u8 emrs1_lut[8][4][4]={ /* [Config][Leveling Rank][Rank] */ + { /* Config 0: 2R2R */ + {0x11, 0x00, 0x91, 0x00}, + {0x00, 0x11, 0x91, 0x00}, + {0x91, 0x00, 0x11, 0x00}, + {0x91, 0x00, 0x00, 0x11} + }, + { // Config 1: 2R1R + {0x11, 0x00, 0x91, 0x00}, + {0x00, 0x11, 0x91, 0x00}, + {0x91, 0x00, 0x11, 0x00}, + {0x00, 0x00, 0x00, 0x00} + }, + { // Config 2: 1R2R + {0x11, 0x00, 0x91, 0x00}, + {0x00, 0x00, 0x00, 0x00}, + {0x91, 0x00, 0x11, 0x00}, + {0x91, 0x00, 0x00, 0x11} + }, + { // Config 3: 1R1R + {0x11, 0x00, 0x91, 0x00}, + {0x00, 0x00, 0x00, 0x00}, + {0x91, 0x00, 0x11, 0x00}, + {0x00, 0x00, 0x00, 0x00} + }, + { // Config 4: 2R0R + {0x11, 0x00, 0x00, 0x00}, + {0x00, 0x11, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00} + }, + { // Config 5: 0R2R + {0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x11, 0x00}, + {0x00, 0x00, 0x00, 0x11} + }, + { // Config 6: 1R0R + {0x11, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00} + }, + { // Config 7: 0R1R + {0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x11, 0x00}, + {0x00, 0x00, 0x00, 0x00} + } + }; + + if (wl_enable) { + printk(RAM_DEBUG, "Entering WL mode\n"); + printk(RAM_DEBUG, "Using WL ODT values\n"); + emrs1 = emrs1_lut[config][rank0][rank1]; + } else { + printk(RAM_DEBUG, "Exiting WL mode\n"); + emrs1 = ddr3_emrs1_config[s->dimm_config[channel]][rank1]; + } + printk(RAM_DEBUG, "Setting ODT for rank%d to ", rank1); + switch (emrs1) { + case 0: + printk(RAM_DEBUG, "0 Ohm\n"); + break; + case 0x11: + printk(RAM_DEBUG, "40 Ohm\n"); + break; + case 0x81: + printk(RAM_DEBUG, "30 Ohm\n"); + break; + case 0x80: + printk(RAM_DEBUG, "20 Ohm\n"); + break; + case 0x10: + printk(RAM_DEBUG, "120 Ohm\n"); + break; + case 0x01: + printk(RAM_DEBUG, "60 Ohm\n"); + break; + default: + printk(BIOS_WARNING, "ODT value Undefined!\n"); + break; + } + + emrs1 <<= 2; + emrs1 |= (1 << 1); + + if (wl_enable && (rank0 != rank1)) + printk(RAM_DEBUG, "Disabling output for rank%d\n", rank1); + emrs1 |= (1 << 12); + if (wl_enable && (rank0 == rank1)) { + printk(RAM_DEBUG, "Enabling WL for rank%d\n", rank1); + emrs1 |= (1 << 7); + } + send_jedec_cmd(s, rank1, channel, EMRS1_CMD, emrs1); +} + +static void set_db(struct sysinfo *s, struct dll_setting *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 (dqs_setting->tap < (db_tap & 0xf)) { + dqs_setting->db_en = 1; + dqs_setting->db_sel = 1; + } else if ((dqs_setting->tap == (db_tap & 0xf)) + && (dqs_setting->pi < (db_pi & 0xf))) { + dqs_setting->db_en = 1; + dqs_setting->db_sel = 1; + } else if (dqs_setting->tap < (db_tap >> 4)) { + dqs_setting->db_en = 0; + dqs_setting->db_sel = 0; + } else if ((dqs_setting->tap == (db_tap >> 4)) + && (dqs_setting->pi < (db_pi >> 4))) { + dqs_setting->db_en = 0; + dqs_setting->db_sel = 0; + } else { + dqs_setting->db_en = 1; + dqs_setting->db_sel = 0; + } +} + +const static u8 max_tap[3] = {12, 10, 13}; + +static int increment_dqs(struct sysinfo *s, struct dll_setting *dqs_setting) +{ + u8 max_tap_val = max_tap[s->selected_timings.mem_clk + - MEM_CLOCK_800MHz]; + /* PI */ + if (dqs_setting->pi < 6) { + dqs_setting->pi += 1; + } else if (dqs_setting->tap < max_tap_val) { + dqs_setting->pi = 0; + dqs_setting->tap += 1; + } else if (dqs_setting->clk_delay < 2) { + dqs_setting->pi = 0; + dqs_setting->tap = 0; + dqs_setting->clk_delay += 1; + } else if (dqs_setting->coarse < 1) { + dqs_setting->pi = 0; + dqs_setting->tap = 0; + dqs_setting->clk_delay = 0; + dqs_setting->coarse += 1; + } else { + return 1; + } + set_db(s, dqs_setting); + return 0; +} + +static int decrement_dqs(struct sysinfo *s, struct dll_setting *dqs_setting) +{ + u8 max_tap_val = max_tap[s->selected_timings.mem_clk + - MEM_CLOCK_800MHz]; + if (dqs_setting->pi > 0) { + dqs_setting->pi -= 1; + } else if (dqs_setting->tap > 0) { + dqs_setting->pi = 6; + dqs_setting->tap -= 1; + } else if (dqs_setting->clk_delay > 0) { + dqs_setting->pi = 6; + dqs_setting->tap = max_tap_val; + dqs_setting->clk_delay -= 1; + } else if (dqs_setting->coarse > 0) { + dqs_setting->pi = 6; + dqs_setting->tap = max_tap_val; + dqs_setting->clk_delay += 1; + dqs_setting->coarse -= 1; + } else { + return 1; + } + set_db(s, dqs_setting); + return 0; +} + +#define N_LOOPS 2 /* Most likely needs to be even */ +#define N_SAMPLES 5 + +static u8 sample_dq(struct sysinfo *s, u8 channel, u8 rank, u8 pass) { + u32 address = (channel << 29) | rank * 128 * MiB; + u8 all_found = 0xff; + int samples, lane; + + for (samples = 0; samples < N_SAMPLES; samples++) { + write32((u32 *)address, 0x12341234); + write32((u32 *)address + 4, 0x12341234); + udelay(10); + for (lane = 0; lane < 8; lane++) { + if (((MCHBAR8(0x561 + 0x400 * channel + (lane * 4)) + & 0x80) >> 7) != (pass % 2)) + all_found &= ~(1 << lane); + } + } + return all_found; +} + +static void increment_to_dqs_edge(struct sysinfo *s, u8 channel, u8 rank) +{ + int loop, lane, status, temp0, temp1 = 0; + u8 saved_24d; + struct dll_setting dqs_setting[8]; + u8 all_found; + u8 bytelane = 0xff; + + for (lane = 0; lane < 8; lane++) { + dqs_setting[lane].pi = 0; + dqs_setting[lane].tap = 0; + dqs_setting[lane].clk_delay = 0; + dqs_setting[lane].db_en = 0; + dqs_setting[lane].db_sel = 0; + dqs_setting[lane].coarse = 0; + } + + for (lane = 0; lane < 8; lane++) { + printk(RAM_DEBUG, "Lane %d\n", lane); + while (!increment_dqs(s, &dqs_setting[lane])) { + temp0 = (sample_dq(s, 0, 0, 1) >> lane) & 1 ; + if (temp0 != temp1) { + temp1 = temp0; + printk(RAM_DEBUG, "Found EDGE for lane %d at: ", + lane); + print_dll_setting(&dqs_setting[lane], 1); + } + + printk(RAM_DEBUG,"\tDQ: %s ", (temp0 ? + "LOW" : "HIGH")); + dqsset(channel, lane, &dqs_setting[lane]); + } + } + + switch (s->selected_timings.mem_clk) { + default: + case MEM_CLOCK_800MHz: + for (lane = 0; lane < 8; lane++) + dqs_setting[lane] = ddr3_dll_setting_800[s->nmode - 1][DQS1 + lane]; + break; + case MEM_CLOCK_1066MHz: + for (lane = 0; lane < 8; lane++) + dqs_setting[lane] = ddr3_dll_setting_1066[s->nmode - 1][DQS1 + lane]; + break; + case MEM_CLOCK_1333MHz: + for (lane = 0; lane < 8; lane++) + dqs_setting[lane] = ddr3_dll_setting_1333[s->nmode - 1][DQS1 + lane]; + break; + } + + saved_24d = MCHBAR8(0x24d + 0x400 * channel); + + for (loop = 0; loop < N_LOOPS; loop++) { + bytelane = 0xff; + do { + all_found = sample_dq(s, channel, rank, loop); + for (lane = 0; lane < 8; lane++) { + if (!(bytelane & (1 << lane))) + continue; + if (all_found & (1 << lane)) { + bytelane &= ~(1 << lane); + continue; + } else { + if ((loop % 2) == 0) { + status = decrement_dqs(s, &dqs_setting[lane]); + } else { + status = increment_dqs(s, &dqs_setting[lane]); + if (status) + printk(BIOS_WARNING, + " DQS DLL at MAX at lane %d," + " we might have a problem here...\n" + , lane); + } + if (status) { + /* limits are not an invalid settings */ + printk(RAM_SPEW, " lane %d reached limit. Continuing\n", lane); + bytelane &= ~(1 << lane); + status = 0; + } + } + dqsset(channel, lane, &dqs_setting[lane]); + } + } while (bytelane); + printk(BIOS_DEBUG, "DQS settings on PAS #%d:\n", loop); + for (lane = 0; lane < 8; lane++) { + printk(BIOS_DEBUG, "lane %d: ", lane); + print_dll_setting(&dqs_setting[lane], 1); + } + } + + for (lane = 0; lane < 8; lane++) + s->dqs_settings[channel][lane] = dqs_setting[lane]; + + MCHBAR8(0x24d + 0x400 * channel) = saved_24d; +} + +void search_write_leveling(struct sysinfo *s) +{ + int ch, count; + u8 config, rank0, rank1, lane; + u16 saved_x5c4; + struct dll_setting dqs_setting; + + return; /* Sampling DQ does not work :( */ + + u8 chanconfig_lut[16]={ 0, 6, 4, 6, 7, 3, 1, 3, 5, 2, 0, 2, 7, 3, 1, 3 + }; + + u8 odt_force[8][4] = { /* [Config][leveling rank] */ + {0x5, 0x6, 0x5, 0x9}, + {0x5, 0x6, 0x5, 0x0}, + {0x5, 0x0, 0x5, 0x9}, + {0x5, 0x0, 0x5, 0x0}, + {0x1, 0x2, 0x0, 0x0}, + {0x0, 0x0, 0x4, 0x8}, + {0x1, 0x0, 0x0, 0x0}, + {0x0, 0x0, 0x4, 0x0} + }; + + printk(BIOS_DEBUG, "Starting write levelling.\n"); + + MCHBAR8(0x5dc) = MCHBAR8(0x5dc) & ~0x80; + MCHBAR16(0x127) = (MCHBAR16(0x127) & ~0x7ff) | 0x540; + FOR_EACH_POPULATED_CHANNEL(s->dimms, ch) { + printk(BIOS_DEBUG, "\tCH%d\n", ch); + config = chanconfig_lut[s->dimm_config[ch]]; + saved_x5c4 = MCHBAR16(0x5c4 + ch * 0x400); + + MCHBAR8(0x5d8 + 0x400 * ch) = MCHBAR8(0x5d8 + 0x400 * ch) & ~0x0e; + MCHBAR16(0x5c4 + 0x400 * ch) = (MCHBAR16(0x5c4 + 0x400 * ch) & + ~0x3fff) | 0x3fff; + MCHBAR8(0x265 + 0x400 * ch) = MCHBAR8(0x265 + 0x400 * ch) & ~0x1f; + FOR_EACH_POPULATED_RANK_IN_CHANNEL(s->dimms, ch, rank0) { + FOR_EACH_POPULATED_RANK_IN_CHANNEL(s->dimms, ch, rank1) { + do_write_level(s, ch, config, rank1, rank0, 1); + } + + MCHBAR8(0x298 + 2 + 0x400 * ch) = (MCHBAR8(0x298 + 2 + 0x400 * ch) & ~0x0f) + | odt_force[config][rank0]; + MCHBAR8(0x271 + 0x400 * ch) = (MCHBAR8(0x271 + 0x400 * ch) & ~0x7e) + | 0x4e; + MCHBAR8(0x5d9 + 0x400 * ch) = (MCHBAR8(0x5d9 + 0x400 * ch) & ~0x04) + | 0x04; + MCHBAR32(0x1a0) = (MCHBAR32(0x1a0) & ~0x07ffffff) | 0x00014000; + + increment_to_dqs_edge(s, ch, rank0); + + MCHBAR8(0x298 + 2 + 0x400 * ch) = MCHBAR8(0x298 + 2 + 0x400 * ch) & ~0x0f; + MCHBAR8(0x271 + 0x400 * ch) = (MCHBAR8(0x271 + 0x400 * ch) & ~0x7e) + | 0x0e; + MCHBAR8(0x5d9 + 0x400 * ch) = (MCHBAR8(0x5d9 + 0x400 * ch) & ~0x04); + MCHBAR32(0x1a0) = (MCHBAR32(0x1a0) & ~0x07ffffff) | 0x00555801; + FOR_EACH_POPULATED_RANK_IN_CHANNEL(s->dimms, ch, rank1) { + if (rank0 == rank1) { + do_write_level(s, ch, config, rank1, rank0, 0); + send_jedec_cmd(s, rank1 + 4 * ch, ch, NORMALOP_CMD, 1 << 12); + } + } + break; /* Break on first populated rank */ + } + MCHBAR8(0x5d8 + 0x400 * ch) = (MCHBAR8(0x5d8 + 0x400 * ch) & ~0x0e) | 0x0e; + MCHBAR16(0x5c4 + 0x400 * ch) = (MCHBAR16(0x5c4 + 0x400 * ch) & + ~0x3fff) | 0x1807; + MCHBAR8(0x265 + 0x400 * ch) = MCHBAR8(0x265 + 0x400 * ch) & ~0x1f; + FOR_EACH_POPULATED_RANK_IN_CHANNEL(s->dimms, ch, rank0) { + do_write_level(s, ch, config, rank0, rank0, 0); + } + + } + MCHBAR8(0x5dc) = (MCHBAR8(0x5dc) & ~0x80) | 0x80; + MCHBAR16(0x127) = (MCHBAR16(0x127) & ~0x7ff) | 0x2bf; + + switch (s->selected_timings.mem_clk) { + default: + case MEM_CLOCK_800MHz: + count = 39; + break; + case MEM_CLOCK_1066MHz: + count = 32; + break; + case MEM_CLOCK_1333MHz: + count = 42; + break; + } + /* TODO: restore on S3 */ + FOR_EACH_POPULATED_CHANNEL(s->dimms, ch) { + for (lane = 0; lane < 8; lane++) { + dqs_setting = s->dqs_settings[ch][lane]; + dqsset(ch, lane, &dqs_setting); + while (count--) + increment_dqs(s, &dqs_setting); + dqset(ch, lane, &dqs_setting); + } + } + + printk(BIOS_DEBUG, "Done write levelling.\n"); +} diff --git a/src/northbridge/intel/x4x/raminit_ddr23.c b/src/northbridge/intel/x4x/raminit_ddr23.c index a289788..9e7583c 100644 --- a/src/northbridge/intel/x4x/raminit_ddr23.c +++ b/src/northbridge/intel/x4x/raminit_ddr23.c @@ -369,6 +369,8 @@
void dqsset(u8 ch, u8 lane, const struct dll_setting *setting) { + printk(RAM_SPEW, "DQS setting: ch%d, l%d ", ch, lane); + print_dll_setting(setting, 0); MCHBAR32(0x400*ch + 0x5fc) = (MCHBAR32(0x400*ch + 0x5fc) & ~(2 << (lane*4))) | (setting->coarse << ((lane * 4) + 1));
@@ -414,6 +416,10 @@
void dqset(u8 ch, u8 lane, const struct dll_setting *setting) { + + printk(RAM_SPEW, "DQ setting: ch%d, l%d ", ch, lane); + print_dll_setting(setting, 0); + MCHBAR32(0x400*ch + 0x5fc) = (MCHBAR32(0x400*ch + 0x5fc) & ~(1 << (lane*4))) | (setting->coarse << (lane * 4)); MCHBAR32(0x400*ch + 0x5a4) = (MCHBAR32(0x400*ch + 0x5a4) & ~(0x201 << lane)) | @@ -765,6 +771,20 @@ for (lane = 0; lane < 8; lane++) { dqset(channel, lane, &dll_setting[DQ1 + lane]); } +} + +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 program_dll(struct sysinfo *s) @@ -1411,6 +1431,33 @@ } printk(BIOS_DEBUG, "MRS done\n"); } + +static void write_leveling(struct sysinfo *s) +{ + if (s->boot_path == BOOT_PATH_NORMAL) { + search_write_leveling(s); + } else { + /* TODO restore write leveling */ + } +} + +static void software_ddr3_reset(struct sysinfo *s) +{ + printk(BIOS_DEBUG, "Software initiated DDR3 reset.\n"); + MCHBAR8(0x1a8) = MCHBAR8(0x1a8) | 0x02; + MCHBAR8(0x5da) = MCHBAR8(0x5da) & ~0x80; + MCHBAR8(0x1a8) = MCHBAR8(0x1a8) & ~0x02; + MCHBAR8(0x5da) = (MCHBAR8(0x5da) & ~0x03) | 1; + udelay(200); + MCHBAR8(0x1a8) = MCHBAR8(0x1a8) & ~0x02; + MCHBAR8(0x5da) = MCHBAR8(0x5da) | 0x80; + MCHBAR8(0x5da) = MCHBAR8(0x5da) & ~0x80; + udelay(500); + MCHBAR8(0x5da) = MCHBAR8(0x5da) | 0x03; + MCHBAR8(0x5da) = MCHBAR8(0x5da) & ~0x03; + jedec_ddr3(s); +} + static u8 sampledqs(u16 mchloc, u32 addr, u8 hilow, u8 repeat) { u8 dqsmatch = 1; @@ -2216,6 +2263,17 @@ MCHBAR8(0x9d8) = MCHBAR8(0x9d8) | 0x7; }
+ /* DDR3 reset */ + if ((s->spd_type == DDR3) && (s->boot_path != BOOT_PATH_RESUME)) { + printk(BIOS_DEBUG, "DDR3 Reset.\n"); + MCHBAR8(0x1a8) = MCHBAR8(0x1a8) & ~0x2; + MCHBAR8(0x5da) = MCHBAR8(0x5da) | 0x80; + udelay(500); + MCHBAR8(0x1a8) = MCHBAR8(0x1a8) & ~0x2; + MCHBAR8(0x5da) = MCHBAR8(0x5da) & ~0x80; + udelay(500); + } + // Pre jedec MCHBAR8(0x40) = MCHBAR8(0x40) | 0x2; FOR_EACH_POPULATED_CHANNEL(s->dimms, ch) { @@ -2232,6 +2290,12 @@ jedec_ddr3(s); }
+ if (s->spd_type == DDR3) { + write_leveling(s); + if (s->boot_path == BOOT_PATH_NORMAL) + software_ddr3_reset(s); + } + printk(BIOS_DEBUG, "Done jedec steps\n");
// After JEDEC reset diff --git a/src/northbridge/intel/x4x/raminit_tables.c b/src/northbridge/intel/x4x/raminit_tables.c index 3349f62..e3001f2 100644 --- a/src/northbridge/intel/x4x/raminit_tables.c +++ b/src/northbridge/intel/x4x/raminit_tables.c @@ -134,7 +134,7 @@ {7, 6, 0, 0, 0, 0}, {9, 2, 1, 0, 0, 0}, {9, 2, 1, 0, 0, 0}, - {2, 5, 1, 1, 1, 0}, + {2, 5, 1, 1, 1, 0}, /* DQS1 */ {5, 1, 0, 0, 1, 0}, {6, 6, 0, 0, 1, 0}, {8, 0, 0, 0, 1, 0}, @@ -158,7 +158,7 @@ {0, 6, 1, 1, 0, 0}, {2, 2, 1, 1, 0, 0}, {2, 2, 1, 1, 0, 0}, - {6, 4, 0, 0, 0, 0}, + {6, 4, 0, 0, 0, 0}, /* DQS1 */ {9, 1, 1, 0, 0, 0}, {10, 6, 1, 0, 0, 0}, {1, 0, 1, 1, 1, 0}, diff --git a/src/northbridge/intel/x4x/spd_ddr3_decode.c b/src/northbridge/intel/x4x/spd_ddr3_decode.c index eab0548..857f6e7 100644 --- a/src/northbridge/intel/x4x/spd_ddr3_decode.c +++ b/src/northbridge/intel/x4x/spd_ddr3_decode.c @@ -82,6 +82,7 @@ }
min_tCLK = MAX(min_tCLK, saved_timings->min_tclk); + min_tCLK = TCK_400MHZ; /* Hardcode since write leveling does not work */ normalize_tCLK(&min_tCLK); if (min_tCLK == 0) { printk(BIOS_ERR, "DRAM frequency is under lowest supported "