Add support for Intel Sandybridge CPU (northbridge part)
[coreboot.git] / src / northbridge / intel / i440bx / raminit.c
index c480f0a5b9726e683739f035f967b2d359ab572c..232b87249c02165ca8508825a755563718e4e22d 100644 (file)
@@ -1,7 +1,8 @@
 /*
  * This file is part of the coreboot project.
  *
- * Copyright (C) 2007 Uwe Hermann <uwe@hermann-uwe.de>
+ * 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
  */
 
 #include <spd.h>
-#include <sdram_mode.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"
 
 /*-----------------------------------------------------------------------------
 Macros and definitions.
 -----------------------------------------------------------------------------*/
 
-/* Uncomment this to enable debugging output. */
-#define DEBUG_RAM_SETUP 1
+#define NB PCI_DEV(0, 0, 0)
 
 /* Debugging macros. */
-#if defined(DEBUG_RAM_SETUP)
-#define PRINT_DEBUG(x)         print_debug(x)
-#define PRINT_DEBUG_HEX8(x)    print_debug_hex8(x)
-#define PRINT_DEBUG_HEX16(x)   print_debug_hex16(x)
-#define PRINT_DEBUG_HEX32(x)   print_debug_hex32(x)
-#define DUMPNORTH()            dump_pci_device(PCI_DEV(0, 0, 0))
+#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 PRINT_DEBUG_HEX8(x)
-#define PRINT_DEBUG_HEX16(x)
-#define PRINT_DEBUG_HEX32(x)
+#define PRINT_DEBUG(x...)
 #define DUMPNORTH()
 #endif
 
@@ -67,7 +67,7 @@ static const uint32_t refresh_rate_map[] = {
 };
 
 /* Table format: register, bitmask, value. */
