Add support for Intel Sandybridge CPU (northbridge part)
[coreboot.git] / src / northbridge / intel / i440bx / raminit.c
index 737f2bf45890ab92858b4657adc93c93709f5c76..232b87249c02165ca8508825a755563718e4e22d 100644 (file)
-#include <cpu/x86/mtrr.h>
-#include "raminit.h"
-
-/*
-This software and ancillary information (herein called SOFTWARE )
-called LinuxBIOS          is made available under the terms described
-here.  The SOFTWARE has been approved for release with associated
-LA-CC Number 00-34   .  Unless otherwise indicated, this SOFTWARE has
-been authored by an employee or employees of the University of
-California, operator of the Los Alamos National Laboratory under
-Contract No. W-7405-ENG-36 with the U.S. Department of Energy.  The
-U.S. Government has rights to use, reproduce, and distribute this
-SOFTWARE.  The public may copy, distribute, prepare derivative works
-and publicly display this SOFTWARE without charge, provided that this
-Notice and any statement of authorship are reproduced on all copies.
-Neither the Government nor the University makes any warranty, express 
-or implied, or assumes any liability or responsibility for the use of
-this SOFTWARE.  If SOFTWARE is modified to produce derivative works,
-such modified SOFTWARE should be clearly marked, so as not to confuse
-it with the version available from LANL.
- */
-/* Copyright 2000, Ron Minnich, Advanced Computing Lab, LANL
- * rminnich@lanl.gov
- */
 /*
- * 11/26/02 - kevinh@ispiri.com - The existing comments implied that
- * this didn't work yet.  Therefore, I've updated it so that it works
- * correctly - at least on my VIA epia motherboard.  64MB DIMM in slot 0.
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2007-2008 Uwe Hermann <uwe@hermann-uwe.de>
+ * Copyright (C) 2010 Keith Hui <buurin@gmail.com>
+ *
+ * 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.
+ *
+ * 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
  */
 
-/* Added automatic detection of first equipped bank and its MA mapping type.
- * (Rest of configuration is done in C)
- * 5/19/03 by SONE Takeshi <ts1@tsn.or.jp>
- */
-/* converted to C 9/2003 Ron Minnich */
+#include <spd.h>
+#include <delay.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <arch/io.h>
+#include <arch/romcc_io.h>
+#include <device/pci_def.h>
+#include <console/console.h>
+#include "i440bx.h"
+#include "raminit.h"
 
-/* Modified for the i440bx Richard Smith 01/2005 */
+/*-----------------------------------------------------------------------------
+Macros and definitions.
+-----------------------------------------------------------------------------*/
 
-/* Set to 1 if your DIMMs are PC133 Note that I'm assuming CPU's FSB
- * frequency is 133MHz. If your CPU runs at another bus speed, you
- * might need to change some of register values.
- */
-#ifndef DIMM_PC133
-#define DIMM_PC133 0
+#define NB PCI_DEV(0, 0, 0)
+
+/* Debugging macros. */
+#if CONFIG_DEBUG_RAM_SETUP
+#define PRINT_DEBUG(x...)      printk(BIOS_DEBUG, x)
+#define DUMPNORTH()            dump_pci_device(NB)
+#else
+#define PRINT_DEBUG(x...)
+#define DUMPNORTH()
 #endif
 
