From 2efc8808b8bfaee0a0e8f3ee387ecd9a3f049705 Mon Sep 17 00:00:00 2001 From: Patrick Georgi Date: Tue, 6 Nov 2012 11:03:53 +0100 Subject: intel/gm45: new northbridge The code supports DDR3 boards only. RAM init for DDR2 is sufficiently different that it requires separate code, and we have no boards to test that. Change-Id: I9076546faf8a2033c89eb95f5eec524439ab9fe1 Signed-off-by: Patrick Georgi Signed-off-by: Nico Huber Reviewed-on: http://review.coreboot.org/1689 Tested-by: build bot (Jenkins) Reviewed-by: Stefan Reinauer --- .../gm45/raminit_receive_enable_calibration.c | 308 +++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 src/northbridge/intel/gm45/raminit_receive_enable_calibration.c (limited to 'src/northbridge/intel/gm45/raminit_receive_enable_calibration.c') diff --git a/src/northbridge/intel/gm45/raminit_receive_enable_calibration.c b/src/northbridge/intel/gm45/raminit_receive_enable_calibration.c new file mode 100644 index 0000000000..49d22f29e1 --- /dev/null +++ b/src/northbridge/intel/gm45/raminit_receive_enable_calibration.c @@ -0,0 +1,308 @@ +/* + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include "gm45.h" + +#define CxRECy_MCHBAR(x, y) (0x14a0 + (x * 0x0100) + ((3 - y) * 4)) +#define CxRECy_SHIFT_L 0 +#define CxRECy_MASK_L (3 << CxRECy_SHIFT_L) +#define CxRECy_SHIFT_H 16 +#define CxRECy_MASK_H (3 << CxRECy_SHIFT_H) +#define CxRECy_T_SHIFT 28 +#define CxRECy_T_MASK (0xf << CxRECy_T_SHIFT) +#define CxRECy_T(t) ((t << CxRECy_T_SHIFT) & CxRECy_T_MASK) +#define CxRECy_P_SHIFT 24 +#define CxRECy_P_MASK (0x7 << CxRECy_P_SHIFT) +#define CxRECy_P(p) ((p << CxRECy_P_SHIFT) & CxRECy_P_MASK) +#define CxRECy_PH_SHIFT 22 +#define CxRECy_PH_MASK (0x3 << CxRECy_PH_SHIFT) +#define CxRECy_PH(p) ((p << CxRECy_PH_SHIFT) & CxRECy_PH_MASK) +#define CxRECy_PM_SHIFT 20 +#define CxRECy_PM_MASK (0x3 << CxRECy_PM_SHIFT) +#define CxRECy_PM(p) ((p << CxRECy_PM_SHIFT) & CxRECy_PM_MASK) +#define CxRECy_TIMING_MASK (CxRECy_T_MASK | CxRECy_P_MASK | \ + CxRECy_PH_MASK | CxRECy_PM_MASK) + +#define CxDRT3_C_SHIFT 7 +#define CxDRT3_C_MASK (0xf << CxDRT3_C_SHIFT) +#define CxDRT3_C(c) ((c << CxDRT3_C_SHIFT) & CxDRT3_C_MASK) +/* group to byte-lane mapping: (cardF X group X 2 per group) */ +static const char bytelane_map[2][4][2] = { +/* A,B,C */{ { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 } }, +/* F */{ { 0, 2 }, { 1, 3 }, { 4, 6 }, { 5, 7 } }, +}; + +#define PH_BOUND 4 +#define PH_STEP 2 +#define PM_BOUND 3 +#define C_BOUND 16 +typedef struct { + int c; + int pre; + int ph; + int t; + const int t_bound; + int p; + const int p_bound; +} rec_timing_t; +static void normalize_rec_timing(rec_timing_t *const timing) +{ + while (timing->p >= timing->p_bound) { + timing->t++; + timing->p -= timing->p_bound; + } + while (timing->p < 0) { + timing->t--; + timing->p += timing->p_bound; + } + while (timing->t >= timing->t_bound) { + timing->ph += PH_STEP; + timing->t -= timing->t_bound; + } + while (timing->t < 0) { + timing->ph -= PH_STEP; + timing->t += timing->t_bound; + } + while (timing->ph >= PH_BOUND) { + timing->c++; + timing->ph -= PH_BOUND; + } + while (timing->ph < 0) { + timing->c--; + timing->ph += PH_BOUND; + } + if (timing->c < 0 || timing->c >= C_BOUND) + die("Timing under-/overflow during " + "receive-enable calibration.\n"); +} + +static void rec_full_backstep(rec_timing_t *const timing) +{ + timing->c--; +} +static void rec_half_backstep(rec_timing_t *const timing) +{ + timing->ph -= PH_STEP; +} +static void rec_quarter_step(rec_timing_t *const timing) +{ + timing->t += (timing->t_bound) >> 1; + timing->p += (timing->t_bound & 1) * (timing->p_bound >> 1); +} +static void rec_quarter_backstep(rec_timing_t *const timing) +{ + timing->t -= (timing->t_bound) >> 1; + timing->p -= (timing->t_bound & 1) * (timing->p_bound >> 1); +} +static void rec_smallest_step(rec_timing_t *const timing) +{ + timing->p++; +} + +static void program_timing(int channel, int group, + rec_timing_t timings[][4]) +{ + rec_timing_t *const timing = &timings[channel][group]; + + normalize_rec_timing(timing); + + /* C value is per channel. */ + unsigned int mchbar = CxDRT3_MCHBAR(channel); + MCHBAR32(mchbar) = (MCHBAR32(mchbar) & ~CxDRT3_C_MASK) | + CxDRT3_C(timing->c); + + /* All other per group. */ + mchbar = CxRECy_MCHBAR(channel, group); + u32 reg = MCHBAR32(mchbar); + reg &= ~CxRECy_TIMING_MASK; + reg |= CxRECy_T(timing->t) | CxRECy_P(timing->p) | + CxRECy_PH(timing->ph) | CxRECy_PM(timing->pre); + MCHBAR32(mchbar) = reg; +} + +static int read_dqs_level(const int channel, const int lane) +{ + unsigned int mchbar = 0x14f0 + (channel * 0x0100); + MCHBAR32(mchbar) &= ~(1 << 9); + MCHBAR32(mchbar) |= (1 << 9); + + /* Read from this channel. */ + read32(raminit_get_rank_addr(channel, 0)); + + mchbar = 0x14b0 + (channel * 0x0100) + ((7 - lane) * 4); + return MCHBAR32(mchbar) & (1 << 30); +} + +static void find_dqs_low(const int channel, const int group, + rec_timing_t timings[][4], const char lane_map[][2]) +{ + /* Look for DQS low, using quarter steps. */ + while (read_dqs_level(channel, lane_map[group][0]) || + read_dqs_level(channel, lane_map[group][1])) { + rec_quarter_step(&timings[channel][group]); + program_timing(channel, group, timings); + } +} +static void find_dqs_high(const int channel, const int group, + rec_timing_t timings[][4], const char lane_map[][2]) +{ + /* Look for _any_ DQS high, using quarter steps. */ + while (!read_dqs_level(channel, lane_map[group][0]) && + !read_dqs_level(channel, lane_map[group][1])) { + rec_quarter_step(&timings[channel][group]); + program_timing(channel, group, timings); + } +} +static void find_dqs_edge_lowhigh(const int channel, const int group, + rec_timing_t timings[][4], + const char lane_map[][2]) +{ + /* Advance beyond previous high to low transition. */ + timings[channel][group].t += 2; + program_timing(channel, group, timings); + + /* Coarsely look for DQS high. */ + find_dqs_high(channel, group, timings, lane_map); + + /* Go back and perform finer search. */ + rec_quarter_backstep(&timings[channel][group]); + program_timing(channel, group, timings); + while (!read_dqs_level(channel, lane_map[group][0]) || + !read_dqs_level(channel, lane_map[group][1])) { + rec_smallest_step(&timings[channel][group]); + program_timing(channel, group, timings); + } +} +static void find_preamble(const int channel, const int group, + rec_timing_t timings[][4], const char lane_map[][2]) +{ + /* Look for DQS low, backstepping. */ + while (read_dqs_level(channel, lane_map[group][0]) || + read_dqs_level(channel, lane_map[group][1])) { + rec_full_backstep(&timings[channel][group]); + program_timing(channel, group, timings); + } +} + +static void receive_enable_calibration(const timings_t *const timings, + const dimminfo_t *const dimms) +{ + /* Override group to byte-lane mapping for raw card type F DIMMS. */ + static const char over_bytelane_map[2][4][2] = { + /* A,B,C */{ { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 } }, + /* F */{ { 0, 0 }, { 3, 3 }, { 6, 6 }, { 5, 5 } }, + }; + + const int cardF[] = + { dimms[0].card_type == 0xf, dimms[0].card_type == 0xf }; + const unsigned t_bound = + (timings->mem_clock == MEM_CLOCK_1067MT) ? 9 : 12; + const unsigned p_bound = + (timings->mem_clock == MEM_CLOCK_1067MT) ? 8 : 1; + + rec_timing_t rec_timings[2][4] = { + { + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound }, + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound }, + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound }, + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound } + }, { + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound }, + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound }, + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound }, + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound } + } + }; + + int ch, group; + FOR_EACH_POPULATED_CHANNEL(dimms, ch) { + const char (*const map)[2] = over_bytelane_map[cardF[ch]]; + for (group = 0; group < 4; ++group) { + program_timing(ch, group, rec_timings); + find_dqs_low(ch, group, rec_timings, map); + find_dqs_edge_lowhigh(ch, group, rec_timings, map); + + rec_quarter_step(&rec_timings[ch][group]); + program_timing(ch, group, rec_timings); + find_preamble(ch, group, rec_timings, map); + find_dqs_edge_lowhigh(ch, group, rec_timings, map); + rec_half_backstep(&rec_timings[ch][group]); + normalize_rec_timing(&rec_timings[ch][group]); + if (cardF[ch]) { + rec_timings[ch][group].t++; + program_timing(ch, group, rec_timings); + } + } + int c_min = C_BOUND; + for (group = 0; group < 4; ++group) { + if (rec_timings[ch][group].c < c_min) + c_min = rec_timings[ch][group].c; + } + for (group = 0; group < 4; ++group) { + rec_timings[ch][group].pre = + rec_timings[ch][group].c - c_min; + rec_timings[ch][group].c = c_min; + program_timing(ch, group, rec_timings); + printk(BIOS_SPEW, "Final timings for group %d " + "on channel %d: %d.%d.%d.%d.%d\n", + group, ch, + rec_timings[ch][group].c, + rec_timings[ch][group].pre, + rec_timings[ch][group].ph, + rec_timings[ch][group].t, + rec_timings[ch][group].p); + } + } +} + +void raminit_receive_enable_calibration(const timings_t *const timings, + const dimminfo_t *const dimms) +{ + int ch; + + /* Setup group to byte-lane mapping. */ + FOR_EACH_POPULATED_CHANNEL(dimms, ch) { + const char (*const map)[2] = + bytelane_map[dimms[ch].card_type == 0xf]; + unsigned int group; + for (group = 0; group < 4; ++group) { + const unsigned int mchbar = CxRECy_MCHBAR(ch, group); + u32 reg = MCHBAR32(mchbar); + reg &= ~((3 << 16) | (1 << 8) | 3); + reg |= (map[group][0] - group); + reg |= (map[group][1] - group - 1) << 16; + MCHBAR32(mchbar) = reg; + } + } + + MCHBAR32(0x12a4) |= 1 << 31; + MCHBAR32(0x13a4) |= 1 << 31; + MCHBAR32(0x14f0) = (MCHBAR32(0x14f0) & ~(3 << 9)) | (1 << 9); + MCHBAR32(0x15f0) = (MCHBAR32(0x15f0) & ~(3 << 9)) | (1 << 9); + + receive_enable_calibration(timings, dimms); + + MCHBAR32(0x12a4) &= ~(1 << 31); + MCHBAR32(0x13a4) &= ~(1 << 31); + raminit_reset_readwrite_pointers(); +} -- cgit v1.2.3