-static const long register_values[] = {
+static const u8 register_values[] = {
        /* NBXCFG - NBX Configuration Register
         * 0x50 - 0x53
         *
@@ -128,9 +128,8 @@ static const long register_values[] = {
         *         0 = A7# is sampled asserted (i.e., 0)
         * [01:00] Reserved
         */
-       // TODO
        NBXCFG + 0, 0x00, 0x0c,
-       // NBXCFG + 1, 0x00, 0xa0,
+       // TODO: Bit 15 should be 0 for multiprocessor boards
        NBXCFG + 1, 0x00, 0x80,
        NBXCFG + 2, 0x00, 0x00,
        NBXCFG + 3, 0x00, 0xff,
@@ -140,7 +139,12 @@ static const long register_values[] = {
         *
         * [7:6] Reserved
         * [5:5] Module Mode Configuration (MMCONFIG)
-        *       TODO
+        *       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
@@ -185,20 +189,29 @@ static const long register_values[] = {
         * 10 = Write Only (Writes to DRAM, reads to memory mapped I/O space)
         * 11 = Read/Write (all access goes to DRAM)
         */
-       // TODO
-       PAM0, 0x00, 0x00,
-       PAM1, 0x00, 0x00,
-       PAM2, 0x00, 0x00,
-       PAM3, 0x00, 0x00,
-       PAM4, 0x00, 0x00,
-       PAM5, 0x00, 0x00,
-       PAM6, 0x00, 0x00,
+
+       /*
+        * 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.   
+        * 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)
@@ -240,12 +253,25 @@ static const long register_values[] = {
         * Sets the row page size for SDRAM. For EDO memory, the page
         * size is fixed at 2 KB.
         *
-        * [15:0] Page Size (PS)
-        *        TODO
+        * 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
         */
-       // TODO
-       RPS + 0, 0x00, 0x00,
-       RPS + 1, 0x00, 0x00,
+       /* Power on defaults to 2KB. Will be set later. */
+       // RPS + 0, 0x00, 0x00,
+       // RPS + 1, 0x00, 0x00,
 
        /* SDRAMC - SDRAM Control Register
         * 0x76 - 0x77
@@ -254,8 +280,7 @@ static const long register_values[] = {
         * [09:08] Idle/Pipeline DRAM Leadoff Timing (IPDLT)
         *         00 = Illegal
         *         01 = Add a clock delay to the lead-off clock count
-        *         10 = Illegal
-        *         11 = Illegal
+        *         1x = Illegal
         * [07:05] SDRAM Mode Select (SMS)
         *         000 = Normal SDRAM Operation (default)
         *         001 = NOP Command Enable
@@ -281,14 +306,20 @@ static const long register_values[] = {
         *         0 = 3 clocks of RAS# precharge
         *         1 = 2 clocks of RAS# precharge
         */
-       SDRAMC + 0, 0x00, 0x00,
-       SDRAMC + 0, 0x00, 0x00,
+#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)
-        *         TODO
+        *         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
@@ -304,7 +335,6 @@ static const long register_values[] = {
         *         0111 = 32 clocks
         *         1xxx = Infinite (pages are not closed for idle condition)
         */
-       // TODO
        PGPOL + 0, 0x00, 0x00,
        PGPOL + 1, 0x00, 0xff,
 
@@ -338,8 +368,10 @@ static const long register_values[] = {
        /* Enable normal refresh and the gated clock. */
        // TODO: Only do this later?
        // PMCR, 0x00, 0x14,
-       // PMCR, 0x00, 0x10,
        PMCR, 0x00, 0x00,
+
+       /* Enable SCRR.SRRAEN and let BX choose the SRR. */
+       SCRR + 1, 0x00, 0x10,
 };
 
 /*-----------------------------------------------------------------------------
@@ -349,173 +381,584 @@ SDRAM configuration functions.
 /**
  * Send the specified RAM command to all DIMMs.
  *
- * @param Memory controller
- * @param TODO
- * @param TODO
+ * @param command The RAM command to send to the DIMM(s).
  */
-static void do_ram_command(const struct mem_controller *ctrl,
-                          uint32_t command, uint32_t addr_offset)
+static void do_ram_command(u32 command)
 {
-       int i;
-       uint16_t reg;
-
-       /* TODO: Support for multiple DIMMs. */
+       int i, caslatency;
+       u8 dimm_start, dimm_end;
+       u16 reg16;
+       u32 addr, addr_offset;
 
        /* Configure the RAM command. */
-       reg = pci_read_config16(ctrl->d0, SDRAMC);
-       reg &= 0xff1f;          /* Clear bits 7-5. */
-       reg |= (uint16_t) (command << 5);
-       pci_write_config16(ctrl->d0, SDRAMC, reg);
-
-       /* RAM_COMMAND_NORMAL affects only the memory controller and
-          doesn't need to be "sent" to the DIMMs. */
-       /* if (command == RAM_COMMAND_NORMAL) return; */
-
-       PRINT_DEBUG("    Sending RAM command 0x");
-       PRINT_DEBUG_HEX16(reg);
-       PRINT_DEBUG(" to 0x");
-       PRINT_DEBUG_HEX32(0 + addr_offset); // FIXME
-       PRINT_DEBUG("\r\n");
-
-       /* Read from (DIMM start address + addr_offset). */
-       read32(0 + addr_offset); // FIXME
+       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;
+       }
+}
+
+static void set_dram_buffer_strength(void)
+{
+       /* 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;
+       }
+
+       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);
 }
 
 /*-----------------------------------------------------------------------------
 DIMM-independant configuration functions.
 -----------------------------------------------------------------------------*/
 
-/**
- * TODO.
- *
- * @param Memory controller
- */
-static void spd_enable_refresh(const struct mem_controller *ctrl)
+static void spd_enable_refresh(void)
 {
        int i, value;
        uint8_t reg;
 
-       reg = pci_read_config8(ctrl->d0, DRAMC);
+       reg = pci_read_config8(NB, DRAMC);
 
        for (i = 0; i < DIMM_SOCKETS; i++) {
-               value = spd_read_byte(ctrl->channel0[i], SPD_REFRESH);
+               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");
-               PRINT_DEBUG_HEX8(reg);
-               PRINT_DEBUG(") for DIMM ");
-               PRINT_DEBUG_HEX8(i);
-               PRINT_DEBUG("\r\n");
+               PRINT_DEBUG("    Enabling refresh (DRAMC = 0x%02x) for DIMM %02x\n", reg, i);
        }
 
-       pci_write_config8(ctrl->d0, DRAMC, reg);
+       pci_write_config8(NB, DRAMC, reg);
 }
 
 /*-----------------------------------------------------------------------------
 Public interface.
 -----------------------------------------------------------------------------*/
 
-/**
- * TODO.
- *
- * @param Memory controller
- */
-static void sdram_set_registers(const struct mem_controller *ctrl)
+void sdram_set_registers(void)
 {
        int i, max;
        uint8_t reg;
 
-       PRINT_DEBUG("Northbridge prior to SDRAM init:\r\n");
+       PRINT_DEBUG("Northbridge prior to SDRAM init:\n");
        DUMPNORTH();
 
-       max = sizeof(register_values) / sizeof(register_values[0]);
+       max = ARRAY_SIZE(register_values);
 
        /* Set registers as specified in the register_values[] array. */
        for (i = 0; i < max; i += 3) {
-               reg = pci_read_config8(ctrl->d0, register_values[i]);
+               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(ctrl->d0, register_values[i], reg);
-
-               PRINT_DEBUG("    Set register 0x");
-               PRINT_DEBUG_HEX8(register_values[i]);
-               PRINT_DEBUG(" to 0x");
-               PRINT_DEBUG_HEX8(reg);
-               PRINT_DEBUG("\r\n");
+               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
        }
 }
 
-/**
- * TODO.
- *
- * @param Memory controller
- */
-static void sdram_set_spd_registers(const struct mem_controller *ctrl)
+struct dimm_size {
+       u32 side1;
+       u32 side2;
+};
+
+static struct dimm_size spd_get_dimm_size(unsigned int device)
 {
-       /* TODO: Don't hardcode the values here, get info via SPD. */
+       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;
+               }
+       }
 
-       /* 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.
+       /* 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.
+        */
+       sz.side1 *= 4;
+       sz.side2 *= 4;
+
+       /* It is possible to partially use larger then supported
+        * modules by setting them to a supported size.
         */
-       pci_write_config8(ctrl->d0, PAM0, 0x30);
-       pci_write_config8(ctrl->d0, PAM1, 0x33);
-       pci_write_config8(ctrl->d0, PAM2, 0x33);
-       pci_write_config8(ctrl->d0, PAM3, 0x33);
-       pci_write_config8(ctrl->d0, PAM4, 0x33);
-       pci_write_config8(ctrl->d0, PAM5, 0x33);
-       pci_write_config8(ctrl->d0, PAM6, 0x33);
-
-       /* TODO: Set DRB0-DRB7. */
-       /* Currently this is hardcoded to one 64 MB DIMM in slot 0. */
-       pci_write_config8(ctrl->d0, DRB0, 0x08);
-       pci_write_config8(ctrl->d0, DRB1, 0x08);
-       pci_write_config8(ctrl->d0, DRB2, 0x08);
-       pci_write_config8(ctrl->d0, DRB3, 0x08);
-       pci_write_config8(ctrl->d0, DRB4, 0x08);
-       pci_write_config8(ctrl->d0, DRB5, 0x08);
-       pci_write_config8(ctrl->d0, DRB6, 0x08);
-       pci_write_config8(ctrl->d0, DRB7, 0x08);
-
-       /* TODO: Set DRAMC. Don't enable refresh for now. */
-       pci_write_config8(ctrl->d0, DRAMC, 0x08);
-
-       /* TODO: Set RPS. */
-       pci_write_config16(ctrl->d0, RPS, 0x0001);
-
-       /* TODO: Set SDRAMC. */
-       // pci_write_config16(ctrl->d0, SDRAMC, 0x010f); // FIXME?
-       pci_write_config16(ctrl->d0, SDRAMC, 0x0003); // FIXME?
-
-       /* TODO: Set PGPOL. */
-       // pci_write_config16(ctrl->d0, PGPOL, 0x0107);
-       pci_write_config16(ctrl->d0, PGPOL, 0x0123);
-
-       /* TODO: Set NBXCFG. */
-       // pci_write_config32(ctrl->d0, NBXCFG, 0x0100220c); // FIXME?
-       pci_write_config32(ctrl->d0, NBXCFG, 0xff00800c);
+       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;
+               }
+       }
+
+       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 */
+
+       edo = 0;
+       sd = 0;
+       regsd = 1;
+       rps = 0;
+       drb = 0;
+       bpr = 0;
+
+       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);
+}
+
+void sdram_set_spd_registers(void)
+{
+       /* 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(ctrl->d0, PMCR, 0x14);
-       pci_write_config8(ctrl->d0, PMCR, 0x10);
-
-       /* TODO? */
-       pci_write_config8(ctrl->d0, PCI_LATENCY_TIMER, 0x40);
-       pci_write_config8(ctrl->d0, DRAMT, 0x03);
-       pci_write_config8(ctrl->d0, MBSC, 0x03);
-       pci_write_config8(ctrl->d0, SCRR, 0x38);
+       // 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);
 }
 