-// Set to 1 if your DIMMs are CL=2
-#ifndef DIMM_CL2
-#define DIMM_CL2 0
+/* SDRAMC[7:5] - SDRAM Mode Select (SMS). */
+#define RAM_COMMAND_NORMAL     0x0
+#define RAM_COMMAND_NOP                0x1
+#define RAM_COMMAND_PRECHARGE  0x2
+#define RAM_COMMAND_MRS                0x3
+#define RAM_COMMAND_CBR                0x4
+
+/* Map the JEDEC SPD refresh rates (array index) to 440BX refresh rates as
+ * defined in DRAMC[2:0].
+ *
+ * [0] == Normal        15.625 us ->  15.6 us
+ * [1] == Reduced(.25X)    3.9 us ->   7.8 ns
+ * [2] == Reduced(.5X)     7.8 us ->   7.8 us
+ * [3] == Extended(2x)    31.3 us ->  31.2 us
+ * [4] == Extended(4x)    62.5 us ->  62.4 us
+ * [5] == Extended(8x)     125 us -> 124.8 us
+ */
+static const uint32_t refresh_rate_map[] = {
+       1, 5, 5, 2, 3, 4
+};
+
+/* Table format: register, bitmask, value. */
+static const u8 register_values[] = {
+       /* NBXCFG - NBX Configuration Register
+        * 0x50 - 0x53
+        *
+        * [31:24] SDRAM Row Without ECC
+        *         0 = ECC components are populated in this row
+        *         1 = ECC components are not populated in this row
+        * [23:19] Reserved
+        * [18:18] Host Bus Fast Data Ready Enable (HBFDRE)
+        *         Assertion of DRAM data on host bus occurs...
+        *         0 = ...one clock after sampling snoop results (default)
+        *         1 = ...on the same clock the snoop result is being sampled
+        *             (this mode is faster by one clock cycle)
+        * [17:17] ECC - EDO static Drive mode
+        *         0 = Normal mode (default)
+        *         1 = ECC signals are always driven
+        * [16:16] IDSEL_REDIRECT
+        *         0 = IDSEL1 is allocated to this bridge (default)
+        *         1 = IDSEL7 is allocated to this bridge
+        * [15:15] WSC# Handshake Disable
+        *         1 = Uni-processor mode
+        *         0 = Dual-processor mode with external IOAPIC (default)
+        * [14:14] Intel Reserved
+        * [13:12] Host/DRAM Frequency
+        *         00 = 100 MHz
+        *         01 = Reserved
+        *         10 = 66 MHz
+        *         11 = Reserved
+        * [11:11] AGP to PCI Access Enable
+        *         1 = Enable
+        *         0 = Disable
+        * [10:10] PCI Agent to Aperture Access Disable
+        *         1 = Disable
+        *         0 = Enable (default)
+        * [09:09] Aperture Access Global Enable
+        *         1 = Enable
+        *         0 = Disable
+        * [08:07] DRAM Data Integrity Mode (DDIM)
+        *         00 = Non-ECC
+        *         01 = EC-only
+        *         10 = ECC Mode
+        *         11 = ECC Mode with hardware scrubbing enabled
+        * [06:06] ECC Diagnostic Mode Enable (EDME)
+        *         1 = Enable
+        *         0 = Normal operation mode (default)
+        * [05:05] MDA Present (MDAP)
+        *         Works in conjunction with the VGA_EN bit.
+        *         VGA_EN MDAP
+        *           0     x   All VGA cycles are sent to PCI
+        *           1     0   All VGA cycles are sent to AGP
+        *           1     1   All VGA cycles are sent to AGP, except for
+        *                     cycles in the MDA range.
+        * [04:04] Reserved
+        * [03:03] USWC Write Post During I/O Bridge Access Enable (UWPIO)
+        *         1 = Enable
+        *         0 = Disable
+        * [02:02] In-Order Queue Depth (IOQD)
+        *         1 = In-order queue = maximum
+        *         0 = A7# is sampled asserted (i.e., 0)
+        * [01:00] Reserved
+        */
+       NBXCFG + 0, 0x00, 0x0c,
+       // TODO: Bit 15 should be 0 for multiprocessor boards
+       NBXCFG + 1, 0x00, 0x80,
+       NBXCFG + 2, 0x00, 0x00,
+       NBXCFG + 3, 0x00, 0xff,
+
+       /* DRAMC - DRAM Control Register
+        * 0x57
+        *
+        * [7:6] Reserved
+        * [5:5] Module Mode Configuration (MMCONFIG)
+        *       The combination of SDRAMPWR and this bit (which is set by an
+        *       external strapping option) determine how CKE works.
+        *       SDRAMPWR MMCONFIG
+        *       0        0         = 3 DIMM, CKE0[5:0] driven
+        *       X        1         = 3 DIMM, CKE0 only
+        *       1        0         = 4 DIMM, GCKE only
+        * [4:3] DRAM Type (DT)
+        *       00 = EDO
+        *       01 = SDRAM
+        *       10 = Registered SDRAM
+        *       11 = Reserved
+        *       Note: EDO, SDRAM and Registered SDRAM cannot be mixed.
+        * [2:0] DRAM Refresh Rate (DRR)
+        *       000 = Refresh disabled
+        *       001 = 15.6 us
+        *       010 = 31.2 us
+        *       011 = 62.4 us
+        *       100 = 124.8 us
+        *       101 = 249.6 us
+        *       110 = Reserved
+        *       111 = Reserved
+        */
+       /* Choose SDRAM (not registered), and disable refresh for now. */
+       DRAMC, 0x00, 0x08,
+
+       /*
+        * PAM[6:0] - Programmable Attribute Map Registers
+        * 0x59 - 0x5f
+        *
+        * 0x59 [3:0] Reserved
+        * 0x59 [5:4] 0xF0000 - 0xFFFFF BIOS area
+        * 0x5a [1:0] 0xC0000 - 0xC3FFF ISA add-on BIOS
+        * 0x5a [5:4] 0xC4000 - 0xC7FFF ISA add-on BIOS
+        * 0x5b [1:0] 0xC8000 - 0xCBFFF ISA add-on BIOS
+        * 0x5b [5:4] 0xCC000 - 0xCFFFF ISA add-on BIOS
+        * 0x5c [1:0] 0xD0000 - 0xD3FFF ISA add-on BIOS
+        * 0x5c [5:4] 0xD4000 - 0xD7FFF ISA add-on BIOS
+        * 0x5d [1:0] 0xD8000 - 0xDBFFF ISA add-on BIOS
+        * 0x5d [5:4] 0xDC000 - 0xDFFFF ISA add-on BIOS
+        * 0x5e [1:0] 0xE0000 - 0xE3FFF BIOS entension
+        * 0x5e [5:4] 0xE4000 - 0xE7FFF BIOS entension
+        * 0x5f [1:0] 0xE8000 - 0xEBFFF BIOS entension
+        * 0x5f [5:4] 0xEC000 - 0xEFFFF BIOS entension
+        *
+        * Bit assignment:
+        * 00 = DRAM Disabled (all access goes to memory mapped I/O space)
+        * 01 = Read Only (Reads to DRAM, writes to memory mapped I/O space)
+        * 10 = Write Only (Writes to DRAM, reads to memory mapped I/O space)
+        * 11 = Read/Write (all access goes to DRAM)
+        */
+
+       /*
+        * Map all legacy regions to RAM (read/write). This is required if
+        * you want to use the RAM area from 768 KB - 1 MB. If the PAM
+        * registers are not set here appropriately, the RAM in that region
+        * will not be accessible, thus a RAM check of it will also fail.
+        *
+        * TODO: This was set in sdram_set_spd_registers().
+        * Test if it still works when set here.
+        */
+       PAM0, 0x00, 0x30,
+       PAM1, 0x00, 0x33,
+       PAM2, 0x00, 0x33,
+       PAM3, 0x00, 0x33,
+       PAM4, 0x00, 0x33,
+       PAM5, 0x00, 0x33,
+       PAM6, 0x00, 0x33,
+
+       /* DRB[0:7] - DRAM Row Boundary Registers
+        * 0x60 - 0x67
+        *
+        * An array of 8 byte registers, which hold the ending memory address
+        * assigned to each pair of DIMMs, in 8MB granularity.
+        *
+        * 0x60 DRB0 = Total memory in row0 (in 8 MB)
+        * 0x61 DRB1 = Total memory in row0+1 (in 8 MB)
+        * 0x62 DRB2 = Total memory in row0+1+2 (in 8 MB)
+        * 0x63 DRB3 = Total memory in row0+1+2+3 (in 8 MB)
+        * 0x64 DRB4 = Total memory in row0+1+2+3+4 (in 8 MB)
+        * 0x65 DRB5 = Total memory in row0+1+2+3+4+5 (in 8 MB)
+        * 0x66 DRB6 = Total memory in row0+1+2+3+4+5+6 (in 8 MB)
+        * 0x67 DRB7 = Total memory in row0+1+2+3+4+5+6+7 (in 8 MB)
+        */
+       /* Set the DRBs to zero for now, this will be fixed later. */
+       DRB0, 0x00, 0x00,
+       DRB1, 0x00, 0x00,
+       DRB2, 0x00, 0x00,
+       DRB3, 0x00, 0x00,
+       DRB4, 0x00, 0x00,
+       DRB5, 0x00, 0x00,
+       DRB6, 0x00, 0x00,
+       DRB7, 0x00, 0x00,
+
+       /* FDHC - Fixed DRAM Hole Control Register
+        * 0x68
+        *
+        * Controls two fixed DRAM holes: 512 KB - 640 KB and 15 MB - 16 MB.
+        *
+        * [7:6] Hole Enable (HEN)
+        *       00 = None
+        *       01 = 512 KB - 640 KB (128 KB)
+        *       10 = 15 MB - 16 MB (1 MB)
+        *       11 = Reserved
+        * [5:0] Reserved
+        */
+       /* No memory holes. */
+       FDHC, 0x00, 0x00,
+
+       /* RPS - SDRAM Row Page Size Register
+        * 0x74 - 0x75
+        *
+        * Sets the row page size for SDRAM. For EDO memory, the page
+        * size is fixed at 2 KB.
+        *
+        * Bits[1:0] Page Size
+        * 00        2 KB
+        * 01        4 KB
+        * 10        8 KB
+        * 11        Reserved
+        *
+        * RPS bits Corresponding DRB register
+        * [01:00]  DRB[0], row 0
+        * [03:02]  DRB[1], row 1
+        * [05:04]  DRB[2], row 2
+        * [07:06]  DRB[3], row 3
+        * [09:08]  DRB[4], row 4
+        * [11:10]  DRB[5], row 5
+        * [13:12]  DRB[6], row 6
+        * [15:14]  DRB[7], row 7
+        */
+       /* Power on defaults to 2KB. Will be set later. */
+       // RPS + 0, 0x00, 0x00,
+       // RPS + 1, 0x00, 0x00,
+
+       /* SDRAMC - SDRAM Control Register
+        * 0x76 - 0x77
+        *
+        * [15:10] Reserved
+        * [09:08] Idle/Pipeline DRAM Leadoff Timing (IPDLT)
+        *         00 = Illegal
+        *         01 = Add a clock delay to the lead-off clock count
+        *         1x = Illegal
+        * [07:05] SDRAM Mode Select (SMS)
+        *         000 = Normal SDRAM Operation (default)
+        *         001 = NOP Command Enable
+        *         010 = All Banks Precharge Enable
+        *         011 = Mode Register Set Enable
+        *         100 = CBR Enable
+        *         101 = Reserved
+        *         110 = Reserved
+        *         111 = Reserved
+        * [04:04] SDRAMPWR
+        *         0 = 3 DIMM configuration
+        *         1 = 4 DIMM configuration
+        * [03:03] Leadoff Command Timing (LCT)
+        *         0 = 4 CS# Clock
+        *         1 = 3 CS# Clock
+        * [02:02] CAS# Latency (CL)
+        *         0 = 3 DCLK CAS# latency
+        *         1 = 2 DCLK CAS# latency
+        * [01:01] SDRAM RAS# to CAS# Delay (SRCD)
+        *         0 = 3 clocks between a row activate and a read or write cmd.
+        *         1 = 2 clocks between a row activate and a read or write cmd.
+        * [00:00] SDRAM RAS# Precharge (SRP)
+        *         0 = 3 clocks of RAS# precharge
+        *         1 = 2 clocks of RAS# precharge
+        */
+#if CONFIG_SDRAMPWR_4DIMM
+       SDRAMC + 0, 0x00, 0x10, /* The board has 4 DIMM slots. */
+#else
+       SDRAMC + 0, 0x00, 0x00, /* The board has 3 DIMM slots. */
 #endif
+       SDRAMC + 1, 0x00, 0x00,
+
+       /* PGPOL - Paging Policy Register
+        * 0x78 - 0x79
+        *
+        * [15:08] Banks per Row (BPR)
+        *         Each bit in this field corresponds to one row of the memory
+        *         array. Bit 15 corresponds to row 7 while bit 8 corresponds
+        *         to row 0. Bits for empty rows are "don't care".
+        *         0 = 2 banks
+        *         1 = 4 banks
+        * [07:05] Reserved
+        * [04:04] Intel Reserved
+        * [03:00] DRAM Idle Timer (DIT)
+        *         0000 = 0 clocks
+        *         0001 = 2 clocks
+        *         0010 = 4 clocks
+        *         0011 = 8 clocks
+        *         0100 = 10 clocks
+        *         0101 = 12 clocks
+        *         0110 = 16 clocks
+        *         0111 = 32 clocks
+        *         1xxx = Infinite (pages are not closed for idle condition)
+        */
+       PGPOL + 0, 0x00, 0x00,
+       PGPOL + 1, 0x00, 0xff,
+
+       /* PMCR - Power Management Control Register
+        * 0x7a
+        *
+        * [07:07] Power Down SDRAM Enable (PDSE)
+        *         1 = Enable
+        *         0 = Disable
+        * [06:06] ACPI Control Register Enable (SCRE)
+        *         1 = Enable
+        *         0 = Disable (default)
+        * [05:05] Suspend Refresh Type (SRT)
+        *         1 = Self refresh mode
+        *         0 = CBR fresh mode
+        * [04:04] Normal Refresh Enable (NREF_EN)
+        *         1 = Enable
+        *         0 = Disable
+        * [03:03] Quick Start Mode (QSTART)
+        *         1 = Quick start mode for the processor is enabled
+        * [02:02] Gated Clock Enable (GCLKEN)
+        *         1 = Enable
+        *         0 = Disable
+        * [01:01] AGP Disable (AGP_DIS)
+        *         1 = Disable
+        *         0 = Enable
+        * [00:00] CPU reset without PCIRST enable (CRst_En)
+        *         1 = Enable
+        *         0 = Disable
+        */
+       /* Enable normal refresh and the gated clock. */
+       // TODO: Only do this later?
+       // PMCR, 0x00, 0x14,
+       PMCR, 0x00, 0x00,
+
+       /* Enable SCRR.SRRAEN and let BX choose the SRR. */
+       SCRR + 1, 0x00, 0x10,
+};
+
+/*-----------------------------------------------------------------------------
+SDRAM configuration functions.
+-----------------------------------------------------------------------------*/
 
