aboutsummaryrefslogtreecommitdiff
path: root/src/northbridge/intel/i945/rcven.c
diff options
context:
space:
mode:
authorStefan Reinauer <stepan@coresystems.de>2008-10-29 04:51:07 +0000
committerStefan Reinauer <stepan@openbios.org>2008-10-29 04:51:07 +0000
commit278534d00734a1f4c3f252f11ca234a6517a590b (patch)
tree8847383901be6363843888dca10c597664624e5b /src/northbridge/intel/i945/rcven.c
parent00a889c8aabd7b731622d5ff0e765f69e158de2b (diff)
Support for the Intel 945 northbridge.
Signed-off-by: Stefan Reinauer <stepan@coresystems.de> Acked-by: Ronald G. Minnich <rminnich@gmail.com> git-svn-id: svn://svn.coreboot.org/coreboot/trunk@3703 2b7e53f0-3cfb-0310-b3e9-8179ed1497e1
Diffstat (limited to 'src/northbridge/intel/i945/rcven.c')
-rw-r--r--src/northbridge/intel/i945/rcven.c338
1 files changed, 338 insertions, 0 deletions
diff --git a/src/northbridge/intel/i945/rcven.c b/src/northbridge/intel/i945/rcven.c
new file mode 100644
index 0000000000..e7eea611f2
--- /dev/null
+++ b/src/northbridge/intel/i945/rcven.c
@@ -0,0 +1,338 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2007-2008 coresystems GmbH
+ *
+ * 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 "raminit.h"
+
+/**
+ * sample the strobes signal
+ */
+static u32 sample_strobes(int channel_offset, struct sys_info *sysinfo)
+{
+ u32 reg32, addr;
+ int i;
+
+ MCHBAR32(C0DRC1 + channel_offset) |= (1 << 6);
+
+ MCHBAR32(C0DRC1 + channel_offset) &= ~(1 << 6);
+
+ addr = 0;
+
+ if (channel_offset != 0) { /* must be dual channel */
+ if (sysinfo->interleaved == 1) {
+ addr |= (1 << 6);
+ } else {
+ addr = ((u32)MCHBAR8(C0DRB3)) << 25;
+ }
+ }
+
+ for (i = 0; i < 28; i++) {
+ read32(addr);
+ read32(addr + 0x80);
+ }
+
+ reg32 = MCHBAR32(RCVENMT);
+ if (channel_offset == 0) {
+ reg32 = reg32 << 2;
+ }
+
+ /**
+ * [19] = 1: all bits are high
+ * [18] = 1: all bits are low
+ * [19:18] = 00: bits are mixed high, low
+ */
+ return reg32;
+}
+
+/**
+ * This function sets receive enable coarse and medium timing parameters
+ */
+
+static void set_receive_enable(int channel_offset, u8 medium, u8 coarse)
+{
+ u32 reg32;
+
+ printk_spew(" set_receive_enable() medium=0x%x, coarse=0x%x\r\n", medium, coarse);
+
+ reg32 = MCHBAR32(C0DRT1 + channel_offset);
+ reg32 &= 0xf0ffffff;
+ reg32 |= ((u32)coarse & 0x0f) << 24;
+ MCHBAR32(C0DRT1 + channel_offset) = reg32;
+
+ if (coarse > 0x0f)
+ printk_debug("set_receive_enable: coarse overflow: 0x%02x.\n", coarse);
+
+ /* medium control
+ *
+ * 00 - 1/4 clock
+ * 01 - 1/2 clock
+ * 10 - 3/4 clock
+ * 11 - 1 clock
+ */
+
+ reg32 = MCHBAR32(RCVENMT);
+ if (!channel_offset) {
+
+ reg32 &= ~(3 << 2);
+ reg32 |= medium << 2;
+ } else {
+
+ reg32 &= ~(3 << 0);
+ reg32 |= medium;
+ }
+ MCHBAR32(RCVENMT) = reg32;
+
+}
+
+static int normalize(int channel_offset, u8 * mediumcoarse, u8 * fine)
+{
+ printk_spew(" normalize()\r\n");
+
+ if (*fine < 0x80)
+ return 0;
+
+ *fine -= 0x80;
+ *mediumcoarse += 1;
+
+ if (*mediumcoarse >= 0x40) {
+ printk_debug("Normalize Error\r\n");
+ return -1;
+ }
+
+ set_receive_enable(channel_offset, *mediumcoarse & 3,
+ *mediumcoarse >> 2);
+
+ MCHBAR8(C0WL0REOST + channel_offset) = *fine;
+
+ return 0;
+}
+
+static int find_preamble(int channel_offset, u8 * mediumcoarse,
+ struct sys_info *sysinfo)
+{
+ /* find start of the data phase */
+ u32 reg32;
+
+ printk_spew(" find_preamble()\r\n");
+
+ do {
+ if (*mediumcoarse < 4) {
+
+ printk_debug("No Preamble found.\r\n");
+ return -1;
+ }
+ *mediumcoarse -= 4;
+
+ set_receive_enable(channel_offset, *mediumcoarse & 3,
+ *mediumcoarse >> 2);
+
+ reg32 = sample_strobes(channel_offset, sysinfo);
+
+ } while (reg32 & (1 << 19));
+
+ if (!(reg32 & (1 << 18))) {
+
+ printk_debug("No Preamble found (neither high nor low).\r\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * add a quarter clock to the current receive enable settings
+ */
+
+static int add_quarter_clock(int channel_offset, u8 * mediumcoarse, u8 * fine)
+{
+ printk_spew(" add_quarter_clock() mediumcoarse=%02x fine=%02x\r\n",
+ *mediumcoarse, *fine);
+ if (*fine >= 0x80) {
+ *fine -= 0x80;
+
+ *mediumcoarse += 2;
+ if (*mediumcoarse >= 0x40) {
+ printk_debug("clocks at max.\r\n");
+ return -1;
+ }
+
+ set_receive_enable(channel_offset, *mediumcoarse & 3,
+ *mediumcoarse >> 2);
+ } else {
+ *fine += 0x80;
+ }
+
+ MCHBAR8(C0WL0REOST + channel_offset) = *fine;
+
+ return 0;
+}
+
+static int find_strobes_low(int channel_offset, u8 * mediumcoarse, u8 * fine,
+ struct sys_info *sysinfo)
+{
+ u32 rcvenmt;
+
+ printk_spew(" find_strobes_low()\r\n");
+
+ for (;;) {
+ MCHBAR8(C0WL0REOST + channel_offset) = *fine;
+
+ set_receive_enable(channel_offset, *mediumcoarse & 3,
+ *mediumcoarse >> 2);
+
+ rcvenmt = sample_strobes(channel_offset, sysinfo);
+
+ if (((rcvenmt & (1 << 18)) != 0))
+ return 0;
+
+
+ *fine -= 0x80;
+ if (*fine == 0)
+ continue;
+
+ *mediumcoarse -= 2;
+ if (*mediumcoarse < 0xfe)
+ continue;
+
+ break;
+
+ }
+
+ printk_debug("Could not find low strobe\r\n");
+ return 0;
+}
+
+static int find_strobes_edge(int channel_offset, u8 * mediumcoarse, u8 * fine,
+ struct sys_info *sysinfo)
+{
+ int counter;
+ u32 rcvenmt;
+
+ printk_spew(" find_strobes_edge()\r\n");
+
+ counter = 8;
+ set_receive_enable(channel_offset, *mediumcoarse & 3,
+ *mediumcoarse >> 2);
+
+ for (;;) {
+ MCHBAR8(C0WL0REOST + channel_offset) = *fine;
+ rcvenmt = sample_strobes(channel_offset, sysinfo);
+
+ if ((rcvenmt & (1 << 19)) == 0) {
+ counter = 8;
+ } else {
+ counter--;
+ if (!counter)
+ break;
+ }
+
+ *fine = *fine + 1;
+ if (*fine < 0xf8) {
+ if (*fine & (1 << 3)) {
+ *fine &= ~(1 << 3);
+ *fine += 0x10;
+ }
+ continue;
+ }
+
+ *fine = 0;
+ *mediumcoarse += 2;
+ if (*mediumcoarse <= 0x40) {
+ set_receive_enable(channel_offset, *mediumcoarse & 3,
+ *mediumcoarse >> 2);
+ continue;
+ }
+
+ printk_debug("could not find rising edge.\r\n");
+ return -1;
+ }
+
+ *fine -= 7;
+ if (*fine >= 0xf9) {
+ *mediumcoarse -= 2;
+ set_receive_enable(channel_offset, *mediumcoarse & 3,
+ *mediumcoarse >> 2);
+ }
+
+ *fine &= ~(1 << 3);
+ MCHBAR8(C0WL0REOST + channel_offset) = *fine;
+
+ return 0;
+}
+
+/**
+ * Here we use a trick. The RCVEN channel 0 registers are all at an
+ * offset of 0x80 to the channel 0 registers. We don't want to waste
+ * a lot of if()s so let's just pass 0 or 0x80 for the channel offset.
+ */
+
+static int receive_enable_autoconfig(int channel_offset,
+ struct sys_info *sysinfo)
+{
+ u8 mediumcoarse;
+ u8 fine;
+
+ printk_spew("receive_enable_autoconfig() for channel %d\r\n",
+ channel_offset ? 1 : 0);
+
+ /* Set initial values */
+ mediumcoarse = (sysinfo->cas << 2) | 3;
+ fine = 0;
+
+ if (find_strobes_low(channel_offset, &mediumcoarse, &fine, sysinfo))
+ return -1;
+
+ if (find_strobes_edge(channel_offset, &mediumcoarse, &fine, sysinfo))
+ return -1;
+
+ if (add_quarter_clock(channel_offset, &mediumcoarse, &fine))
+ return -1;
+
+ if (find_preamble(channel_offset, &mediumcoarse, sysinfo))
+ return -1;
+
+ if (add_quarter_clock(channel_offset, &mediumcoarse, &fine))
+ return -1;
+
+ if (normalize(channel_offset, &mediumcoarse, &fine))
+ return -1;
+
+ /* This is a debug check to see if the rcven code is fully working.
+ * It can be removed when the output message is not printed anymore
+ */
+ if (MCHBAR8(C0WL0REOST + channel_offset) == 0) {
+ printk_debug("Weird. No C%sWL0REOST\n", channel_offset?"1":"0");
+ }
+
+ return 0;
+}
+
+void receive_enable_adjust(struct sys_info *sysinfo)
+{
+ /* Is channel 0 populated? */
+ if (sysinfo->dimm[0] != SYSINFO_DIMM_NOT_POPULATED
+ || sysinfo->dimm[1] != SYSINFO_DIMM_NOT_POPULATED)
+ if (receive_enable_autoconfig(0, sysinfo))
+ return;
+
+ /* Is channel 1 populated? */
+ if (sysinfo->dimm[2] != SYSINFO_DIMM_NOT_POPULATED
+ || sysinfo->dimm[3] != SYSINFO_DIMM_NOT_POPULATED)
+ if (receive_enable_autoconfig(0x80, sysinfo))
+ return;
+}