-/**
- * Enable SDRAM.
- *
- * @param Number of controllers
- * @param Memory controller
- */
-static void sdram_enable(int controllers, const struct mem_controller *ctrl)
+void sdram_enable(void)
 {
        int i;
 
@@ -523,38 +966,38 @@ static void sdram_enable(int controllers, const struct mem_controller *ctrl)
        udelay(200);
 
        /* 1. Apply NOP. Wait 200 clock cycles (200us should do). */
-       PRINT_DEBUG("RAM Enable 1: Apply NOP\r\n");
-       do_ram_command(ctrl, RAM_COMMAND_NOP, 0);
+       PRINT_DEBUG("RAM Enable 1: Apply NOP\n");
+       do_ram_command(RAM_COMMAND_NOP);
        udelay(200);
 
        /* 2. Precharge all. Wait tRP. */
-       PRINT_DEBUG("RAM Enable 2: Precharge all\r\n");
-       do_ram_command(ctrl, RAM_COMMAND_PRECHARGE, 0);
+       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\r\n");
+       PRINT_DEBUG("RAM Enable 3: CBR\n");
        for (i = 0; i < 8; i++) {
-               do_ram_command(ctrl, RAM_COMMAND_CBR, 0);
+               do_ram_command(RAM_COMMAND_CBR);
                udelay(1);
        }
 
        /* 4. Mode register set. Wait two memory cycles. */
-       PRINT_DEBUG("RAM Enable 4: Mode register set\r\n");
-       do_ram_command(ctrl, RAM_COMMAND_MRS, 0x1d0);
+       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\r\n");
-       do_ram_command(ctrl, RAM_COMMAND_NORMAL, 0);
+       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\r\n");
-       // pci_write_config8(ctrl->d0, PMCR, 0x10);
-       spd_enable_refresh(ctrl);
+       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:\r\n");
+       PRINT_DEBUG("Northbridge following SDRAM init:\n");
        DUMPNORTH();
 }