/* * This file is part of the coreboot project. * * Copyright (C) 2012 secunet Security Networks AG * (Written by Nico Huber for secunet) * * 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; version 2 of the License. * * 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 #include #include #include #include "gm45.h" typedef struct { u32 addr[RANKS_PER_CHANNEL]; unsigned count; } address_bunch_t; /* Read Training. */ #define CxRDTy_MCHBAR(ch, bl) (0x14b0 + (ch * 0x0100) + ((7 - bl) * 4)) #define CxRDTy_T_SHIFT 20 #define CxRDTy_T_MASK (0xf << CxRDTy_T_SHIFT) #define CxRDTy_T(t) ((t << CxRDTy_T_SHIFT) & CxRDTy_T_MASK) #define CxRDTy_P_SHIFT 16 #define CxRDTy_P_MASK (0x7 << CxRDTy_P_SHIFT) #define CxRDTy_P(p) ((p << CxRDTy_P_SHIFT) & CxRDTy_P_MASK) static const u32 read_training_schedule[] = { 0xfefefefe, 0x7f7f7f7f, 0xbebebebe, 0xdfdfdfdf, 0xeeeeeeee, 0xf7f7f7f7, 0xfafafafa, 0xfdfdfdfd, 0x00000000, 0x81818181, 0x40404040, 0x21212121, 0x10101010, 0x09090909, 0x04040404, 0x03030303, 0x10101010, 0x11111111, 0xeeeeeeee, 0xefefefef, 0x10101010, 0x11111111, 0xeeeeeeee, 0xefefefef, 0x10101010, 0xefefefef, 0x10101010, 0xefefefef, 0x10101010, 0xefefefef, 0x10101010, 0xefefefef, 0x00000000, 0xffffffff, 0x00000000, 0xffffffff, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, }; #define READ_TIMING_P_SHIFT 3 #define READ_TIMING_P_BOUND (1 << READ_TIMING_P_SHIFT) #define READ_TIMING_T_BOUND 14 typedef struct { int t; int p; } read_timing_t; static void print_read_timing(const int msg_lvl, const char *const msg, const int lane, const int channel, const read_timing_t *const timing) { printk(msg_lvl, "%sbyte lane %d, ch %d: %d.%d\n", msg, lane, channel, timing->t, timing->p); } static int normalize_read_timing(read_timing_t *const timing) { while (timing->p >= READ_TIMING_P_BOUND) { timing->t++; timing->p -= READ_TIMING_P_BOUND; } while (timing->p < 0) { timing->t--; timing->p += READ_TIMING_P_BOUND; } if (timing->t < 0) { printk(BIOS_WARNING, "Timing underflow during read training.\n"); timing->t = 0; timing->p = 0; return -1; } else if (timing->t >= READ_TIMING_T_BOUND) { printk(BIOS_WARNING, "Timing overflow during read training.\n"); timing->t = READ_TIMING_T_BOUND - 1; timing->p = READ_TIMING_P_BOUND - 1; return -1; } return 0; } static int program_read_timing(const int ch, const int lane, read_timing_t *const timing) { if (normalize_read_timing(timing) < 0) return -1; u32 reg = MCHBAR32(CxRDTy_MCHBAR(ch, lane)); reg &= ~(CxRDTy_T_MASK | CxRDTy_P_MASK); reg |= CxRDTy_T(timing->t) | CxRDTy_P(timing->p); MCHBAR32(CxRDTy_MCHBAR(ch, lane)) = reg; return 0; } /* Returns 1 on success, 0 on failure. */ static int read_training_test(const int channel, const int lane, const address_bunch_t *const addresses) { int i; const int lane_offset = lane & 4; const int lane_mask = 0xff << ((lane & ~4) << 3); for (i = 0; i < addresses->count; ++i) { unsigned int offset; for (offset = lane_offset; offset < 320; offset += 8) { const u32 read = read32((u32 *)(addresses->addr[i] + offset)); const u32 good = read_training_schedule[offset >> 3]; if ((read & lane_mask) != (good & lane_mask)) return 0; } } return 1; } static int read_training_find_lower(const int channel, const int lane, const address_bunch_t *const addresses, read_timing_t *const lower) { /* Coarse search for good t. */ program_read_timing(channel, lane, lower); while (!read_training_test(channel, lane, addresses)) { ++lower->t; if (program_read_timing(channel, lane, lower) < 0) return -1; } /* Step back, then fine search for good p. */ if (lower->t <= 0) /* Can't step back, zero is good. */ return 0; --lower->t; program_read_timing(channel, lane, lower); while (!read_training_test(channel, lane, addresses)) { ++lower->p; if (program_read_timing(channel, lane, lower) < 0) return -1; } return 0; } static int read_training_find_upper(const int channel, const int lane, const address_bunch_t *const addresses, read_timing_t *const upper) { if (program_read_timing(channel, lane, upper) < 0) return -1; if (!read_training_test(channel, lane, addresses)) { printk(BIOS_WARNING, "Read training failure: limits too narrow.\n"); return -1; } /* Coarse search for bad t. */ do { ++upper->t; if (program_read_timing(channel, lane, upper) < 0) return -1; } while (read_training_test(channel, lane, addresses)); /* Fine search for bad p. */ --upper->t; program_read_timing(channel, lane, upper); while (read_training_test(channel, lane, addresses)) { ++upper->p; if (program_read_timing(channel, lane, upper) < 0) return -1; } return 0; } static void read_training_per_lane(const int channel, const int lane, const address_bunch_t *const addresses) { read_timing_t lower, upper; MCHBAR32(CxRDTy_MCHBAR(channel, lane)) |= 3 << 25; /*** Search lower bound. ***/ /* Start at zero. */ lower.t = 0; lower.p = 0; if (read_training_find_lower(channel, lane, addresses, &lower) < 0) die("Read training failure: lower bound.\n"); print_read_timing(RAM_DEBUG, "Lower bound for ", lane, channel, &lower); /*** Search upper bound. ***/ /* Start at lower + 1t. */ upper.t = lower.t + 1; upper.p = lower.p; if (read_training_find_upper(channel, lane, addresses, &upper) < 0) /* Overflow on upper edge is not fatal. */ printk(BIOS_WARNING, "Read training failure: upper bound.\n"); print_read_timing(RAM_DEBUG, "Upper bound for ", lane, channel, &upper); /*** Calculate and program mean value. ***/ lower.p += lower.t << READ_TIMING_P_SHIFT; upper.p += upper.t << READ_TIMING_P_SHIFT; const int mean_p = (lower.p + upper.p) >> 1; /* lower becomes the mean value. */ lower.t = mean_p >> READ_TIMING_P_SHIFT; lower.p = mean_p & (READ_TIMING_P_BOUND - 1); program_read_timing(channel, lane, &lower); printk(RAM_DEBUG, "Final timings for "); print_read_timing(BIOS_DEBUG, "", lane, channel, &lower); } static void perform_read_training(const dimminfo_t *const dimms) { int ch, i; FOR_EACH_POPULATED_CHANNEL(dimms, ch) { address_bunch_t addresses = { { 0, }, 0 }; FOR_EACH_POPULATED_RANK_IN_CHANNEL(dimms, ch, i) addresses.addr[addresses.count++] = raminit_get_rank_addr(ch, i); for (i = 0; i < addresses.count; ++i) { /* Write test pattern. */ unsigned int offset; for (offset = 0; offset < 320; offset += 4) write32((u32 *)(addresses.addr[i] + offset), read_training_schedule[offset >> 3]); } for (i = 0; i < 8; ++i) read_training_per_lane(ch, i, &addresses); } } static void read_training_store_results(void) { u8 bytes[TOTAL_CHANNELS * 8]; int ch, i; /* Store one timing pair in one byte each. */ FOR_EACH_CHANNEL(ch) { for (i = 0; i < 8; ++i) { const u32 bl_reg = MCHBAR32(CxRDTy_MCHBAR(ch, i)); bytes[(ch * 8) + i] = (((bl_reg & CxRDTy_T_MASK) >> CxRDTy_T_SHIFT) << 4) | ((bl_reg & CxRDTy_P_MASK) >> CxRDTy_P_SHIFT); } } /* Store everything in CMOS above 128 bytes. */ for (i = 0; i < (TOTAL_CHANNELS * 8); ++i) cmos_write(bytes[i], CMOS_READ_TRAINING + i); } static void read_training_restore_results(void) { u8 bytes[TOTAL_CHANNELS * 8]; int ch, i; /* Read from CMOS. */ for (i = 0; i < (TOTAL_CHANNELS * 8); ++i) bytes[i] = cmos_read(CMOS_READ_TRAINING + i); /* Program restored results. */ FOR_EACH_CHANNEL(ch) { for (i = 0; i < 8; ++i) { const int t = bytes[(ch * 8) + i] >> 4; const int p = bytes[(ch * 8) + i] & 7; u32 bl_reg = MCHBAR32(CxRDTy_MCHBAR(ch, i)); bl_reg &= ~(CxRDTy_T_MASK | CxRDTy_P_MASK); bl_reg |= (3 << 25) | CxRDTy_T(t) | CxRDTy_P(p); MCHBAR32(CxRDTy_MCHBAR(ch, i)) = bl_reg; printk(BIOS_DEBUG, "Restored timings for byte lane " "%d on channel %d: %d.%d\n", i, ch, t, p); } } } void raminit_read_training(const dimminfo_t *const dimms, const int s3resume) { if (!s3resume) { perform_read_training(dimms); read_training_store_results(); } else { read_training_restore_results(); } raminit_reset_readwrite_pointers(); } /* Write Training. */ #define CxWRTy_T_SHIFT 28 #define CxWRTy_T_MASK (0xf << CxWRTy_T_SHIFT) #define CxWRTy_T(t) ((t << CxWRTy_T_SHIFT) & CxWRTy_T_MASK) #define CxWRTy_P_SHIFT 24 #define CxWRTy_P_MASK (0x7 << CxWRTy_P_SHIFT) #define CxWRTy_P(p) ((p << CxWRTy_P_SHIFT) & CxWRTy_P_MASK) #define CxWRTy_F_SHIFT 18 #define CxWRTy_F_MASK (0x3 << CxWRTy_F_SHIFT) #define CxWRTy_F(f) ((f << CxWRTy_F_SHIFT) & CxWRTy_F_MASK) #define CxWRTy_D_SHIFT 16 #define CxWRTy_D_MASK (0x3 << CxWRTy_D_SHIFT) #define CxWRTy_BELOW_D (0x3 << CxWRTy_D_SHIFT) #define CxWRTy_ABOVE_D (0x1 << CxWRTy_D_SHIFT) static const u32 write_training_schedule[] = { 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, }; /* for raw card types A, B and C: MEM_CLOCK_1067MT? X group X lower/upper */ static const u32 write_training_bytelane_masks_abc[2][4][2] = { { /* clock < MEM_CLOCK_1067MT */ { 0xffffffff, 0x00000000 }, { 0x00000000, 0x00000000 }, { 0x00000000, 0xffffffff }, { 0x00000000, 0x00000000 }, }, { /* clock == MEM_CLOCK_1067MT */ { 0x0000ffff, 0x00000000 }, { 0xffff0000, 0x00000000 }, { 0x00000000, 0x0000ffff }, { 0x00000000, 0xffff0000 }, }, }; /* for raw card type F: group X lower/upper */ static const u32 write_training_bytelane_masks_f[4][2] = { { 0xff00ff00, 0x00000000 }, { 0x00ff00ff, 0x00000000 }, { 0x00000000, 0xff00ff00 }, { 0x00000000, 0x00ff00ff }, }; #define WRITE_TIMING_P_SHIFT 3 #define WRITE_TIMING_P_BOUND (1 << WRITE_TIMING_P_SHIFT) #define WRITE_TIMING_F_BOUND 4 typedef struct { int f; int t; const int t_bound; int p; } write_timing_t; static void print_write_timing(const int msg_lvl, const char *const msg, const int group, const int channel, const write_timing_t *const timing) { printk(msg_lvl, "%sgroup %d, ch %d: %d.%d.%d\n", msg, group, channel, timing->f, timing->t, timing->p); } static int normalize_write_timing(write_timing_t *const timing) { while (timing->p >= WRITE_TIMING_P_BOUND) { timing->t++; timing->p -= WRITE_TIMING_P_BOUND; } while (timing->p < 0) { timing->t--; timing->p += WRITE_TIMING_P_BOUND; } while (timing->t >= timing->t_bound) { timing->f++; timing->t -= timing->t_bound; } while (timing->t < 0) { timing->f--; timing->t += timing->t_bound; } if (timing->f < 0) { printk(BIOS_WARNING, "Timing underflow during write training.\n"); timing->f = 0; timing->t = 0; timing->p = 0; return -1; } else if (timing->f >= WRITE_TIMING_F_BOUND) { printk(BIOS_WARNING, "Timing overflow during write training.\n"); timing->f = WRITE_TIMING_F_BOUND - 1; timing->t = timing->t_bound - 1; timing->p = WRITE_TIMING_P_BOUND - 1; return -1; } return 0; } static int program_write_timing(const int ch, const int group, write_timing_t *const timing, int memclk1067) { /* MEM_CLOCK_1067MT? X lower/upper */ const u32 d_bounds[2][2] = { { 1, 6 }, { 2, 9 } }; if (normalize_write_timing(timing) < 0) return -1; const int f = timing->f; const int t = timing->t; const int p = (memclk1067 && (((t == 9) && (timing->p >= 4)) || ((t == 10) && (timing->p < 4)))) ? 4 : timing->p; const int d = (t <= d_bounds[memclk1067][0]) ? CxWRTy_BELOW_D : ((t > d_bounds[memclk1067][1]) ? CxWRTy_ABOVE_D : 0); u32 reg = MCHBAR32(CxWRTy_MCHBAR(ch, group)); reg &= ~(CxWRTy_T_MASK | CxWRTy_P_MASK | CxWRTy_F_MASK); reg &= ~CxWRTy_D_MASK; reg |= CxWRTy_T(t) | CxWRTy_P(p) | CxWRTy_F(f) | d; MCHBAR32(CxWRTy_MCHBAR(ch, group)) = reg; return 0; } /* Returns 1 on success, 0 on failure. */ static int write_training_test(const address_bunch_t *const addresses, const u32 *const masks) { int i, ret = 0; const u32 mmarb0 = MCHBAR32(0x0220); const u8 wrcctl = MCHBAR8(0x0218); MCHBAR32(0x0220) |= 0xf << 28; MCHBAR8(0x0218) |= 0x1 << 4; for (i = 0; i < addresses->count; ++i) { const unsigned int addr = addresses->addr[i]; unsigned int off; for (off = 0; off < 640; off += 8) { const u32 pattern = write_training_schedule[off >> 3]; write32((u32 *)(addr + off), pattern); write32((u32 *)(addr + off + 4), pattern); } MCHBAR8(0x78) |= 1; for (off = 0; off < 640; off += 8) { const u32 good = write_training_schedule[off >> 3]; const u32 read1 = read32((u32 *)(addr + off)); if ((read1 & masks[0]) != (good & masks[0])) goto _bad_timing_out; const u32 read2 = read32((u32 *)(addr + off + 4)); if ((read2 & masks[1]) != (good & masks[1])) goto _bad_timing_out; } } ret = 1; _bad_timing_out: MCHBAR32(0x0220) = mmarb0; MCHBAR8(0x0218) = wrcctl; return ret; } static int write_training_find_lower(const int ch, const int group, const address_bunch_t *const addresses, const u32 masks[][2], const int memclk1067, write_timing_t *const lower) { program_write_timing(ch, group, lower, memclk1067); /* Coarse search for good t. */ while (!write_training_test(addresses, masks[group])) { ++lower->t; if (program_write_timing(ch, group, lower, memclk1067) < 0) return -1; } /* Step back, then fine search for good p. */ if ((lower->f <= 0) && (lower->t <= 0)) /* Can't step back, zero is good. */ return 0; --lower->t; program_write_timing(ch, group, lower, memclk1067); while (!write_training_test(addresses, masks[group])) { ++lower->p; if (program_write_timing(ch, group, lower, memclk1067) < 0) return -1; } return 0; } static int write_training_find_upper(const int ch, const int group, const address_bunch_t *const addresses, const u32 masks[][2], const int memclk1067, write_timing_t *const upper) { if (program_write_timing(ch, group, upper, memclk1067) < 0) return -1; if (!write_training_test(addresses, masks[group])) { printk(BIOS_WARNING, "Write training failure; limits too narrow.\n"); return -1; } /* Coarse search for bad t. */ while (write_training_test(addresses, masks[group])) { ++upper->t; if (program_write_timing(ch, group, upper, memclk1067) < 0) return -1; } /* Fine search for bad p. */ --upper->t; program_write_timing(ch, group, upper, memclk1067); while (write_training_test(addresses, masks[group])) { ++upper->p; if (program_write_timing(ch, group, upper, memclk1067) < 0) return -1; } return 0; } static void write_training_per_group(const int ch, const int group, const address_bunch_t *const addresses, const u32 masks[][2], const int memclk1067) { const int t_bound = memclk1067 ? 12 : 11; write_timing_t lower = { 0, 0, t_bound, 0 }, upper = { 0, 0, t_bound, 0 }; /*** Search lower bound. ***/ /* Start at -1f from current values. */ const u32 reg = MCHBAR32(CxWRTy_MCHBAR(ch, group)); lower.t = (reg >> 12) & 0xf; lower.p = (reg >> 8) & 0x7; lower.f = ((reg >> 2) & 0x3) - 1; if (write_training_find_lower(ch, group, addresses, masks, memclk1067, &lower) < 0) die("Write training failure: lower bound.\n"); print_write_timing(RAM_DEBUG, "Lower bound for ", group, ch, &lower); /*** Search upper bound. ***/ /* Start at lower + 3t. */ upper.t = lower.t + 3; upper.p = lower.p; upper.f = lower.f; if (write_training_find_upper(ch, group, addresses, masks, memclk1067, &upper) < 0) printk(BIOS_WARNING, "Write training failure: upper bound.\n"); print_write_timing(RAM_DEBUG, "Upper bound for ", group, ch, &upper); /*** Calculate and program mean value. ***/ lower.t += lower.f * lower.t_bound; lower.p += lower.t << WRITE_TIMING_P_SHIFT; upper.t += upper.f * upper.t_bound; upper.p += upper.t << WRITE_TIMING_P_SHIFT; /* lower becomes the mean value. */ const int mean_p = (lower.p + upper.p) >> 1; lower.f = mean_p / (lower.t_bound << WRITE_TIMING_P_SHIFT); lower.t = (mean_p >> WRITE_TIMING_P_SHIFT) % lower.t_bound; lower.p = mean_p & (WRITE_TIMING_P_BOUND - 1); program_write_timing(ch, group, &lower, memclk1067); printk(RAM_DEBUG, "Final timings for "); print_write_timing(BIOS_DEBUG, "", group, ch, &lower); } static void perform_write_training(const int memclk1067, const dimminfo_t *const dimms) { const int cardF[] = { dimms[0].card_type == 0xf, dimms[1].card_type == 0xf }; int ch, r, group; address_bunch_t addr[2] = { { { 0, }, 0 }, { { 0, }, 0 }, }; /* Add check if channel A is populated, i.e. if cardF[0] is valid. * Otherwise we would write channel A registers when DIMM in channel B * is of raw card type A, B or C (cardF[1] == 0) even if channel A is * not populated. * Needs raw card type A, B or C for testing. */ if ((dimms[0].card_type != 0) && (cardF[0] == cardF[1])) { /* Common path for both channels. */ FOR_EACH_POPULATED_RANK(dimms, ch, r) addr[0].addr[addr[0].count++] = raminit_get_rank_addr(ch, r); } else { FOR_EACH_POPULATED_RANK(dimms, ch, r) addr[ch].addr[addr[ch].count++] = raminit_get_rank_addr(ch, r); } FOR_EACH_CHANNEL(ch) if (addr[ch].count > 0) { const u32 (*const masks)[2] = (!cardF[ch]) ? write_training_bytelane_masks_abc[memclk1067] : write_training_bytelane_masks_f; for (group = 0; group < 4; ++group) { if (!masks[group][0] && !masks[group][1]) continue; write_training_per_group( ch, group, &addr[ch], masks, memclk1067); } } } static void write_training_store_results(void) { u8 bytes[TOTAL_CHANNELS * 4 * 2]; /* two bytes per group */ int ch, i; /* Store one T/P pair in one, F in the other byte. */ /* We could save six bytes by putting all F values in two bytes. */ FOR_EACH_CHANNEL(ch) { for (i = 0; i < 4; ++i) { const u32 reg = MCHBAR32(CxWRTy_MCHBAR(ch, i)); bytes[(ch * 8) + (i * 2)] = (((reg & CxWRTy_T_MASK) >> CxWRTy_T_SHIFT) << 4) | ((reg & CxWRTy_P_MASK) >> CxWRTy_P_SHIFT); bytes[(ch * 8) + (i * 2) + 1] = ((reg & CxWRTy_F_MASK) >> CxWRTy_F_SHIFT); } } /* Store everything in CMOS above 128 bytes. */ for (i = 0; i < (TOTAL_CHANNELS * 4 * 2); ++i) cmos_write(bytes[i], CMOS_WRITE_TRAINING + i); } static void write_training_restore_results(const int memclk1067) { const int t_bound = memclk1067 ? 12 : 11; u8 bytes[TOTAL_CHANNELS * 4 * 2]; /* two bytes per group */ int ch, i; /* Read from CMOS. */ for (i = 0; i < (TOTAL_CHANNELS * 4 * 2); ++i) bytes[i] = cmos_read(CMOS_WRITE_TRAINING + i); /* Program with original program_write_timing(). */ FOR_EACH_CHANNEL(ch) { for (i = 0; i < 4; ++i) { write_timing_t timing = { 0, 0, t_bound, 0 }; timing.f = bytes[(ch * 8) + (i * 2) + 1] & 3; timing.t = bytes[(ch * 8) + (i * 2)] >> 4; timing.p = bytes[(ch * 8) + (i * 2)] & 7; program_write_timing(ch, i, &timing, memclk1067); printk(BIOS_DEBUG, "Restored timings for group %d " "on channel %d: %d.%d.%d\n", i, ch, timing.f, timing.t, timing.p); } } } void raminit_write_training(const mem_clock_t ddr3clock, const dimminfo_t *const dimms, const int s3resume) { const int memclk1067 = ddr3clock == MEM_CLOCK_1067MT; if (!s3resume) { perform_write_training(memclk1067, dimms); write_training_store_results(); } else { write_training_restore_results(memclk1067); } raminit_reset_readwrite_pointers(); }