-void dimms_read(unsigned long x) 
+/**
+ * Send the specified RAM command to all DIMMs.
+ *
+ * @param command The RAM command to send to the DIMM(s).
+ */
+static void do_ram_command(u32 command)
 {
-       uint8_t c;
-       unsigned long eax; 
-       volatile unsigned long y;
-       eax =  x;
-       for(c = 0; c < 6; c++) {
-               y = * (volatile unsigned long *) eax;
-               eax += 0x10000000;
+       int i, caslatency;
+       u8 dimm_start, dimm_end;
+       u16 reg16;
+       u32 addr, addr_offset;
+
+       /* Configure the RAM command. */
+       reg16 = pci_read_config16(NB, SDRAMC);
+       reg16 &= 0xff1f;                /* Clear bits 7-5. */
+       reg16 |= (u16) (command << 5);  /* Write command into bits 7-5. */
+       pci_write_config16(NB, SDRAMC, reg16);
+
+       /*
+        * RAM_COMMAND_NORMAL affects only the memory controller and
+        * doesn't need to be "sent" to the DIMMs.
+        */
+       if (command == RAM_COMMAND_NORMAL)
+               return;
+
+       /* Send the RAM command to each row of memory. */
+       dimm_start = 0;
+       for (i = 0; i < (DIMM_SOCKETS * 2); i++) {
+               addr_offset = 0;
+               caslatency = 3; /* TODO: Dynamically get CAS latency later. */
+               if (command == RAM_COMMAND_MRS) {
+                       /*
+                        * MAA[12:11,9:0] must be inverted when sent to DIMM
+                        * 2 or 3 (no inversion if sent to DIMM 0 or 1).
+                        */
+                       if ((i >= 0 && i <= 3) && caslatency == 3)
+                               addr_offset = 0x1d0;
+                       if ((i >= 4 && i <= 7) && caslatency == 3)
+                               addr_offset = 0x1e28;
+                       if ((i >= 0 && i <= 3) && caslatency == 2)
+                               addr_offset = 0x150;
+                       if ((i >= 4 && i <= 7) && caslatency == 2)
+                               addr_offset = 0x1ea8;
+               }
+
+               dimm_end = pci_read_config8(NB, DRB + i);
+
+               addr = (dimm_start * 8 * 1024 * 1024) + addr_offset;
+               if (dimm_end > dimm_start) {
+#if 0
+                       PRINT_DEBUG("    Sending RAM command 0x%04x to 0x%08x\n",
+                                       reg16, addr);
+#endif
+
+                       read32(addr);
+               }
+
+               /* Set the start of the next DIMM. */
+               dimm_start = dimm_end;
        }
 }
 
-void dimms_write(int x) 
+static void set_dram_buffer_strength(void)
 {
-       uint8_t c;
-       unsigned long eax = x;
-       for(c = 0; c < 6; c++) {
-               *(volatile unsigned long *) eax = 0;
-               eax += 0x10000000;
+       /* To give some breathing room for romcc,
+        * mbsc0 doubles as drb
+        * mbsc1 doubles as drb1
+        * mbfs0 doubles as i and reg
+        */
+       uint8_t mbsc0,mbsc1,mbsc3,mbsc4,mbfs0,mbfs2,fsb;
+
+       /* Tally how many rows between rows 0-3 and rows 4-7 are populated.
+        * This determines how to program MBFS and MBSC.
+        */
+       uint8_t dimm03 = 0;
+       uint8_t dimm47 = 0;
+
+       mbsc0 = 0;
+       for (mbfs0 = DRB0; mbfs0 <= DRB7; mbfs0++) {
+               mbsc1 = pci_read_config8(NB, mbfs0);
+               if (mbsc0 != mbsc1) {
+                       if (mbfs0 <= DRB3) {
+                               dimm03++;
+                       } else {
+                               dimm47++;
+                       }
+                       mbsc0 = mbsc1;
+               }
        }
-}
 
+       /* Algorithm bitmap for programming MBSC[39:0] and MBFS[23:0].
+        *
+        * The 440BX datasheet says buffer frequency is independent from bus
+        * frequency and mismatch both ways are possible. This is how it is
+        * programmed in the ASUS P2B-LS mainboard.
+        *
+        * There are four main conditions to check when programming DRAM buffer
+        * frequency and strength:
+        *
+        * a: >2 rows populated across DIMM0,1
+        * b: >2 rows populated across DIMM2,3
+        * c: >4 rows populated across all DIMM slots
+        * and either one of:
+        * 1: NBXCFG[13] strapped as 100MHz, or
+        * 6: NBXCFG[13] strapped as 66MHz
+        *
+        * CKE0/FENA ----------------------------------------------------------+
+        * CKE1/GCKE -------------------[    MBFS    ]------------------------+|
+        * DQMA/CASA[764320]# ----------[ 0 = 66MHz  ]-----------------------+||
+        * DQMB1/CASB1# ----------------[ 1 = 100MHz ]----------------------+|||
+        * DQMB5/CASB5# ---------------------------------------------------+||||
+        * DQMA1/CASA1# --------------------------------------------------+|||||
+        * DQMA5/CASA5# -------------------------------------------------+||||||
+        * CSA0-5#,CSB0-5# ----------------------------------------++++++|||||||
+        * CSA6#/CKE2# -------------------------------------------+|||||||||||||
+        * CSB6#/CKE4# ------------------------------------------+||||||||||||||
+        * CSA7#/CKE3# -----------------------------------------+|||||||||||||||
+        * CSB7#/CKE5# ----------------------------------------+||||||||||||||||
+        * MECC[7:0] #2/#1 (100MHz) -------------------------++|||||||||||||||||
+        * MD[63:0] #2/#1 (100MHz) ------------------------++|||||||||||||||||||
+        * MAB[12:11,9:0]#,MAB[13,10],WEB#,SRASB#,SCASB# -+|||||||||||||||||||||
+        * MAA[13:0],WEA#,SRASA#,SCASA# -----------------+||||||||||||||||||||||
+        * Reserved ------------------------------------+|||||||||||||||||||||||
+        *                                              ||||||||||||||||||||||||
+        *   3        32        21        10        0 * 2  21        10        0
+        *   9876543210987654321098765432109876543210 * 321098765432109876543210
+        * a 10------------------------1010---------- * -1---------------11-----  a
+        *!a 11------------------------1111---------- * -0---------------00----- !a
+        * b --10--------------------------1010------ * --1----------------11---  b
+        *!b --11--------------------------1111------ * --0----------------00--- !b
+        * c ----------------------------------1100-- * ----------------------1-  c
+        *!c ----------------------------------1011-- * ----------------------0- !c
+        * 1 ----1010101000000000000000------------00 * ---11111111111111----1-0  1
+        * 6 ----000000000000000000000010101010----00 * ---1111111111111100000-0  6
+        *   | | | | | | | | | | ||||||| | | | | | |
+        *   | | | | | | | | | | ||||||| | | | | | +- CKE0/FENA
+        *   | | | | | | | | | | ||||||| | | | | +--- CKE1/GCKE
+        *   | | | | | | | | | | ||||||| | | | +----- DQMA/CASA[764320]#
+        *   | | | | | | | | | | ||||||| | | +------- DQMB1/CASB1#
+        *   | | | | | | | | | | ||||||| | +--------- DQMB5/CASB5#
+        *   | | | | | | | | | | ||||||| +----------- DQMA1/CASA1#
+        *   | | | | | | | | | | ||||||+------------- DQMA5/CASA5#
+        *   | | | | | | | | | | ++++++-------------- CSA0-5#,CSB0-5# [ 0=1x;1=2x ]
+        *   | | | | | | | | | +--------------------- CSA6#/CKE2#
+        *   | | | | | | | | +---[    MBSC    ]------ CSB6#/CKE4#
+        *   | | | | | | | +-----[ 00 = 1x    ]------ CSA7#/CKE3#
+        *   | | | | | | +-------[ 01 invalid ]------ CSB7#/CKE5#
+        *   | | | | | +---------[ 10 = 2x    ]------ MECC[7:0] #1 (2x)
+        *   | | | | +-----------[ 11 = 3x    ]------ MECC[7:0] #2 (2x)
+        *   | | | +--------------------------------- MD[63:0] #1 (2x)
+        *   | | +----------------------------------- MD[63:0] #2 (2x)
+        *   | +------------------------------------- MAB[12:11,9:0]#,MAB[13,10],WEB#,SRASB#,SCASB#
+        *   +--------------------------------------- MAA[13:0],WEA#,SRASA#,SCASA#
+        * MBSC[47:40] and MBFS[23] are reserved.
+        *
+        * This algorithm is checked against the ASUS P2B-LS (which has
+        * 4 DIMM slots) factory BIOS.
+        * Therefore it assumes a board with 4 slots, and will need testing
+        * on boards with 3 DIMM slots.
+        */
 
+       mbsc0 = 0x80;
+       mbsc1 = 0x2a;
+       mbfs2 = 0x1f;
+       if (pci_read_config8(NB, NBXCFG + 1) & 0x30) {
+               fsb = 66;
+               mbsc3 = 0x00;
+               mbsc4 = 0x00;
+               mbfs0 = 0x80;
+       } else {
+               fsb = 100;
+               mbsc3 = 0xa0;
+               mbsc4 = 0x0a;
+               mbfs0 = 0x84;
+       }
 
-#ifdef DEBUG_SETNORTHB
-void setnorthb(device_t north, uint8_t reg, uint8_t val) 
-{
-       print_debug("setnorth: reg ");
-       print_debug_hex8(reg);
-       print_debug(" to ");
-       print_debug_hex8(val);
-       print_debug("\r\n");
-       pci_write_config8(north, reg, val);
+       if (dimm03 > 2) {
+               mbsc4 = mbsc4 | 0x80;
+               mbsc1 = mbsc1 | 0x28;
+               mbfs2 = mbfs2 | 0x40;
+               mbfs0 = mbfs0 | 0x60;
+       } else {
+               mbsc4 = mbsc4 | 0xc0;
+               if (fsb == 100) {
+                       mbsc1 = mbsc1 | 0x3c;
+               }
+       }
+       if (dimm47 > 2) {
+               mbsc4 = mbsc4 | 0x20;
+               mbsc1 = mbsc1 | 0x02;
+               mbsc0 = mbsc0 | 0x80;
+               mbfs2 = mbfs2 | 0x20;
+               mbfs0 = mbfs0 | 0x18;
+       } else {
+               mbsc4 = mbsc4 | 0x30;
+               if (fsb == 100) {
+                       mbsc1 = mbsc1 | 0x03;
+                       mbsc0 = mbsc0 | 0xc0;
+               }
+       }
+       if ((dimm03 + dimm47) > 4) {
+               mbsc0 = mbsc0 | 0x30;
+               mbfs0 = mbfs0 | 0x02;
+       } else {
+               mbsc0 = mbsc0 | 0x2c;
+       }
+
+       pci_write_config8(NB, MBSC + 0, mbsc0);
+       pci_write_config8(NB, MBSC + 1, mbsc1);
+       pci_write_config8(NB, MBSC + 2, 0x00);
+       pci_write_config8(NB, MBSC + 3, mbsc3);
+       pci_write_config8(NB, MBSC + 4, mbsc4);
+       pci_write_config8(NB, MBFS + 0, mbfs0);
+       pci_write_config8(NB, MBFS + 1, 0xff);
+       pci_write_config8(NB, MBFS + 2, mbfs2);
 }
-#else
-#define setnorthb pci_write_config8
-#endif
 
-void
-dumpnorth(device_t north) 
+/*-----------------------------------------------------------------------------
+DIMM-independant configuration functions.
+-----------------------------------------------------------------------------*/
+
+static void spd_enable_refresh(void)
 {
-       unsigned int r, c;
-       for(r = 0; ; r += 16) {
-               print_debug_hex8(r);
-               print_debug(":");
-               for(c = 0; c < 16; c++) {
-                       print_debug_hex8(pci_read_config8(north, r+c));
-                       print_debug(" ");
-               }
-               print_debug("\r\n");
-               if (r >= 240)
-                       break;
-  }
+       int i, value;
+       uint8_t reg;
+
+       reg = pci_read_config8(NB, DRAMC);
+
+       for (i = 0; i < DIMM_SOCKETS; i++) {
+               value = spd_read_byte(DIMM0 + i, SPD_REFRESH);
+               if (value < 0)
+                       continue;
+               reg = (reg & 0xf8) | refresh_rate_map[(value & 0x7f)];
+
+               PRINT_DEBUG("    Enabling refresh (DRAMC = 0x%02x) for DIMM %02x\n", reg, i);
+       }
+
+       pci_write_config8(NB, DRAMC, reg);
 }
 
-static void sdram_set_registers(const struct mem_controller *ctrl) 
+/*-----------------------------------------------------------------------------
+Public interface.
+-----------------------------------------------------------------------------*/
+
+void sdram_set_registers(void)
 {
-       device_t north = (device_t) 0;
-       uint8_t c, r;
-
-       print_err("vt8601 init starting\r\n");
-       north = pci_locate_device(PCI_ID(0x1106, 0x8601), 0);
-       north = 0;
-       print_debug_hex32(north);
-       print_debug(" is the north\n");
-       print_debug_hex16(pci_read_config16(north, 0));
-       print_debug(" ");
-       print_debug_hex16(pci_read_config16(north, 2));
-       print_debug("\r\n");
-       
-       /* All we are doing now is setting initial known-good values that will
-        * be revised later as we read SPD
-        */     
-       // memory clk enable. We are not using ECC
-       pci_write_config8(north,0x78, 0x01);
-       print_debug_hex8(pci_read_config8(north, 0x78));
-       // dram control, see the book. 
-#if DIMM_PC133
-       pci_write_config8(north,0x68, 0x52);
-#else
-       pci_write_config8(north,0x68, 0x42);
-#endif
-       // dram control, see the book. 
-       pci_write_config8(north,0x6B, 0x0c);
-       // Initial setting, 256MB in each bank, will be rewritten later.
-       pci_write_config8(north,0x5A, 0x20);
-       print_debug_hex8(pci_read_config8(north, 0x5a));
-       pci_write_config8(north,0x5B, 0x40);
-       pci_write_config8(north,0x5C, 0x60);
-       pci_write_config8(north,0x5D, 0x80);
-       pci_write_config8(north,0x5E, 0xA0);
-       pci_write_config8(north,0x5F, 0xC0);
-       // It seems we have to take care of these 2 registers as if 
-       // they are bank 6 and 7.
-       pci_write_config8(north,0x56, 0xC0);
-       pci_write_config8(north,0x57, 0xC0);
-       
-       // SDRAM in all banks
-       pci_write_config8(north,0x60, 0x3F);
-       // DRAM timing. I'm suspicious of this
-       // This is for all banks, 64 is 0,1.  65 is 2,3. 66 is 4,5.
-       // ras precharge 4T, RAS pulse 5T
-       // cas2 is 0xd6, cas3 is 0xe6
-       // we're also backing off write pulse width to 2T, so result is 0xee
-#if DIMM_CL2
-       pci_write_config8(north,0x64, 0xd4);
-       pci_write_config8(north,0x65, 0xd4);
-       pci_write_config8(north,0x66, 0xd4);
-#else // CL=3
-       pci_write_config8(north,0x64, 0xe4);
-       pci_write_config8(north,0x65, 0xe4);
-       pci_write_config8(north,0x66, 0xe4);
-#endif
+       int i, max;
+       uint8_t reg;
 
-       // dram frequency select.
-       // enable 4K pages for 64M dram. 
-#if DIMM_PC133
-       pci_write_config8(north,0x69, 0x3c);
-#else
-       pci_write_config8(north,0x69, 0xac);
-#endif
+       PRINT_DEBUG("Northbridge prior to SDRAM init:\n");
+       DUMPNORTH();
+
+       max = ARRAY_SIZE(register_values);
 
-       /* IMPORTANT -- disable refresh counter */
-       // refresh counter, disabled.
-       pci_write_config8(north,0x6A, 0x00);
-       
-
-       // clkenable configuration. kevinh FIXME - add precharge
-       pci_write_config8(north,0x6C, 0x00);
-       // dram read latch delay of 1 ns, MD drive 8 mA,
-       // high drive strength on MA[2: 13], we#, cas#, ras#
-       // As per Cindy Lee, set to 0x37, not 0x57
-       pci_write_config8(north,0x6D, 0x7f);
+       /* Set registers as specified in the register_values[] array. */
+       for (i = 0; i < max; i += 3) {
+               reg = pci_read_config8(NB, register_values[i]);
+               reg &= register_values[i + 1];
+               reg |= register_values[i + 2] & ~(register_values[i + 1]);
+               pci_write_config8(NB, register_values[i], reg);
+#if 0
+               PRINT_DEBUG("    Set register 0x%02x to 0x%02x\n",
+                               register_values[i], reg);
+#endif
+       }
 }
 
-/* slot is the dram slot. Return size of side0 in lower 16-bit,
- * side1 in upper 16-bit, in units of 8MB */
-static unsigned long 
-spd_module_size(unsigned char slot) 
-{ 
-       /* for all the DRAMS, see if they are there and get the size of each
-        * module. This is just a very early first cut at sizing.
+struct dimm_size {
+       u32 side1;
+       u32 side2;
+};
+
+static struct dimm_size spd_get_dimm_size(unsigned int device)
+{
+       struct dimm_size sz;
+       int i, module_density, dimm_banks;
+       sz.side1 = 0;
+       module_density = spd_read_byte(device, SPD_DENSITY_OF_EACH_ROW_ON_MODULE);
+       dimm_banks = spd_read_byte(device, SPD_NUM_DIMM_BANKS);
+
+       /* Find the size of side1. */
+       /* Find the larger value. The larger value is always side1. */
+       for (i = 512; i >= 0; i >>= 1) {
+               if ((module_density & i) == i) {
+                       sz.side1 = i;
+                       break;
+               }
+       }
+
+       /* Set to 0 in case it's single sided. */
+       sz.side2 = 0;
+
+       /* Test if it's a dual-sided DIMM. */
+       if (dimm_banks > 1) {
+               /* Test if there's a second value. If so it's asymmetrical. */
+               if (module_density != i) {
+                       /*
+                        * Find second value, picking up where we left off.
+                        * i >>= 1 done initially to make sure we don't get
+                        * the same value again.
+                        */
+                       for (i >>= 1; i >= 0; i >>= 1) {
+                               if (module_density == (sz.side1 | i)) {
+                                       sz.side2 = i;
+                                       break;
+                               }
+                       }
+                       /* If not, it's symmetrical. */
+               } else {
+                       sz.side2 = sz.side1;
+               }
+       }
+
+       /*
+        * SPD byte 31 is the memory size divided by 4 so we
+        * need to muliply by 4 to get the total size.
         */
-       /* we may run out of registers ... */
-       unsigned int banks, rows, cols, reg;
-       unsigned int value = 0;
-       /* unsigned int module = ((0x50 + slot) << 1) + 1; */
-       unsigned int module = 0x50 + slot;
-       /* is the module there? if byte 2 is not 4, then we'll assume it 
-        * is useless. 
+       sz.side1 *= 4;
+       sz.side2 *= 4;
+
+       /* It is possible to partially use larger then supported
+        * modules by setting them to a supported size.
         */
-       print_info("Slot "); 
-       print_info_hex8(slot); 
-       if (smbus_read_byte(module, 2) != 4) {
-               print_info(" is empty\r\n");
-               return 0;
-       }
-       print_info(" is SDRAM ");
-       
-       banks = smbus_read_byte(module, 17);
-       /* we're going to assume symmetric banks. Sorry. */
-       cols = smbus_read_byte(module, 4)  & 0xf;
-       rows = smbus_read_byte(module, 3)  & 0xf;
-       /* grand total. You have rows+cols addressing, * times of banks, times
-        * width of data in bytes */
-       /* Width is assumed to be 64 bits == 8 bytes */
-       value = (1 << (cols + rows)) * banks * 8;
-       print_info_hex32(value);
-       print_info(" bytes ");
-       /* Return in 8MB units */
-       value >>= 23;
-
-       /* We should have single or double side */
-       if (smbus_read_byte(module, 5) == 2) {
-               print_info("x2");
-               value = (value << 16) | value;
+       if(sz.side1 > 128) {
+               PRINT_DEBUG("Side1 was %dMB but only 128MB will be used.\n",
+                       sz.side1);
+               sz.side1 = 128;
+
+               if(sz.side2 > 128) {
+                       PRINT_DEBUG("Side2 was %dMB but only 128MB will be used.\n",
+                               sz.side2);
+                       sz.side2 = 128;
+               }
        }
-       print_info("\r\n");
-       return value;
 
+       return sz;
 }
+/*
+ * Sets DRAM attributes one DIMM at a time, based on SPD data.
+ * Northbridge settings that are set: NBXCFG[31:24], DRB0-DRB7, RPS, DRAMC.
+ */
+static void set_dram_row_attributes(void)
+{
+       int i, dra, drb, col, width, value, rps;
+       u8 bpr; /* Top 8 bits of PGPOL */
+       u8 nbxecc = 0; /* NBXCFG[31:24] */
+       u8 edo, sd, regsd; /* EDO, SDRAM, registered SDRAM */
 
-static int
-spd_num_chips(unsigned char slot) 
-{ 
-/*     unsigned int module = ((0x50 + slot) << 1) + 1; */
-       unsigned int module = 0x50 + slot;
-       unsigned int width;
-
-       width = smbus_read_byte(module, 13);
-       if (width == 0)
-               width = 8;
-       return 64 / width;
-}
+       edo = 0;
+       sd = 0;
+       regsd = 1;
+       rps = 0;
+       drb = 0;
+       bpr = 0;
 
-static void sdram_set_spd_registers(const struct mem_controller *ctrl)
-{
-#define T133 7
-       unsigned char Trp = 1, Tras = 1, casl = 2, val;
-       unsigned char timing = 0xe4;
-       /* read Trp */
-       val = smbus_read_byte(0x50, 27);
-       if (val < 2*T133)
-               Trp = 1;
-       val = smbus_read_byte(0x50, 30);
-       if (val < 5*T133)
-               Tras = 0;
-       val = smbus_read_byte(0x50, 18);
-       if (val < 8)
-               casl = 1;
-       if (val < 4)
-               casl = 0;
-       
-       val = (Trp << 7) | (Tras << 6) | (casl << 4) | 4;
-       
-       print_debug_hex8(val); print_debug(" is the computed timing\n");
-       /* don't set it. Experience shows that this screwy chipset should just
-        * be run with the most conservative timing.
-        * pci_write_config8(0, 0x64, val);
-        */
+       for (i = 0; i < DIMM_SOCKETS; i++) {
+               unsigned int device;
+               device = DIMM0 + i;
+               bpr >>= 2;
+               nbxecc >>= 2;
+
+               /* First check if a DIMM is actually present. */
+               value = spd_read_byte(device, SPD_MEMORY_TYPE);
+               /* This is 440BX! We do EDO too! */
+               if (value == SPD_MEMORY_TYPE_EDO
+                       || value == SPD_MEMORY_TYPE_SDRAM) {
+
+                       if (value == SPD_MEMORY_TYPE_EDO) {
+                               edo = 1;
+                       } else if (value == SPD_MEMORY_TYPE_SDRAM) {
+                               sd = 1;
+                       }
+                       PRINT_DEBUG("Found DIMM in slot %d\n", i);
+
+                       if (edo && sd) {
+                               print_err("Mixing EDO/SDRAM unsupported!\n");
+                               die("HALT\n");
+                       }
+
+                       /* "DRA" is our RPS for the two rows on this DIMM. */
+                       dra = 0;
+
+                       /* Columns */
+                       col = spd_read_byte(device, SPD_NUM_COLUMNS);
+
+                       /*
+                        * Is this an ECC DIMM? Actually will be a 2 if so.
+                        * TODO: Other register than NBXCFG also needs this
+                        * ECC information.
+                        */
+                       value = spd_read_byte(device, SPD_DIMM_CONFIG_TYPE);
+
+                       /* Data width */
+                       width = spd_read_byte(device, SPD_MODULE_DATA_WIDTH_LSB);
+
+                       /* Exclude error checking data width from page size calculations */
+                       if (value) {
+                               value = spd_read_byte(device,
+                                       SPD_ERROR_CHECKING_SDRAM_WIDTH);
+                               width -= value;
+                               /* ### ECC */
+                               /* Clear top 2 bits to help set up NBXCFG. */
+                               nbxecc &= 0x3f;
+                       } else {
+                               /* Without ECC, top 2 bits should be 11. */
+                               nbxecc |= 0xc0;
+                       }
+
+                       /* If any installed DIMM is *not* registered, this system cannot be
+                        * configured for registered SDRAM.
+                        * By registered, only the address and control lines need to be, which
+                        * we can tell by reading SPD byte 21, bit 1.
+                        */
+                       value = spd_read_byte(device, SPD_MODULE_ATTRIBUTES);
+
+                       PRINT_DEBUG("DIMM is ");
+                       if ((value & MODULE_REGISTERED) == 0) {
+                               regsd = 0;
+                               PRINT_DEBUG("not ");
+                       }
+                       PRINT_DEBUG("registered\n");
+
+                       /* Calculate page size in bits. */
+                       value = ((1 << col) * width);
+
+                       /* Convert to KB. */
+                       dra = (value >> 13);
+
+                       /* Number of banks of DIMM (single or double sided). */
+                       value = spd_read_byte(device, SPD_NUM_DIMM_BANKS);
+
+                       /* Once we have dra, col is done and can be reused.
+                        * So it's reused for number of banks.
+                        */
+                       col = spd_read_byte(device, SPD_NUM_BANKS_PER_SDRAM);
+
+                       if (value == 1) {
+                               /*
+                                * Second bank of 1-bank DIMMs "doesn't have
+                                * ECC" - or anything.
+                                */
+                               if (dra == 2) {
+                                       dra = 0x0; /* 2KB */
+                               } else if (dra == 4) {
+                                       dra = 0x1; /* 4KB */
+                               } else if (dra == 8) {
+                                       dra = 0x2; /* 8KB */
+                               } else if (dra >= 16) {
+                                       /* Page sizes larger than supported are
+                                        * set to 8KB to use module partially.
+                                        */
+                                       PRINT_DEBUG("Page size forced to 8KB.\n");
+                                       dra = 0x2; /* 8KB */
+                               } else {
+                                       dra = -1;
+                               }
+                               /*
+                                * Sets a flag in PGPOL[BPR] if this DIMM has
+                                * 4 banks per row.
+                                */
+                               if (col == 4)
+                                       bpr |= 0x40;
+                       } else if (value == 2) {
+                               if (dra == 2) {
+                                       dra = 0x0; /* 2KB */
+                               } else if (dra == 4) {
+                                       dra = 0x05; /* 4KB */
+                               } else if (dra == 8) {
+                                       dra = 0x0a; /* 8KB */
+                               } else if (dra >= 16) {
+                                       /* Ditto */
+                                       PRINT_DEBUG("Page size forced to 8KB.\n");
+                                       dra = 0x0a; /* 8KB */
+                               } else {
+                                       dra = -1;
+                               }
+                               /* Ditto */
+                               if (col == 4)
+                                       bpr |= 0xc0;
+                       } else {
+                               print_err("# of banks of DIMM unsupported!\n");
+                               die("HALT\n");
+                       }
+                       if (dra == -1) {
+                               print_err("Page size not supported\n");
+                               die("HALT\n");
+                       }
+
+                       /*
+                        * 440BX supports asymmetrical dual-sided DIMMs,
+                        * but can't handle DIMMs smaller than 8MB per
+                        * side.
+                        */
+                       struct dimm_size sz = spd_get_dimm_size(device);
+                       if ((sz.side1 < 8)) {
+                               print_err("DIMMs smaller than 8MB per side\n"
+                                         "are not supported on this NB.\n");
+                               die("HALT\n");
+                       }
+
+                       /* Divide size by 8 to set up the DRB registers. */
+                       drb += (sz.side1 / 8);
+
+                       /*
+                        * Build the DRB for the next row in MSB so it gets
+                        * placed in DRB[n+1] where it belongs when written
+                        * as a 16-bit word.
+                        */
+                       drb &= 0xff;
+                       drb |= (drb + (sz.side2 / 8)) << 8;
+               } else {
+#if 0
+                       PRINT_DEBUG("No DIMM found in slot %d\n", i);
+#endif
+
+                       /* If there's no DIMM in the slot, set dra to 0x00. */
+                       dra = 0x00;
+                       /* Still have to propagate DRB over. */
+                       drb &= 0xff;
+                       drb |= (drb << 8);
+               }
+
+               pci_write_config16(NB, DRB + (2 * i), drb);
+#if 0
+               PRINT_DEBUG("DRB has been set to 0x%04x\n", drb);
+#endif
+
+               /* Brings the upper DRB back down to be base for
+                * DRB calculations for the next two rows.
+                */
+               drb >>= 8;
+
+               rps |= (dra & 0x0f) << (i * 4);
+       }
+
+       /* Set paging policy register. */
+       pci_write_config8(NB, PGPOL + 1, bpr);
+       PRINT_DEBUG("PGPOL[BPR] has been set to 0x%02x\n", bpr);
+
+       /* Set DRAM row page size register. */
+       pci_write_config16(NB, RPS, rps);
+       PRINT_DEBUG("RPS has been set to 0x%04x\n", rps);
+
+       /* ### ECC */
+       pci_write_config8(NB, NBXCFG + 3, nbxecc);
+       PRINT_DEBUG("NBXECC[31:24] has been set to 0x%02x\n", nbxecc);
+
+       /* Set DRAMC[4:3] to proper memory type (EDO/SDRAM/Registered SDRAM). */
+
+       /* i will be used to set DRAMC[4:3]. */
+       if (regsd && sd) {
+               i = 0x10; // Registered SDRAM
+       } else if (sd) {
+               i = 0x08; // SDRAM
+       } else {
+               i = 0; // EDO
+       }
+
+       value = pci_read_config8(NB, DRAMC) & 0xe7;
+       value |= i;
+       pci_write_config8(NB, DRAMC, value);
+       PRINT_DEBUG("DRAMC has been set to 0x%02x\n", value);
 }
 
-static void set_ma_mapping(device_t north, int slot, int type)
+void sdram_set_spd_registers(void)
 {
-    unsigned char reg, val;
-    int shift;
-
-    reg = 0x58 + slot/2;
-    if (slot%2 >= 1)
-        shift = 0;
-    else
-        shift = 4;
-
-    val = pci_read_config8(north, reg);
-    val &= ~(0xf << shift);
-    val |= type << shift;
-    pci_write_config8(north, reg, val);
-}
+       /* Setup DRAM row boundary registers and other attributes. */
+       set_dram_row_attributes();
+
+       /* Setup DRAM buffer strength. */
+       set_dram_buffer_strength();
 
+       /* TODO: Set PMCR? */
+       // pci_write_config8(NB, PMCR, 0x14);
+       pci_write_config8(NB, PMCR, 0x10);
+
+       /* TODO: This is for EDO memory only. */
+       pci_write_config8(NB, DRAMT, 0x03);
+}
 
-static void sdram_enable(int controllers, const struct mem_controller *ctrl) 
+void sdram_enable(void)
 {
-       unsigned char i;
-       static const uint8_t ramregs[] = {
-               0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x56, 0x57
-       };
-       device_t north = 0;
-       uint32_t size, base, slot, ma;
-       /* begin to initialize*/
-       // I forget why we need this, but we do
-       dimms_write(0xa55a5aa5);
-       
-       /* set NOP*/
-       pci_write_config8(north,0x6C, 0x01);
-       print_debug("NOP\r\n");
-       /* wait 200us*/
-       // You need to do the memory reference. That causes the nop cycle. 
-       dimms_read(0);
-       udelay(400);
-       print_debug("PRECHARGE\r\n");
-       /* set precharge */
-       pci_write_config8(north,0x6C, 0x02);
-       print_debug("DUMMY READS\r\n");
-       /* dummy reads*/
-       dimms_read(0);
-       udelay(200);
-       print_debug("CBR\r\n");
-       /* set CBR*/
-       pci_write_config8(north,0x6C, 0x04);
-       
-       /* do 8 reads and wait >100us between each - from via*/
-       dimms_read(0);
-       udelay(200);
-       dimms_read(0);
-       udelay(200);
-       dimms_read(0);
-       udelay(200);
-       dimms_read(0);
-       udelay(200);
-       dimms_read(0);
-       udelay(200);
-       dimms_read(0);
-       udelay(200);
-       dimms_read(0);
-       udelay(200);
-       dimms_read(0);
-       udelay(200);
-       print_debug("MRS\r\n");
-       /* set MRS*/
-       pci_write_config8(north,0x6c, 0x03);
-#if DIMM_CL2
-       dimms_read(0x150);
-#else // CL=3
-       dimms_read(0x1d0);
-#endif
+       int i;
+
+       /* 0. Wait until power/voltages and clocks are stable (200us). */
        udelay(200);
-       print_debug("NORMAL\r\n");
-       /* set to normal mode */
-       pci_write_config8(north,0x6C, 0x08);
-       
-       dimms_write(0x55aa55aa);
-       dimms_read(0);
+
+       /* 1. Apply NOP. Wait 200 clock cycles (200us should do). */
+       PRINT_DEBUG("RAM Enable 1: Apply NOP\n");
+       do_ram_command(RAM_COMMAND_NOP);
        udelay(200);
-       print_debug("set ref. rate\r\n");
-       // Set the refresh rate. 
-#if DIMM_PC133
-       pci_write_config8(north,0x6A, 0x86);
-#else
-       pci_write_config8(north,0x6A, 0x65);
-#endif
-       print_debug("enable multi-page open\r\n");
-       // enable multi-page open
-       pci_write_config8(north,0x6B, 0x0d);
-       
-       base = 0;
-       for(slot = 0; slot < 4; slot++) {
-               size = spd_module_size(slot);
-               /* side 0 */
-               base += size & 0xffff;
-               pci_write_config8(north, ramregs[2*slot], base);
-               /* side 1 */
-               base += size >> 16;
-               if (base > 0xff)
-                       base = 0xff;
-               pci_write_config8(north, ramregs[2*slot + 1], base);
-
-               if (!size)
-                       continue;
 
-               /* Calculate the value of MA mapping type register,
-                * based on size of SDRAM chips. */
-               size = (size & 0xffff) << (3 + 3);
-                       /* convert module size to be in Mbits */
-               size /= spd_num_chips(slot);
-               print_debug_hex16(size);
-               print_debug(" is the chip size\r\n");
-               if (size < 64)
-                       ma = 0;
-               if (size < 256)
-                       ma = 8;
-               else
-                       ma = 0xe;
-               print_debug_hex16(ma);
-               print_debug(" is the MA type\r\n");
-               set_ma_mapping(north, slot, ma);
+       /* 2. Precharge all. Wait tRP. */
+       PRINT_DEBUG("RAM Enable 2: Precharge all\n");
+       do_ram_command(RAM_COMMAND_PRECHARGE);
+       udelay(1);
+
+       /* 3. Perform 8 refresh cycles. Wait tRC each time. */
+       PRINT_DEBUG("RAM Enable 3: CBR\n");
+       for (i = 0; i < 8; i++) {
+               do_ram_command(RAM_COMMAND_CBR);
+               udelay(1);
        }
-       print_err("vt8601 done\r\n");
+
+       /* 4. Mode register set. Wait two memory cycles. */
+       PRINT_DEBUG("RAM Enable 4: Mode register set\n");
+       do_ram_command(RAM_COMMAND_MRS);
+       udelay(2);
+
+       /* 5. Normal operation. */
+       PRINT_DEBUG("RAM Enable 5: Normal operation\n");
+       do_ram_command(RAM_COMMAND_NORMAL);
+       udelay(1);
+
+       /* 6. Finally enable refresh. */
+       PRINT_DEBUG("RAM Enable 6: Enable refresh\n");
+       // pci_write_config8(NB, PMCR, 0x10);
+       spd_enable_refresh();
+       udelay(1);
+
+       PRINT_DEBUG("Northbridge following SDRAM init:\n");
+       DUMPNORTH();
 }