[coreboot-gerrit] New patch to review for coreboot: cf12d9b sandy/ivybridge: Native raminit.

Vladimir Serbinenko (phcoder@gmail.com) gerrit at coreboot.org
Tue Jul 29 00:30:31 CEST 2014


Vladimir Serbinenko (phcoder at gmail.com) just uploaded a new patch set to gerrit, which you can find at http://review.coreboot.org/6391

-gerrit

commit cf12d9bcf002e118460edbbb8dd928e0af1dd026
Author: Vladimir Serbinenko <phcoder at gmail.com>
Date:   Sun May 18 11:05:56 2014 +0200

    sandy/ivybridge: Native raminit.
    
    Based on damo22's work and my X230 tracing.
    
    Works for my X230 in a variety of RAM configs.
    
    Change-Id: Ia435716ec1fc873e563070ac9af3bac4e8db1a96
    Also-By: Damien Zammit <damien at zamaudio.com>
    Signed-off-by: Vladimir Serbinenko <phcoder at gmail.com>
    Signed-off-by: Damien Zammit <damien at zamaudio.com>
---
 src/cpu/intel/Makefile.inc                         |    1 +
 src/cpu/x86/smm/smmhandler_tseg.S                  |    2 +-
 src/cpu/x86/smm/smmrelocate.S                      |    4 +-
 src/device/dram/ddr3.c                             |   11 +-
 src/include/device/dram/ddr3.h                     |    5 +
 src/mainboard/lenovo/x230/Kconfig                  |   12 +-
 src/mainboard/lenovo/x230/romstage.c               |  135 +-
 src/northbridge/intel/Makefile.inc                 |    1 +
 src/northbridge/intel/sandybridge/Kconfig          |   13 +-
 src/northbridge/intel/sandybridge/Makefile.inc     |   10 +-
 src/northbridge/intel/sandybridge/gma.c            |    4 +-
 src/northbridge/intel/sandybridge/raminit_native.c | 3893 ++++++++++++++++++++
 src/northbridge/intel/sandybridge/raminit_native.h |   29 +
 .../intel/sandybridge/raminit_patterns.h           |  639 ++++
 src/southbridge/intel/bd82x6x/Makefile.inc         |    6 +-
 src/southbridge/intel/bd82x6x/early_me_native.c    |  272 ++
 src/southbridge/intel/bd82x6x/early_pch_native.c   |  375 ++
 src/southbridge/intel/bd82x6x/early_thermal.c      |   70 +
 src/southbridge/intel/bd82x6x/pch.h                |    2 +
 src/southbridge/intel/bd82x6x/smi.c                |    3 -
 src/southbridge/intel/bd82x6x/usb_ehci.c           |   33 +
 21 files changed, 5411 insertions(+), 109 deletions(-)

diff --git a/src/cpu/intel/Makefile.inc b/src/cpu/intel/Makefile.inc
index 0392f69..68ea05d 100644
--- a/src/cpu/intel/Makefile.inc
+++ b/src/cpu/intel/Makefile.inc
@@ -19,6 +19,7 @@ subdirs-$(CONFIG_CPU_INTEL_SOCKET_RPGA989) += socket_rPGA989
 subdirs-$(CONFIG_NORTHBRIDGE_INTEL_NEHALEM) += model_2065x
 subdirs-$(CONFIG_NORTHBRIDGE_INTEL_SANDYBRIDGE) += model_206ax
 subdirs-$(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE) += model_206ax
+subdirs-$(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE) += model_206ax
 subdirs-$(CONFIG_NORTHBRIDGE_INTEL_HASWELL) += haswell
 subdirs-$(CONFIG_NORTHBRIDGE_INTEL_FSP_SANDYBRIDGE) += fsp_model_206ax
 subdirs-$(CONFIG_NORTHBRIDGE_INTEL_FSP_IVYBRIDGE) += fsp_model_206ax
diff --git a/src/cpu/x86/smm/smmhandler_tseg.S b/src/cpu/x86/smm/smmhandler_tseg.S
index b33fcdf..380935a 100644
--- a/src/cpu/x86/smm/smmhandler_tseg.S
+++ b/src/cpu/x86/smm/smmhandler_tseg.S
@@ -57,7 +57,7 @@
 #define SMI_UNLOCKED	1
 
 #define __PRE_RAM__
-#if CONFIG_NORTHBRIDGE_INTEL_SANDYBRIDGE || CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE
+#if CONFIG_NORTHBRIDGE_INTEL_SANDYBRIDGE || CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE || CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE
 #include <northbridge/intel/sandybridge/sandybridge.h>
 #define TSEG_BAR (DEFAULT_PCIEXBAR | TSEG)
 #elif CONFIG_NORTHBRIDGE_INTEL_NEHALEM
diff --git a/src/cpu/x86/smm/smmrelocate.S b/src/cpu/x86/smm/smmrelocate.S
index bdc9771..bc90fab 100644
--- a/src/cpu/x86/smm/smmrelocate.S
+++ b/src/cpu/x86/smm/smmrelocate.S
@@ -48,7 +48,7 @@
 
 #if CONFIG_SMM_TSEG
 
-#if CONFIG_NORTHBRIDGE_INTEL_SANDYBRIDGE || CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE
+#if CONFIG_NORTHBRIDGE_INTEL_SANDYBRIDGE || CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE || CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE
 #include <northbridge/intel/sandybridge/sandybridge.h>
 #define TSEG_BAR (DEFAULT_PCIEXBAR | TSEG)
 #elif CONFIG_NORTHBRIDGE_INTEL_NEHALEM
@@ -195,7 +195,7 @@ smm_relocate:
 	xorl	%edx, %edx
 	wrmsr
 
-#if CONFIG_NORTHBRIDGE_INTEL_SANDYBRIDGE || CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE
+#if CONFIG_NORTHBRIDGE_INTEL_SANDYBRIDGE || CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE || CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE
 	/*
 	 * IED base is top 4M of TSEG
 	 */
diff --git a/src/device/dram/ddr3.c b/src/device/dram/ddr3.c
index 9b4f490..a43caed 100644
--- a/src/device/dram/ddr3.c
+++ b/src/device/dram/ddr3.c
@@ -110,7 +110,7 @@ int spd_decode_ddr3(dimm_attr * dimm, spd_raw_data spd)
 {
 	int ret;
 	u16 crc, spd_crc;
-	u8 ftb_divisor, ftb_dividend, capacity_shift, bus_width, sdram_width;
+	u8 ftb_divisor, ftb_dividend, capacity_shift, bus_width;
 	u8 reg8;
 	u32 mtb;		/* medium time base */
 	unsigned int val, param;
@@ -209,8 +209,8 @@ int spd_decode_ddr3(dimm_attr * dimm, spd_raw_data spd)
 		printram("  Invalid SDRAM width\n");
 		ret = SPD_STATUS_INVALID_FIELD;
 	}
-	sdram_width = (4 << val);
-	printram("  SDRAM width       : %u\n", sdram_width);
+	dimm->width = (4 << val);
+	printram("  SDRAM width       : %u\n", dimm->width);
 
 	/* Memory bus width */
 	reg8 = spd[8];
@@ -236,7 +236,7 @@ int spd_decode_ddr3(dimm_attr * dimm, spd_raw_data spd)
 	 * capacity_shift
 	 * The rest is the JEDEC formula */
 	dimm->size_mb = ((1 << (capacity_shift + (25 - 20))) * bus_width
-			 * dimm->ranks) / sdram_width;
+			 * dimm->ranks) / dimm->width;
 
 	/* Fine Timebase (FTB) Dividend/Divisor */
 	/* Dividend */
@@ -334,6 +334,9 @@ int spd_decode_ddr3(dimm_attr * dimm, spd_raw_data spd)
 		printram("  DIMM Rank1 Address bits mirrored!!!\n");
 	}
 
+	dimm->reference_card = spd[62] & 0x1f;
+	printram("  DIMM Reference card %c\n", 'A' + dimm->reference_card);
+
 	return ret;
 }
 
diff --git a/src/include/device/dram/ddr3.h b/src/include/device/dram/ddr3.h
index b19c51c..ef0d0ec 100644
--- a/src/include/device/dram/ddr3.h
+++ b/src/include/device/dram/ddr3.h
@@ -37,6 +37,7 @@
  * @{
  */
 #define TCK_1066MHZ     240
+#define TCK_933MHZ	275
 #define TCK_800MHZ      320
 #define TCK_666MHZ      384
 #define TCK_533MHZ      480
@@ -137,6 +138,8 @@ typedef struct dimm_attr_st {
 	u16 cas_supported;
 	/* Flags extracted from SPD */
 	dimm_flags_t flags;
+	/* SDRAM width */
+	u8 width;
 	/* Number of ranks */
 	u8 ranks;
 	/* Number or row address bits */
@@ -158,6 +161,8 @@ typedef struct dimm_attr_st {
 	u32 tWTR;
 	u32 tRTP;
 	u32 tFAW;
+
+	u8 reference_card;
 } dimm_attr;
 
 /** Result of the SPD decoding process */
diff --git a/src/mainboard/lenovo/x230/Kconfig b/src/mainboard/lenovo/x230/Kconfig
index eb9cadc..cc6228d 100644
--- a/src/mainboard/lenovo/x230/Kconfig
+++ b/src/mainboard/lenovo/x230/Kconfig
@@ -3,7 +3,7 @@ if BOARD_LENOVO_X230
 config BOARD_SPECIFIC_OPTIONS # dummy
 	def_bool y
 	select CPU_INTEL_SOCKET_RPGA989
-	select NORTHBRIDGE_INTEL_IVYBRIDGE
+	select NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE
 	select SOUTHBRIDGE_INTEL_C216
 	select EC_LENOVO_PMH7
 	select EC_LENOVO_H8
@@ -18,8 +18,8 @@ config BOARD_SPECIFIC_OPTIONS # dummy
 	select SERIRQ_CONTINUOUS_MODE
 
 config HAVE_MRC_CACHE
-       bool
-       default n
+	bool
+	default y
 
 config HAVE_IFD_BIN
 	bool
@@ -41,10 +41,6 @@ config MMCONF_BASE_ADDRESS
 	hex
 	default 0xf0000000
 
-config CACHE_ROM_SIZE_OVERRIDE
-	hex
-	default 0x800000
-
 config IRQ_SLOT_COUNT
 	int
 	default 18
@@ -57,7 +53,7 @@ config USBDEBUG_HCD_INDEX
        int
        default 2
 
-config DRAM_GATE_GPIO
+config DRAM_RESET_GATE_GPIO
 	int
 	default 10
 
diff --git a/src/mainboard/lenovo/x230/romstage.c b/src/mainboard/lenovo/x230/romstage.c
index 8441db3..8c82e61 100644
--- a/src/mainboard/lenovo/x230/romstage.c
+++ b/src/mainboard/lenovo/x230/romstage.c
@@ -33,7 +33,7 @@
 #include <cbmem.h>
 #include <console/console.h>
 #include "northbridge/intel/sandybridge/sandybridge.h"
-#include "northbridge/intel/sandybridge/raminit.h"
+#include "northbridge/intel/sandybridge/raminit_native.h"
 #include "southbridge/intel/bd82x6x/pch.h"
 #include "southbridge/intel/bd82x6x/gpio.h"
 #include <arch/cpu.h>
@@ -109,66 +109,58 @@ static void rcba_config(void)
 	RCBA32(BUC) = 0;
 }
 
+static void
+init_usb (void)
+{
+	const u32 rcba_dump[64] = {
+		/* 3500 */ 0x20000153, 0x20000153, 0x20000f57, 0x20000f57,
+		/* 3510 */ 0x20000f57, 0x20000f57, 0x20000153, 0x2000055b,
+		/* 3520 */ 0x20000153, 0x2000055b, 0x20000f57, 0x20000f57,
+		/* 3530 */ 0x20000f57, 0x20000f57, 0x00000000, 0x00000000,
+		/* 3540 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+		/* 3550 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+		/* 3560 */ 0x024c8001, 0x000024a3, 0x00040002, 0x01000050,
+		/* 3570 */ 0x02000772, 0x16000f9f, 0x1800ff4f, 0x0001d630,
+		/* 3580 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+		/* 3590 */ 0x00000000, 0x00000000, 0x00000000, 0x00000040,
+		/* 35a0 */ 0x04000201, 0x00000200, 0x00000000, 0x00000000,
+		/* 35b0 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+		/* 35c0 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+		/* 35d0 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+		/* 35e0 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+		/* 35f0 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	};
+	int i;
+	/* Activate PMBAR.  */
+	pci_write_config32(PCI_DEV(0, 0x1f, 0), PMBASE, DEFAULT_PMBASE | 1);
+	pci_write_config32(PCI_DEV(0, 0x1f, 0), PMBASE + 4, 0);
+	pci_write_config8(PCI_DEV(0, 0x1f, 0), 0x44 /* ACPI_CNTL */ , 0x80); /* Enable ACPI BAR */
+
+	/* Unlock registers.  */
+	outw (inw (DEFAULT_PMBASE | 0x003c) | 2, DEFAULT_PMBASE | 0x003c);
+
+	for (i = 0; i < 64; i++)
+		write32 (DEFAULT_RCBABASE | (0x3500 + 4 * i), rcba_dump[i]);
+
+	pcie_write_config32 (PCI_DEV (0, 0x14, 0), 0xe4, 0x00000000);
+
+	/* Relock registers.  */
+	outw (0x0000, DEFAULT_PMBASE | 0x003c);
+}
+
+
 void main(unsigned long bist)
 {
-	int boot_mode = 0;
-	int cbmem_was_initted;
+	int s3resume = 0;
 	u32 pm1_cnt;
 	u16 pm1_sts;
+	spd_raw_data spd[4];
 
 	if (MCHBAR16(SSKPD) == 0xCAFE) {
 		outb(0x6, 0xcf9);
 		hlt ();
 	}
 
-	struct pei_data pei_data = {
-		.pei_version = PEI_VERSION,
-		.mchbar = DEFAULT_MCHBAR,
-		.dmibar = DEFAULT_DMIBAR,
-		.epbar = DEFAULT_EPBAR,
-		.pciexbar = CONFIG_MMCONF_BASE_ADDRESS,
-		.smbusbar = SMBUS_IO_BASE,
-		.wdbbar = 0x4000000,
-		.wdbsize = 0x1000,
-		.hpet_address = CONFIG_HPET_ADDRESS,
-		.rcba = DEFAULT_RCBABASE,
-		.pmbase = DEFAULT_PMBASE,
-		.gpiobase = DEFAULT_GPIOBASE,
-		.thermalbase = 0xfed08000,
-		.system_type = 0, // 0 Mobile, 1 Desktop/Server
-		.tseg_size = CONFIG_SMM_TSEG_SIZE,
-		.spd_addresses = { 0xA0, 0x00,0xA2,0x00 },
-		.ts_addresses = { 0x00, 0x00, 0x00, 0x00 },
-		.ec_present = 1,
-		.gbe_enable = 1,
-		.ddr3lv_support = 0,
-		// 0 = leave channel enabled
-		// 1 = disable dimm 0 on channel
-		// 2 = disable dimm 1 on channel
-		// 3 = disable dimm 0+1 on channel
-		.dimm_channel0_disabled = 2,
-		.dimm_channel1_disabled = 2,
-		.max_ddr3_freq = 1600,
-		.usb_port_config = {
-			 /* enabled   usb oc pin    length */
-			{ 1, 0, 0x0080 }, /* P0 (left, fan side), OC 0 */
-			{ 1, 1, 0x0080 }, /* P1 (left touchpad side), OC 1 */
-			{ 1, 3, 0x0080 }, /* P2: dock, OC 3 */
-			{ 1, 0, 0x0040 }, /* P3: wwan, no OC */
-			{ 1, 0, 0x0080 }, /* P4: Wacom tablet on X230t, otherwise empty */
-			{ 1, 0, 0x0080 }, /* P5: Expresscard, no OC */
-			{ 0, 0, 0x0000 }, /* P6: Empty */
-			{ 1, 0, 0x0080 }, /* P7: dock, no OC */
-			{ 0, 0, 0x0000 }, /* P8: Empty */
-			{ 1, 5, 0x0080 }, /* P9: Right (EHCI debug), OC 5 */
-			{ 1, 0, 0x0040 }, /* P10: fingerprint reader, no OC */
-			{ 1, 0, 0x0040 }, /* P11: bluetooth, no OC. */
-			{ 1, 0, 0x0040 }, /* P12: wlan, no OC */
-			{ 1, 0, 0x0080 }, /* P13: webcam, no OC */
-		},
-		.ddr_refresh_rate_config = 2, /* Force double refresh rate */
-	};
-
 	timestamp_init(get_initial_timestamp());
 	timestamp_add_now(TS_START_ROMSTAGE);
 
@@ -183,6 +175,8 @@ void main(unsigned long bist)
 
 	setup_pch_gpios(&x230_gpio_map);
 
+	init_usb();
+
 	/* Initialize console device(s) */
 	console_init();
 
@@ -204,7 +198,7 @@ void main(unsigned long bist)
 	if ((pm1_sts & WAK_STS) && ((pm1_cnt >> 10) & 7) == 5) {
 		if (acpi_s3_resume_allowed()) {
 			printk(BIOS_DEBUG, "Resume from S3 detected.\n");
-			boot_mode = 2;
+			s3resume = 1;
 			/* Clear SLP_TYPE. This will break stage2 but
 			 * we care for that when we get there.
 			 */
@@ -218,31 +212,16 @@ void main(unsigned long bist)
 	/* Enable SPD ROMs and DDR-III DRAM */
 	enable_smbus();
 
-	/* Prepare USB controller early in S3 resume */
-	if (boot_mode == 2)
-		enable_usb_bar();
-
 	post_code(0x39);
 
 	post_code(0x3a);
-	pei_data.boot_mode = boot_mode;
 	timestamp_add_now(TS_BEFORE_INITRAM);
 
-	/* MRC.bin has a bug and sometimes halts (instead of reboot?).
-	 */
-	if (boot_mode != 2)
-	  {
-		  RCBA32(GCS) = RCBA32(GCS) & ~(1 << 5);	/* reset */
-		  outw((0 << 11), DEFAULT_PMBASE | 0x60 | 0x08);	/* let timer go */
-	  }
-
-	sdram_initialize(&pei_data);
+	memset (spd, 0, sizeof (spd));
+	read_spd (&spd[0], 0x50);
+	read_spd (&spd[2], 0x51);
 
-	if (boot_mode != 2)
-	  {
-		  RCBA32(GCS) = RCBA32(GCS) | (1 << 5);	/* No reset */
-		  outw((1 << 11), DEFAULT_PMBASE | 0x60 | 0x08);	/* halt timer */
-	  }
+	init_dram_ddr3 (spd, 1, TCK_800MHZ, s3resume);
 
 	timestamp_add_now(TS_AFTER_INITRAM);
 	post_code(0x3c);
@@ -250,13 +229,7 @@ void main(unsigned long bist)
 	rcba_config();
 	post_code(0x3d);
 
-	quick_ram_check();
-	post_code(0x3e);
-
 	MCHBAR16(SSKPD) = 0xCAFE;
-	cbmem_was_initted = !cbmem_recovery(boot_mode==2);
-	if (boot_mode!=2)
-		save_mrc_data(&pei_data);
 
 #if CONFIG_HAVE_ACPI_RESUME
 	/* If there is no high memory area, we didn't boot before, so
@@ -266,18 +239,14 @@ void main(unsigned long bist)
 	*(u32 *)CBMEM_BOOT_MODE = 0;
 	*(u32 *)CBMEM_RESUME_BACKUP = 0;
 
-	if ((boot_mode == 2) && cbmem_was_initted) {
+	if (s3resume) {
 		void *resume_backup_memory = cbmem_find(CBMEM_ID_RESUME);
 		if (resume_backup_memory) {
-			*(u32 *)CBMEM_BOOT_MODE = boot_mode;
+			*(u32 *)CBMEM_BOOT_MODE = 2;
 			*(u32 *)CBMEM_RESUME_BACKUP = (u32)resume_backup_memory;
 		}
 		/* Magic for S3 resume */
 		pci_write_config32(PCI_DEV(0, 0x00, 0), SKPAD, 0xcafed00d);
-	} else if (boot_mode == 2) {
-		/* Failed S3 resume, reset to come up cleanly */
-		outb(0x6, 0xcf9);
-		hlt();
 	} else {
 		pci_write_config32(PCI_DEV(0, 0x00, 0), SKPAD, 0xcafebabe);
 	}
diff --git a/src/northbridge/intel/Makefile.inc b/src/northbridge/intel/Makefile.inc
index 808a1b2..f1c2540 100644
--- a/src/northbridge/intel/Makefile.inc
+++ b/src/northbridge/intel/Makefile.inc
@@ -15,6 +15,7 @@ subdirs-$(CONFIG_NORTHBRIDGE_INTEL_I5000) += i5000
 subdirs-$(CONFIG_NORTHBRIDGE_INTEL_NEHALEM) += nehalem
 subdirs-$(CONFIG_NORTHBRIDGE_INTEL_SANDYBRIDGE) += sandybridge
 subdirs-$(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE) += sandybridge
+subdirs-$(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE) += sandybridge
 subdirs-$(CONFIG_NORTHBRIDGE_INTEL_HASWELL) += haswell
 subdirs-$(CONFIG_NORTHBRIDGE_INTEL_FSP_SANDYBRIDGE) += fsp_sandybridge
 subdirs-$(CONFIG_NORTHBRIDGE_INTEL_FSP_IVYBRIDGE) += fsp_sandybridge
diff --git a/src/northbridge/intel/sandybridge/Kconfig b/src/northbridge/intel/sandybridge/Kconfig
index f13b58d..2ceb945 100644
--- a/src/northbridge/intel/sandybridge/Kconfig
+++ b/src/northbridge/intel/sandybridge/Kconfig
@@ -33,7 +33,15 @@ config NORTHBRIDGE_INTEL_IVYBRIDGE
 	select DYNAMIC_CBMEM
 	select CPU_INTEL_MODEL_306AX
 
-if NORTHBRIDGE_INTEL_SANDYBRIDGE || NORTHBRIDGE_INTEL_IVYBRIDGE
+config NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE
+	bool
+	select CACHE_MRC_BIN
+	select MMCONF_SUPPORT
+	select MMCONF_SUPPORT_DEFAULT
+	select CPU_INTEL_MODEL_306AX
+	select HAVE_DEBUG_RAM_SETUP
+
+if NORTHBRIDGE_INTEL_SANDYBRIDGE || NORTHBRIDGE_INTEL_IVYBRIDGE || NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE
 
 config VGA_BIOS_ID
 	string
@@ -50,7 +58,8 @@ config MRC_CACHE_SIZE
 
 config DCACHE_RAM_BASE
 	hex
-	default 0xff7e0000
+	default 0xff7e0000 if !NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE
+	default 0xfefe0000 if NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE
 
 config DCACHE_RAM_SIZE
 	hex
diff --git a/src/northbridge/intel/sandybridge/Makefile.inc b/src/northbridge/intel/sandybridge/Makefile.inc
index a201745..536656b 100644
--- a/src/northbridge/intel/sandybridge/Makefile.inc
+++ b/src/northbridge/intel/sandybridge/Makefile.inc
@@ -25,7 +25,10 @@ ramstage-$(CONFIG_GENERATE_ACPI_TABLES) += acpi.c
 ramstage-y += mrccache.c
 
 romstage-y += ram_calc.c
-romstage-y += raminit.c
+romstage-$(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE) += raminit.c
+romstage-$(CONFIG_NORTHBRIDGE_INTEL_SANDYBRIDGE) += raminit.c
+romstage-$(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE) += raminit_native.c
+romstage-$(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE) += ../../../device/dram/ddr3.c
 romstage-y += mrccache.c
 romstage-y += early_init.c
 romstage-y += report_platform.c
@@ -48,7 +51,10 @@ $(obj)/mrc.cache: $(obj)/config.h
 
 cbfs-files-$(CONFIG_HAVE_MRC_CACHE) += mrc.cache
 mrc.cache-file := $(obj)/mrc.cache
-mrc.cache-position := 0xfffd0000
+mrc-cache-position-$(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE) := 0xfffd0000
+mrc-cache-position-$(CONFIG_NORTHBRIDGE_INTEL_SANDYBRIDGE) := 0xfffd0000
+mrc-cache-position-$(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE) := 0xfffe0000
+mrc.cache-position := $(mrc-cache-position-y)
 mrc.cache-type := 0xac
 endif
 
diff --git a/src/northbridge/intel/sandybridge/gma.c b/src/northbridge/intel/sandybridge/gma.c
index d271a1a..a82a284 100644
--- a/src/northbridge/intel/sandybridge/gma.c
+++ b/src/northbridge/intel/sandybridge/gma.c
@@ -650,9 +650,7 @@ static void gma_func0_init(struct device *dev)
 	physbase = pci_read_config32(dev, 0x5c) & ~0xf;
 	graphics_base = dev->resource_list[1].base;
 
-	int lightup_ok = i915lightup(conf, physbase, iobase, mmiobase, graphics_base);
-	if (lightup_ok)
-		gfx_set_init_done(1);
+	i915lightup(conf, physbase, iobase, mmiobase, graphics_base);
 #endif
 }
 
diff --git a/src/northbridge/intel/sandybridge/raminit_native.c b/src/northbridge/intel/sandybridge/raminit_native.c
new file mode 100644
index 0000000..b1e29a2
--- /dev/null
+++ b/src/northbridge/intel/sandybridge/raminit_native.c
@@ -0,0 +1,3893 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2014 Damien Zammit <damien at zamaudio.com>
+ * Copyright (C) 2014 Vladimir Serbinenko <phcoder at 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; 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 <console/console.h>
+#include <console/usb.h>
+#include <bootmode.h>
+#include <string.h>
+#include <arch/hlt.h>
+#include <arch/io.h>
+#include <cbmem.h>
+#include <arch/cbfs.h>
+#include <cbfs.h>
+#include <ip_checksum.h>
+#include <pc80/mc146818rtc.h>
+#include <device/pci_def.h>
+#include "raminit_native.h"
+#include "sandybridge.h"
+#include <delay.h>
+#include <lib.h>
+
+/* Management Engine is in the southbridge */
+#include "southbridge/intel/bd82x6x/me.h"
+/* For SPD.  */
+#include "southbridge/intel/bd82x6x/smbus.h"
+#include "arch/cpu.h"
+#include "cpu/x86/msr.h"
+
+/* FIXME: no ECC support.  */
+/* FIXME: no support for 3-channel chipsets.  */
+/* FIXME: no S3.  */
+
+#define BASEFREQ 133
+#define tDLLK 512
+
+#define IS_SANDY_CPU(x) ((x & 0xffff0) == 0x206a0)
+#define IS_SANDY_CPU_C(x) ((x & 0xf) == 4)
+#define IS_SANDY_CPU_D0(x) ((x & 0xf) == 5)
+#define IS_SANDY_CPU_D1(x) ((x & 0xf) == 6)
+#define IS_SANDY_CPU_D2(x) ((x & 0xf) == 7)
+
+#define IS_IVY_CPU(x) ((x & 0xffff0) == 0x306a0)
+#define IS_IVY_CPU_C(x) ((x & 0xf) == 4)
+#define IS_IVY_CPU_K(x) ((x & 0xf) == 5)
+#define IS_IVY_CPU_D(x) ((x & 0xf) == 6)
+#define IS_IVY_CPU_E(x) ((x & 0xf) >= 8)
+
+#define NUM_CHANNELS 2
+#define NUM_SLOTRANKS 4
+#define NUM_SLOTS 2
+#define NUM_LANES 8
+
+/* FIXME: Vendor BIOS uses 64 but our algorithms are less
+   performant and even 1 seems to be enough in practice.  */
+#define NUM_PATTERNS 4
+
+typedef struct odtmap_st {
+	u16 rttwr;
+	u16 rttnom;
+} odtmap;
+
+typedef struct dimm_info_st {
+	dimm_attr dimm[NUM_CHANNELS][NUM_SLOTS];
+} dimm_info;
+
+struct ram_rank_timings {
+	/* Register 4024. One byte per slotrank.  */
+	u8 val_4024;
+	/* Register 4028. One nibble per slotrank.  */
+	u8 val_4028;
+
+	int val_320c;
+
+	struct ram_lane_timings {
+		/* lane register offset 0x10.  */
+		u16 timA;	/* bits 0 - 5, bits 16 - 18 */
+		u8 rising;	/* bits 8 - 14 */
+		u8 falling;	/* bits 20 - 26.  */
+
+		/* lane register offset 0x20.  */
+		int timC;	/* bit 0 - 5, 19.  */
+		u16 timB;	/* bits 8 - 13, 15 - 17.  */
+	} lanes[NUM_LANES];
+};
+
+struct ramctr_timing_st;
+
+typedef struct ramctr_timing_st {
+	int mobile;
+
+	u16 cas_supported;
+	/* tLatencies are in units of ns, scaled by x256 */
+	u32 tCK;
+	u32 tAA;
+	u32 tWR;
+	u32 tRCD;
+	u32 tRRD;
+	u32 tRP;
+	u32 tRAS;
+	u32 tRFC;
+	u32 tWTR;
+	u32 tRTP;
+	u32 tFAW;
+	/* Latencies in terms of clock cycles
+	 * They are saved separately as they are needed for DRAM MRS commands*/
+	u8 CAS;			/* CAS read latency */
+	u8 CWL;			/* CAS write latency */
+
+	u32 tREFI;
+	u32 tMOD;
+	u32 tXSOffset;
+	u32 tWLO;
+	u32 tCKE;
+	u32 tXPDLL;
+	u32 tXP;
+	u32 tAONPD;
+
+	u16 reg_5064b0; /* bits 0-11. */
+
+	u8 rankmap[NUM_CHANNELS];
+	int ref_card_offset[NUM_CHANNELS];
+	u32 mad_dimm[NUM_CHANNELS];
+	int channel_size_mb[NUM_CHANNELS];
+	u32 reg_4004_b30[NUM_CHANNELS];
+
+	int reg_c14_offset;
+	int reg_320c_range_threshold;
+
+	int edge_offset[3];
+	int timC_offset[3];
+
+	int extended_temperature_range;
+	int auto_self_refresh;
+
+	int rank_mirror[NUM_CHANNELS][NUM_SLOTRANKS];
+
+	struct ram_rank_timings timings[NUM_CHANNELS][NUM_SLOTRANKS];
+} ramctr_timing;
+
+#define SOUTHBRIDGE PCI_DEV(0, 0x1f, 0)
+#define NORTHBRIDGE PCI_DEV(0, 0x0, 0)
+#define FOR_ALL_LANES for (lane = 0; lane < NUM_LANES; lane++)
+#define FOR_ALL_CHANNELS for (channel = 0; channel < NUM_CHANNELS; channel++)
+#define FOR_ALL_POPULATED_RANKS for (slotrank = 0; slotrank < NUM_SLOTRANKS; slotrank++) if (ctrl->rankmap[channel] & (1 << slotrank))
+#define FOR_ALL_POPULATED_CHANNELS for (channel = 0; channel < NUM_CHANNELS; channel++) if (ctrl->rankmap[channel])
+#define MAX_EDGE_TIMING 71
+#define MAX_TIMC 127
+#define MAX_TIMB 511
+#define MAX_TIMA 127
+
+static void program_timings(ramctr_timing * ctrl, int channel);
+
+static const char *ecc_decoder[] = {
+	"inactive",
+	"active on IO",
+	"disabled on IO",
+	"active"
+};
+
+static void wait_txt_clear(void)
+{
+	struct cpuid_result cp;
+
+	cp = cpuid_ext(0x1, 0x0);
+	/* Check if TXT is supported?  */
+	if (!(cp.ecx & 0x40))
+		return;
+	/* Some TXT public bit.  */
+	if (!(read32(0xfed30010) & 1))
+		return;
+	/* Wait for TXT clear.  */
+	while (!(read8(0xfed40000) & (1 << 7))) ;
+}
+
+static void sfence(void)
+{
+	asm volatile ("sfence");
+}
+
+/*
+ * Dump in the log memory controller configuration as read from the memory
+ * controller registers.
+ */
+static void report_memory_config(void)
+{
+	u32 addr_decoder_common, addr_decode_ch[NUM_CHANNELS];
+	int i;
+
+	addr_decoder_common = MCHBAR32(0x5000);
+	addr_decode_ch[0] = MCHBAR32(0x5004);
+	addr_decode_ch[1] = MCHBAR32(0x5008);
+
+	printk(BIOS_DEBUG, "memcfg DDR3 clock %d MHz\n",
+	       (MCHBAR32(0x5e04) * 13333 * 2 + 50) / 100);
+	printk(BIOS_DEBUG, "memcfg channel assignment: A: %d, B % d, C % d\n",
+	       addr_decoder_common & 3, (addr_decoder_common >> 2) & 3,
+	       (addr_decoder_common >> 4) & 3);
+
+	for (i = 0; i < ARRAY_SIZE(addr_decode_ch); i++) {
+		u32 ch_conf = addr_decode_ch[i];
+		printk(BIOS_DEBUG, "memcfg channel[%d] config (%8.8x):\n", i,
+		       ch_conf);
+		printk(BIOS_DEBUG, "   ECC %s\n",
+		       ecc_decoder[(ch_conf >> 24) & 3]);
+		printk(BIOS_DEBUG, "   enhanced interleave mode %s\n",
+		       ((ch_conf >> 22) & 1) ? "on" : "off");
+		printk(BIOS_DEBUG, "   rank interleave %s\n",
+		       ((ch_conf >> 21) & 1) ? "on" : "off");
+		printk(BIOS_DEBUG, "   DIMMA %d MB width x%d %s rank%s\n",
+		       ((ch_conf >> 0) & 0xff) * 256,
+		       ((ch_conf >> 19) & 1) ? 16 : 8,
+		       ((ch_conf >> 17) & 1) ? "dual" : "single",
+		       ((ch_conf >> 16) & 1) ? "" : ", selected");
+		printk(BIOS_DEBUG, "   DIMMB %d MB width x%d %s rank%s\n",
+		       ((ch_conf >> 8) & 0xff) * 256,
+		       ((ch_conf >> 20) & 1) ? 16 : 8,
+		       ((ch_conf >> 18) & 1) ? "dual" : "single",
+		       ((ch_conf >> 16) & 1) ? ", selected" : "");
+	}
+}
+
+static void post_system_agent_init(void)
+{
+	/* If PCIe init is skipped, set the PEG clock gating */
+	MCHBAR32(0x7010) = MCHBAR32(0x7010) | 0x01;
+}
+
+void read_spd(spd_raw_data * spd, u8 addr)
+{
+	int j;
+	for (j = 0; j < 256; j++)
+		(*spd)[j] = do_smbus_read_byte(SMBUS_IO_BASE, addr, j);
+}
+
+static void dram_find_spds_ddr3(spd_raw_data * spd, dimm_info * dimm,
+				ramctr_timing * ctrl)
+{
+	int dimms = 0;
+	int channel, slot, spd_slot;
+
+	memset (ctrl->rankmap, 0, sizeof (ctrl->rankmap));
+
+	ctrl->extended_temperature_range = 1;
+	ctrl->auto_self_refresh = 1;
+
+	FOR_ALL_CHANNELS {
+		ctrl->channel_size_mb[channel] = 0;
+
+		for (slot = 0; slot < NUM_SLOTS; slot++) {
+			spd_slot = 2 * channel + slot;
+			spd_decode_ddr3(&dimm->dimm[channel][slot], spd[spd_slot]);
+			if (dimm->dimm[channel][slot].dram_type != SPD_MEMORY_TYPE_SDRAM_DDR3) {
+				// set dimm invalid
+				dimm->dimm[channel][slot].ranks = 0;
+				dimm->dimm[channel][slot].size_mb = 0;
+				continue;
+			}
+
+			dram_print_spd_ddr3(&dimm->dimm[channel][slot]);
+			dimms++;
+			ctrl->rank_mirror[channel][slot * 2] = 0;
+			ctrl->rank_mirror[channel][slot * 2 + 1] = dimm->dimm[channel][slot].flags.pins_mirrored;
+			ctrl->channel_size_mb[channel] += dimm->dimm[channel][slot].size_mb;
+
+			ctrl->auto_self_refresh &= dimm->dimm[channel][slot].flags.asr;
+			ctrl->extended_temperature_range &= dimm->dimm[channel][slot].flags.ext_temp_refresh;
+
+			ctrl->rankmap[channel] = ((1 << dimm->dimm[channel][slot].ranks) - 1) << (2 * slot);
+			printk(BIOS_DEBUG, "rankmap[%d] = 0x%x\n", channel, ctrl->rankmap[channel]);
+		}
+		if ((ctrl->rankmap[channel] & 3) && (ctrl->rankmap[channel] & 0xc)
+			&& dimm->dimm[channel][0].reference_card <= 5 && dimm->dimm[channel][1].reference_card <= 5) {
+			const int ref_card_offset_table[6][6] = {
+				{ 0, 0, 0, 0, 2, 2, },
+				{ 0, 0, 0, 0, 2, 2, },
+				{ 0, 0, 0, 0, 2, 2, },
+				{ 0, 0, 0, 0, 1, 1, },
+				{ 2, 2, 2, 1, 0, 0, },
+				{ 2, 2, 2, 1, 0, 0, },
+			};
+			ctrl->ref_card_offset[channel] = ref_card_offset_table[dimm->dimm[channel][0].reference_card]
+				[dimm->dimm[channel][1].reference_card];
+		} else
+			ctrl->ref_card_offset[channel] = 0;
+	}
+	
+	if (!dimms)
+		die("No DIMMs were found");
+}
+
+static void dram_find_common_params(const dimm_info * dimms,
+				    ramctr_timing * ctrl)
+{
+	size_t valid_dimms;
+	int channel, slot;
+	ctrl->cas_supported = 0xff;
+	valid_dimms = 0;
+	FOR_ALL_CHANNELS for (slot = 0; slot < 2; slot++) {
+		const dimm_attr *dimm = &dimms->dimm[channel][slot];
+		if (dimm->dram_type != SPD_MEMORY_TYPE_SDRAM_DDR3)
+			continue;
+		valid_dimms++;
+
+		/* Find all possible CAS combinations */
+		ctrl->cas_supported &= dimm->cas_supported;
+
+		/* Find the smallest common latencies supported by all DIMMs */
+		ctrl->tCK = MAX(ctrl->tCK, dimm->tCK);
+		ctrl->tAA = MAX(ctrl->tAA, dimm->tAA);
+		ctrl->tWR = MAX(ctrl->tWR, dimm->tWR);
+		ctrl->tRCD = MAX(ctrl->tRCD, dimm->tRCD);
+		ctrl->tRRD = MAX(ctrl->tRRD, dimm->tRRD);
+		ctrl->tRP = MAX(ctrl->tRP, dimm->tRP);
+		ctrl->tRAS = MAX(ctrl->tRAS, dimm->tRAS);
+		ctrl->tRFC = MAX(ctrl->tRFC, dimm->tRFC);
+		ctrl->tWTR = MAX(ctrl->tWTR, dimm->tWTR);
+		ctrl->tRTP = MAX(ctrl->tRTP, dimm->tRTP);
+		ctrl->tFAW = MAX(ctrl->tFAW, dimm->tFAW);
+	}
+
+	if (!ctrl->cas_supported)
+		die("Unsupported DIMM combination. "
+		    "DIMMS do not support common CAS latency");
+	if (!valid_dimms)
+		die("No valid DIMMs found");
+}
+
+static u8 get_CWL(u8 CAS)
+{
+	/* Get CWL based on CAS using the following rule:
+	 *       _________________________________________
+	 * CAS: | 4T | 5T | 6T | 7T | 8T | 9T | 10T | 11T |
+	 * CWL: | 5T | 5T | 5T | 6T | 6T | 7T |  7T |  8T |
+	 */
+	static const u8 cas_cwl_map[] = { 5, 5, 5, 6, 6, 7, 7, 8 };
+	if (CAS > 11)
+		return 8;
+	return cas_cwl_map[CAS - 4];
+}
+
+/* Frequency multiplier.  */
+static u32 get_FRQ(u32 tCK)
+{
+	u32 FRQ;
+	FRQ = 256000 / (tCK * BASEFREQ);
+	if (FRQ > 8)
+		return 8;
+	if (FRQ < 3)
+		return 3;
+	return FRQ;
+}
+
+static u32 get_REFI(u32 tCK)
+{
+	/* Get REFI based on MCU frequency using the following rule:
+	 *        _________________________________________
+	 * FRQ : | 3    | 4    | 5    | 6    | 7    | 8    |
+	 * REFI: | 3120 | 4160 | 5200 | 6240 | 7280 | 8320 |
+	 */
+	static const u32 frq_refi_map[] =
+	    { 3120, 4160, 5200, 6240, 7280, 8320 };
+	return frq_refi_map[get_FRQ(tCK) - 3];
+}
+
+static u8 get_XSOffset(u32 tCK)
+{
+	/* Get XSOffset based on MCU frequency using the following rule:
+	 *             _________________________
+	 * FRQ      : | 3 | 4 | 5 | 6 | 7  | 8  |
+	 * XSOffset : | 4 | 6 | 7 | 8 | 10 | 11 |
+	 */
+	static const u8 frq_xs_map[] = { 4, 6, 7, 8, 10, 11 };
+	return frq_xs_map[get_FRQ(tCK) - 3];
+}
+
+static u8 get_MOD(u32 tCK)
+{
+	/* Get MOD based on MCU frequency using the following rule:
+	 *        _____________________________
+	 * FRQ : | 3  | 4  | 5  | 6  | 7  | 8  |
+	 * MOD : | 12 | 12 | 12 | 12 | 15 | 16 |
+	 */
+	static const u8 frq_mod_map[] = { 12, 12, 12, 12, 15, 16 };
+	return frq_mod_map[get_FRQ(tCK) - 3];
+}
+
+static u8 get_WLO(u32 tCK)
+{
+	/* Get WLO based on MCU frequency using the following rule:
+	 *        _______________________
+	 * FRQ : | 3 | 4 | 5 | 6 | 7 | 8 |
+	 * WLO : | 4 | 5 | 6 | 6 | 8 | 8 |
+	 */
+	static const u8 frq_wlo_map[] = { 4, 5, 6, 6, 8, 8 };
+	return frq_wlo_map[get_FRQ(tCK) - 3];
+}
+
+static u8 get_CKE(u32 tCK)
+{
+	/* Get CKE based on MCU frequency using the following rule:
+	 *        _______________________
+	 * FRQ : | 3 | 4 | 5 | 6 | 7 | 8 |
+	 * CKE : | 3 | 3 | 4 | 4 | 5 | 6 |
+	 */
+	static const u8 frq_cke_map[] = { 3, 3, 4, 4, 5, 6 };
+	return frq_cke_map[get_FRQ(tCK) - 3];
+}
+
+static u8 get_XPDLL(u32 tCK)
+{
+	/* Get XPDLL based on MCU frequency using the following rule:
+	 *          _____________________________
+	 * FRQ   : | 3  | 4  | 5  | 6  | 7  | 8  |
+	 * XPDLL : | 10 | 13 | 16 | 20 | 23 | 26 |
+	 */
+	static const u8 frq_xpdll_map[] = { 10, 13, 16, 20, 23, 26 };
+	return frq_xpdll_map[get_FRQ(tCK) - 3];
+}
+
+static u8 get_XP(u32 tCK)
+{
+	/* Get XP based on MCU frequency using the following rule:
+	 *        _______________________
+	 * FRQ : | 3 | 4 | 5 | 6 | 7 | 8 |
+	 * XP  : | 3 | 4 | 4 | 5 | 6 | 7 |
+	 */
+	static const u8 frq_xp_map[] = { 3, 4, 4, 5, 6, 7 };
+	return frq_xp_map[get_FRQ(tCK) - 3];
+}
+
+static u8 get_AONPD(u32 tCK)
+{
+	/* Get AONPD based on MCU frequency using the following rule:
+	 *          ________________________
+	 * FRQ   : | 3 | 4 | 5 | 6 | 7 | 8  |
+	 * AONPD : | 4 | 5 | 6 | 8 | 8 | 10 |
+	 */
+	static const u8 frq_aonpd_map[] = { 4, 5, 6, 8, 8, 10 };
+	return frq_aonpd_map[get_FRQ(tCK) - 3];
+}
+
+static u32 get_COMP2(u32 tCK)
+{
+	/* Get COMP2 based on MCU frequency using the following rule:
+	 *         ___________________________________________________________
+	 * FRQ  : | 3       | 4       | 5       | 6       | 7       | 8       |
+	 * COMP : | D6BEDCC | CE7C34C | CA57A4C | C6369CC | C42514C | C21410C |
+	 */
+	static const u32 frq_comp2_map[] = { 0xD6BEDCC, 0xCE7C34C, 0xCA57A4C,
+		0xC6369CC, 0xC42514C, 0xC21410C
+	};
+	return frq_comp2_map[get_FRQ(tCK) - 3];
+}
+
+static void dram_timing(ramctr_timing * ctrl)
+{
+	u8 val;
+	u32 val32;
+
+	/* Maximum supported DDR3 frequency is 1066MHz (DDR3 2133) so make sure
+	 * we cap it if we have faster DIMMs.
+	 * Then, align it to the closest JEDEC standard frequency */
+	if (ctrl->tCK <= TCK_1066MHZ) {
+		ctrl->tCK = TCK_1066MHZ;
+		ctrl->edge_offset[0] = 16;
+		ctrl->edge_offset[1] = 7;
+		ctrl->edge_offset[2] = 7;
+		ctrl->timC_offset[0] = 18;
+		ctrl->timC_offset[1] = 7;
+		ctrl->timC_offset[2] = 7;
+		ctrl->reg_c14_offset = 16;
+		ctrl->reg_5064b0 = 0x218;
+		ctrl->reg_320c_range_threshold = 13;
+	} else if (ctrl->tCK <= TCK_933MHZ) {
+		ctrl->tCK = TCK_933MHZ;
+		ctrl->edge_offset[0] = 14;
+		ctrl->edge_offset[1] = 6;
+		ctrl->edge_offset[2] = 6;
+		ctrl->timC_offset[0] = 15;
+		ctrl->timC_offset[1] = 6;
+		ctrl->timC_offset[2] = 6;
+		ctrl->reg_c14_offset = 14;
+		ctrl->reg_5064b0 = 0x1d5;
+		ctrl->reg_320c_range_threshold = 15;
+	} else if (ctrl->tCK <= TCK_800MHZ) {
+		ctrl->tCK = TCK_800MHZ;
+		ctrl->edge_offset[0] = 13;
+		ctrl->edge_offset[1] = 5;
+		ctrl->edge_offset[2] = 5;
+		ctrl->timC_offset[0] = 14;
+		ctrl->timC_offset[1] = 5;
+		ctrl->timC_offset[2] = 5;
+		ctrl->reg_c14_offset = 12;
+		ctrl->reg_5064b0 = 0x193;
+		ctrl->reg_320c_range_threshold = 15;
+	} else if (ctrl->tCK <= TCK_666MHZ) {
+		ctrl->tCK = TCK_666MHZ;
+		ctrl->edge_offset[0] = 10;
+		ctrl->edge_offset[1] = 4;
+		ctrl->edge_offset[2] = 4;
+		ctrl->timC_offset[0] = 11;
+		ctrl->timC_offset[1] = 4;
+		ctrl->timC_offset[2] = 4;
+		ctrl->reg_c14_offset = 10;
+		ctrl->reg_5064b0 = 0x150;
+		ctrl->reg_320c_range_threshold = 16;
+	} else if (ctrl->tCK <= TCK_533MHZ) {
+		ctrl->tCK = TCK_533MHZ;
+		ctrl->edge_offset[0] = 8;
+		ctrl->edge_offset[1] = 3;
+		ctrl->edge_offset[2] = 3;
+		ctrl->timC_offset[0] = 9;
+		ctrl->timC_offset[1] = 3;
+		ctrl->timC_offset[2] = 3;
+		ctrl->reg_c14_offset = 8;
+		ctrl->reg_5064b0 = 0x10d;
+		ctrl->reg_320c_range_threshold = 17;
+	} else  {
+		ctrl->tCK = TCK_400MHZ;
+		ctrl->edge_offset[0] = 6;
+		ctrl->edge_offset[1] = 2;
+		ctrl->edge_offset[2] = 2;
+		ctrl->timC_offset[0] = 6;
+		ctrl->timC_offset[1] = 2;
+		ctrl->timC_offset[2] = 2;
+		ctrl->reg_c14_offset = 8;
+		ctrl->reg_5064b0 = 0xcd;
+		ctrl->reg_320c_range_threshold = 17;
+	}
+
+	val32 = (1000 << 8) / ctrl->tCK;
+	printk(BIOS_DEBUG, "Selected DRAM frequency: %u MHz\n", val32);
+
+	/* Find CAS and CWL latencies */
+	val = (ctrl->tAA + ctrl->tCK - 1) / ctrl->tCK;
+	printk(BIOS_DEBUG, "Minimum  CAS latency   : %uT\n", val);
+	/* Find lowest supported CAS latency that satisfies the minimum value */
+	while (!((ctrl->cas_supported >> (val - 4)) & 1)
+	       && (ctrl->cas_supported >> (val - 4))) {
+		val++;
+	}
+	/* Is CAS supported */
+	if (!(ctrl->cas_supported & (1 << (val - 4))))
+		printk(BIOS_DEBUG, "CAS not supported\n");
+	printk(BIOS_DEBUG, "Selected CAS latency   : %uT\n", val);
+	ctrl->CAS = val;
+	ctrl->CWL = get_CWL(ctrl->CAS);
+	printk(BIOS_DEBUG, "Selected CWL latency   : %uT\n", ctrl->CWL);
+
+	/* Find tRCD */
+	ctrl->tRCD = (ctrl->tRCD + ctrl->tCK - 1) / ctrl->tCK;
+	printk(BIOS_DEBUG, "Selected tRCD          : %uT\n", ctrl->tRCD);
+
+	ctrl->tRP = (ctrl->tRP + ctrl->tCK - 1) / ctrl->tCK;
+	printk(BIOS_DEBUG, "Selected tRP           : %uT\n", ctrl->tRP);
+
+	/* Find tRAS */
+	ctrl->tRAS = (ctrl->tRAS + ctrl->tCK - 1) / ctrl->tCK;
+	printk(BIOS_DEBUG, "Selected tRAS          : %uT\n", ctrl->tRAS);
+
+	/* Find tWR */
+	ctrl->tWR = (ctrl->tWR + ctrl->tCK - 1) / ctrl->tCK;
+	printk(BIOS_DEBUG, "Selected tWR           : %uT\n", ctrl->tWR);
+
+	/* Find tFAW */
+	ctrl->tFAW = (ctrl->tFAW + ctrl->tCK - 1) / ctrl->tCK;
+	printk(BIOS_DEBUG, "Selected tFAW          : %uT\n", ctrl->tFAW);
+
+	/* Find tRRD */
+	ctrl->tRRD = (ctrl->tRRD + ctrl->tCK - 1) / ctrl->tCK;
+	printk(BIOS_DEBUG, "Selected tRRD          : %uT\n", ctrl->tRRD);
+
+	/* Find tRTP */
+	ctrl->tRTP = (ctrl->tRTP + ctrl->tCK - 1) / ctrl->tCK;
+	printk(BIOS_DEBUG, "Selected tRTP          : %uT\n", ctrl->tRTP);
+
+	/* Find tWTR */
+	ctrl->tWTR = (ctrl->tWTR + ctrl->tCK - 1) / ctrl->tCK;
+	printk(BIOS_DEBUG, "Selected tWTR          : %uT\n", ctrl->tWTR);
+
+	/* Refresh-to-Active or Refresh-to-Refresh (tRFC) */
+	ctrl->tRFC = (ctrl->tRFC + ctrl->tCK - 1) / ctrl->tCK;
+	printk(BIOS_DEBUG, "Selected tRFC          : %uT\n", ctrl->tRFC);
+
+	ctrl->tREFI = get_REFI(ctrl->tCK);
+	ctrl->tMOD = get_MOD(ctrl->tCK);
+	ctrl->tXSOffset = get_XSOffset(ctrl->tCK);
+	ctrl->tWLO = get_WLO(ctrl->tCK);
+	ctrl->tCKE = get_CKE(ctrl->tCK);
+	ctrl->tXPDLL = get_XPDLL(ctrl->tCK);
+	ctrl->tXP = get_XP(ctrl->tCK);
+	ctrl->tAONPD = get_AONPD(ctrl->tCK);
+}
+
+static void dram_freq(ramctr_timing * ctrl)
+{
+	u8 val2;
+	u32 reg1 = 0;
+
+	/* Step 1 - Set target PCU frequency */
+
+	if (ctrl->tCK <= TCK_1066MHZ) {
+		ctrl->tCK = TCK_1066MHZ;
+	} else if (ctrl->tCK <= TCK_933MHZ) {
+		ctrl->tCK = TCK_933MHZ;
+	} else if (ctrl->tCK <= TCK_800MHZ) {
+		ctrl->tCK = TCK_800MHZ;
+	} else if (ctrl->tCK <= TCK_666MHZ) {
+		ctrl->tCK = TCK_666MHZ;
+	} else if (ctrl->tCK <= TCK_533MHZ) {
+		ctrl->tCK = TCK_533MHZ;
+	} else {
+		ctrl->tCK = TCK_400MHZ;
+	}
+
+	/* Frequency mulitplier.  */
+	u32 FRQ = get_FRQ(ctrl->tCK);
+
+	/* Step 2 - Select frequency in the MCU */
+	reg1 = FRQ;
+	reg1 |= 0x80000000;	// set running bit
+	MCHBAR32(0x5e00) = reg1;
+	while (reg1 & 0x80000000) {
+		printk(BIOS_DEBUG, " PLL busy...");
+		reg1 = MCHBAR32(0x5e00);
+	}
+	printk(BIOS_DEBUG, "done\n");
+
+	/* Step 3 - Verify lock frequency */
+	reg1 = MCHBAR32(0x5e04);
+	val2 = (u8) reg1;
+	if (val2 < FRQ) {
+		printk(BIOS_DEBUG, "Lock frequency is lower, recalculating\n");
+		ctrl->tCK = 256000 / (val2 * BASEFREQ);
+	}
+	printk(BIOS_DEBUG, "MCU frequency is set at : %d MHz\n",
+	       (1000 << 8) / ctrl->tCK);
+}
+
+static void dram_xover(ramctr_timing * ctrl)
+{
+	u32 reg;
+	int channel;
+
+	FOR_ALL_CHANNELS {
+		// enable xover clk
+		printk(BIOS_DEBUG, "[%x] = %x\n", channel * 0x100 + 0xc14,
+		       (ctrl->rankmap[channel] << 24));
+		MCHBAR32(channel * 0x100 + 0xc14) = (ctrl->rankmap[channel] << 24);
+
+		// enable xover ctl
+		reg = 0;
+		if (ctrl->rankmap[channel] & 0x5) {
+			reg |= 0x20000;
+		}
+		if (ctrl->rankmap[channel] & 0xa) {
+			reg |= 0x4000000;
+		}
+		// enable xover cmd
+		reg |= 0x4000;
+		printk(BIOS_DEBUG, "[%x] = %x\n", 0x100 * channel + 0x320c,
+		       reg);
+		MCHBAR32(0x100 * channel + 0x320c) = reg;
+	}
+}
+
+static void dram_timing_regs(ramctr_timing * ctrl)
+{
+	u32 reg, addr, val32, cpu, stretch;
+	struct cpuid_result cpures;
+	int channel;
+
+	FOR_ALL_CHANNELS {
+		// DBP
+		reg = 0;
+		reg |= ctrl->tRCD;
+		reg |= (ctrl->tRP << 4);
+		reg |= (ctrl->CAS << 8);
+		reg |= (ctrl->CWL << 12);
+		reg |= (ctrl->tRAS << 16);
+		printk(BIOS_DEBUG, "[%x] = %x\n", 0x400 * channel + 0x4000,
+		       reg);
+		MCHBAR32(0x400 * channel + 0x4000) = reg;
+
+		// RAP
+		reg = 0;
+		reg |= ctrl->tRRD;
+		reg |= (ctrl->tRTP << 4);
+		reg |= (ctrl->tCKE << 8);
+		reg |= (ctrl->tWTR << 12);
+		reg |= (ctrl->tFAW << 16);
+		reg |= (ctrl->tWR << 24);
+		reg |= (3 << 30);
+		printk(BIOS_DEBUG, "[%x] = %x\n", 0x400 * channel + 0x4004,
+		       reg);
+		MCHBAR32(0x400 * channel + 0x4004) = reg;
+
+		// OTHP
+		addr = 0x400 * channel + 0x400c;
+		reg = 0;
+		reg |= ctrl->tXPDLL;
+		reg |= (ctrl->tXP << 5);
+		reg |= (ctrl->tAONPD << 8);
+		reg |= 0xa0000;
+		printk(BIOS_DEBUG, "[%x] = %x\n", addr, reg);
+		MCHBAR32(addr) = reg;
+
+		MCHBAR32(0x400 * channel + 0x4014) = 0;
+
+		MCHBAR32(addr) |= 0x00020000;
+
+		// ODT stretch
+		reg = 0;
+
+		cpures = cpuid(0);
+		cpu = cpures.eax;
+		if (IS_IVY_CPU(cpu)
+		    || (IS_SANDY_CPU(cpu) && IS_SANDY_CPU_D2(cpu))) {
+			stretch = 2;
+			addr = 0x400 * channel + 0x400c;
+			printk(BIOS_DEBUG, "[%x] = %x\n",
+			       0x400 * channel + 0x400c, reg);
+			reg = MCHBAR32(addr);
+
+			if (((ctrl->rankmap[channel] & 3) == 0)
+			    || (ctrl->rankmap[channel] & 0xc) == 0) {
+
+				// Rank 0 - operate on rank 2
+				reg = (reg & ~0xc0000) | (stretch << 18);
+
+				// Rank 2 - operate on rank 0
+				reg = (reg & ~0x30000) | (stretch << 16);
+
+				printk(BIOS_DEBUG, "[%x] = %x\n", addr, reg);
+				MCHBAR32(addr) = reg;
+			}
+
+		} else if (IS_SANDY_CPU(cpu) && IS_SANDY_CPU_C(cpu)) {
+			stretch = 3;
+			addr = 0x400 * channel + 0x401c;
+			reg = MCHBAR32(addr);
+
+			if (((ctrl->rankmap[channel] & 3) == 0)
+			    || (ctrl->rankmap[channel] & 0xc) == 0) {
+
+				// Rank 0 - operate on rank 2
+				reg = (reg & ~0x3000) | (stretch << 12);
+
+				// Rank 2 - operate on rank 0
+				reg = (reg & ~0xc00) | (stretch << 10);
+
+				printk(BIOS_DEBUG, "[%x] = %x\n", addr, reg);
+				MCHBAR32(addr) = reg;
+			}
+		} else {
+			stretch = 0;
+		}
+
+		// REFI
+		reg = 0;
+		val32 = ctrl->tREFI;
+		reg = (reg & ~0xffff) | val32;
+		val32 = ctrl->tRFC;
+		reg = (reg & ~0x1ff0000) | (val32 << 16);
+		val32 = (u32) (ctrl->tREFI * 9) / 1024;
+		reg = (reg & ~0xfe000000) | (val32 << 25);
+		printk(BIOS_DEBUG, "[%x] = %x\n", 0x400 * channel + 0x4298,
+		       reg);
+		MCHBAR32(0x400 * channel + 0x4298) = reg;
+
+		MCHBAR32(0x400 * channel + 0x4294) |= 0xff;
+
+		// SRFTP
+		reg = 0;
+		val32 = tDLLK;
+		reg = (reg & ~0xfff) | val32;
+		val32 = ctrl->tXSOffset;
+		reg = (reg & ~0xf000) | (val32 << 12);
+		val32 = tDLLK - ctrl->tXSOffset;
+		reg = (reg & ~0x3ff0000) | (val32 << 16);
+		val32 = ctrl->tMOD - 8;
+		reg = (reg & ~0xf0000000) | (val32 << 28);
+		printk(BIOS_DEBUG, "[%x] = %x\n", 0x400 * channel + 0x42a4,
+		       reg);
+		MCHBAR32(0x400 * channel + 0x42a4) = reg;
+	}
+}
+
+static void dram_dimm_mapping(dimm_info * info, ramctr_timing * ctrl)
+{
+	int t;
+	u32 reg, val32;
+	int channel;
+
+	FOR_ALL_CHANNELS {
+		dimm_attr *dimmA = 0;
+		dimm_attr *dimmB = 0;
+		reg = 0;
+		val32 = 0;
+		if (info->dimm[channel][0].size_mb >=
+		    info->dimm[channel][1].size_mb) {
+			// dimm 0 is bigger, set it to dimmA
+			dimmA = &info->dimm[channel][0];
+			dimmB = &info->dimm[channel][1];
+			reg |= (0 << 16);
+		} else {
+			// dimm 1 is bigger, set it to dimmA
+			dimmA = &info->dimm[channel][1];
+			dimmB = &info->dimm[channel][0];
+			reg |= (1 << 16);
+			// swap dimm info
+			t = ctrl->rank_mirror[channel][1];
+			ctrl->rank_mirror[channel][1] =
+			    ctrl->rank_mirror[channel][3];
+			ctrl->rank_mirror[channel][3] = t;
+		}
+		// dimmA
+		if (dimmA && (dimmA->ranks > 0)) {
+			val32 = dimmA->size_mb / 256;
+			reg = (reg & ~0xff) | val32;
+			val32 = dimmA->ranks - 1;
+			reg = (reg & ~0x20000) | (val32 << 17);
+			val32 = (dimmA->width / 8) - 1;
+			reg = (reg & ~0x80000) | (val32 << 19);
+		}
+		// dimmB
+		if (dimmB && (dimmB->ranks > 0)) {
+			val32 = dimmB->size_mb / 256;
+			reg = (reg & ~0xff00) | (val32 << 8);
+			val32 = dimmB->ranks - 1;
+			reg = (reg & ~0x40000) | (val32 << 18);
+			val32 = (dimmB->width / 8) - 1;
+			reg = (reg & ~0x100000) | (val32 << 20);
+		}
+		reg = (reg & ~0x200000) | (1 << 21);	// rank interleave
+		reg = (reg & ~0x400000) | (1 << 22);	// enhanced interleave
+
+		// Save MAD-DIMM register
+		if ((dimmA && (dimmA->ranks > 0))
+		    || (dimmB && (dimmB->ranks > 0))) {
+			ctrl->mad_dimm[channel] = reg;
+		} else {
+			ctrl->mad_dimm[channel] = 0;
+		}
+	}
+}
+
+static void dram_dimm_set_mapping(ramctr_timing * ctrl)
+{
+	int channel;
+	FOR_ALL_CHANNELS {
+		MCHBAR32(0x5004 + channel * 4) = ctrl->mad_dimm[channel];
+	}
+}
+
+static void dram_zones(ramctr_timing * ctrl, int training)
+{
+	u32 reg, ch0size, ch1size;
+	u8 val;
+	reg = 0;
+	val = 0;
+	if (training) {
+		ch0size = ctrl->channel_size_mb[0] ? 256 : 0;
+		ch1size = ctrl->channel_size_mb[1] ? 256 : 0;
+	} else {
+		ch0size = ctrl->channel_size_mb[0];
+		ch1size = ctrl->channel_size_mb[1];
+	}
+
+	if (ch0size >= ch1size) {
+		reg = MCHBAR32(0x5014);
+		val = ch1size / 256;
+		reg = (reg & ~0xff000000) | val << 24;
+		reg = (reg & ~0xff0000) | (2 * val) << 16;
+		MCHBAR32(0x5014) = reg;
+		MCHBAR32(0x5000) = 0x24;
+	} else {
+		reg = MCHBAR32(0x5014);
+		val = ch0size / 256;
+		reg = (reg & ~0xff000000) | val << 24;
+		reg = (reg & ~0xff0000) | (2 * val) << 16;
+		MCHBAR32(0x5014) = reg;
+		MCHBAR32(0x5000) = 0x21;
+	}
+}
+
+static void dram_memorymap(ramctr_timing * ctrl, int me_uma_size)
+{
+	u32 reg, val, reclaim;
+	u32 tom, gfxstolen, gttsize;
+	size_t tsegsize, mmiosize, toludbase, touudbase, gfxstolenbase, gttbase,
+	    tsegbase, mestolenbase;
+	size_t tsegbasedelta, remapbase, remaplimit;
+	uint16_t ggc;
+
+	mmiosize = 0x400;
+
+	ggc = pci_read_config16(NORTHBRIDGE, GGC);
+	if (!(ggc & 2)) {
+		gfxstolen = ((ggc >> 3) & 0x1f) * 32;
+		gttsize = ((ggc >> 8) & 0x3);
+	} else {
+		gfxstolen = 0;
+		gttsize = 0;
+	}
+
+	tsegsize = CONFIG_SMM_TSEG_SIZE >> 20;
+
+	tom = ctrl->channel_size_mb[0] + ctrl->channel_size_mb[1];
+
+	mestolenbase = tom - me_uma_size;
+
+	toludbase = MIN(4096 - mmiosize + gfxstolen + gttsize + tsegsize,
+			tom - me_uma_size);
+	gfxstolenbase = toludbase - gfxstolen;
+	gttbase = gfxstolenbase - gttsize;
+
+	tsegbase = gttbase - tsegsize;
+
+	// Round tsegbase down to nearest address aligned to tsegsize
+	tsegbasedelta = tsegbase & (tsegsize - 1);
+	tsegbase &= ~(tsegsize - 1);
+
+	gttbase -= tsegbasedelta;
+	gfxstolenbase -= tsegbasedelta;
+	toludbase -= tsegbasedelta;
+
+	// Test if it is possible to reclaim a hole in the ram addressing
+	if (tom - me_uma_size > toludbase) {
+		// Reclaim is possible
+		reclaim = 1;
+		remapbase = MAX(4096, tom - me_uma_size);
+		remaplimit =
+		    remapbase + MIN(4096, tom - me_uma_size) - toludbase - 1;
+		touudbase = remaplimit + 1;
+	} else {
+		// Reclaim not possible
+		reclaim = 0;
+		touudbase = tom - me_uma_size;
+	}
+
+	// Update memory map in pci-e configuration space
+
+	// TOM (top of memory)
+	reg = pcie_read_config32(PCI_DEV(0, 0, 0), 0xa0);
+	val = tom & 0xfff;
+	reg = (reg & ~0xfff00000) | (val << 20);
+	printk(BIOS_DEBUG, "PCI:[%x] = %x\n", 0xa0, reg);
+	pcie_write_config32(PCI_DEV(0, 0, 0), 0xa0, reg);
+
+	reg = pcie_read_config32(PCI_DEV(0, 0, 0), 0xa4);
+	val = tom & 0xfffff000;
+	reg = (reg & ~0x000fffff) | (val >> 12);
+	printk(BIOS_DEBUG, "PCI:[%x] = %x\n", 0xa4, reg);
+	pcie_write_config32(PCI_DEV(0, 0, 0), 0xa4, reg);
+
+	// TOLUD (top of low used dram)
+	reg = pcie_read_config32(PCI_DEV(0, 0, 0), 0xbc);
+	val = toludbase & 0xfff;
+	reg = (reg & ~0xfff00000) | (val << 20);
+	printk(BIOS_DEBUG, "PCI:[%x] = %x\n", 0xbc, reg);
+	pcie_write_config32(PCI_DEV(0, 0, 0), 0xbc, reg);
+
+	// TOUUD LSB (top of upper usable dram)
+	reg = pcie_read_config32(PCI_DEV(0, 0, 0), 0xa8);
+	val = touudbase & 0xfff;
+	reg = (reg & ~0xfff00000) | (val << 20);
+	printk(BIOS_DEBUG, "PCI:[%x] = %x\n", 0xa8, reg);
+	pcie_write_config32(PCI_DEV(0, 0, 0), 0xa8, reg);
+
+	// TOUUD MSB
+	reg = pcie_read_config32(PCI_DEV(0, 0, 0), 0xac);
+	val = touudbase & 0xfffff000;
+	reg = (reg & ~0x000fffff) | (val >> 12);
+	printk(BIOS_DEBUG, "PCI:[%x] = %x\n", 0xac, reg);
+	pcie_write_config32(PCI_DEV(0, 0, 0), 0xac, reg);
+
+	if (reclaim) {
+		// REMAP BASE
+		pcie_write_config32(PCI_DEV(0, 0, 0), 0x90, remapbase << 20);
+		pcie_write_config32(PCI_DEV(0, 0, 0), 0x94, remapbase >> 12);
+
+		// REMAP LIMIT
+		pcie_write_config32(PCI_DEV(0, 0, 0), 0x98, remaplimit << 20);
+		pcie_write_config32(PCI_DEV(0, 0, 0), 0x9c, remaplimit >> 12);
+	}
+	// TSEG
+	reg = pcie_read_config32(PCI_DEV(0, 0, 0), 0xb8);
+	val = tsegbase & 0xfff;
+	reg = (reg & ~0xfff00000) | (val << 20);
+	printk(BIOS_DEBUG, "PCI:[%x] = %x\n", 0xb8, reg);
+	pcie_write_config32(PCI_DEV(0, 0, 0), 0xb8, reg);
+
+	// GFX stolen memory
+	reg = pcie_read_config32(PCI_DEV(0, 0, 0), 0xb0);
+	val = gfxstolenbase & 0xfff;
+	reg = (reg & ~0xfff00000) | (val << 20);
+	printk(BIOS_DEBUG, "PCI:[%x] = %x\n", 0xb0, reg);
+	pcie_write_config32(PCI_DEV(0, 0, 0), 0xb0, reg);
+
+	// GTT stolen memory
+	reg = pcie_read_config32(PCI_DEV(0, 0, 0), 0xb4);
+	val = gttbase & 0xfff;
+	reg = (reg & ~0xfff00000) | (val << 20);
+	printk(BIOS_DEBUG, "PCI:[%x] = %x\n", 0xb4, reg);
+	pcie_write_config32(PCI_DEV(0, 0, 0), 0xb4, reg);
+
+	if (me_uma_size) {
+		reg = pcie_read_config32(PCI_DEV(0, 0, 0), 0x7c);
+		val = (0x80000 - me_uma_size) & 0xfffff000;
+		reg = (reg & ~0x000fffff) | (val >> 12);
+		printk(BIOS_DEBUG, "PCI:[%x] = %x\n", 0x7c, reg);
+		pcie_write_config32(PCI_DEV(0, 0, 0), 0x7c, reg);
+
+		// ME base
+		reg = pcie_read_config32(PCI_DEV(0, 0, 0), 0x70);
+		val = mestolenbase & 0xfff;
+		reg = (reg & ~0xfff00000) | (val << 20);
+		printk(BIOS_DEBUG, "PCI:[%x] = %x\n", 0x70, reg);
+		pcie_write_config32(PCI_DEV(0, 0, 0), 0x70, reg);
+
+		reg = pcie_read_config32(PCI_DEV(0, 0, 0), 0x74);
+		val = mestolenbase & 0xfffff000;
+		reg = (reg & ~0x000fffff) | (val >> 12);
+		printk(BIOS_DEBUG, "PCI:[%x] = %x\n", 0x74, reg);
+		pcie_write_config32(PCI_DEV(0, 0, 0), 0x74, reg);
+
+		// ME mask
+		reg = pcie_read_config32(PCI_DEV(0, 0, 0), 0x78);
+		val = (0x80000 - me_uma_size) & 0xfff;
+		reg = (reg & ~0xfff00000) | (val << 20);
+		reg = (reg & ~0x400) | (1 << 10);	// set lockbit on ME mem
+
+		reg = (reg & ~0x800) | (1 << 11);	// set ME memory enable
+		printk(BIOS_DEBUG, "PCI:[%x] = %x\n", 0x78, reg);
+		pcie_write_config32(PCI_DEV(0, 0, 0), 0x78, reg);
+	}
+}
+
+static void dram_ioregs(ramctr_timing * ctrl)
+{
+	u32 reg, comp2;
+
+	int channel;
+
+	// IO clock
+	FOR_ALL_CHANNELS {
+		MCHBAR32(0xc00 + 0x100 * channel) = ctrl->rankmap[channel];
+	}
+
+	// IO command
+	FOR_ALL_CHANNELS {
+		MCHBAR32(0x3200 + 0x100 * channel) = ctrl->rankmap[channel];
+	}
+
+	// IO control
+	FOR_ALL_POPULATED_CHANNELS {
+		program_timings(ctrl, channel);
+	}
+
+	// Rcomp
+	printk(BIOS_DEBUG, "RCOMP...");
+	reg = 0;
+	while (reg == 0) {
+		reg = MCHBAR32(0x5084) & 0x10000;
+	}
+	printk(BIOS_DEBUG, "done\n");
+
+	// Set comp2
+	comp2 = get_COMP2(ctrl->tCK);
+	MCHBAR32(0x3714) = comp2;
+	printk(BIOS_DEBUG, "COMP2 done\n");
+
+	// Set comp1
+	FOR_ALL_POPULATED_CHANNELS {
+		reg = MCHBAR32(0x1810 + channel * 0x100);	//ch0
+		reg = (reg & ~0xe00) | (1 << 9);	//odt
+		reg = (reg & ~0xe00000) | (1 << 21);	//clk drive up
+		reg = (reg & ~0x38000000) | (1 << 27);	//ctl drive up
+		MCHBAR32(0x1810 + channel * 0x100) = reg;
+	}
+	printk(BIOS_DEBUG, "COMP1 done\n");
+
+	printk(BIOS_DEBUG, "FORCE RCOMP and wait 20us...");
+	MCHBAR32(0x5f08) |= 0x100;
+	udelay(20);
+	printk(BIOS_DEBUG, "done\n");
+}
+
+static void wait_428c(int channel)
+{
+	while (1) {
+		if (read32(DEFAULT_MCHBAR | 0x428c | (channel << 10)) & 0x50)
+			return;
+	}
+}
+
+static void write_reset(ramctr_timing * ctrl)
+{
+	int channel, slotrank;
+
+	/* choose a populated channel.  */
+	channel = (ctrl->rankmap[0]) ? 0 : 1;
+
+	wait_428c(channel);
+
+	/* choose a populated rank.  */
+	slotrank = (ctrl->rankmap[channel] & 1) ? 0 : 2;
+
+	write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x0f003);
+	write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel, 0x80c01);
+
+	write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+		(slotrank << 24) | 0x60000);
+
+	write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0);
+
+	write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 0x400001);
+	wait_428c(channel);
+}
+
+static void dram_jedecreset(ramctr_timing * ctrl)
+{
+	u32 reg, addr;
+	int channel;
+
+	while (!(MCHBAR32(0x5084) & 0x10000)) ;
+	do {
+		reg = MCHBAR32(0x428c);
+	} while ((reg & 0x14) == 0);
+
+	// Set state of memory controller
+	reg = 0x112;
+	MCHBAR32(0x5030) = reg;
+	MCHBAR32(0x4ea0) = 0;
+	reg |= 2;		//ddr reset
+	MCHBAR32(0x5030) = reg;
+
+	// Assert dimm reset signal
+	reg = MCHBAR32(0x5030);
+	reg &= ~0x2;
+	MCHBAR32(0x5030) = reg;
+
+	// Wait 200us
+	udelay(200);
+
+	// Deassert dimm reset signal
+	MCHBAR32(0x5030) |= 2;
+
+	// Wait 500us
+	udelay(500);
+
+	// Enable DCLK
+	MCHBAR32(0x5030) |= 4;
+
+	// XXX Wait 20ns
+	udelay(1);
+
+	FOR_ALL_CHANNELS {
+		// Set valid rank CKE
+		reg = 0;
+		reg = (reg & ~0xf) | ctrl->rankmap[channel];
+		addr = 0x400 * channel + 0x42a0;
+		MCHBAR32(addr) = reg;
+
+		// Wait 10ns for ranks to settle
+		//udelay(0.01);
+
+		reg = (reg & ~0xf0) | (ctrl->rankmap[channel] << 4);
+		MCHBAR32(addr) = reg;
+
+		// Write reset using a NOP
+		write_reset(ctrl);
+	}
+}
+
+static odtmap get_ODT(ramctr_timing * ctrl, u8 rank)
+{
+	/* Get ODT based on rankmap: */
+	int dimms_per_ch = 0;
+	int channel;
+
+	FOR_ALL_CHANNELS {
+		dimms_per_ch = max ((ctrl->rankmap[channel] & 1)
+				    + ((ctrl->rankmap[channel] >> 2) & 1),
+				    dimms_per_ch);
+	}
+
+	if (dimms_per_ch == 1) {
+		return (const odtmap){60, 60};
+	} else if (dimms_per_ch == 2) {
+		return (const odtmap){120, 30};
+	} else {
+		printk(BIOS_DEBUG,
+		       "Huh, no dimms? m0 = %d m1 = %d dpc = %d\n",
+		       ctrl->rankmap[0],
+		       ctrl->rankmap[1], dimms_per_ch);
+		die("");
+	}
+}
+
+static void write_mrreg(ramctr_timing * ctrl, int channel, int slotrank,
+			int reg, u32 val)
+{
+	wait_428c(channel);
+
+	printk(BIOS_SPEW, "MRd: %x <= %x\n", reg, val);
+
+	if (ctrl->rank_mirror[channel][slotrank]) {
+		reg = ((reg >> 1) & 1) | ((reg << 1) & 2);
+		val = (val & ~0x1f8) | ((val >> 1) & 0xa8)
+		    | ((val & 0xa8) << 1);
+	}
+
+	printk(BIOS_SPEW, "MRd: %x <= %x\n", reg, val);
+
+	write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x0f000);
+	write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel, 0x41001);
+	write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+		(slotrank << 24) | (reg << 20) | val | 0x60000);
+	write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0);
+
+	write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel, 0x1f000);
+	write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel, 0x41001);
+	write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+		(slotrank << 24) | (reg << 20) | val | 0x60000);
+	write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0);
+
+	write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel, 0x0f000);
+	write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel,
+		0x1001 | (ctrl->tMOD << 16));
+	write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel,
+		(slotrank << 24) | (reg << 20) | val | 0x60000);
+	write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel, 0);
+	write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 0x80001);
+}
+
+static u32 make_mr0(ramctr_timing * ctrl, u8 rank)
+{
+	u16 mr0reg, mch_cas, mch_wr;
+	static const u8 mch_wr_t[12] = { 1, 2, 3, 4, 0, 5, 0, 6, 0, 7, 0, 0 };
+	mr0reg = 0x100;
+
+	// Convert CAS to MCH register friendly
+	if (ctrl->CAS < 12) {
+		mch_cas = (u16) ((ctrl->CAS - 4) << 1);
+	} else {
+		mch_cas = (u16) (ctrl->CAS - 12);
+		mch_cas = ((mch_cas << 1) | 0x1);
+	}
+
+	// Convert tWR to MCH register friendly
+	mch_wr = mch_wr_t[ctrl->tWR - 5];
+
+	mr0reg = (mr0reg & ~0x4) | (mch_cas & 0x1);
+	mr0reg = (mr0reg & ~0x70) | ((mch_cas & 0xe) << 3);
+	mr0reg = (mr0reg & ~0xe00) | (mch_wr << 9);
+	// Fast (desktop) 0x1 or slow (mobile) 0x0
+	mr0reg = (mr0reg & ~0x1000) | (!ctrl->mobile << 12);
+	return mr0reg;
+}
+
+static void dram_mr0(ramctr_timing * ctrl, u8 rank)
+{
+	int channel;
+
+	FOR_ALL_POPULATED_CHANNELS write_mrreg(ctrl, channel, rank, 0,
+					       make_mr0(ctrl, rank));
+}
+
+static u32 encode_odt(u32 odt)
+{
+	switch (odt) {
+	case 30:
+		return (1 << 9) | (1 << 2);	// RZQ/8, RZQ/4
+	case 60:
+		return (1 << 2);	// RZQ/4
+	case 120:
+		return (1 << 6);	// RZQ/2
+	default:
+	case 0:
+		return 0;
+	}
+}
+
+static u32 make_mr1(ramctr_timing * ctrl, u8 rank)
+{
+	odtmap odt;
+	u32 mr1reg;
+
+	odt = get_ODT(ctrl, rank);
+	mr1reg = 0x2;
+
+	mr1reg |= encode_odt(odt.rttnom);
+
+	return mr1reg;
+}
+
+static void dram_mr1(ramctr_timing * ctrl, u8 rank)
+{
+	u16 mr1reg;
+	int channel;
+
+	mr1reg = make_mr1(ctrl, rank);
+
+	FOR_ALL_CHANNELS {
+		write_mrreg(ctrl, channel, rank, 1, mr1reg);
+	}
+}
+
+static void dram_mr2(ramctr_timing * ctrl, u8 rank)
+{
+	u16 pasr, cwl, mr2reg;
+	odtmap odt;
+	int channel;
+	int srt;
+
+	pasr = 0;
+	cwl = ctrl->CWL - 5;
+	odt = get_ODT(ctrl, rank);
+
+	srt = ctrl->extended_temperature_range && !ctrl->auto_self_refresh;
+
+	mr2reg = 0;
+	mr2reg = (mr2reg & ~0x7) | pasr;
+	mr2reg = (mr2reg & ~0x38) | (cwl << 3);
+	mr2reg = (mr2reg & ~0x40) | (ctrl->auto_self_refresh << 6);
+	mr2reg = (mr2reg & ~0x80) | (srt << 7);
+	mr2reg |= (odt.rttwr / 60) << 9;
+
+	FOR_ALL_CHANNELS {
+		write_mrreg(ctrl, channel, rank, 2, mr2reg);
+	}
+}
+
+static void dram_mr3(ramctr_timing * ctrl, u8 rank)
+{
+	int channel;
+
+	FOR_ALL_CHANNELS {
+		write_mrreg(ctrl, channel, rank, 3, 0);
+	}
+}
+
+static void dram_mrscommands(ramctr_timing * ctrl)
+{
+	u8 rank;
+	u32 reg, addr;
+	int channel;
+
+	for (rank = 0; rank < 4; rank++) {
+		// MR2
+		printk(BIOS_SPEW, "MR2 rank %d...", rank);
+		dram_mr2(ctrl, rank);
+		printk(BIOS_SPEW, "done\n");
+
+		// MR3
+		printk(BIOS_SPEW, "MR3 rank %d...", rank);
+		dram_mr3(ctrl, rank);
+		printk(BIOS_SPEW, "done\n");
+
+		// MR1
+		printk(BIOS_SPEW, "MR1 rank %d...", rank);
+		dram_mr1(ctrl, rank);
+		printk(BIOS_SPEW, "done\n");
+
+		// MR0
+		printk(BIOS_SPEW, "MR0 rank %d...", rank);
+		dram_mr0(ctrl, rank);
+		printk(BIOS_SPEW, "done\n");
+	}
+
+	write32(DEFAULT_MCHBAR + 0x4e20, 0x7);
+	write32(DEFAULT_MCHBAR + 0x4e30, 0xf1001);
+	write32(DEFAULT_MCHBAR + 0x4e00, 0x60002);
+	write32(DEFAULT_MCHBAR + 0x4e10, 0);
+	write32(DEFAULT_MCHBAR + 0x4e24, 0x1f003);
+	write32(DEFAULT_MCHBAR + 0x4e34, 0x1901001);
+	write32(DEFAULT_MCHBAR + 0x4e04, 0x60400);
+	write32(DEFAULT_MCHBAR + 0x4e14, 0x288);
+	write32(DEFAULT_MCHBAR + 0x4e84, 0x40004);
+
+	// Drain
+	FOR_ALL_CHANNELS {
+		// Wait for ref drained
+		wait_428c(channel);
+	}
+
+	// Refresh enable
+	MCHBAR32(0x5030) |= 8;
+
+	FOR_ALL_POPULATED_CHANNELS {
+		addr = 0x400 * channel + 0x4020;
+		reg = MCHBAR32(addr);
+		reg &= ~0x200000;
+		MCHBAR32(addr) = reg;
+
+		wait_428c(channel);
+
+		rank = (ctrl->rankmap[channel] & 1) ? 0 : 2;
+
+		// Drain
+		wait_428c(channel);
+
+		write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x0f003);
+		write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel, 0x659001);
+		write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+			(rank << 24) | 0x60000);
+		write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0x3e0);
+		write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 0x1);
+
+		// Drain
+		wait_428c(channel);
+	}
+}
+
+const u32 lane_registers[] = {
+	0x0000, 0x0200, 0x0400, 0x0600,
+	0x1000, 0x1200, 0x1400, 0x1600,
+	0x0800
+};
+
+static void program_timings(ramctr_timing * ctrl, int channel)
+{
+	u32 reg32, reg_4024, reg_c14, reg_c18, reg_4028;
+	int lane;
+	int slotrank, slot;
+	int full_shift = 0;
+	u16 slot320c[NUM_SLOTS];
+
+	FOR_ALL_POPULATED_RANKS {
+		if (full_shift < -ctrl->timings[channel][slotrank].val_320c)
+			full_shift = -ctrl->timings[channel][slotrank].val_320c;
+	}
+
+	for (slot = 0; slot < NUM_SLOTS; slot++)
+		switch ((ctrl->rankmap[channel] >> (2 * slot)) & 3) {
+		case 0:
+		default:
+			slot320c[slot] = 0x7f;
+			break;
+		case 1:
+			slot320c[slot] =
+			    ctrl->timings[channel][2 * slot + 0].val_320c +
+			    full_shift;
+			break;
+		case 2:
+			slot320c[slot] =
+			    ctrl->timings[channel][2 * slot + 1].val_320c +
+			    full_shift;
+			break;
+		case 3:
+			slot320c[slot] =
+			    (ctrl->timings[channel][2 * slot].val_320c +
+			     ctrl->timings[channel][2 * slot +
+						    1].val_320c) / 2 +
+			    full_shift;
+			break;
+		}
+
+	reg32 = (1 << 17) | (1 << 14);
+	reg32 |= ((slot320c[0] & 0x3f) << 6) | ((slot320c[0] & 0x40) << 9);
+	reg32 |= (slot320c[1] & 0x7f) << 18;
+	reg32 |= (full_shift & 0x3f) | ((full_shift & 0x40) << 6);
+
+	MCHBAR32(0x320c + 0x100 * channel) = reg32;
+
+	reg_c14 = ctrl->rankmap[channel] << 24;
+	reg_c18 = 0;
+
+	FOR_ALL_POPULATED_RANKS {
+		int shift =
+		    ctrl->timings[channel][slotrank].val_320c + full_shift;
+		int offset_val_c14;
+		if (shift < 0)
+			shift = 0;
+		offset_val_c14 = ctrl->reg_c14_offset + shift;
+		reg_c14 |= (offset_val_c14 & 0x3f) << (6 * slotrank);
+		reg_c18 |= ((offset_val_c14 >> 6) & 1) << slotrank;
+	}
+
+	MCHBAR32(0xc14 + channel * 0x100) = reg_c14;
+	MCHBAR32(0xc18 + channel * 0x100) = reg_c18;
+
+	reg_4028 = MCHBAR32(0x4028 + 0x400 * channel);
+	reg_4028 &= 0xffff0000;
+
+	reg_4024 = 0;
+
+	FOR_ALL_POPULATED_RANKS {
+		int post_timA_min_high = 7, post_timA_max_high = 0;
+		int pre_timA_min_high = 7, pre_timA_max_high = 0;
+		int shift_402x = 0;
+		int shift =
+		    ctrl->timings[channel][slotrank].val_320c + full_shift;
+
+		if (shift < 0)
+			shift = 0;
+
+		FOR_ALL_LANES {
+			if (post_timA_min_high >
+			    ((ctrl->timings[channel][slotrank].lanes[lane].
+			      timA + shift) >> 6))
+				post_timA_min_high =
+				    ((ctrl->timings[channel][slotrank].
+				      lanes[lane].timA + shift) >> 6);
+			if (pre_timA_min_high >
+			    (ctrl->timings[channel][slotrank].lanes[lane].
+			     timA >> 6))
+				pre_timA_min_high =
+				    (ctrl->timings[channel][slotrank].
+				     lanes[lane].timA >> 6);
+			if (post_timA_max_high <
+			    ((ctrl->timings[channel][slotrank].lanes[lane].
+			      timA + shift) >> 6))
+				post_timA_max_high =
+				    ((ctrl->timings[channel][slotrank].
+				      lanes[lane].timA + shift) >> 6);
+			if (pre_timA_max_high <
+			    (ctrl->timings[channel][slotrank].lanes[lane].
+			     timA >> 6))
+				pre_timA_max_high =
+				    (ctrl->timings[channel][slotrank].
+				     lanes[lane].timA >> 6);
+		}
+
+		if (pre_timA_max_high - pre_timA_min_high <
+		    post_timA_max_high - post_timA_min_high)
+			shift_402x = +1;
+		else if (pre_timA_max_high - pre_timA_min_high >
+			 post_timA_max_high - post_timA_min_high)
+			shift_402x = -1;
+
+		reg_4028 |=
+		    (ctrl->timings[channel][slotrank].val_4028 + shift_402x -
+		     post_timA_min_high) << (4 * slotrank);
+		reg_4024 |=
+		    (ctrl->timings[channel][slotrank].val_4024 +
+		     shift_402x) << (8 * slotrank);
+
+		FOR_ALL_LANES {
+			MCHBAR32(lane_registers[lane] + 0x10 + 0x100 * channel +
+				 4 * slotrank)
+			    =
+			    (((ctrl->timings[channel][slotrank].lanes[lane].
+			       timA + shift) & 0x3f)
+			     |
+			     ((ctrl->timings[channel][slotrank].lanes[lane].
+			       rising + shift) << 8)
+			     |
+			     (((ctrl->timings[channel][slotrank].lanes[lane].
+				timA + shift -
+				(post_timA_min_high << 6)) & 0x1c0) << 10)
+			     | (ctrl->timings[channel][slotrank].lanes[lane].
+				falling << 20));
+
+			MCHBAR32(lane_registers[lane] + 0x20 + 0x100 * channel +
+				 4 * slotrank)
+			    =
+			    (((ctrl->timings[channel][slotrank].lanes[lane].
+			       timC + shift) & 0x3f)
+			     |
+			     (((ctrl->timings[channel][slotrank].lanes[lane].
+				timB + shift) & 0x3f) << 8)
+			     |
+			     (((ctrl->timings[channel][slotrank].lanes[lane].
+				timB + shift) & 0x1c0) << 9)
+			     |
+			     (((ctrl->timings[channel][slotrank].lanes[lane].
+				timC + shift) & 0x40) << 13));
+		}
+	}
+	MCHBAR32(0x4024 + 0x400 * channel) = reg_4024;
+	MCHBAR32(0x4028 + 0x400 * channel) = reg_4028;
+}
+
+static void test_timA(ramctr_timing * ctrl, int channel, int slotrank)
+{
+	wait_428c(channel);
+
+	write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x1f000);
+	write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+		(0xc01 | (ctrl->tMOD << 16)));
+	write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+		(slotrank << 24) | 0x360004);
+	write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0);
+
+	write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel, 0x1f105);
+	write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel, 0x4040c01);
+	write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel, (slotrank << 24));
+	write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0);
+
+	write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel, 0x1f105);
+	write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel,
+		0x100f | ((ctrl->CAS + 36) << 16));
+	write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel,
+		(slotrank << 24) | 0x60000);
+	write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel, 0);
+
+	write32(DEFAULT_MCHBAR + 0x422c + 0x400 * channel, 0x1f000);
+	write32(DEFAULT_MCHBAR + 0x423c + 0x400 * channel,
+		(0xc01 | (ctrl->tMOD << 16)));
+	write32(DEFAULT_MCHBAR + 0x420c + 0x400 * channel,
+		(slotrank << 24) | 0x360000);
+	write32(DEFAULT_MCHBAR + 0x421c + 0x400 * channel, 0);
+
+	write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 0xc0001);
+
+	wait_428c(channel);
+}
+
+static int does_lane_work(ramctr_timing * ctrl, int channel, int slotrank,
+			  int lane)
+{
+	u32 timA = ctrl->timings[channel][slotrank].lanes[lane].timA;
+	return ((read32
+		 (DEFAULT_MCHBAR + lane_registers[lane] + channel * 0x100 + 4 +
+		  ((timA / 32) & 1) * 4)
+		 >> (timA % 32)) & 1);
+}
+
+struct run {
+	int middle;
+	int end;
+	int start;
+	int all;
+	int length;
+};
+
+static struct run get_longest_zero_run(int *seq, int sz)
+{
+	int i, ls;
+	int bl = 0, bs = 0;
+	struct run ret;
+
+	ls = 0;
+	for (i = 0; i < 2 * sz; i++)
+		if (seq[i % sz]) {
+			if (i - ls > bl) {
+				bl = i - ls;
+				bs = ls;
+			}
+			ls = i + 1;
+		}
+	if (bl == 0) {
+		ret.middle = sz / 2;
+		ret.start = 0;
+		ret.end = sz;
+		ret.all = 1;
+		return ret;
+	}
+
+	ret.start = bs % sz;
+	ret.end = (bs + bl - 1) % sz;
+	ret.middle = (bs + (bl - 1) / 2) % sz;
+	ret.length = bl;
+	ret.all = 0;
+
+	return ret;
+}
+
+static void discover_timA_coarse(ramctr_timing * ctrl, int channel,
+				 int slotrank, int *upperA)
+{
+	int timA;
+	int statistics[NUM_LANES][128];
+	int lane;
+
+	for (timA = 0; timA < 128; timA++) {
+		FOR_ALL_LANES {
+			ctrl->timings[channel][slotrank].lanes[lane].timA = timA;
+		}
+		program_timings(ctrl, channel);
+
+		test_timA(ctrl, channel, slotrank);
+
+		FOR_ALL_LANES {
+			statistics[lane][timA] =
+			    !does_lane_work(ctrl, channel, slotrank, lane);
+			printk(BIOS_SPEW, "Astat: %d, %d, %d, %x, %x\n",
+			       channel, slotrank, lane, timA,
+			       statistics[lane][timA]);
+		}
+	}
+	FOR_ALL_LANES {
+		struct run rn = get_longest_zero_run(statistics[lane], 128);
+		ctrl->timings[channel][slotrank].lanes[lane].timA = rn.middle;
+		upperA[lane] = rn.end;
+		if (upperA[lane] < rn.middle)
+			upperA[lane] += 128;
+		printk(BIOS_SPEW, "Aval: %d, %d, %d, %x\n", channel, slotrank,
+		       lane, ctrl->timings[channel][slotrank].lanes[lane].timA);
+		printk(BIOS_SPEW, "Aend: %d, %d, %d, %x\n", channel, slotrank,
+		       lane, upperA[lane]);
+	}
+}
+
+static void discover_timA_fine(ramctr_timing * ctrl, int channel, int slotrank,
+			       int *upperA)
+{
+	int timA_delta;
+	int statistics[NUM_LANES][51];
+	int lane, i;
+
+	memset(statistics, 0, sizeof(statistics));
+
+	for (timA_delta = -25; timA_delta <= 25; timA_delta++) {
+		FOR_ALL_LANES ctrl->timings[channel][slotrank].lanes[lane].
+		    timA = upperA[lane] + timA_delta + 0x40;
+		program_timings(ctrl, channel);
+
+		for (i = 0; i < 100; i++) {
+			test_timA(ctrl, channel, slotrank);
+			FOR_ALL_LANES {
+				statistics[lane][timA_delta + 25] +=
+				    does_lane_work(ctrl, channel, slotrank,
+						   lane);
+			}
+		}
+	}
+	FOR_ALL_LANES {
+		int last_zero, first_all;
+
+		for (last_zero = -25; last_zero <= 25; last_zero++)
+			if (statistics[lane][last_zero + 25])
+				break;
+		last_zero--;
+		for (first_all = -25; first_all <= 25; first_all++)
+			if (statistics[lane][first_all + 25] == 100)
+				break;
+
+		printk(BIOS_SPEW, "lane %d: %d, %d\n", lane, last_zero,
+		       first_all);
+
+		ctrl->timings[channel][slotrank].lanes[lane].timA =
+		    (last_zero + first_all) / 2 + upperA[lane];
+		printk(BIOS_SPEW, "Aval: %d, %d, %d, %x\n", channel, slotrank,
+		       lane, ctrl->timings[channel][slotrank].lanes[lane].timA);
+	}
+}
+
+static void discover_402x(ramctr_timing * ctrl, int channel, int slotrank,
+			  int *upperA)
+{
+	int works[NUM_LANES];
+	int lane;
+	while (1) {
+		int all_works = 1, some_works = 0;
+		program_timings(ctrl, channel);
+		test_timA(ctrl, channel, slotrank);
+		FOR_ALL_LANES {
+			works[lane] =
+			    !does_lane_work(ctrl, channel, slotrank, lane);
+			if (works[lane])
+				some_works = 1;
+			else
+				all_works = 0;
+		}
+		if (all_works)
+			return;
+		if (!some_works) {
+			if (ctrl->timings[channel][slotrank].val_4024 < 2)
+				die("402x discovery failed");
+			ctrl->timings[channel][slotrank].val_4024 -= 2;
+			printk(BIOS_SPEW, "4024 -= 2;\n");
+			continue;
+		}
+		ctrl->timings[channel][slotrank].val_4028 += 2;
+		printk(BIOS_SPEW, "4028 += 2;\n");
+		if (ctrl->timings[channel][slotrank].val_4028 >= 0x10)
+			die("402x discovery failed");
+		FOR_ALL_LANES if (works[lane]) {
+			ctrl->timings[channel][slotrank].lanes[lane].timA +=
+			    128;
+			upperA[lane] += 128;
+			printk(BIOS_SPEW, "increment %d, %d, %d\n", channel,
+			       slotrank, lane);
+		}
+	}
+}
+
+struct timA_minmax {
+	int timA_min_high, timA_max_high;
+};
+
+static void pre_timA_change(ramctr_timing * ctrl, int channel, int slotrank,
+			    struct timA_minmax *mnmx)
+{
+	int lane;
+	mnmx->timA_min_high = 7;
+	mnmx->timA_max_high = 0;
+
+	FOR_ALL_LANES {
+		if (mnmx->timA_min_high >
+		    (ctrl->timings[channel][slotrank].lanes[lane].timA >> 6))
+			mnmx->timA_min_high =
+			    (ctrl->timings[channel][slotrank].lanes[lane].
+			     timA >> 6);
+		if (mnmx->timA_max_high <
+		    (ctrl->timings[channel][slotrank].lanes[lane].timA >> 6))
+			mnmx->timA_max_high =
+			    (ctrl->timings[channel][slotrank].lanes[lane].
+			     timA >> 6);
+	}
+}
+
+static void post_timA_change(ramctr_timing * ctrl, int channel, int slotrank,
+			     struct timA_minmax *mnmx)
+{
+	struct timA_minmax post;
+	int shift_402x = 0;
+
+	/* Get changed maxima.  */
+	pre_timA_change(ctrl, channel, slotrank, &post);
+
+	if (mnmx->timA_max_high - mnmx->timA_min_high <
+	    post.timA_max_high - post.timA_min_high)
+		shift_402x = +1;
+	else if (mnmx->timA_max_high - mnmx->timA_min_high >
+		 post.timA_max_high - post.timA_min_high)
+		shift_402x = -1;
+	else
+		shift_402x = 0;
+
+	ctrl->timings[channel][slotrank].val_4028 += shift_402x;
+	ctrl->timings[channel][slotrank].val_4024 += shift_402x;
+	printk(BIOS_SPEW, "4024 += %d;\n", shift_402x);
+	printk(BIOS_SPEW, "4028 += %d;\n", shift_402x);
+}
+
+static void read_training(ramctr_timing * ctrl)
+{
+	int channel, slotrank, lane;
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS {
+		u32 r32;
+		int all_high, some_high;
+		int upperA[NUM_LANES];
+		struct timA_minmax mnmx;
+
+		 wait_428c(channel);
+		 write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x1f002);
+		 write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+			 0xc01 | (ctrl->tRP << 16));
+		 write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+			 (slotrank << 24) | 0x60400);
+		 write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0);
+		 write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 1);
+
+		 write32(DEFAULT_MCHBAR + 0x3400, (slotrank << 2) | 0x8001);
+
+		 ctrl->timings[channel][slotrank].val_4028 = 4;
+		 ctrl->timings[channel][slotrank].val_4024 = 55;
+		 program_timings(ctrl, channel);
+
+		 discover_timA_coarse(ctrl, channel, slotrank, upperA);
+
+		 all_high = 1;
+		 some_high = 0;
+		 FOR_ALL_LANES {
+			 if (ctrl->timings[channel][slotrank].lanes[lane].
+			     timA >= 0x40)
+				 some_high = 1;
+			 else
+				 all_high = 0;
+		 }
+
+		if (all_high) {
+			ctrl->timings[channel][slotrank].val_4028--;
+			printk(BIOS_SPEW, "4028--;\n");
+			FOR_ALL_LANES {
+				ctrl->timings[channel][slotrank].lanes[lane].
+				    timA -= 0x40;
+				upperA[lane] -= 0x40;
+		
+			}
+		} else if (some_high) {
+			ctrl->timings[channel][slotrank].val_4024++;
+			ctrl->timings[channel][slotrank].val_4028++;
+			printk(BIOS_SPEW, "4024++;\n");
+			printk(BIOS_SPEW, "4028++;\n");
+		}
+
+		program_timings(ctrl, channel);
+
+		pre_timA_change(ctrl, channel, slotrank, &mnmx);
+
+		discover_402x(ctrl, channel, slotrank, upperA);
+
+		post_timA_change(ctrl, channel, slotrank, &mnmx);
+		pre_timA_change(ctrl, channel, slotrank, &mnmx);
+
+		discover_timA_fine(ctrl, channel, slotrank, upperA);
+
+		post_timA_change(ctrl, channel, slotrank, &mnmx);
+		pre_timA_change(ctrl, channel, slotrank, &mnmx);
+
+		FOR_ALL_LANES {
+			ctrl->timings[channel][slotrank].lanes[lane].timA -= mnmx.timA_min_high * 0x40;
+		}
+		ctrl->timings[channel][slotrank].val_4028 -= mnmx.timA_min_high;
+		printk(BIOS_SPEW, "4028 -= %d;\n", mnmx.timA_min_high);
+
+		post_timA_change(ctrl, channel, slotrank, &mnmx);
+
+		printk(BIOS_SPEW, "4/8: %d, %d, %x, %x\n", channel, slotrank,
+		       ctrl->timings[channel][slotrank].val_4024,
+		       ctrl->timings[channel][slotrank].val_4028);
+
+		FOR_ALL_LANES
+		    printk(BIOS_SPEW, "%d, %d, %d, %x\n", channel, slotrank,
+			   lane,
+			   ctrl->timings[channel][slotrank].lanes[lane].timA);
+
+		write32(DEFAULT_MCHBAR + 0x3400, 0);
+
+		r32 = read32(DEFAULT_MCHBAR + 0x5030);
+		write32(DEFAULT_MCHBAR + 0x5030, r32 | 0x20);
+		udelay(1);
+
+		write32(DEFAULT_MCHBAR + 0x5030, r32 & ~0x20);
+
+		udelay(1);
+	}
+
+	FOR_ALL_POPULATED_CHANNELS {
+		program_timings(ctrl, channel);
+	}
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+		write32(DEFAULT_MCHBAR + 0x4080 + 0x400 * channel
+			+ 4 * lane, 0);
+	}
+}
+
+static void test_timC(ramctr_timing * ctrl, int channel, int slotrank)
+{
+	int lane;
+
+	FOR_ALL_LANES {
+		write32(DEFAULT_MCHBAR + 0x4340 + 0x400 * channel + 4 * lane, 0);
+		read32(DEFAULT_MCHBAR + 0x4140 + 0x400 * channel + 4 * lane);
+	}
+
+	wait_428c(channel);
+
+	write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x1f006);
+	write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+		(max((ctrl->tFAW >> 2) + 1, ctrl->tRRD) << 10)
+		| 4 | (ctrl->tRCD << 16));
+
+	write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+		(slotrank << 24) | (6 << 16));
+
+	write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0x244);
+
+	write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel, 0x1f207);
+	write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel, 0x8041001);
+	write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+		(slotrank << 24) | 8);
+	write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0x3e0);
+
+	write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel, 0x1f201);
+	write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel, 0x80411f4);
+	write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel, (slotrank << 24));
+	write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel, 0x242);
+
+	write32(DEFAULT_MCHBAR + 0x422c + 0x400 * channel, 0x1f207);
+	write32(DEFAULT_MCHBAR + 0x423c + 0x400 * channel,
+		0x8000c01 | ((ctrl->CWL + ctrl->tWTR + 5) << 16));
+	write32(DEFAULT_MCHBAR + 0x420c + 0x400 * channel,
+		(slotrank << 24) | 8);
+	write32(DEFAULT_MCHBAR + 0x421c + 0x400 * channel, 0x3e0);
+
+	write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 0xc0001);
+
+	wait_428c(channel);
+
+	write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x1f002);
+	write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+		0xc01 | (ctrl->tRP << 16));
+	write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+		(slotrank << 24) | 0x60400);
+	write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0x240);
+
+	write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel, 0x1f006);
+	write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel,
+		(max(ctrl->tRRD, (ctrl->tFAW >> 2) + 1) << 10)
+		| 8 | (ctrl->CAS << 16));
+
+	write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+		(slotrank << 24) | 0x60000);
+
+	write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0x244);
+
+	write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel, 0x1f105);
+	write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel,
+		0x40011f4 | (max(ctrl->tRTP, 8) << 16));
+	write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel, (slotrank << 24));
+	write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel, 0x242);
+
+	write32(DEFAULT_MCHBAR + 0x422c + 0x400 * channel, 0x1f002);
+	write32(DEFAULT_MCHBAR + 0x423c + 0x400 * channel,
+		0xc01 | (ctrl->tRP << 16));
+	write32(DEFAULT_MCHBAR + 0x420c + 0x400 * channel,
+		(slotrank << 24) | 0x60400);
+	write32(DEFAULT_MCHBAR + 0x421c + 0x400 * channel, 0x240);
+	write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 0xc0001);
+	wait_428c(channel);
+}
+
+static void discover_timC(ramctr_timing * ctrl, int channel, int slotrank)
+{
+	int timC;
+	int statistics[NUM_LANES][MAX_TIMC + 1];
+	int lane;
+
+	wait_428c(channel);
+
+	write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x1f002);
+	write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+		0xc01 | (ctrl->tRP << 16));
+	write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+		(slotrank << 24) | 0x60400);
+	write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0x240);
+	write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 1);
+
+	for (timC = 0; timC <= MAX_TIMC; timC++) {
+		FOR_ALL_LANES ctrl->timings[channel][slotrank].lanes[lane].
+		    timC = timC;
+		program_timings(ctrl, channel);
+
+		test_timC(ctrl, channel, slotrank);
+
+		FOR_ALL_LANES {
+			statistics[lane][timC] =
+			    read32(DEFAULT_MCHBAR + 0x4340 + 4 * lane +
+				   0x400 * channel);
+			printk(BIOS_SPEW, "Cstat: %d, %d, %d, %x, %x\n",
+			       channel, slotrank, lane, timC,
+			       statistics[lane][timC]);
+		}
+	}
+	FOR_ALL_LANES {
+		struct run rn =
+		    get_longest_zero_run(statistics[lane], MAX_TIMC + 1);
+		ctrl->timings[channel][slotrank].lanes[lane].timC = rn.middle;
+		if (rn.all)
+			die("timC discovery failed");
+		printk(BIOS_SPEW, "Cval: %d, %d, %d, %x\n", channel, slotrank,
+		       lane, ctrl->timings[channel][slotrank].lanes[lane].timC);
+	}
+}
+
+static int get_precedening_channels(ramctr_timing * ctrl, int target_channel)
+{
+	int channel, ret = 0;
+	FOR_ALL_POPULATED_CHANNELS if (channel < target_channel)
+		 ret++;
+	return ret;
+}
+
+static void fill_pattern0(ramctr_timing * ctrl, int channel, u32 a, u32 b)
+{
+	unsigned j;
+	unsigned channel_offset =
+	    get_precedening_channels(ctrl, channel) * 0x40;
+	printk(BIOS_SPEW, "channel_offset=%x\n", channel_offset);
+	for (j = 0; j < 16; j++)
+		write32(0x04000000 + channel_offset + 4 * j, j & 2 ? b : a);
+	sfence();
+}
+
+static int num_of_channels(const ramctr_timing * ctrl)
+{
+	int ret = 0;
+	int channel;
+	FOR_ALL_POPULATED_CHANNELS ret++;
+	return ret;
+}
+
+static void fill_pattern1(ramctr_timing * ctrl, int channel)
+{
+	unsigned j;
+	unsigned channel_offset =
+	    get_precedening_channels(ctrl, channel) * 0x40;
+	unsigned channel_step = 0x40 * num_of_channels(ctrl);
+	for (j = 0; j < 16; j++)
+		write32(0x04000000 + channel_offset + j * 4, 0xffffffff);
+	for (j = 0; j < 16; j++)
+		write32(0x04000000 + channel_offset + channel_step + j * 4, 0);
+	sfence();
+}
+
+static void precharge(ramctr_timing * ctrl)
+{
+	int channel, slotrank, lane;
+
+	FOR_ALL_POPULATED_CHANNELS {
+		FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+			ctrl->timings[channel][slotrank].lanes[lane].falling =
+			    16;
+			ctrl->timings[channel][slotrank].lanes[lane].rising =
+			    16;
+		} program_timings(ctrl, channel);
+
+		FOR_ALL_POPULATED_RANKS {
+			wait_428c(channel);
+
+			write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel,
+				0x1f000);
+			write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+				0xc01 | (ctrl->tMOD << 16));
+			write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+				(slotrank << 24) | 0x360004);
+			write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel,
+				0x1f105);
+			write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel,
+				0x4041003);
+			write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+				(slotrank << 24) | 0);
+			write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel,
+				0x1f105);
+			write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel,
+				0x1001 | ((ctrl->CAS + 8) << 16));
+			write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel,
+				(slotrank << 24) | 0x60000);
+			write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x422c + 0x400 * channel,
+				0x1f000);
+			write32(DEFAULT_MCHBAR + 0x423c + 0x400 * channel,
+				0xc01 | (ctrl->tMOD << 16));
+			write32(DEFAULT_MCHBAR + 0x420c + 0x400 * channel,
+				(slotrank << 24) | 0x360000);
+			write32(DEFAULT_MCHBAR + 0x421c + 0x400 * channel, 0);
+			write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel,
+				0xc0001);
+
+			wait_428c(channel);
+		}
+
+		FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+			ctrl->timings[channel][slotrank].lanes[lane].falling =
+			    48;
+			ctrl->timings[channel][slotrank].lanes[lane].rising =
+			    48;
+		}
+
+		program_timings(ctrl, channel);
+
+		FOR_ALL_POPULATED_RANKS {
+			wait_428c(channel);
+
+			write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel,
+				0x1f000);
+			write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+				0xc01 | (ctrl->tMOD << 16));
+			write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+				(slotrank << 24) | 0x360004);
+			write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel,
+				0x1f105);
+			write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel,
+				0x4041003);
+			write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+				(slotrank << 24) | 0);
+			write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel,
+				0x1f105);
+			write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel,
+				0x1001 | ((ctrl->CAS + 8) << 16));
+			write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel,
+				(slotrank << 24) | 0x60000);
+			write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x422c + 0x400 * channel,
+				0x1f000);
+			write32(DEFAULT_MCHBAR + 0x423c + 0x400 * channel,
+				0xc01 | (ctrl->tMOD << 16));
+
+			write32(DEFAULT_MCHBAR + 0x420c + 0x400 * channel,
+				(slotrank << 24) | 0x360000);
+			write32(DEFAULT_MCHBAR + 0x421c + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel,
+				0xc0001);
+			wait_428c(channel);
+		}
+	}
+}
+
+static void test_timB(ramctr_timing * ctrl, int channel, int slotrank)
+{
+	write_mrreg(ctrl, channel, slotrank, 1,
+		    0x80 | make_mr1(ctrl, slotrank));
+
+	wait_428c(channel);
+	write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x1f207);
+	write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+		0x8000c01 | ((ctrl->CWL + ctrl->tWLO) << 16));
+	write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+		8 | (slotrank << 24));
+	write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0);
+
+	write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel, 0x1f107);
+	write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel,
+		0x4000c01 | ((ctrl->CAS + 38) << 16));
+	write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+		(slotrank << 24) | 4);
+	write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0);
+
+	write32(DEFAULT_MCHBAR + 0x400 * channel + 0x4284, 0x40001);
+	wait_428c(channel);
+
+	write_mrreg(ctrl, channel, slotrank, 1,
+		    0x1080 | make_mr1(ctrl, slotrank));
+}
+
+static void discover_timB(ramctr_timing * ctrl, int channel, int slotrank)
+{
+	int timB;
+	int statistics[NUM_LANES][128];
+	int lane;
+
+	write32(DEFAULT_MCHBAR + 0x3400, 0x108052 | (slotrank << 2));
+
+	for (timB = 0; timB < 128; timB++) {
+		FOR_ALL_LANES {
+			ctrl->timings[channel][slotrank].lanes[lane].timB = timB;
+		}
+		program_timings(ctrl, channel);
+
+		test_timB(ctrl, channel, slotrank);
+
+		FOR_ALL_LANES {
+			statistics[lane][timB] =
+			    !((read32
+			       (DEFAULT_MCHBAR + lane_registers[lane] +
+				channel * 0x100 + 4 + ((timB / 32) & 1) * 4)
+			       >> (timB % 32)) & 1);
+			printk(BIOS_SPEW, "Bstat: %d, %d, %d, %x, %x\n",
+			       channel, slotrank, lane, timB,
+			       statistics[lane][timB]);
+		}
+	}
+	FOR_ALL_LANES {
+		struct run rn = get_longest_zero_run(statistics[lane], 128);
+		ctrl->timings[channel][slotrank].lanes[lane].timB = rn.start;
+		if (rn.all)
+			die("timB discovery failed");
+		printk(BIOS_SPEW, "Bval: %d, %d, %d, %x\n", channel, slotrank,
+		       lane, ctrl->timings[channel][slotrank].lanes[lane].timB);
+	}
+}
+
+static int get_timB_high_adjust(u64 val)
+{
+	int i;
+	if (val >= 0xfffffffffffff000LL)
+		return 3;
+	if (val >= 0xfffffffffff00000LL)
+		return -1;
+	if (val >= 0xfffffff000000000LL)
+		return -2;
+	if (val >= 0xfff0000000000000LL)
+		return -3;
+
+	for (i = 0; i < 8; i++)
+		if (val >> (8 * (7 - i) + 4))
+			return i;
+	return 8;
+}
+
+static void adjust_high_timB(ramctr_timing * ctrl)
+{
+	int channel, slotrank, lane;
+	write32(DEFAULT_MCHBAR + 0x3400, 0x200);
+	FOR_ALL_POPULATED_CHANNELS {
+		fill_pattern1(ctrl, channel);
+		write32(DEFAULT_MCHBAR | 0x4288 | (channel << 10), 1);
+	}
+	FOR_ALL_POPULATED_CHANNELS FOR_ALL_POPULATED_RANKS {
+
+		write32(DEFAULT_MCHBAR + 0x4288 + 0x400 * channel, 0x10001);
+
+		wait_428c(channel);
+
+		write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x1f006);
+		write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+			0xc01 | (ctrl->tRCD << 16));
+		write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+			(slotrank << 24) | 0x60000);
+		write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0);
+
+		write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel, 0x1f207);
+		write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel, 0x8040c01);
+		write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+			(slotrank << 24) | 0x8);
+		write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0x3e0);
+
+		write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel, 0x1f201);
+		write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel, 0x8041003);
+		write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel,
+			(slotrank << 24));
+		write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel, 0x3e2);
+
+		write32(DEFAULT_MCHBAR + 0x422c + 0x400 * channel, 0x1f207);
+		write32(DEFAULT_MCHBAR + 0x423c + 0x400 * channel,
+			0x8000c01 | ((ctrl->CWL + ctrl->tWTR + 5) << 16));
+		write32(DEFAULT_MCHBAR + 0x420c + 0x400 * channel,
+			(slotrank << 24) | 0x8);
+		write32(DEFAULT_MCHBAR + 0x421c + 0x400 * channel, 0x3e0);
+
+		write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 0xc0001);
+
+		wait_428c(channel);
+
+		write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x1f002);
+		write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+			0xc01 | ((ctrl->tRP) << 16));
+		write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+			(slotrank << 24) | 0x60400);
+		write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0x240);
+
+		write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel, 0x1f006);
+		write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel,
+			0xc01 | ((ctrl->tRCD) << 16));
+		write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+			(slotrank << 24) | 0x60000);
+		write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0);
+
+		write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel, 0x3f105);
+		write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel,
+			0x4000c01 |
+			((ctrl->tRP +
+			  ctrl->timings[channel][slotrank].val_4024 +
+			  ctrl->timings[channel][slotrank].val_4028) << 16));
+		write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel,
+			(slotrank << 24) | 0x60008);
+		write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel, 0);
+
+		write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 0x80001);
+		wait_428c(channel);
+		FOR_ALL_LANES {
+			u64 res =
+			    read32(DEFAULT_MCHBAR + lane_registers[lane] +
+				   0x100 * channel + 4);
+			 res |=
+			    ((u64) read32(DEFAULT_MCHBAR + lane_registers[lane] +
+					  0x100 * channel + 8)) << 32;
+			 ctrl->timings[channel][slotrank].lanes[lane].timB +=
+			    get_timB_high_adjust(res) * 64;
+
+			 printk(BIOS_DEBUG, "High adjust %d:%016llx\n", lane, res);
+			 printk(BIOS_SPEW, "Bval+: %d, %d, %d, %x\n", channel,
+				slotrank, lane,
+				ctrl->timings[channel][slotrank].lanes[lane].
+				timB);
+		}
+	}
+	write32(DEFAULT_MCHBAR + 0x3400, 0);
+}
+
+static void write_op(ramctr_timing * ctrl, int channel)
+{
+	int slotrank;
+
+	wait_428c(channel);
+
+	/* choose an existing rank.  */
+	slotrank = !(ctrl->rankmap[channel] & 1) ? 2 : 0;
+
+	write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x0f003);
+	write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel, 0x41001);
+
+	write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+		(slotrank << 24) | 0x60000);
+
+	write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0x3e0);
+
+	write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 1);
+	wait_428c(channel);
+}
+
+static void write_training(ramctr_timing * ctrl)
+{
+	int channel, slotrank, lane;
+	u32 r32;
+
+	FOR_ALL_POPULATED_CHANNELS
+	    write32(DEFAULT_MCHBAR + 0x4008 + 0x400 * channel,
+		    read32(DEFAULT_MCHBAR + 0x4008 +
+			   0x400 * channel) | 0x8000000);
+
+	FOR_ALL_POPULATED_CHANNELS {
+		write_op(ctrl, channel);
+		write32(DEFAULT_MCHBAR + 0x4020 + 0x400 * channel,
+			read32(DEFAULT_MCHBAR + 0x4020 +
+			       0x400 * channel) | 0x200000);
+	}
+	write32(DEFAULT_MCHBAR + 0x5030, read32(DEFAULT_MCHBAR + 0x5030) & ~8);
+	FOR_ALL_POPULATED_CHANNELS {
+		write_op(ctrl, channel);
+	}
+
+	FOR_ALL_CHANNELS
+	    FOR_ALL_POPULATED_RANKS
+		write_mrreg(ctrl, channel, slotrank, 1,
+			    make_mr1(ctrl, slotrank) | 0x1080);
+
+	write32(DEFAULT_MCHBAR + 0x3400, 0x108052);
+
+	r32 = read32(DEFAULT_MCHBAR + 0x5030);
+	write32(DEFAULT_MCHBAR + 0x5030, r32 | 0x20);
+	udelay(1);
+
+	write32(DEFAULT_MCHBAR + 0x5030, r32 & ~0x20);
+
+	udelay(1);
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS
+		discover_timB(ctrl, channel, slotrank);
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS
+		write_mrreg(ctrl, channel,
+			    slotrank, 1, make_mr1(ctrl, slotrank));
+
+	write32(DEFAULT_MCHBAR + 0x3400, 0);
+
+	FOR_ALL_POPULATED_CHANNELS
+		wait_428c(channel);
+
+	write32(DEFAULT_MCHBAR + 0x5030, read32(DEFAULT_MCHBAR + 0x5030) | 8);
+
+	FOR_ALL_POPULATED_CHANNELS {
+		write32(DEFAULT_MCHBAR + 0x4020 + 0x400 * channel,
+			~0x00200000 & read32(DEFAULT_MCHBAR + 0x4020 +
+					     0x400 * channel));
+		read32(DEFAULT_MCHBAR + 0x428c + 0x400 * channel);
+		wait_428c(channel);
+
+		write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x0f003);
+		write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel, 0x659001);
+		write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel, 0x60000);
+		write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0x3e0);
+
+		write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 1);
+		wait_428c(channel);
+	}
+
+	r32 = read32(DEFAULT_MCHBAR + 0x5030);
+	write32(DEFAULT_MCHBAR + 0x5030, r32 | 0x20);
+	udelay(1);
+
+	write32(DEFAULT_MCHBAR + 0x5030, r32 & ~0x20);
+
+	udelay(1);
+
+	printk(BIOS_SPEW, "CPE\n");
+	precharge(ctrl);
+	printk(BIOS_SPEW, "CPF\n");
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+		read32(DEFAULT_MCHBAR + 0x4080 + 0x400 * channel + 4 * lane);
+		write32(DEFAULT_MCHBAR + 0x4080 + 0x400 * channel + 4 * lane,
+			0);
+	}
+
+	FOR_ALL_POPULATED_CHANNELS {
+		fill_pattern0(ctrl, channel, 0xaaaaaaaa, 0x55555555);
+		write32(DEFAULT_MCHBAR | 0x4288 | (channel << 10), 0);
+	}
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS
+		discover_timC(ctrl, channel, slotrank);
+
+	FOR_ALL_POPULATED_CHANNELS
+		program_timings(ctrl, channel);
+
+	adjust_high_timB(ctrl);
+
+	FOR_ALL_POPULATED_CHANNELS
+		program_timings(ctrl, channel);
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+		read32(DEFAULT_MCHBAR + 0x4080 + 0x400 * channel + 4 * lane);
+		write32(DEFAULT_MCHBAR + 0x4080 + 0x400 * channel + 4 * lane,
+			0);
+	}
+}
+
+static int test_320c(ramctr_timing * ctrl, int channel, int slotrank)
+{
+	struct ram_rank_timings saved_rt = ctrl->timings[channel][slotrank];
+	int timC_delta;
+	int lanes_ok = 0;
+	int ctr = 0;
+	int lane;
+
+	for (timC_delta = -5; timC_delta <= 5; timC_delta++) {
+		FOR_ALL_LANES {
+			ctrl->timings[channel][slotrank].lanes[lane].timC =
+			    saved_rt.lanes[lane].timC + timC_delta;
+		}
+		program_timings(ctrl, channel);
+		FOR_ALL_LANES {
+			write32(DEFAULT_MCHBAR + 4 * lane + 0x4f40, 0);
+		}
+
+		write32(DEFAULT_MCHBAR + 0x4288 + 0x400 * channel, 0x1f);
+
+		wait_428c(channel);
+
+		write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x1f006);
+		write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+			((max(ctrl->tRRD, (ctrl->tFAW >> 2) + 1)) << 10)
+			| 8 | (ctrl->tRCD << 16));
+
+		write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+			(slotrank << 24) | ctr | 0x60000);
+
+		write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0x244);
+
+		write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel, 0x1f201);
+		write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel,
+			0x8001020 | ((ctrl->CWL + ctrl->tWTR + 8) << 16));
+		write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+			(slotrank << 24));
+		write32(DEFAULT_MCHBAR + 0x4244 + 0x400 * channel, 0x389abcd);
+		write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0x20e42);
+
+		write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel, 0x1f105);
+		write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel,
+			0x4001020 | (max(ctrl->tRTP, 8) << 16));
+		write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel,
+			(slotrank << 24));
+		write32(DEFAULT_MCHBAR + 0x4248 + 0x400 * channel, 0x389abcd);
+		write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel, 0x20e42);
+
+		write32(DEFAULT_MCHBAR + 0x422c + 0x400 * channel, 0x1f002);
+		write32(DEFAULT_MCHBAR + 0x423c + 0x400 * channel, 0xf1001);
+		write32(DEFAULT_MCHBAR + 0x420c + 0x400 * channel,
+			(slotrank << 24) | 0x60400);
+		write32(DEFAULT_MCHBAR + 0x421c + 0x400 * channel, 0x240);
+
+		write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 0xc0001);
+		wait_428c(channel);
+		FOR_ALL_LANES {
+			u32 r32 =
+			    read32(DEFAULT_MCHBAR + 0x4340 + 4 * lane +
+				   0x400 * channel);
+
+			if (r32 == 0)
+				lanes_ok |= 1 << lane;
+		}
+		ctr++;
+		if (lanes_ok == ((1 << NUM_LANES) - 1))
+			break;
+	}
+
+	ctrl->timings[channel][slotrank] = saved_rt;
+
+	printk(BIOS_SPEW, "3lanes: %x\n", lanes_ok);
+	return lanes_ok != ((1 << NUM_LANES) - 1);
+}
+
+#include "raminit_patterns.h"
+
+static void fill_pattern5(ramctr_timing * ctrl, int channel, int patno)
+{
+	unsigned i, j;
+	unsigned channel_offset =
+	    get_precedening_channels(ctrl, channel) * 0x40;
+	unsigned channel_step = 0x40 * num_of_channels(ctrl);
+
+	if (patno) {
+		u8 base8 = 0x80 >> ((patno - 1) % 8);
+		u32 base = base8 | (base8 << 8) | (base8 << 16) | (base8 << 24);
+		for (i = 0; i < 32; i++) {
+			for (j = 0; j < 16; j++) {
+				u32 val = use_base[patno - 1][i] & (1 << (j / 2)) ? base : 0;
+				if (invert[patno - 1][i] & (1 << (j / 2)))
+					val = ~val;
+				write32(0x04000000 + channel_offset + i * channel_step +
+					j * 4, val);
+			}
+		}
+
+	} else {
+		for (i = 0; i < sizeof(pattern) / sizeof(pattern[0]); i++) {
+			for (j = 0; j < 16; j++)
+				write32(0x04000000 + channel_offset + i * channel_step +
+					j * 4, pattern[i][j]);
+		}
+		sfence();
+	}
+}
+
+static void reprogram_320c(ramctr_timing * ctrl)
+{
+	int channel, slotrank;
+	u32 r32;
+
+	FOR_ALL_POPULATED_CHANNELS {
+		wait_428c(channel);
+
+		/* choose an existing rank.  */
+		slotrank = !(ctrl->rankmap[channel] & 1) ? 2 : 0;
+
+		write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x0f003);
+		write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel, 0x41001);
+
+		write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+			(slotrank << 24) | 0x60000);
+
+		write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0x3e0);
+
+		write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 1);
+		wait_428c(channel);
+		write32(DEFAULT_MCHBAR + 0x4020 + 0x400 * channel,
+			read32(DEFAULT_MCHBAR + 0x4020 +
+			       0x400 * channel) | 0x200000);
+	}
+	write32(DEFAULT_MCHBAR + 0x5030, read32(DEFAULT_MCHBAR + 0x5030) & ~8);
+	FOR_ALL_POPULATED_CHANNELS {
+		wait_428c(channel);
+
+		/* choose an existing rank.  */
+		slotrank = !(ctrl->rankmap[channel] & 1) ? 2 : 0;
+
+		write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x0f003);
+		write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel, 0x41001);
+
+		write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+			(slotrank << 24) | 0x60000);
+
+		write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0x3e0);
+
+		write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 1);
+		wait_428c(channel);
+	}
+
+	/* jedec reset */
+	dram_jedecreset(ctrl);
+	/* mrs commands. */
+	dram_mrscommands(ctrl);
+
+	r32 = read32(DEFAULT_MCHBAR + 0x5030);
+	write32(DEFAULT_MCHBAR + 0x5030, r32 | 0x20);
+	udelay(1);
+
+	write32(DEFAULT_MCHBAR + 0x5030, r32 & ~0x20);
+
+	udelay(1);
+}
+
+#define MIN_C320C_LEN 13
+
+static int try_reg_4004_b30(ramctr_timing * ctrl, int r4004b30)
+{
+	struct ram_rank_timings saved_timings[NUM_CHANNELS][NUM_SLOTRANKS];
+	int channel, slotrank;
+	int c320c;
+	int stat[NUM_SLOTRANKS][256];
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS {
+		saved_timings[channel][slotrank] = ctrl->timings[channel][slotrank];
+	}
+
+	FOR_ALL_POPULATED_CHANNELS {
+		ctrl->reg_4004_b30[channel] = r4004b30;
+	}
+
+	FOR_ALL_POPULATED_CHANNELS
+	    MCHBAR32(0x4004 + 0x400 * channel) =
+		ctrl->tRRD
+		| (ctrl->tRTP << 4)
+		| (ctrl->tCKE << 8)
+		| (ctrl->tWTR << 12)
+		| (ctrl->tFAW << 16)
+		| (ctrl->tWR << 24)
+		| (ctrl->reg_4004_b30[channel] << 30);
+
+
+	FOR_ALL_CHANNELS {
+		int delta = 0;
+		if (ctrl->reg_4004_b30[channel] == 2)
+			delta = 2;
+		else if (ctrl->reg_4004_b30[channel] == 0)
+			delta = 4;
+
+		FOR_ALL_POPULATED_RANKS {
+			ctrl->timings[channel][slotrank].val_4024 -= delta;
+		}
+	}
+
+	FOR_ALL_POPULATED_CHANNELS {
+		for (c320c = -127; c320c <= 127; c320c++) {
+			FOR_ALL_POPULATED_RANKS {
+				ctrl->timings[channel][slotrank].val_320c = c320c;
+			}
+			program_timings(ctrl, channel);
+			reprogram_320c(ctrl);
+			FOR_ALL_POPULATED_RANKS {
+				stat[slotrank][c320c + 127] =
+				    test_320c(ctrl, channel, slotrank);
+				printk(BIOS_SPEW, "3stat: %d, %d, %d: %d\n",
+				       channel, slotrank, c320c,
+				       stat[slotrank][c320c + 127]);
+			}
+		}
+		FOR_ALL_POPULATED_RANKS {
+			struct run rn =
+			    get_longest_zero_run(stat[slotrank], 255);
+			ctrl->timings[channel][slotrank].val_320c =
+			    rn.middle - 127;
+			printk(BIOS_SPEW, "3val: %d, %d: %d\n", channel,
+			       slotrank,
+			       ctrl->timings[channel][slotrank].val_320c);
+			if (rn.all || rn.length < MIN_C320C_LEN) {
+				FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS {
+					ctrl->timings[channel][slotrank] = saved_timings[channel][slotrank];
+				}
+				return 0;
+			}
+		}
+	}
+	return 1;
+}
+
+static void command_training(ramctr_timing * ctrl)
+{
+	int channel;
+
+	FOR_ALL_POPULATED_CHANNELS {
+		fill_pattern5(ctrl, channel, 0);
+		write32(DEFAULT_MCHBAR + 0x4288 + 0x400 * channel, 0x1f);
+	}
+
+	if (!try_reg_4004_b30(ctrl, 0) && !try_reg_4004_b30(ctrl, 2))
+		die("c320c discovery failed");		
+
+	FOR_ALL_POPULATED_CHANNELS {
+		program_timings(ctrl, channel);
+	}
+
+	reprogram_320c(ctrl);
+}
+
+static void discover_edges_real(ramctr_timing * ctrl, int channel, int slotrank,
+				int *edges)
+{
+	int edge;
+	int statistics[NUM_LANES][MAX_EDGE_TIMING + 1];
+	int lane;
+
+	for (edge = 0; edge <= MAX_EDGE_TIMING; edge++) {
+		FOR_ALL_LANES {
+			ctrl->timings[channel][slotrank].lanes[lane].rising =
+			    edge;
+			ctrl->timings[channel][slotrank].lanes[lane].falling =
+			    edge;
+		}
+		printk(BIOS_SPEW, "edge %02x\n", edge);
+		program_timings(ctrl, channel);
+
+		FOR_ALL_LANES {
+			write32(DEFAULT_MCHBAR + 0x4340 + 0x400 * channel +
+				4 * lane, 0);
+			read32(DEFAULT_MCHBAR + 0x400 * channel + 4 * lane +
+			       0x4140);
+		}
+
+		wait_428c(channel);
+
+		write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x1f000);
+		write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+			(0xc01 | (ctrl->tMOD << 16)));
+		write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+			(slotrank << 24) | 0x360004);
+		write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0);
+
+		write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel, 0x1f105);
+		write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel, 0x40411f4);
+		write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+			(slotrank << 24));
+		write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0);
+
+		write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel, 0x1f105);
+		write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel,
+			0x1001 | ((ctrl->CAS + 8) << 16));
+		write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel,
+			(slotrank << 24) | 0x60000);
+		write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel, 0);
+
+		write32(DEFAULT_MCHBAR + 0x422c + 0x400 * channel, 0x1f000);
+		write32(DEFAULT_MCHBAR + 0x423c + 0x400 * channel,
+			(0xc01 | (ctrl->tMOD << 16)));
+		write32(DEFAULT_MCHBAR + 0x420c + 0x400 * channel,
+			(slotrank << 24) | 0x360000);
+		write32(DEFAULT_MCHBAR + 0x421c + 0x400 * channel, 0);
+
+		write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel, 0xc0001);
+
+		wait_428c(channel);
+
+		FOR_ALL_LANES {
+			statistics[lane][edge] =
+			    read32(DEFAULT_MCHBAR + 0x4340 + 0x400 * channel +
+				   lane * 4);
+		}
+	}
+	FOR_ALL_LANES {
+		struct run rn =
+		    get_longest_zero_run(statistics[lane], MAX_EDGE_TIMING + 1);
+		edges[lane] = rn.middle;
+		if (rn.all)
+			die("edge discovery failed");
+		printk(BIOS_SPEW, "eval %d, %d, %d, %02x\n", channel, slotrank,
+		       lane, edges[lane]);
+	}
+}
+
+static void discover_edges(ramctr_timing * ctrl)
+{
+	int falling_edges[NUM_CHANNELS][NUM_SLOTRANKS][NUM_LANES];
+	int rising_edges[NUM_CHANNELS][NUM_SLOTRANKS][NUM_LANES];
+	int channel, slotrank, lane;
+	u32 r32;
+
+	write32(DEFAULT_MCHBAR + 0x3400, 0);
+
+	r32 = read32(DEFAULT_MCHBAR + 0x5030);
+	write32(DEFAULT_MCHBAR + 0x5030, r32 | 0x20);
+	udelay(1);
+
+	write32(DEFAULT_MCHBAR + 0x5030, r32 & ~0x20);
+
+	udelay(1);
+
+	FOR_ALL_POPULATED_CHANNELS FOR_ALL_LANES {
+		write32(DEFAULT_MCHBAR + 4 * lane +
+			0x400 * channel + 0x4080, 0);
+	}
+
+	FOR_ALL_POPULATED_CHANNELS {
+		fill_pattern0(ctrl, channel, 0, 0);
+		write32(DEFAULT_MCHBAR | 0x4288 | (channel << 10), 0);
+		FOR_ALL_LANES {
+			read32(DEFAULT_MCHBAR + 0x400 * channel +
+			       lane * 4 + 0x4140);
+		}
+
+		FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+			ctrl->timings[channel][slotrank].lanes[lane].falling =
+			    16;
+			ctrl->timings[channel][slotrank].lanes[lane].rising =
+			    16;
+		}
+
+		program_timings(ctrl, channel);
+
+		FOR_ALL_POPULATED_RANKS {
+			wait_428c(channel);
+
+			write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel,
+				0x1f000);
+			write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+				0xc01 | (ctrl->tMOD << 16));
+			write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+				(slotrank << 24) | 0x360004);
+			write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel,
+				0x1f105);
+			write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel,
+				0x4041003);
+			write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+				(slotrank << 24) | 0);
+			write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel,
+				0x1f105);
+			write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel,
+				0x1001 | ((ctrl->CAS + 8) << 16));
+			write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel,
+				(slotrank << 24) | 0x60000);
+			write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x422c + 0x400 * channel,
+				0x1f000);
+			write32(DEFAULT_MCHBAR + 0x423c + 0x400 * channel,
+				0xc01 | (ctrl->tMOD << 16));
+			write32(DEFAULT_MCHBAR + 0x420c + 0x400 * channel,
+				(slotrank << 24) | 0x360000);
+			write32(DEFAULT_MCHBAR + 0x421c + 0x400 * channel, 0);
+			write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel,
+				0xc0001);
+
+			wait_428c(channel);
+		}
+
+		FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+			ctrl->timings[channel][slotrank].lanes[lane].falling =
+			    48;
+			ctrl->timings[channel][slotrank].lanes[lane].rising =
+			    48;
+		}
+
+		program_timings(ctrl, channel);
+
+		FOR_ALL_POPULATED_RANKS {
+			wait_428c(channel);
+
+			write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel,
+				0x1f000);
+			write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+				0xc01 | (ctrl->tMOD << 16));
+			write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+				(slotrank << 24) | 0x360004);
+			write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel,
+				0x1f105);
+			write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel,
+				0x4041003);
+			write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+				(slotrank << 24) | 0);
+			write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel,
+				0x1f105);
+			write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel,
+				0x1001 | ((ctrl->CAS + 8) << 16));
+			write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel,
+				(slotrank << 24) | 0x60000);
+			write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x422c + 0x400 * channel,
+				0x1f000);
+			write32(DEFAULT_MCHBAR + 0x423c + 0x400 * channel,
+				0xc01 | (ctrl->tMOD << 16));
+			write32(DEFAULT_MCHBAR + 0x420c + 0x400 * channel,
+				(slotrank << 24) | 0x360000);
+			write32(DEFAULT_MCHBAR + 0x421c + 0x400 * channel, 0);
+
+			write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel,
+				0xc0001);
+			wait_428c(channel);
+		}
+
+		FOR_ALL_LANES {
+			write32(DEFAULT_MCHBAR + 0x4080 + 0x400 * channel +
+				lane * 4,
+				~read32(DEFAULT_MCHBAR + 0x4040 +
+					0x400 * channel + lane * 4) & 0xff);
+		}
+
+		fill_pattern0(ctrl, channel, 0, 0xffffffff);
+		write32(DEFAULT_MCHBAR | 0x4288 | (channel << 10), 0);
+	}
+
+	/* FIXME: under some conditions (older chipsets?) vendor BIOS sets both edges to the same value.  */
+	write32(DEFAULT_MCHBAR + 0x4eb0, 0x300);
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS {
+		discover_edges_real(ctrl, channel, slotrank,
+				    falling_edges[channel][slotrank]);
+	}
+
+	write32(DEFAULT_MCHBAR + 0x4eb0, 0x200);
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS {
+		discover_edges_real(ctrl, channel, slotrank,
+				    rising_edges[channel][slotrank]);
+	}
+
+	write32(DEFAULT_MCHBAR + 0x4eb0, 0);
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+		ctrl->timings[channel][slotrank].lanes[lane].falling =
+		    falling_edges[channel][slotrank][lane];
+		ctrl->timings[channel][slotrank].lanes[lane].rising =
+		    rising_edges[channel][slotrank][lane];
+	}
+
+	FOR_ALL_POPULATED_CHANNELS {
+		program_timings(ctrl, channel);
+	}
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+		write32(DEFAULT_MCHBAR + 0x4080 + 0x400 * channel + 4 * lane,
+			0);
+	}
+}
+
+static void discover_edges_write_real(ramctr_timing * ctrl, int channel,
+				      int slotrank, int *edges)
+{
+	int edge;
+	u32 raw_statistics[MAX_EDGE_TIMING + 1];
+	int statistics[MAX_EDGE_TIMING + 1];
+	const int reg3000b24[] = { 0, 0xc, 0x2c };
+	int lane, i;
+	int lower[NUM_LANES];
+	int upper[NUM_LANES];
+	int pat;
+
+	FOR_ALL_LANES {
+		lower[lane] = 0;
+		upper[lane] = MAX_EDGE_TIMING;
+	}
+
+	for (i = 0; i < 3; i++) {
+		write32(DEFAULT_MCHBAR + 0x3000 + 0x100 * channel,
+			reg3000b24[i] << 24);
+		for (pat = 0; pat < NUM_PATTERNS; pat++) {
+			fill_pattern5(ctrl, channel, pat);
+			write32(DEFAULT_MCHBAR + 0x4288 + 0x400 * channel, 0x1f);
+			printk(BIOS_SPEW, "patterned\n");
+			printk(BIOS_SPEW, "[%x] = 0x%08x\n(%d, %d)\n",
+			       0x3000 + 0x100 * channel, reg3000b24[i] << 24, channel,
+			       slotrank);
+			for (edge = 0; edge <= MAX_EDGE_TIMING; edge++) {
+				FOR_ALL_LANES {
+					ctrl->timings[channel][slotrank].lanes[lane].
+						rising = edge;
+					ctrl->timings[channel][slotrank].lanes[lane].
+						falling = edge;
+				}
+				program_timings(ctrl, channel);
+
+				FOR_ALL_LANES {
+					write32(DEFAULT_MCHBAR + 0x4340 +
+						0x400 * channel + 4 * lane, 0);
+					read32(DEFAULT_MCHBAR + 0x400 * channel +
+					       4 * lane + 0x4140);
+				}
+				wait_428c(channel);
+
+				write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel,
+					0x1f006);
+				write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+					0x4 | (ctrl->tRCD << 16)
+					| (max(ctrl->tRRD, (ctrl->tFAW >> 2) + 1) <<
+					   10));
+				write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+					(slotrank << 24) | 0x60000);
+				write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel,
+					0x240);
+
+				write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel,
+					0x1f201);
+				write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel,
+					0x8005020 | ((ctrl->tWTR + ctrl->CWL + 8) <<
+						     16));
+				write32(DEFAULT_MCHBAR + 0x4204 + 0x400 * channel,
+					(slotrank << 24));
+				write32(DEFAULT_MCHBAR + 0x4214 + 0x400 * channel,
+					0x242);
+
+				write32(DEFAULT_MCHBAR + 0x4228 + 0x400 * channel,
+					0x1f105);
+				write32(DEFAULT_MCHBAR + 0x4238 + 0x400 * channel,
+					0x4005020 | (max(ctrl->tRTP, 8) << 16));
+				write32(DEFAULT_MCHBAR + 0x4208 + 0x400 * channel,
+					(slotrank << 24));
+				write32(DEFAULT_MCHBAR + 0x4218 + 0x400 * channel,
+					0x242);
+
+				write32(DEFAULT_MCHBAR + 0x422c + 0x400 * channel,
+					0x1f002);
+				write32(DEFAULT_MCHBAR + 0x423c + 0x400 * channel,
+					0xc01 | (ctrl->tRP << 16));
+				write32(DEFAULT_MCHBAR + 0x420c + 0x400 * channel,
+					(slotrank << 24) | 0x60400);
+				write32(DEFAULT_MCHBAR + 0x421c + 0x400 * channel, 0);
+
+				write32(DEFAULT_MCHBAR + 0x4284 + 0x400 * channel,
+					0xc0001);
+				wait_428c(channel);
+				FOR_ALL_LANES {
+					read32(DEFAULT_MCHBAR + 0x4340 +
+					       0x400 * channel + lane * 4);
+				}
+
+				raw_statistics[edge] =
+					MCHBAR32(0x436c + 0x400 * channel);
+			}
+			FOR_ALL_LANES {
+				struct run rn;
+				for (edge = 0; edge <= MAX_EDGE_TIMING; edge++)
+					statistics[edge] =
+						! !(raw_statistics[edge] & (1 << lane));
+				rn = get_longest_zero_run(statistics,
+							  MAX_EDGE_TIMING + 1);
+				printk(BIOS_SPEW,
+				       "edges: %d, %d, %d: 0x%x-0x%x-0x%x, 0x%x-0x%x\n",
+				       channel, slotrank, i, rn.start, rn.middle,
+				       rn.end, rn.start + ctrl->edge_offset[i],
+				       rn.end - ctrl->edge_offset[i]);
+				lower[lane] =
+					max(rn.start + ctrl->edge_offset[i], lower[lane]);
+				upper[lane] =
+					min(rn.end - ctrl->edge_offset[i], upper[lane]);
+				edges[lane] = (lower[lane] + upper[lane]) / 2;
+
+			}
+		}
+	}
+
+	write32(DEFAULT_MCHBAR + 0x3000, 0);
+	printk(BIOS_SPEW, "CPA\n");
+}
+
+static void discover_edges_write(ramctr_timing * ctrl)
+{
+	int falling_edges[NUM_CHANNELS][NUM_SLOTRANKS][NUM_LANES];
+	int rising_edges[NUM_CHANNELS][NUM_SLOTRANKS][NUM_LANES];
+	int channel, slotrank, lane;
+
+	/* FIXME: under some conditions (older chipsets?) vendor BIOS sets both edges to the same value.  */
+	write32(DEFAULT_MCHBAR + 0x4eb0, 0x300);
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS {
+		discover_edges_write_real(ctrl, channel, slotrank,
+					  falling_edges[channel][slotrank]);
+	}
+
+	write32(DEFAULT_MCHBAR + 0x4eb0, 0x200);
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS {
+		discover_edges_write_real(ctrl, channel, slotrank,
+					  rising_edges[channel][slotrank]);
+	}
+	
+	write32(DEFAULT_MCHBAR + 0x4eb0, 0);
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+		ctrl->timings[channel][slotrank].lanes[lane].falling =
+		    falling_edges[channel][slotrank][lane];
+		ctrl->timings[channel][slotrank].lanes[lane].rising =
+		    rising_edges[channel][slotrank][lane];
+	}
+
+	FOR_ALL_POPULATED_CHANNELS
+		program_timings(ctrl, channel);
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+		write32(DEFAULT_MCHBAR + 0x4080 + 0x400 * channel + 4 * lane,
+			0);
+	}
+}
+
+static void test_timC_write(ramctr_timing *ctrl, int channel, int slotrank)
+{
+	wait_428c(channel);
+	write32(DEFAULT_MCHBAR + 0x4220 + 0x400 * channel, 0x1f006);
+	write32(DEFAULT_MCHBAR + 0x4230 + 0x400 * channel,
+		(max((ctrl->tFAW >> 2) + 1, ctrl->tRRD)
+		 << 10) | (ctrl->tRCD << 16) | 4);
+	write32(DEFAULT_MCHBAR + 0x4200 + 0x400 * channel,
+		(slotrank << 24) | 0x60000);
+	write32(DEFAULT_MCHBAR + 0x4210 + 0x400 * channel, 0x244);
+
+	write32(DEFAULT_MCHBAR + 0x4224 + 0x400 * channel, 0x1f201);
+	write32(DEFAULT_MCHBAR + 0x4234 + 0x400 * channel,
+		0x80011e0 |
+		((ctrl->tWTR + ctrl->CWL + 8) << 16));
+	write32(DEFAULT_MCHBAR + 0x4204 +
+		0x400 * channel, (slotrank << 24));
+	write32(DEFAULT_MCHBAR + 0x4214 +
+		0x400 * channel, 0x242);
+
+	write32(DEFAULT_MCHBAR + 0x4228 +
+		0x400 * channel, 0x1f105);
+	write32(DEFAULT_MCHBAR + 0x4238 +
+		0x400 * channel,
+		0x40011e0 | (max(ctrl->tRTP, 8) << 16));
+	write32(DEFAULT_MCHBAR + 0x4208 +
+		0x400 * channel, (slotrank << 24));
+	write32(DEFAULT_MCHBAR + 0x4218 +
+		0x400 * channel, 0x242);
+
+	write32(DEFAULT_MCHBAR + 0x422c +
+		0x400 * channel, 0x1f002);
+	write32(DEFAULT_MCHBAR + 0x423c +
+		0x400 * channel,
+		0x1001 | (ctrl->tRP << 16));
+	write32(DEFAULT_MCHBAR + 0x420c +
+		0x400 * channel,
+		(slotrank << 24) | 0x60400);
+	write32(DEFAULT_MCHBAR + 0x421c +
+		0x400 * channel, 0);
+
+	write32(DEFAULT_MCHBAR + 0x4284 +
+		0x400 * channel, 0xc0001);
+	wait_428c(channel);
+}
+
+static void discover_timC_write(ramctr_timing * ctrl)
+{
+	const u8 rege3c_b24[3] = { 0, 0xf, 0x2f };
+	int i, pat;
+
+	int lower[NUM_CHANNELS][NUM_SLOTRANKS][NUM_LANES];
+	int upper[NUM_CHANNELS][NUM_SLOTRANKS][NUM_LANES];
+	int channel, slotrank, lane;
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+		lower[channel][slotrank][lane] = 0;
+		upper[channel][slotrank][lane] = MAX_TIMC;
+	}
+
+	write32(DEFAULT_MCHBAR + 0x4ea8, 1);
+
+	for (i = 0; i < 3; i++)
+		FOR_ALL_POPULATED_CHANNELS {
+			write32(DEFAULT_MCHBAR + 0xe3c + (channel * 0x100),
+				(rege3c_b24[i] << 24)
+				| (read32(DEFAULT_MCHBAR + 0xe3c + (channel * 0x100))
+				   & ~0x3f000000));
+			udelay(2);
+			for (pat = 0; pat < NUM_PATTERNS; pat++) {
+				FOR_ALL_POPULATED_RANKS {
+					int timC;
+					u32 raw_statistics[MAX_TIMC + 1];
+					int statistics[MAX_TIMC + 1];
+
+					fill_pattern5(ctrl, channel, pat);
+					write32(DEFAULT_MCHBAR + 0x4288 + 0x400 * channel, 0x1f);
+					for (timC = 0; timC < MAX_TIMC + 1; timC++) {
+						FOR_ALL_LANES
+							ctrl->timings[channel][slotrank].lanes[lane].timC = timC;
+						program_timings(ctrl, channel);
+
+						test_timC_write (ctrl, channel, slotrank);
+
+						raw_statistics[timC] =
+							MCHBAR32(0x436c + 0x400 * channel);
+					}
+					FOR_ALL_LANES {
+						struct run rn;
+						for (timC = 0; timC <= MAX_TIMC; timC++)
+							statistics[timC] =
+								!!(raw_statistics[timC] &
+								   (1 << lane));
+						rn = get_longest_zero_run(statistics,
+									  MAX_TIMC + 1);
+						if (rn.all)
+							die("timC write discovery failed");
+						printk(BIOS_SPEW,
+						       "timC: %d, %d, %d: 0x%x-0x%x-0x%x, 0x%x-0x%x\n",
+						       channel, slotrank, i, rn.start,
+						       rn.middle, rn.end,
+						       rn.start + ctrl->timC_offset[i],
+						       rn.end - ctrl->timC_offset[i]);
+						lower[channel][slotrank][lane] =
+							max(rn.start + ctrl->timC_offset[i],
+							    lower[channel][slotrank][lane]);
+						upper[channel][slotrank][lane] =
+							min(rn.end - ctrl->timC_offset[i],
+							    upper[channel][slotrank][lane]);
+
+					}
+				}
+			}
+		}
+
+	FOR_ALL_CHANNELS {
+		write32(DEFAULT_MCHBAR + (channel * 0x100) + 0xe3c,
+			0 | (read32(DEFAULT_MCHBAR + (channel * 0x100) + 0xe3c) &
+			     ~0x3f000000));
+		udelay(2);
+	}
+
+	write32(DEFAULT_MCHBAR + 0x4ea8, 0);
+
+	printk(BIOS_SPEW, "CPB\n");
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+		printk(BIOS_SPEW, "timC [%d, %d, %d] = 0x%x\n", channel,
+		       slotrank, lane,
+		       (lower[channel][slotrank][lane] +
+			upper[channel][slotrank][lane]) / 2);
+		ctrl->timings[channel][slotrank].lanes[lane].timC =
+		    (lower[channel][slotrank][lane] +
+		     upper[channel][slotrank][lane]) / 2;
+	}
+	FOR_ALL_POPULATED_CHANNELS {
+		program_timings(ctrl, channel);
+	}
+}
+
+static void normalize_training(ramctr_timing * ctrl)
+{
+	int channel, slotrank, lane;
+	int mat = 0;
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS {
+		int delta;
+		FOR_ALL_LANES mat =
+		    max(ctrl->timings[channel][slotrank].lanes[lane].timA, mat);
+		 delta = (mat >> 6) - ctrl->timings[channel][slotrank].val_4028;
+		 ctrl->timings[channel][slotrank].val_4024 += delta;
+		 ctrl->timings[channel][slotrank].val_4028 += delta;
+	}
+
+	FOR_ALL_POPULATED_CHANNELS {
+		program_timings(ctrl, channel);
+	}
+}
+
+static void write_controller_mr(ramctr_timing * ctrl)
+{
+	int channel, slotrank;
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS {
+		write32(DEFAULT_MCHBAR | 0x0004 | (channel << 8) |
+			lane_registers[slotrank], make_mr0(ctrl, slotrank));
+		write32(DEFAULT_MCHBAR | 0x0008 | (channel << 8) |
+			lane_registers[slotrank], make_mr1(ctrl, slotrank));
+	}
+}
+
+static void channel_test(ramctr_timing * ctrl)
+{
+	int channel, slotrank, lane;
+
+	FOR_ALL_POPULATED_CHANNELS
+	    if (read32(DEFAULT_MCHBAR | 0x42a0 | (channel << 10)) & 0xa000)
+		 die("Mini channel test failed (1)\n");
+	FOR_ALL_POPULATED_CHANNELS {
+		fill_pattern0(ctrl, channel, 0x12345678, 0x98765432);
+
+		write32(DEFAULT_MCHBAR | 0x4288 | (channel << 10), 0);
+	}
+
+	for (slotrank = 0; slotrank < 4; slotrank++)
+		FOR_ALL_CHANNELS
+			if (ctrl->rankmap[channel] & (1 << slotrank)) {
+		FOR_ALL_LANES {
+			write32(DEFAULT_MCHBAR | (0x4f40 + 4 * lane), 0);
+			write32(DEFAULT_MCHBAR | (0x4d40 + 4 * lane), 0);
+		}
+		wait_428c(channel);
+		write32(DEFAULT_MCHBAR | 0x4220 | (channel << 10), 0x0001f006);
+		write32(DEFAULT_MCHBAR | 0x4230 | (channel << 10), 0x0028a004);
+		write32(DEFAULT_MCHBAR | 0x4200 | (channel << 10),
+			0x00060000 | (slotrank << 24));
+		write32(DEFAULT_MCHBAR | 0x4210 | (channel << 10), 0x00000244);
+		write32(DEFAULT_MCHBAR | 0x4224 | (channel << 10), 0x0001f201);
+		write32(DEFAULT_MCHBAR | 0x4234 | (channel << 10), 0x08281064);
+		write32(DEFAULT_MCHBAR | 0x4204 | (channel << 10),
+			0x00000000 | (slotrank << 24));
+		write32(DEFAULT_MCHBAR | 0x4214 | (channel << 10), 0x00000242);
+		write32(DEFAULT_MCHBAR | 0x4228 | (channel << 10), 0x0001f105);
+		write32(DEFAULT_MCHBAR | 0x4238 | (channel << 10), 0x04281064);
+		write32(DEFAULT_MCHBAR | 0x4208 | (channel << 10),
+			0x00000000 | (slotrank << 24));
+		write32(DEFAULT_MCHBAR | 0x4218 | (channel << 10), 0x00000242);
+		write32(DEFAULT_MCHBAR | 0x422c | (channel << 10), 0x0001f002);
+		write32(DEFAULT_MCHBAR | 0x423c | (channel << 10), 0x00280c01);
+		write32(DEFAULT_MCHBAR | 0x420c | (channel << 10),
+			0x00060400 | (slotrank << 24));
+		write32(DEFAULT_MCHBAR | 0x421c | (channel << 10), 0x00000240);
+		write32(DEFAULT_MCHBAR | 0x4284 | (channel << 10), 0x000c0001);
+		wait_428c(channel);
+		FOR_ALL_LANES
+		    if (read32(DEFAULT_MCHBAR | 0x4340 | (channel << 10)))
+			 die("Mini channel test failed (2)\n");
+	}
+}
+
+static void set_scrambling_seed(ramctr_timing * ctrl)
+{
+	int channel;
+
+	/* FIXME: we hardcode seeds. Do we need to use some PRNG for them?
+	   I don't think so.  */
+	static u32 seeds[NUM_CHANNELS][3] = {
+		{0x00009a36, 0xbafcfdcf, 0x46d1ab68},
+		{0x00028bfa, 0x53fe4b49, 0x19ed5483}
+	};
+	FOR_ALL_POPULATED_CHANNELS {
+		MCHBAR32(0x4020 + 0x400 * channel) &= ~0x10000000;
+		write32(DEFAULT_MCHBAR | 0x4034, seeds[channel][0]);
+		write32(DEFAULT_MCHBAR | 0x403c, seeds[channel][1]);
+		write32(DEFAULT_MCHBAR | 0x4038, seeds[channel][2]);
+	}
+}
+
+static void set_4f8c(void)
+{
+	struct cpuid_result cpures;
+	u32 cpu;
+
+	cpures = cpuid(0);
+	cpu = (cpures.eax);
+	if (IS_SANDY_CPU(cpu) && (IS_SANDY_CPU_D0(cpu) || IS_SANDY_CPU_D1(cpu))) {
+		MCHBAR32(0x4f8c) = 0x141D1519;
+	} else {
+		MCHBAR32(0x4f8c) = 0x551D1519;
+	}
+}
+
+static void prepare_training(ramctr_timing * ctrl)
+{
+	int channel;
+
+	FOR_ALL_POPULATED_CHANNELS {
+		// Always drive command bus
+		MCHBAR32(0x4004 + 0x400 * channel) |= 0x20000000;
+	}
+
+	udelay(1);
+
+	FOR_ALL_POPULATED_CHANNELS {
+		wait_428c(channel);
+	}
+}
+
+static void set_4008c(ramctr_timing * ctrl)
+{
+	int channel, slotrank;
+	u32 reg;
+	FOR_ALL_POPULATED_CHANNELS {
+		u32 b20, b4_8_12;
+		int min_320c = 10000;
+		int max_320c = -10000;
+
+		FOR_ALL_POPULATED_RANKS {
+			max_320c = max(ctrl->timings[channel][slotrank].val_320c, max_320c);
+			min_320c = min(ctrl->timings[channel][slotrank].val_320c, min_320c);
+		}
+
+		if (max_320c - min_320c > 51)
+			b20 = 0;
+		else
+			b20 = ctrl->ref_card_offset[channel];
+
+		if (ctrl->reg_320c_range_threshold < max_320c - min_320c)
+			b4_8_12 = 0x3330;
+		else
+			b4_8_12 = 0x2220;
+
+		reg = read32(DEFAULT_MCHBAR | 0x400c | (channel << 10));
+		write32(DEFAULT_MCHBAR | 0x400c | (channel << 10),
+			(reg & 0xFFF0FFFF)
+			| (ctrl->ref_card_offset[channel] << 16)
+			| (ctrl->ref_card_offset[channel] << 18));
+		write32(DEFAULT_MCHBAR | 0x4008 | (channel << 10),
+			0x0a000000
+			| (b20 << 20)
+			| ((ctrl->ref_card_offset[channel] + 2) << 16)
+			| b4_8_12);
+	}
+}
+
+static void set_42a0(ramctr_timing * ctrl)
+{
+	int channel;
+	FOR_ALL_POPULATED_CHANNELS {
+		write32(DEFAULT_MCHBAR | (0x42a0 + 0x400 * channel),
+			0x00001000 | ctrl->rankmap[channel]);
+		MCHBAR32(0x4004 + 0x400 * channel) &= ~0x20000000;	// OK
+	}
+}
+
+static int encode_5d10(int ns)
+{
+  return (ns + 499) / 500;
+}
+
+/* FIXME: values in this function should be hardware revision-dependent.  */
+static void final_registers(ramctr_timing * ctrl)
+{
+	int channel;
+	int t1_cycles = 0, t1_ns = 0, t2_ns;
+	int t3_ns;
+	u32 r32;
+
+	write32(DEFAULT_MCHBAR | 0x4cd4, 0x00000046);
+
+	write32(DEFAULT_MCHBAR | 0x400c, (read32(DEFAULT_MCHBAR | 0x400c) & 0xFFFFCFFF) | 0x1000);	// OK
+	write32(DEFAULT_MCHBAR | 0x440c, (read32(DEFAULT_MCHBAR | 0x440c) & 0xFFFFCFFF) | 0x1000);	// OK
+	write32(DEFAULT_MCHBAR | 0x4cb0, 0x00000740);
+	write32(DEFAULT_MCHBAR | 0x4380, 0x00000aaa);	// OK
+	write32(DEFAULT_MCHBAR | 0x4780, 0x00000aaa);	// OK
+	write32(DEFAULT_MCHBAR | 0x4f88, 0x5f7003ff);	// OK
+	write32(DEFAULT_MCHBAR | 0x5064, 0x00073000 | ctrl->reg_5064b0); // OK
+
+	FOR_ALL_CHANNELS {
+		switch (ctrl->rankmap[channel]) {
+			/* Unpopulated channel.  */
+		case 0:
+			write32(DEFAULT_MCHBAR + 0x4384 + channel * 0x400, 0);
+			break;
+			/* Only single-ranked dimms.  */
+		case 1:
+		case 4:
+		case 5:
+			write32(DEFAULT_MCHBAR + 0x4384 + channel * 0x400, 0x373131);
+			break;
+			/* Dual-ranked dimms present.  */
+		default:
+			write32(DEFAULT_MCHBAR + 0x4384 + channel * 0x400, 0x9b6ea1);
+			break;
+		}
+	}
+
+	write32 (DEFAULT_MCHBAR | 0x5880, 0xca9171e5);
+	write32 (DEFAULT_MCHBAR | 0x5888,
+		 (read32 (DEFAULT_MCHBAR | 0x5888) & ~0xffffff) | 0xe4d5d0);
+	write32 (DEFAULT_MCHBAR | 0x58a8, read32 (DEFAULT_MCHBAR | 0x58a8) & ~0x1f);
+	write32 (DEFAULT_MCHBAR | 0x4294,
+		 (read32 (DEFAULT_MCHBAR | 0x4294) & ~0x30000)
+		 | (1 << 16));
+	write32 (DEFAULT_MCHBAR | 0x4694,
+		 (read32 (DEFAULT_MCHBAR | 0x4694) & ~0x30000)
+		 | (1 << 16));
+
+	MCHBAR32(0x5030) |= 1;	// OK
+	MCHBAR32(0x5030) |= 0x80;	// OK
+	MCHBAR32(0x5f18) = 0xfa;	// OK
+
+	/* Find a populated channel.  */
+	FOR_ALL_POPULATED_CHANNELS
+		break;
+
+	t1_cycles = ((read32(DEFAULT_MCHBAR + 0x4290 + channel * 0x400) >> 8) & 0xff);
+	r32 = read32(DEFAULT_MCHBAR + 0x5064);
+	if (r32 & 0x20000)
+		t1_cycles += (r32 & 0xfff);
+	t1_cycles += (read32(DEFAULT_MCHBAR + channel * 0x400 + 0x42a4) & 0xfff);
+	t1_ns = t1_cycles * ctrl->tCK / 256 + 544;
+	if (!(r32 & 0x20000))
+		t1_ns += 500;
+
+	t2_ns = 10 * ((read32(DEFAULT_MCHBAR + 0x5f10) >> 8) & 0xfff);
+	if ( read32(DEFAULT_MCHBAR + 0x5f00) & 8 )
+	{
+		t3_ns = 10 * ((read32(DEFAULT_MCHBAR + 0x5f20) >> 8) & 0xfff);
+		t3_ns += 10 * (read32(DEFAULT_MCHBAR + 0x5f18) & 0xff);
+	}
+	else
+	{
+		t3_ns = 500;
+	}
+	printk(BIOS_DEBUG, "t123: %d, %d, %d\n",
+	       t1_ns, t2_ns, t3_ns);
+	write32 (DEFAULT_MCHBAR + 0x5d10,
+		 ((encode_5d10(t1_ns) + encode_5d10(t2_ns)) << 16)
+		 | (encode_5d10(t1_ns) << 8)
+		 | ((encode_5d10(t3_ns) + encode_5d10(t2_ns) + encode_5d10(t1_ns)) << 24)
+		 | (read32(DEFAULT_MCHBAR + 0x5d10) & 0xC0C0C0C0)
+		 | 0xc);
+}
+
+static void save_timings(ramctr_timing * ctrl)
+{
+	struct mrc_data_container *mrcdata;
+	int output_len = ALIGN(sizeof (*ctrl), 16);
+
+	/* Save the MRC S3 restore data to cbmem */
+	mrcdata = cbmem_add
+		(CBMEM_ID_MRCDATA,
+		 output_len + sizeof(struct mrc_data_container));
+
+	printk(BIOS_DEBUG, "Relocate MRC DATA from %p to %p (%u bytes)\n",
+	       ctrl, mrcdata, output_len);
+
+	mrcdata->mrc_signature = MRC_DATA_SIGNATURE;
+	mrcdata->mrc_data_size = output_len;
+	mrcdata->reserved = 0;
+	memcpy(mrcdata->mrc_data, ctrl, sizeof (*ctrl));
+
+	/* Zero the unused space in aligned buffer. */
+	if (output_len > sizeof (*ctrl))
+		memset(mrcdata->mrc_data+sizeof (*ctrl), 0,
+		       output_len - sizeof (*ctrl));
+
+	mrcdata->mrc_checksum = compute_ip_checksum(mrcdata->mrc_data,
+						    mrcdata->mrc_data_size);
+}
+
+static void restore_timings(ramctr_timing * ctrl)
+{
+	int channel, slotrank, lane;
+
+	FOR_ALL_POPULATED_CHANNELS
+	    MCHBAR32(0x4004 + 0x400 * channel) =
+		ctrl->tRRD
+		| (ctrl->tRTP << 4)
+		| (ctrl->tCKE << 8)
+		| (ctrl->tWTR << 12)
+		| (ctrl->tFAW << 16)
+		| (ctrl->tWR << 24)
+		| (ctrl->reg_4004_b30[channel] << 30);
+
+	udelay(1);
+
+	FOR_ALL_POPULATED_CHANNELS {
+		wait_428c(channel);
+	}
+
+	FOR_ALL_CHANNELS FOR_ALL_POPULATED_RANKS FOR_ALL_LANES {
+		write32(DEFAULT_MCHBAR + 0x4080 + 0x400 * channel
+			+ 4 * lane, 0);
+	}
+
+	FOR_ALL_POPULATED_CHANNELS
+	    write32(DEFAULT_MCHBAR + 0x4008 + 0x400 * channel,
+		    read32(DEFAULT_MCHBAR + 0x4008 +
+			   0x400 * channel) | 0x8000000);
+
+	FOR_ALL_POPULATED_CHANNELS {
+		udelay (1);
+		write32(DEFAULT_MCHBAR + 0x4020 + 0x400 * channel,
+			read32(DEFAULT_MCHBAR + 0x4020 +
+			       0x400 * channel) | 0x200000);
+	}
+
+	printk(BIOS_SPEW, "CPE\n");
+
+	write32(DEFAULT_MCHBAR + 0x3400, 0);
+	write32(DEFAULT_MCHBAR + 0x4eb0, 0);
+
+	printk(BIOS_SPEW, "CP5b\n");
+
+	FOR_ALL_POPULATED_CHANNELS {
+		program_timings(ctrl, channel);
+	}
+
+	u32 reg, addr;
+
+	while (!(MCHBAR32(0x5084) & 0x10000)) ;
+	do {
+		reg = MCHBAR32(0x428c);
+	} while ((reg & 0x14) == 0);
+
+	// Set state of memory controller
+	MCHBAR32(0x5030) = 0x116;
+	MCHBAR32(0x4ea0) = 0;
+
+	// Wait 500us
+	udelay(500);
+
+	FOR_ALL_CHANNELS {
+		// Set valid rank CKE
+		reg = 0;
+		reg = (reg & ~0xf) | ctrl->rankmap[channel];
+		addr = 0x400 * channel + 0x42a0;
+		MCHBAR32(addr) = reg;
+
+		// Wait 10ns for ranks to settle
+		//udelay(0.01);
+
+		reg = (reg & ~0xf0) | (ctrl->rankmap[channel] << 4);
+		MCHBAR32(addr) = reg;
+
+		// Write reset using a NOP
+		write_reset(ctrl);
+	}
+
+	/* mrs commands. */
+	dram_mrscommands(ctrl);
+
+	printk(BIOS_SPEW, "CP5c\n");
+
+	write32(DEFAULT_MCHBAR + 0x3000, 0);
+
+	FOR_ALL_CHANNELS {
+		write32(DEFAULT_MCHBAR + (channel * 0x100) + 0xe3c,
+			0 | (read32(DEFAULT_MCHBAR + (channel * 0x100) + 0xe3c) &
+			     ~0x3f000000));
+		udelay(2);
+	}
+
+	write32(DEFAULT_MCHBAR + 0x4ea8, 0);
+}
+
+void init_dram_ddr3(spd_raw_data * spds, int mobile, int min_tck,
+	int s3resume)
+{
+	int me_uma_size;
+	int cbmem_was_inited;
+
+	MCHBAR32(0x5f00) |= 1;
+
+	report_platform_info();
+
+	/* Wait for ME to be ready */
+	intel_early_me_init();
+	me_uma_size = intel_early_me_uma_size();
+
+	printk(BIOS_DEBUG, "Starting native Platform init\n");
+
+	u32 reg_5d10;
+
+	wait_txt_clear();
+
+	wrmsr(0x000002e6, (msr_t) { .lo = 0, .hi = 0 });
+
+	reg_5d10 = read32(DEFAULT_MCHBAR | 0x5d10);	// !!! = 0x00000000
+	if ((pcie_read_config16(SOUTHBRIDGE, 0xa2) & 0xa0) == 0x20	/* 0x0004 */
+	    && reg_5d10 && !s3resume) {
+		write32(DEFAULT_MCHBAR | 0x5d10, 0);
+		/* Need reset.  */
+		outb(0x6, 0xcf9);
+
+		hlt();
+	}
+
+	ramctr_timing ctrl;
+
+	memset(&ctrl, 0, sizeof (ctrl));
+
+	early_pch_init_native();
+	early_thermal_init();
+
+	ctrl.mobile = mobile;
+	ctrl.tCK = min_tck;
+
+	/* FIXME: for non-S3 we should be able to use timing caching with
+	   proper verification. Right now we use timings only for S3 case.
+	 */
+	if (s3resume) {
+		struct mrc_data_container *mrc_cache;
+
+		mrc_cache = find_current_mrc_cache();
+		if (!mrc_cache || mrc_cache->mrc_data_size < sizeof (ctrl)) {
+			/* Failed S3 resume, reset to come up cleanly */
+			outb(0x6, 0xcf9);
+			hlt();
+		}
+		memcpy(&ctrl, mrc_cache->mrc_data, sizeof (ctrl));
+	}
+
+	if (!s3resume) {
+		dimm_info info;
+
+		/* Get DDR3 SPD data */
+		dram_find_spds_ddr3(spds, &info, &ctrl);
+
+		/* Find fastest common supported parameters */
+		dram_find_common_params(&info, &ctrl);
+
+		dram_dimm_mapping(&info, &ctrl);
+	}
+
+	/* Set MCU frequency */
+	dram_freq(&ctrl);
+
+	if (!s3resume) {
+		/* Calculate timings */
+		dram_timing(&ctrl);
+	}
+
+	/* Set version register */
+	MCHBAR32(0x5034) = 0xC04EB002;
+
+	/* Enable crossover */
+	dram_xover(&ctrl);
+
+	/* Set timing and refresh registers */
+	dram_timing_regs(&ctrl);
+
+	/* Power mode preset */
+	MCHBAR32(0x4e80) = 0x5500;
+
+	/* Set scheduler parameters */
+	MCHBAR32(0x4c20) = 0x10100005;
+
+	/* Set cpu specific register */
+	set_4f8c();
+
+	/* Clear IO reset bit */
+	MCHBAR32(0x5030) &= ~0x20;
+
+	/* Set MAD-DIMM registers */
+	dram_dimm_set_mapping(&ctrl);
+	printk(BIOS_DEBUG, "Done dimm mapping\n");
+
+	/* Zone config */
+	dram_zones(&ctrl, 1);
+
+	/* Set memory map */
+	dram_memorymap(&ctrl, me_uma_size);
+	printk(BIOS_DEBUG, "Done memory map\n");
+
+	/* Set IO registers */
+	dram_ioregs(&ctrl);
+	printk(BIOS_DEBUG, "Done io registers\n");
+
+	udelay(1);
+
+	if (s3resume) {
+		restore_timings(&ctrl);
+	} else {
+		/* Do jedec ddr3 reset sequence */
+		dram_jedecreset(&ctrl);
+		printk(BIOS_DEBUG, "Done jedec reset\n");
+
+		/* MRS commands */
+		dram_mrscommands(&ctrl);
+		printk(BIOS_DEBUG, "Done MRS commands\n");
+		dram_mrscommands(&ctrl);
+
+		/* Prepare for memory training */
+		prepare_training(&ctrl);
+
+		read_training(&ctrl);
+		write_training(&ctrl);
+
+		printk(BIOS_SPEW, "CP5a\n");
+
+		discover_edges(&ctrl);
+
+		printk(BIOS_SPEW, "CP5b\n");
+
+		command_training(&ctrl);
+
+		printk(BIOS_SPEW, "CP5c\n");
+
+		discover_edges_write(&ctrl);
+
+		discover_timC_write(&ctrl);
+
+		normalize_training(&ctrl);
+	}
+
+	set_4008c(&ctrl);
+
+	write_controller_mr(&ctrl);
+
+	if (!s3resume) {
+		channel_test(&ctrl);
+	}
+
+	/* FIXME: should be hardware revision-dependent.  */
+	write32(DEFAULT_MCHBAR | 0x5024, 0x00a030ce);
+
+	set_scrambling_seed(&ctrl);
+
+	set_42a0(&ctrl);
+
+	final_registers(&ctrl);
+
+	/* Zone config */
+	dram_zones(&ctrl, 0);
+
+	if (!s3resume)
+		quick_ram_check();
+
+	intel_early_me_status();
+	intel_early_me_init_done(ME_INIT_STATUS_SUCCESS);
+	intel_early_me_status();
+
+	post_system_agent_init();
+	report_memory_config();
+
+	cbmem_was_inited = !cbmem_recovery(s3resume);
+	if (!s3resume)
+		save_timings(&ctrl);
+	if (s3resume && !cbmem_was_inited) {
+		/* Failed S3 resume, reset to come up cleanly */
+		outb(0x6, 0xcf9);
+		hlt();
+	}
+}
diff --git a/src/northbridge/intel/sandybridge/raminit_native.h b/src/northbridge/intel/sandybridge/raminit_native.h
new file mode 100644
index 0000000..7ed75ff
--- /dev/null
+++ b/src/northbridge/intel/sandybridge/raminit_native.h
@@ -0,0 +1,29 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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
+ */
+
+#ifndef RAMINIT_H
+#define RAMINIT_H
+
+#include <device/dram/ddr3.h>
+
+/* The order is ch0dimmA, ch0dimmB, ch1dimmA, ch1dimmB.  */
+void init_dram_ddr3(spd_raw_data *spds, int mobile, int min_tck, int s3resume);
+void read_spd(spd_raw_data *spd, u8 addr);
+
+#endif				/* RAMINIT_H */
diff --git a/src/northbridge/intel/sandybridge/raminit_patterns.h b/src/northbridge/intel/sandybridge/raminit_patterns.h
new file mode 100644
index 0000000..61c67b4
--- /dev/null
+++ b/src/northbridge/intel/sandybridge/raminit_patterns.h
@@ -0,0 +1,639 @@
+const u32 pattern[][16] = {
+	{0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
+	 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
+	 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
+	 0x00000000, 0x00000000, 0xffffffff, 0xffffffff},
+	{0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
+	 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
+	 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
+	 0xffffffff, 0xffffffff, 0x00000000, 0x00000000},
+	{0xe62d6424, 0x9277e09e, 0x8f43dc3f, 0x76eae589,
+	 0x0010fdc6, 0xdc55e01c, 0x5effb0ab, 0x6cba5d29,
+	 0xa43d1e64, 0xab5c2e0f, 0x7796ed16, 0x96023bf4,
+	 0xa74c831d, 0x90f138c0, 0x17830a8a, 0x5ac17c47},
+	{0x359ebbeb, 0x2b9b4512, 0xef584d98, 0x106bf7cb,
+	 0x363525ad, 0xb3a4dfdc, 0xa6b9fcd8, 0xd21689ec,
+	 0x84a3695b, 0xbd9c2e27, 0xdb3d0f44, 0x988158f1,
+	 0xcca91d3f, 0xb62a6d12, 0xe905e4cf, 0x7f1fa626},
+	{0xe58efeae, 0xcd006081, 0xa9119403, 0xbcfbd35f,
+	 0x213b3bf7, 0x7bfcb773, 0xc85143f9, 0x0bdbff50,
+	 0xa3053c90, 0x51d66cb7, 0x296f4387, 0xb715f99e,
+	 0xfaddc989, 0xbb1de8a7, 0x39206b4d, 0x80174a57},
+	{0xa1622ac1, 0xb4f4a5f0, 0x16dc2bc3, 0x50fb0954,
+	 0x2e261721, 0x52b82c3c, 0x821902b8, 0x0d4b6c38,
+	 0x1f618631, 0x047956f3, 0xd4337f5a, 0x591f8002,
+	 0x27f28db2, 0xfae37369, 0xb3f27580, 0x3cdb6397},
+	{0x3dee23be, 0x19f36408, 0x227f4a6a, 0x024603c5,
+	 0xd5e062db, 0x6d8d4c5c, 0x7ff693b0, 0x76641be9,
+	 0x9e74f41c, 0xe7bc7f33, 0x2636f2e9, 0x70279750,
+	 0xce2355aa, 0x32d230ef, 0x22f9b468, 0xadd4e7a2},
+	{0x936c0fed, 0xba0612d5, 0xa97c1ea7, 0x10e29d67,
+	 0x1c4c5dc8, 0x83645621, 0xcd8b521c, 0xb8301817,
+	 0xac7d6571, 0xcc41d200, 0x4ebdefdd, 0xd2917bde,
+	 0x60f75acc, 0x7791534b, 0x26ea2a83, 0x6b74513a},
+	{0xd1957b85, 0xc6f8f9ca, 0xf04fb4be, 0xfeb786fb,
+	 0xa1dea3aa, 0x67fe7db6, 0x25d49c87, 0xe3d54870,
+	 0x93dc1f86, 0x7d0c1a18, 0x9272e128, 0x68e1b876,
+	 0xce284c9e, 0x8fa18792, 0x5785a340, 0xb6fcf198},
+	{0xff7d8e4a, 0x0c21ee43, 0xe820b388, 0xb4443c0e,
+	 0xa1e6e498, 0x5c426110, 0x1b434ef3, 0xbef05b91,
+	 0xa6907968, 0x53662ac3, 0x6defac32, 0x2c11c29c,
+	 0x6175cced, 0xb17dd3ad, 0x6e6a1076, 0x1372b1fa},
+	{0x4408ed06, 0x49460ffd, 0xb49d26cb, 0x6a3662a5,
+	 0x5e857047, 0xa387cd4a, 0x04edc81e, 0xfd94d8d4,
+	 0x2fe48d91, 0x9d2356bc, 0x96131878, 0xaca3fce4,
+	 0xbb312c6c, 0x5023b090, 0x3614be70, 0xa14dfabb},
+	{0xd4cc1e83, 0x757a1930, 0xc3d16a61, 0x9e0d6681,
+	 0x8a081fa9, 0xbd11c888, 0x1672f010, 0xa083f71c,
+	 0x1ec02eef, 0xc4586ca8, 0x6d322b35, 0x56054679,
+	 0x1552a0ff, 0x5cb7707e, 0xdfb55d4a, 0xcc76cc07},
+	{0x507cf71f, 0x2166421a, 0x54be4af0, 0xfd42158c,
+	 0x417b1f7f, 0x9466860b, 0x3a0075bf, 0x2055575c,
+	 0xcedfe7ab, 0xbe85aa5f, 0x39d0c2e3, 0x851c19df,
+	 0x39a35a3f, 0x3fb10d7d, 0x20b14899, 0x703b7f08},
+	{0x8a7d9dd1, 0x33235565, 0xbd3d2e57, 0xa48c2726,
+	 0x0d5e2e13, 0xae421ff9, 0x8784a224, 0xf66c1510,
+	 0x057627aa, 0x8fb0cb41, 0x4289975a, 0xb181adfa,
+	 0x59f2059a, 0xe86feb05, 0x84222fc1, 0x319b3ce9},
+	{0xe1e243b8, 0x3b0bcc1a, 0x70396f00, 0x5caff44d,
+	 0xe96961b3, 0xad73f692, 0x8b841a2d, 0xf5838839,
+	 0xec9c9d04, 0xcc2b5562, 0xf8ca2549, 0xa9c52ff8,
+	 0x3b2fde68, 0x3d4dc7f0, 0xa57387d0, 0x051199ad},
+	{0x5f0ce4fc, 0xd830fbb7, 0x90abeb8f, 0x96d9cdbb,
+	 0x58f80a80, 0x0baaca36, 0x81a23623, 0x77127614,
+	 0xaa8382cd, 0x0922fbca, 0xd84d37e1, 0x721297df,
+	 0x160f3b3a, 0x10a1ecdc, 0x151c92f4, 0xc1fdcdab},
+	{0x261c45cc, 0xfeddd2da, 0xfc3cb1c1, 0x6639641f,
+	 0x2c011892, 0x7108bee2, 0x8545e0b9, 0x7dd36dab,
+	 0x07d91950, 0x1520adcb, 0xf84aa939, 0x07d9bb2d,
+	 0xdf1ed826, 0xaee3c814, 0x1dca1e81, 0xc8e9f486},
+	{0x933d306a, 0xaab7103d, 0xa8be37be, 0x49612f3a,
+	 0xb0cf28e5, 0xf9648902, 0x106d7c11, 0xf32e1813,
+	 0x21af36ef, 0xe695e4c4, 0x7ee1831d, 0x2aeda467,
+	 0x99d0c655, 0x3f0691ab, 0xcd68f7c1, 0xb469a20e},
+	{0x8557aef0, 0x3eb0e373, 0x0853ac31, 0xe5bded62,
+	 0x3eddb0dd, 0x6bbf1caf, 0x2119c3d9, 0xe1732350,
+	 0x55456c75, 0xf6119375, 0x498dd1ad, 0x13f80916,
+	 0xb97f9f5e, 0x921d9f4c, 0xabdee367, 0x1d6bb8bf},
+	{0xd165a3be, 0xd8b41598, 0xa20e1809, 0xefd5c8ce,
+	 0x18935c80, 0xdf1911f9, 0xc9e449eb, 0xb887a4d7,
+	 0x4a324f6f, 0x533e8031, 0x1c21c074, 0xa95f1ea5,
+	 0x765b320a, 0x839d7dfb, 0xc7d3aa93, 0xe534ae3d},
+	{0xbe8592c8, 0x068457e6, 0x89b94fa3, 0xd522ad02,
+	 0x7e7db0b7, 0x2c5b896f, 0x9f8ecb37, 0x05b983ff,
+	 0x3fe9b25f, 0x34a6215b, 0x0592ba34, 0xd564f85a,
+	 0x156c426d, 0x25ad5460, 0xe7b5e8b7, 0xa73285c6},
+	{0x5ad8d838, 0x27b42d36, 0xcc806ad1, 0x157a058a,
+	 0x7297735a, 0xffd6df8d, 0xff96f7a2, 0x155b27ea,
+	 0x84708101, 0x979fd78b, 0x49797d0c, 0x0dc93e3c,
+	 0x20287332, 0xed759f88, 0xe5068529, 0xb83aa781},
+	{0xc38b302c, 0x57b54075, 0xac810692, 0xb0d493e7,
+	 0x4adda486, 0x0665ce2e, 0xb2a9c003, 0xafacc4ce,
+	 0x4d5e906d, 0xb3d52fab, 0xe6962c6b, 0x850f4dd1,
+	 0x5021656c, 0x5df6c06b, 0x9255125b, 0x2363c478},
+	{0x188b715c, 0xe8b884b0, 0x5e6d0b9a, 0x1f0051e1,
+	 0xd2d35d4c, 0xbfeaecbe, 0xc84bb0ad, 0x67a232d6,
+	 0x99001587, 0xbf4313e1, 0x74f64061, 0x2c1fc562,
+	 0xb6fe8ca6, 0x5226a239, 0xf5198574, 0x61b51dca},
+	{0x51dcecd3, 0xbadbe596, 0xebe3e84a, 0x772bfdfc,
+	 0x03656ac5, 0xa7c36e91, 0x6cd32cf0, 0xc3f699dd,
+	 0x7d5aba01, 0x51e38e82, 0x23103a98, 0x20298b9d,
+	 0x19436510, 0x63ad7e6c, 0x8bc2b33f, 0x27079917},
+	{0x8bd5be78, 0xf2403bfa, 0x780ebdb6, 0x94c53b64,
+	 0x6241c2e2, 0x5bfb081e, 0x6799e88f, 0xc997b7d1,
+	 0x466ac8b1, 0xbf5909da, 0x497ea39f, 0x402ffb48,
+	 0xd7470c2d, 0x8510aba9, 0x6c52a1c9, 0x812ca967},
+	{0x031f7ab4, 0xd32fe890, 0x36ae6de5, 0x083dcde4,
+	 0x99a7f12f, 0xe44864a7, 0x02b75fff, 0xf25dda35,
+	 0x7679ff4f, 0xed421e01, 0xd9c2cfa1, 0xd36b4e82,
+	 0x5315d908, 0xc7ebcb2a, 0xb6f3e4c1, 0xf5bfbae9},
+	{0x3f4a2a96, 0x64d8bd5a, 0x19acd70d, 0xf62fcdd9,
+	 0x5de99cdf, 0x32f3b7cb, 0x2c020578, 0x4e9bafb8,
+	 0x74919a08, 0xaba33e91, 0xa6bd2254, 0x2435a9b9,
+	 0x47e2a1b4, 0xe837a28e, 0xe113f1b0, 0x7654bd79},
+	{0x05537a6c, 0x77be1a5c, 0x4c7492c9, 0x9086bfb0,
+	 0x257adc18, 0xf4787fc1, 0xe3fb6d53, 0x9525e589,
+	 0x445a65bc, 0x833f7d08, 0x69cf1f7e, 0x9a6372e1,
+	 0xceedb52e, 0x31032997, 0xd1c36828, 0x132772d6},
+	{0x0a166972, 0x89beaf3b, 0x8d780fbc, 0x8aea5392,
+	 0x58347a41, 0x1e381ec2, 0xcc6280c8, 0xee0863e1,
+	 0x976e2dd2, 0x8c6ee6e2, 0xa0ca57cd, 0x95114a7d,
+	 0x3c096704, 0xa941769d, 0x2de20c05, 0x0bf8f812},
+	{0x22779d6c, 0x94e12e8f, 0x5ce40299, 0xea1b55b0,
+	 0x9ebec05d, 0xe076cd2b, 0x8fef5648, 0x6a284c65,
+	 0xa790b705, 0xf0b19997, 0x0d8ca8af, 0x17440419,
+	 0xef4f702f, 0x33cbcbb1, 0x83d60f26, 0x48988397},
+	{0x0fed7f53, 0xb5acbb67, 0xc031c73f, 0x5364d9ef,
+	 0xa6dbd12d, 0x82174a6c, 0xccf8e7ab, 0xc473c036,
+	 0xcff493d8, 0xad9afc3b, 0x316a24e8, 0x1842bea4,
+	 0x4cc0c82e, 0x28ccd91e, 0xd7311b5d, 0x50a89860},
+};
+
+static const u8 use_base[63][32] = { 
+  {
+    0x0e, 0x9e, 0xa1, 0x39, 0x06, 0x26, 0xc5, 0xe9, 0xed, 0x07, 0x49, 0x3b, 0x34, 0x7f, 0x1c, 0xa8, 
+    0xdf, 0x7b, 0xb7, 0xb8, 0x28, 0xbe, 0x8a, 0x70, 0x17, 0xe5, 0xc0, 0x44, 0x4a, 0x8e, 0x61, 0x3b, 
+  },
+  {
+    0x42, 0xe6, 0xe0, 0x6a, 0xb3, 0x08, 0x28, 0xaf, 0xfa, 0xb9, 0xb7, 0x32, 0x83, 0x5c, 0xef, 0x3d, 
+    0x90, 0x91, 0x64, 0x31, 0xe9, 0x3c, 0x92, 0xe6, 0xa3, 0xd4, 0x6a, 0xc6, 0x01, 0xa6, 0xeb, 0xe6, 
+  },
+  {
+    0x39, 0x7f, 0x6f, 0x81, 0xb4, 0x33, 0x4a, 0xde, 0x4f, 0x77, 0x28, 0x47, 0x08, 0xf9, 0x3a, 0x55, 
+    0x21, 0x57, 0x27, 0x59, 0xf5, 0x96, 0xad, 0xc1, 0x10, 0x33, 0xe0, 0xe2, 0xf8, 0xb6, 0x49, 0xbd, 
+  },
+  {
+    0xdf, 0x57, 0x60, 0x27, 0x95, 0x50, 0x3a, 0x8c, 0x34, 0x8b, 0xae, 0xc5, 0x69, 0x26, 0xca, 0x39, 
+    0x55, 0x98, 0xfb, 0x05, 0x3c, 0x1c, 0x8d, 0xf8, 0xb9, 0x99, 0x05, 0x40, 0xe5, 0x5e, 0x2f, 0xf6, 
+  },
+  {
+    0xc1, 0x6a, 0xea, 0xd6, 0x39, 0x56, 0x08, 0x89, 0x83, 0x4c, 0xef, 0xda, 0xb2, 0x69, 0x76, 0xe4, 
+    0x75, 0x3f, 0x39, 0x13, 0x96, 0xb5, 0x41, 0x84, 0x00, 0x64, 0x79, 0x47, 0xe4, 0xcb, 0xc3, 0xd0, 
+  },
+  {
+    0xf8, 0xb1, 0x19, 0x76, 0x51, 0x99, 0xd7, 0x45, 0x38, 0x40, 0xbf, 0x10, 0x4c, 0x89, 0x43, 0xa9, 
+    0x89, 0xe2, 0x85, 0x3f, 0xb4, 0xe8, 0xbf, 0x5e, 0xc2, 0xb4, 0x16, 0x6d, 0x1c, 0x61, 0xca, 0x40, 
+  },
+  {
+    0x1c, 0xdc, 0xa6, 0xdb, 0x71, 0x8b, 0xf9, 0xbb, 0xee, 0xc2, 0xa5, 0x66, 0xa4, 0xbc, 0xb6, 0x89, 
+    0x58, 0xb9, 0x6f, 0x57, 0x71, 0x57, 0x5c, 0xf0, 0xed, 0xcf, 0x2c, 0x2e, 0x1d, 0x34, 0xc3, 0x00, 
+  },
+  {
+    0x1d, 0x30, 0x03, 0xb9, 0x15, 0x8e, 0x47, 0x8c, 0xf2, 0x4e, 0x2d, 0xf1, 0xbf, 0x96, 0xa7, 0xa1, 
+    0x3f, 0x26, 0xc3, 0xc9, 0x08, 0x0b, 0xa8, 0xdd, 0x9b, 0xeb, 0xbc, 0x77, 0x1c, 0x10, 0x03, 0x77, 
+  },
+  {
+    0x50, 0x7e, 0x62, 0x26, 0xcb, 0x49, 0x7b, 0x1a, 0xd4, 0x54, 0xf1, 0x25, 0x3d, 0xa2, 0xe6, 0x8a, 
+    0xb3, 0x62, 0xf1, 0x7e, 0x03, 0xef, 0x1b, 0x27, 0x21, 0xcc, 0xfc, 0x72, 0x30, 0x0c, 0x69, 0xad, 
+  },
+  {
+    0x11, 0xf5, 0xb2, 0xfa, 0x2d, 0xbc, 0xa1, 0xd9, 0x74, 0x15, 0x59, 0xf2, 0xc6, 0x66, 0x4f, 0xde, 
+    0x84, 0x82, 0x4f, 0xe8, 0x33, 0xd5, 0xc5, 0xdd, 0xba, 0x0c, 0xc7, 0x51, 0x1f, 0x3c, 0x6d, 0x44, 
+  },
+  {
+    0xcf, 0xf5, 0x3b, 0xc1, 0xbd, 0x5f, 0x9c, 0xad, 0x57, 0xfb, 0xfc, 0xbe, 0x95, 0xa0, 0x48, 0x58, 
+    0x8a, 0x68, 0x97, 0x71, 0xf3, 0xc0, 0xd1, 0x31, 0x33, 0xb9, 0x3c, 0xe9, 0x4f, 0xbb, 0x8d, 0xeb, 
+  },
+  {
+    0x29, 0x0c, 0xa1, 0xc8, 0x04, 0xdc, 0xf9, 0x25, 0x85, 0x7e, 0xea, 0x6d, 0x75, 0x28, 0x69, 0x3f, 
+    0x3a, 0x83, 0xe4, 0x33, 0x31, 0x77, 0x57, 0x2e, 0xa9, 0xa8, 0x05, 0xfe, 0x19, 0xb7, 0xc4, 0xd1, 
+  },
+  {
+    0x6d, 0x5d, 0x3f, 0x4f, 0x8a, 0x6a, 0x77, 0x2d, 0xf7, 0x9f, 0x73, 0xab, 0x40, 0xd9, 0x89, 0x57, 
+    0x69, 0xd7, 0xc8, 0xc3, 0x69, 0x54, 0x93, 0x7c, 0x9f, 0x4a, 0xcc, 0xaf, 0xcc, 0x0e, 0xe0, 0xb8, 
+  },
+  {
+    0xb6, 0xd5, 0x36, 0x3f, 0x1c, 0x34, 0x54, 0x9b, 0xfc, 0xec, 0x5b, 0xb0, 0x26, 0xa6, 0xc0, 0x61, 
+    0x6d, 0x4c, 0x4f, 0x86, 0x2a, 0xbd, 0x34, 0x35, 0x52, 0x2c, 0x82, 0x01, 0x66, 0x0e, 0x80, 0x01, 
+  },
+  {
+    0x48, 0x39, 0x43, 0xb8, 0xf9, 0x2b, 0x25, 0xe8, 0xf7, 0xf0, 0x1a, 0xed, 0x33, 0x1e, 0x30, 0xba, 
+    0x15, 0x37, 0xeb, 0xae, 0x97, 0xa7, 0x36, 0xa4, 0xc7, 0x1f, 0x91, 0x01, 0x38, 0x80, 0x5a, 0x76, 
+  },
+  {
+    0x74, 0xbe, 0x15, 0x6b, 0x85, 0x28, 0xe4, 0xc4, 0x13, 0x68, 0x67, 0x03, 0x27, 0x7e, 0x32, 0x08, 
+    0x87, 0x23, 0xda, 0xf2, 0x47, 0xdd, 0xac, 0x2c, 0xc5, 0x7f, 0x06, 0xc8, 0x17, 0x4c, 0x6c, 0x81, 
+  },
+  {
+    0x0d, 0x9f, 0x68, 0xbb, 0xa6, 0x6c, 0x7d, 0x3f, 0x81, 0xcf, 0x9a, 0x52, 0x87, 0xce, 0x98, 0x25, 
+    0x40, 0x2b, 0x1b, 0xdf, 0xd3, 0x6b, 0x53, 0xed, 0x80, 0xd2, 0x3d, 0xca, 0xdf, 0x07, 0x4e, 0x6b, 
+  },
+  {
+    0xff, 0xf1, 0xd2, 0x1f, 0xbd, 0xd6, 0xa7, 0x3d, 0xb4, 0xc6, 0x88, 0x9d, 0x7b, 0x05, 0x04, 0x0b, 
+    0x4c, 0x6f, 0x11, 0x7f, 0x19, 0x18, 0x48, 0xf1, 0x26, 0xad, 0xd0, 0x60, 0xfa, 0x40, 0x35, 0xbb, 
+  },
+  {
+    0xae, 0xec, 0x0e, 0x2e, 0xfd, 0x46, 0xf0, 0x9c, 0x06, 0x1b, 0x62, 0xbf, 0xf0, 0x3e, 0xba, 0xdf, 
+    0xb2, 0xa2, 0x83, 0x83, 0xda, 0x04, 0x15, 0xec, 0x1e, 0x2e, 0x1a, 0x64, 0x08, 0x8e, 0xd3, 0x87, 
+  },
+  {
+    0x61, 0x14, 0x05, 0x0b, 0xdb, 0xf4, 0xf0, 0xa3, 0x41, 0x11, 0x7b, 0xd9, 0xa1, 0x40, 0x4f, 0x62, 
+    0x98, 0x37, 0xa0, 0x90, 0x3d, 0x78, 0x63, 0x24, 0xbc, 0x8e, 0x9e, 0x99, 0x2e, 0xc7, 0xb1, 0x6d, 
+  },
+  {
+    0x0f, 0xf1, 0x5f, 0xe4, 0x94, 0x3d, 0x24, 0x0d, 0xa2, 0xcf, 0xed, 0xbb, 0x55, 0x8e, 0xdb, 0x07, 
+    0x12, 0x05, 0x79, 0x82, 0xb1, 0x3a, 0x71, 0x76, 0xbb, 0x8b, 0xcb, 0xcc, 0x00, 0x40, 0x2e, 0xab, 
+  },
+  {
+    0x48, 0x59, 0xfa, 0x46, 0x15, 0x5e, 0xa2, 0x0d, 0xe8, 0x81, 0x69, 0xe6, 0x2f, 0x5f, 0x6d, 0xaf, 
+    0xad, 0xc7, 0x30, 0xd6, 0xec, 0x03, 0x1e, 0x19, 0xdd, 0x1d, 0x00, 0x94, 0x0b, 0x93, 0x5a, 0x28, 
+  },
+  {
+    0xfa, 0x64, 0x62, 0x57, 0x02, 0x7e, 0x37, 0xb8, 0x10, 0xa9, 0x4b, 0xe7, 0xbf, 0x03, 0xdc, 0x1c, 
+    0xe7, 0x21, 0x68, 0x60, 0x53, 0xec, 0xf0, 0xfc, 0xbf, 0xe5, 0x8e, 0xca, 0x77, 0xdb, 0xa5, 0xae, 
+  },
+  {
+    0xc1, 0xa9, 0x9f, 0xc2, 0x87, 0x11, 0xad, 0x1c, 0xc5, 0x56, 0x61, 0xc0, 0x20, 0x33, 0xc9, 0x85, 
+    0xf0, 0x81, 0x36, 0x18, 0xdb, 0xaa, 0x7e, 0x79, 0x36, 0x2d, 0xfc, 0x8f, 0x72, 0x3c, 0x4c, 0xa3, 
+  },
+  {
+    0xb4, 0xd3, 0x38, 0xed, 0xd3, 0x6c, 0x03, 0x26, 0x0e, 0x1c, 0x8a, 0xa8, 0x72, 0x17, 0xcb, 0xf7, 
+    0x87, 0xf3, 0x3f, 0x73, 0x5d, 0x12, 0xe8, 0x3b, 0x55, 0xb6, 0xcc, 0xc6, 0x85, 0xad, 0x9f, 0x9e, 
+  },
+  {
+    0xe9, 0x97, 0xde, 0x7f, 0xe0, 0x9a, 0xc1, 0xdc, 0x96, 0x4c, 0xc3, 0x0d, 0x2d, 0xd4, 0x98, 0x54, 
+    0x89, 0x1c, 0x57, 0x7d, 0x17, 0x96, 0xe9, 0x2f, 0x02, 0x73, 0x07, 0x5c, 0x8b, 0x44, 0xd7, 0x75, 
+  },
+  {
+    0xaf, 0xf2, 0x39, 0xc4, 0x23, 0xff, 0xd5, 0x85, 0xf3, 0x11, 0x44, 0x9e, 0xab, 0x59, 0x29, 0x33, 
+    0xe6, 0xc2, 0x1d, 0xba, 0xa8, 0x5b, 0x70, 0xc3, 0xc0, 0xf8, 0x56, 0x3b, 0x8e, 0x43, 0x4c, 0x4b, 
+  },
+  {
+    0x46, 0x93, 0xb5, 0x8a, 0x71, 0x67, 0xcb, 0x42, 0x6b, 0x1b, 0x43, 0x58, 0x8b, 0x18, 0xb9, 0xf3, 
+    0xdc, 0x91, 0x1c, 0x08, 0x83, 0xe3, 0x63, 0xfa, 0xf9, 0x99, 0x99, 0xcb, 0x05, 0x15, 0x7b, 0x85, 
+  },
+  {
+    0xcf, 0xc6, 0x3c, 0x01, 0xbd, 0xfb, 0x00, 0x8c, 0x8b, 0x07, 0x69, 0xac, 0x61, 0xdb, 0x30, 0xd5, 
+    0x7c, 0x71, 0x98, 0x69, 0x2e, 0x4a, 0x4a, 0x44, 0xe4, 0x72, 0x6f, 0xf2, 0x9d, 0x58, 0x02, 0x23, 
+  },
+  {
+    0x29, 0x10, 0xe1, 0xe5, 0x3f, 0xe6, 0x26, 0xaf, 0x30, 0x3c, 0xfe, 0x1f, 0x97, 0x21, 0xdd, 0x8c, 
+    0xbb, 0x90, 0xdb, 0x2a, 0xb5, 0xb4, 0x46, 0x43, 0x50, 0x0b, 0x6b, 0xa8, 0x8f, 0x55, 0xfb, 0xd2, 
+  },
+  {
+    0x89, 0x56, 0x19, 0xf9, 0x4a, 0xec, 0x38, 0x99, 0xaa, 0xcc, 0xc5, 0x15, 0xdf, 0x19, 0xee, 0x1a, 
+    0xc0, 0x94, 0xaf, 0x0f, 0x7b, 0xf4, 0x06, 0xf1, 0x70, 0xa3, 0x79, 0x06, 0xba, 0x4e, 0x09, 0xa6, 
+  },
+  {
+    0x53, 0x3a, 0x35, 0x98, 0xa9, 0xcb, 0x2c, 0x72, 0x73, 0xb2, 0xb0, 0x63, 0x38, 0xfc, 0xa3, 0xa5, 
+    0xa3, 0xf1, 0x4d, 0x04, 0x2f, 0x1b, 0x83, 0xbd, 0xed, 0x20, 0x8e, 0xa5, 0x1e, 0x9d, 0xd6, 0x69, 
+  },
+  {
+    0x78, 0x44, 0xe3, 0x22, 0xa4, 0x7c, 0x53, 0xef, 0x60, 0x8e, 0x39, 0x95, 0x7d, 0x9d, 0x3d, 0x0b, 
+    0x4a, 0x4f, 0xf4, 0x73, 0xf8, 0xfe, 0xe7, 0x65, 0x86, 0x72, 0x13, 0x0b, 0x4e, 0x8f, 0x4d, 0x7b, 
+  },
+  {
+    0x46, 0xba, 0xd2, 0x82, 0x46, 0x18, 0x6e, 0x46, 0x96, 0x05, 0x1d, 0xfb, 0xf6, 0xde, 0xba, 0xb5, 
+    0x6b, 0x73, 0x7a, 0xd5, 0x76, 0x11, 0xb7, 0x7c, 0x0d, 0x61, 0xba, 0xf9, 0x05, 0x12, 0x2b, 0x4e, 
+  },
+  {
+    0x41, 0x08, 0x8b, 0x63, 0x10, 0xeb, 0x85, 0x10, 0xf3, 0x05, 0x84, 0xc0, 0x81, 0x1f, 0x7f, 0xd3, 
+    0x9d, 0x01, 0xdc, 0x32, 0xd0, 0xe2, 0xd1, 0xd1, 0x41, 0x8a, 0xa0, 0xf2, 0x64, 0xda, 0x8b, 0x0e, 
+  },
+  {
+    0x99, 0xf1, 0xf2, 0x88, 0xe8, 0x72, 0x8b, 0x40, 0x17, 0xcc, 0xa7, 0x4c, 0x7d, 0xf1, 0xc4, 0x3f, 
+    0xbf, 0xf8, 0xbe, 0xc9, 0xce, 0xce, 0x2b, 0xe9, 0x4d, 0x5b, 0xd1, 0xef, 0x76, 0xae, 0x3b, 0xbb, 
+  },
+  {
+    0x64, 0x69, 0x47, 0x03, 0xe9, 0x3b, 0xc2, 0x2d, 0x2d, 0x1a, 0xdd, 0xa4, 0xac, 0xb6, 0x6e, 0x7a, 
+    0x41, 0x7d, 0x82, 0xed, 0x54, 0xf7, 0x05, 0xd8, 0xae, 0x07, 0x6c, 0x1c, 0xf6, 0x51, 0xa1, 0xc0, 
+  },
+  {
+    0x84, 0xaf, 0xe8, 0x59, 0x0c, 0x14, 0x24, 0xec, 0xa0, 0xd2, 0xf3, 0x8f, 0x2f, 0x22, 0xe4, 0x52, 
+    0xf8, 0xa7, 0x39, 0x74, 0x7f, 0x80, 0x23, 0x3f, 0x30, 0xe3, 0xcb, 0x26, 0x12, 0x5c, 0xa7, 0x93, 
+  },
+  {
+    0x48, 0xc8, 0x41, 0x42, 0xc5, 0xe7, 0x4d, 0x91, 0x54, 0xa6, 0x5b, 0xba, 0x46, 0x89, 0xd5, 0x42, 
+    0x46, 0xbc, 0xf2, 0x67, 0x4a, 0xf9, 0x88, 0xe9, 0x02, 0xa5, 0xc2, 0x38, 0x9d, 0x6a, 0x75, 0xcd, 
+  },
+  {
+    0xab, 0x9b, 0x2c, 0x09, 0xda, 0xaa, 0xe9, 0xe2, 0xd7, 0xf1, 0xa4, 0x86, 0x41, 0x14, 0xcb, 0x4e, 
+    0x2b, 0xd1, 0x5a, 0x9a, 0x46, 0x47, 0x90, 0xb2, 0x63, 0x16, 0xcb, 0x60, 0x1c, 0x12, 0xe3, 0x2b, 
+  },
+  {
+    0x54, 0x21, 0xc0, 0xbc, 0xb8, 0x29, 0xce, 0xa4, 0xf2, 0x38, 0xb4, 0x0d, 0x7d, 0xfa, 0xd4, 0x3e, 
+    0x28, 0xf4, 0xc4, 0xf4, 0x4a, 0x9f, 0x11, 0xf2, 0xe2, 0x31, 0xa3, 0x72, 0x16, 0xbb, 0xd9, 0x05, 
+  },
+  {
+    0xdc, 0x6e, 0xbe, 0x64, 0xa2, 0xb2, 0x64, 0x46, 0x00, 0xee, 0xd2, 0xa5, 0x81, 0x86, 0x44, 0xa4, 
+    0x5c, 0xc4, 0xba, 0xc4, 0xa2, 0x44, 0x36, 0x77, 0xb2, 0xec, 0x0d, 0xbe, 0x8f, 0xc0, 0x80, 0x6f, 
+  },
+  {
+    0x51, 0x51, 0xb2, 0x50, 0x11, 0x0b, 0x60, 0xb8, 0x8e, 0xc2, 0xf7, 0xa8, 0xb4, 0xd2, 0x48, 0xc3, 
+    0x51, 0x43, 0x52, 0xeb, 0x37, 0xac, 0xd0, 0xf7, 0x61, 0xa1, 0xe9, 0xe3, 0xa7, 0xdf, 0x26, 0xa9, 
+  },
+  {
+    0x10, 0xd9, 0xf2, 0xfe, 0x71, 0x8e, 0x49, 0x2c, 0x64, 0x53, 0xb0, 0x9e, 0x7a, 0xcc, 0x86, 0xab, 
+    0x06, 0xd7, 0xc7, 0x13, 0xd1, 0xcb, 0x21, 0x42, 0x97, 0x29, 0x5d, 0xff, 0xaa, 0xec, 0x52, 0x40, 
+  },
+  {
+    0x7b, 0x35, 0x93, 0xb9, 0xca, 0xbb, 0x7e, 0x09, 0x8f, 0x34, 0x32, 0x02, 0xe7, 0x0c, 0xcf, 0xd1, 
+    0x6c, 0x2d, 0x11, 0xcd, 0xa5, 0x3b, 0xba, 0x14, 0x47, 0xc5, 0x2c, 0xb8, 0xf6, 0xfb, 0x9d, 0x77, 
+  },
+  {
+    0x45, 0xce, 0x12, 0x8e, 0xa2, 0x4e, 0x56, 0x58, 0xa7, 0xcf, 0x26, 0x4e, 0x32, 0x21, 0xe0, 0x2b, 
+    0xed, 0x8d, 0xe2, 0xd9, 0x51, 0x32, 0x9b, 0xdc, 0x7b, 0x0e, 0x14, 0x0a, 0xdb, 0x63, 0x6b, 0x42, 
+  },
+  {
+    0x9d, 0x49, 0x72, 0xea, 0x89, 0xc7, 0xa2, 0xfd, 0x9a, 0x55, 0xd7, 0x05, 0x16, 0xeb, 0xf7, 0x49, 
+    0xb6, 0xcf, 0x56, 0xf7, 0x2b, 0xef, 0x6d, 0xbe, 0xdd, 0x0e, 0x90, 0x01, 0xaf, 0x1b, 0xbb, 0xaa, 
+  },
+  {
+    0x2c, 0xb7, 0x26, 0x43, 0x02, 0xc5, 0x7b, 0xc6, 0x71, 0x1f, 0xf2, 0x28, 0xb4, 0xea, 0x5f, 0x02, 
+    0xc6, 0xeb, 0xb4, 0xf7, 0x6f, 0x01, 0x7f, 0xfe, 0x3a, 0xb0, 0xe3, 0xf3, 0x31, 0xd3, 0xcb, 0x0f, 
+  },
+  {
+    0x75, 0x54, 0x90, 0xec, 0xed, 0xcc, 0xbc, 0x5c, 0x9b, 0x44, 0x30, 0x57, 0x57, 0xe0, 0xec, 0x58, 
+    0x20, 0x54, 0x20, 0xb0, 0x92, 0x3a, 0x06, 0x9f, 0x69, 0xcb, 0x88, 0xee, 0x52, 0x6e, 0x4c, 0xf6, 
+  },
+  {
+    0x2a, 0x08, 0xd8, 0xdd, 0x2e, 0x07, 0x54, 0x0b, 0x37, 0xc6, 0x09, 0x26, 0x98, 0xf7, 0x45, 0xf6, 
+    0x05, 0x4f, 0x35, 0xe6, 0xb0, 0x44, 0xff, 0x62, 0x89, 0x47, 0x69, 0x30, 0xea, 0x3b, 0x7b, 0x81, 
+  },
+  {
+    0x0f, 0x7f, 0x34, 0xbf, 0xf9, 0xce, 0xe7, 0x4c, 0xe1, 0x7a, 0x24, 0x74, 0xeb, 0x8b, 0xb0, 0x65, 
+    0x18, 0x19, 0xaf, 0x44, 0x45, 0x5e, 0xd9, 0xa4, 0xf0, 0xd8, 0xe8, 0x09, 0x22, 0x58, 0x4e, 0x4d, 
+  },
+  {
+    0x48, 0x41, 0x7a, 0xd2, 0x26, 0xda, 0x39, 0xb2, 0xba, 0x03, 0x17, 0xe9, 0x80, 0x8e, 0x53, 0x4c, 
+    0xac, 0x64, 0x6b, 0xa7, 0x5f, 0xed, 0x64, 0x60, 0xb7, 0x5e, 0x7a, 0x68, 0x1a, 0x8a, 0xda, 0x2b, 
+  },
+  {
+    0x52, 0x01, 0x25, 0xb0, 0x59, 0xcf, 0x61, 0x30, 0x52, 0xe7, 0x77, 0x66, 0xc9, 0x35, 0xac, 0xfe, 
+    0xc0, 0xa2, 0x18, 0x58, 0xf1, 0xc1, 0x8f, 0x28, 0x72, 0x2f, 0xdc, 0x36, 0x10, 0x5b, 0xd9, 0x6f, 
+  },
+  {
+    0x05, 0xbf, 0x47, 0x32, 0x0e, 0xa7, 0xc6, 0xd6, 0x13, 0xa1, 0x72, 0x38, 0x8c, 0x1a, 0xbc, 0x6d, 
+    0x7c, 0xd8, 0x34, 0xac, 0x69, 0x5b, 0x43, 0xc4, 0x00, 0xf8, 0xf2, 0xa7, 0xaf, 0x9f, 0xb0, 0xde, 
+  },
+  {
+    0x54, 0x67, 0xce, 0xf2, 0xfc, 0x91, 0xd2, 0x4d, 0x73, 0xc6, 0xc9, 0xc3, 0xfe, 0xcf, 0x72, 0xa0, 
+    0xc7, 0x43, 0xff, 0x87, 0x4b, 0xe3, 0x7f, 0xba, 0xd4, 0xa2, 0xa3, 0x14, 0x8f, 0xa0, 0x39, 0x91, 
+  },
+  {
+    0xbd, 0x24, 0x35, 0xb5, 0x0e, 0x7a, 0x13, 0xb3, 0x92, 0x83, 0xaa, 0xbd, 0x0c, 0x22, 0x93, 0xbb, 
+    0x2a, 0xe7, 0x7a, 0x56, 0x2d, 0xae, 0x41, 0x62, 0x52, 0xb2, 0xe9, 0xbd, 0x29, 0x05, 0x51, 0x95, 
+  },
+  {
+    0x97, 0x8f, 0x21, 0x82, 0x72, 0x4f, 0x65, 0xc8, 0xfc, 0x2e, 0x5e, 0x8a, 0x1a, 0xce, 0x34, 0x39, 
+    0x12, 0xe6, 0x9b, 0xc3, 0xab, 0x51, 0xa9, 0x40, 0xf7, 0xbe, 0x94, 0xd0, 0x66, 0xda, 0xa5, 0x39, 
+  },
+  {
+    0x5a, 0x48, 0x43, 0x9a, 0x8e, 0x22, 0x85, 0x9b, 0x28, 0xc9, 0x63, 0xa2, 0x57, 0xa6, 0xe2, 0x16, 
+    0x64, 0xec, 0x3c, 0x59, 0x13, 0xc4, 0x7b, 0x51, 0xea, 0xfe, 0x2e, 0x70, 0xbd, 0xd9, 0x77, 0x85, 
+  },
+  {
+    0x2b, 0x74, 0xb6, 0x95, 0x18, 0x94, 0x54, 0x6d, 0xae, 0xdd, 0xe9, 0xb2, 0xf9, 0xbd, 0xce, 0x27, 
+    0xa9, 0x87, 0x42, 0x13, 0x22, 0x29, 0x87, 0x7a, 0x04, 0xe3, 0xbe, 0x2f, 0x9c, 0x18, 0xbb, 0x13, 
+  },
+  {
+    0x80, 0x95, 0x43, 0xaa, 0x19, 0x90, 0x03, 0x4f, 0x47, 0xbf, 0xf5, 0x8e, 0x2d, 0x55, 0x23, 0xb7, 
+    0x7b, 0x5d, 0xaa, 0x34, 0x37, 0xb2, 0x70, 0x86, 0x5e, 0xc4, 0x94, 0xf3, 0x61, 0xfd, 0x87, 0x65, 
+  },
+  {
+    0xd4, 0xbc, 0x03, 0x65, 0xb0, 0xc5, 0x44, 0x81, 0x7b, 0x06, 0x94, 0x79, 0xca, 0x1f, 0xe2, 0x28, 
+    0x53, 0xc8, 0xa7, 0x10, 0x13, 0x77, 0xb7, 0x5c, 0x9a, 0x34, 0x1e, 0xd5, 0x78, 0xb1, 0x21, 0x61, 
+  },
+  {
+    0x90, 0x0e, 0x7f, 0xa3, 0x24, 0x18, 0x12, 0xbf, 0x45, 0xd2, 0x52, 0xa3, 0x99, 0x74, 0x89, 0xd2, 
+    0x12, 0x8d, 0x32, 0x3c, 0xd0, 0x28, 0x54, 0x98, 0x6c, 0x9e, 0xdd, 0xc0, 0xd5, 0xf1, 0x8a, 0xb1, 
+  },
+  {
+    0x82, 0xad, 0x7a, 0x5c, 0x4d, 0x81, 0x54, 0x41, 0x79, 0x42, 0x54, 0x5c, 0x49, 0x41, 0xed, 0x49, 
+    0xc7, 0x06, 0x61, 0xbb, 0x89, 0x2b, 0x90, 0x04, 0x1f, 0x8c, 0x31, 0x3b, 0x39, 0x4f, 0xf8, 0x33, 
+  },
+};
+static const u8 invert[63][32] = {
+  {
+    0x95, 0xb2, 0xa8, 0xe3, 0xac, 0xcf, 0x27, 0x3e, 0x1c, 0xa3, 0xcf, 0x7a, 0x20, 0xb4, 0x52, 0x83, 
+    0x0e, 0x21, 0x2d, 0xfe, 0x6f, 0x2e, 0x38, 0x13, 0x01, 0x2e, 0xa0, 0x58, 0x58, 0x6d, 0x4a, 0x6f, 
+  },
+  {
+    0x63, 0x26, 0x5c, 0xd2, 0x9a, 0xc6, 0x8c, 0x5d, 0xc2, 0x0d, 0xba, 0x4f, 0x79, 0x88, 0xd1, 0x15, 
+    0x64, 0x55, 0x90, 0x7b, 0x76, 0x2d, 0x60, 0x04, 0x92, 0x77, 0x18, 0xd0, 0xba, 0x7f, 0xee, 0x3a, 
+  },
+  {
+    0x57, 0xc1, 0x0b, 0x23, 0x06, 0x57, 0x0c, 0xde, 0xa1, 0xa5, 0x8d, 0xc6, 0x8e, 0xbd, 0x9e, 0x09, 
+    0xe5, 0xed, 0xe3, 0xfb, 0xb1, 0xa0, 0xda, 0x73, 0xfc, 0x3e, 0x5e, 0x6d, 0x38, 0x36, 0x26, 0xec, 
+  },
+  {
+    0x8e, 0xe5, 0x30, 0x36, 0x9e, 0x30, 0x82, 0x02, 0xf6, 0x7c, 0x06, 0x71, 0xbb, 0x6e, 0x09, 0x68, 
+    0x16, 0xca, 0x10, 0x32, 0x90, 0xcc, 0x7a, 0x99, 0x18, 0x70, 0xe6, 0xe7, 0x3a, 0x78, 0x86, 0xe6, 
+  },
+  {
+    0x18, 0x98, 0x14, 0xa7, 0xb7, 0x1f, 0x24, 0xed, 0xd0, 0xfc, 0x71, 0xa0, 0x7e, 0xef, 0xdd, 0xe2, 
+    0xa2, 0xf8, 0x2a, 0xc2, 0x5d, 0x94, 0x03, 0x13, 0x29, 0x39, 0x86, 0xed, 0x08, 0x99, 0x83, 0xab, 
+  },
+  {
+    0xcd, 0x22, 0xa0, 0xbc, 0xea, 0xe7, 0xde, 0xca, 0x0c, 0x72, 0xbd, 0xf7, 0x40, 0x46, 0x92, 0xc5, 
+    0xa4, 0xf3, 0x48, 0x9a, 0x8f, 0x52, 0xab, 0x19, 0x07, 0x98, 0xae, 0x9b, 0xe7, 0xfc, 0xbd, 0x05, 
+  },
+  {
+    0xd2, 0xce, 0x28, 0x79, 0x3f, 0xdd, 0xa1, 0x1c, 0x21, 0xe4, 0xeb, 0x54, 0x85, 0x5c, 0x9d, 0x64, 
+    0xd5, 0x5b, 0xb6, 0x06, 0x43, 0xcc, 0x80, 0x8b, 0xe3, 0xdb, 0x26, 0xf5, 0x7e, 0x5f, 0x81, 0x9d, 
+  },
+  {
+    0x54, 0x42, 0xe9, 0x30, 0xd2, 0x2c, 0xba, 0x16, 0xa7, 0x99, 0x28, 0xe7, 0x54, 0x61, 0xee, 0x17, 
+    0xf0, 0x70, 0x34, 0xe2, 0xe7, 0x66, 0x16, 0x00, 0x95, 0x7b, 0xbd, 0xd8, 0x07, 0xab, 0xa9, 0x5b, 
+  },
+  {
+    0xe7, 0x58, 0x21, 0x24, 0x0e, 0x32, 0xbd, 0x6b, 0xcb, 0xa6, 0xd9, 0x91, 0xd7, 0xfd, 0xe4, 0x4c, 
+    0x58, 0xd6, 0x06, 0x11, 0x02, 0x90, 0xe5, 0x1d, 0x91, 0x2f, 0x0f, 0x43, 0xe3, 0xc7, 0x66, 0xd8, 
+  },
+  {
+    0x45, 0x65, 0x5e, 0x17, 0xe1, 0x00, 0xfd, 0x42, 0x30, 0x25, 0x0e, 0xa5, 0x26, 0x8a, 0x17, 0xfe, 
+    0xd0, 0xa2, 0xff, 0x7a, 0x09, 0xd3, 0x5a, 0xb4, 0x71, 0x84, 0x29, 0x03, 0x71, 0x70, 0x9b, 0x6e, 
+  },
+  {
+    0xc7, 0x2d, 0xe6, 0xef, 0xba, 0x0b, 0x97, 0x9a, 0x91, 0xf2, 0xda, 0x26, 0x62, 0xe5, 0xbe, 0x5d, 
+    0xc5, 0x5d, 0x71, 0xc1, 0xb7, 0x3f, 0xb3, 0xb8, 0x74, 0xd0, 0x0c, 0x03, 0x74, 0xc0, 0x0c, 0xe4, 
+  },
+  {
+    0x56, 0x38, 0x1e, 0x31, 0xca, 0x3b, 0xb5, 0xc4, 0xff, 0x5a, 0x9e, 0x86, 0xfe, 0x98, 0x0c, 0x27, 
+    0x23, 0x2c, 0xa0, 0x76, 0x6f, 0xae, 0xf3, 0xde, 0x71, 0x40, 0x0c, 0xdc, 0x41, 0xf9, 0x89, 0x99, 
+  },
+  {
+    0x2c, 0x27, 0xed, 0x69, 0x50, 0x53, 0xc5, 0x97, 0xf4, 0x88, 0x9a, 0x2b, 0xce, 0x8a, 0xc5, 0xfb, 
+    0x0d, 0xbc, 0x6f, 0x9c, 0x84, 0x30, 0xf3, 0xcb, 0xc1, 0x30, 0xa4, 0xb5, 0x46, 0xd4, 0xcb, 0xea, 
+  },
+  {
+    0xb7, 0xf0, 0x86, 0x66, 0xd3, 0x55, 0x64, 0xc9, 0x1b, 0x9b, 0x3d, 0x79, 0x13, 0x0a, 0x3e, 0xa1, 
+    0xcf, 0x54, 0x17, 0x77, 0xeb, 0x32, 0x1c, 0x47, 0x7d, 0xf0, 0xb4, 0x11, 0x3d, 0xd7, 0xef, 0x04, 
+  },
+  {
+    0xf7, 0x7e, 0x71, 0xb6, 0x5e, 0xed, 0xf3, 0xfb, 0x56, 0x82, 0x22, 0x61, 0x29, 0xa2, 0x5d, 0xc6, 
+    0xcd, 0x03, 0x47, 0xc7, 0xcc, 0xe4, 0xf2, 0xa4, 0x3f, 0xed, 0x36, 0xae, 0xa7, 0x30, 0x84, 0x22, 
+  },
+  {
+    0x13, 0x70, 0xe7, 0x97, 0x14, 0xfa, 0xa9, 0xb7, 0xd5, 0xa1, 0xa4, 0xfd, 0xe8, 0x0c, 0x92, 0xcf, 
+    0xc4, 0xdc, 0x5d, 0xb9, 0xa4, 0xa2, 0x16, 0xf8, 0x67, 0xdc, 0x12, 0x47, 0xb7, 0x75, 0xfd, 0x3c, 
+  },
+  {
+    0x29, 0xaa, 0xdd, 0xb5, 0xdc, 0x7f, 0xce, 0xad, 0x02, 0x65, 0x27, 0x5e, 0xa5, 0x5d, 0x23, 0x0f, 
+    0xa7, 0x51, 0x4d, 0xf2, 0x7d, 0x2a, 0x31, 0xbf, 0x32, 0x3b, 0x80, 0xe3, 0xda, 0x56, 0xdb, 0xfc, 
+  },
+  {
+    0x70, 0xd0, 0x50, 0x6d, 0xf2, 0xb3, 0x6f, 0xc1, 0x9a, 0x98, 0x02, 0x87, 0xb5, 0x31, 0x3d, 0x19, 
+    0xaa, 0xf4, 0xd2, 0xd4, 0x48, 0xb1, 0x08, 0x06, 0x98, 0x39, 0x00, 0x06, 0x20, 0xe5, 0x0c, 0xe1, 
+  },
+  {
+    0xe6, 0xaf, 0x94, 0xa0, 0xdf, 0xc3, 0x6b, 0xb4, 0xcf, 0x78, 0xc0, 0xe8, 0x56, 0xdc, 0xac, 0xbb, 
+    0x5e, 0x9e, 0xda, 0x90, 0x1e, 0x7f, 0x44, 0x06, 0xe0, 0x00, 0x6a, 0xd9, 0xd1, 0xf9, 0x56, 0xac, 
+  },
+  {
+    0x15, 0xa2, 0x90, 0x13, 0x4f, 0xa0, 0x9d, 0x0d, 0x9c, 0xf8, 0xc9, 0x20, 0x1c, 0x8e, 0x68, 0xcb, 
+    0x1f, 0x75, 0xb3, 0xb2, 0x14, 0xff, 0x19, 0x20, 0x5f, 0x30, 0xb1, 0x05, 0x36, 0x7c, 0xa2, 0xed, 
+  },
+  {
+    0x9a, 0xb2, 0xf5, 0xfd, 0x04, 0x3e, 0x6b, 0x4a, 0x1d, 0x3a, 0x63, 0x96, 0x00, 0xad, 0x6c, 0x7c, 
+    0x4f, 0xaf, 0x4d, 0xb5, 0x03, 0x4a, 0xf7, 0x28, 0x7f, 0x1f, 0x38, 0xad, 0xfd, 0xc7, 0x4b, 0x7f, 
+  },
+  {
+    0xf4, 0x5a, 0x9f, 0xf6, 0xd0, 0x1a, 0x23, 0x76, 0xee, 0x15, 0x10, 0x2c, 0x30, 0xbd, 0x45, 0xfc, 
+    0x65, 0x60, 0x20, 0xc5, 0x9b, 0xb4, 0x42, 0x83, 0xe9, 0x03, 0xd5, 0xec, 0xba, 0xb2, 0x3b, 0xb8, 
+  },
+  {
+    0xf4, 0x1b, 0xc1, 0x73, 0x1a, 0x6c, 0x88, 0xfd, 0xc2, 0xfb, 0xe8, 0x7e, 0xcb, 0x8a, 0x0e, 0x0e, 
+    0x6a, 0x13, 0x54, 0xb0, 0x7b, 0xb8, 0x68, 0x90, 0x21, 0x38, 0x4e, 0x1f, 0x86, 0x51, 0x14, 0x2c, 
+  },
+  {
+    0x6c, 0xd3, 0xc3, 0x8f, 0x06, 0x45, 0xec, 0x65, 0x87, 0x02, 0x3d, 0x89, 0x61, 0xde, 0x80, 0x42, 
+    0xf6, 0xe0, 0x8d, 0x91, 0xf0, 0x3a, 0x7a, 0x66, 0xba, 0x1c, 0xc7, 0xb6, 0x3d, 0xc4, 0x7f, 0x91, 
+  },
+  {
+    0x53, 0xf6, 0x90, 0x34, 0x88, 0x3e, 0xb7, 0xef, 0x56, 0x39, 0x6e, 0x1f, 0x48, 0x14, 0xe4, 0x09, 
+    0xc6, 0xea, 0xc4, 0xd9, 0xed, 0x2e, 0x2e, 0x33, 0x03, 0x00, 0xb9, 0xac, 0x22, 0x65, 0xe9, 0x1b, 
+  },
+  {
+    0x55, 0x78, 0x89, 0x36, 0xa0, 0x07, 0xa6, 0x99, 0xbf, 0x7c, 0xb5, 0xbd, 0xb6, 0x1e, 0xc3, 0x58, 
+    0xb3, 0x0f, 0x78, 0x64, 0x74, 0x77, 0x00, 0x50, 0x2e, 0x4c, 0x6a, 0xa1, 0xe8, 0x93, 0x89, 0x5d, 
+  },
+  {
+    0x09, 0xf8, 0xdd, 0xe0, 0x42, 0xa4, 0x2e, 0x9d, 0xff, 0x0e, 0x70, 0x73, 0x9c, 0x87, 0xa0, 0x81, 
+    0x4d, 0xb1, 0xc3, 0xf3, 0xff, 0x96, 0x3b, 0x2a, 0xdf, 0x6d, 0x97, 0xba, 0x06, 0xa7, 0x7e, 0x0a, 
+  },
+  {
+    0x1f, 0x46, 0xb4, 0x72, 0x14, 0x5b, 0x85, 0x01, 0x83, 0xcc, 0x24, 0x17, 0xc2, 0x07, 0xda, 0x60, 
+    0x6c, 0xab, 0xfa, 0xe5, 0xd9, 0xb4, 0xf0, 0x3f, 0xca, 0xf1, 0x30, 0x8d, 0xd2, 0x4e, 0xe3, 0xb4, 
+  },
+  {
+    0x4f, 0xb3, 0x0d, 0x98, 0x38, 0x70, 0x28, 0xa2, 0xca, 0x5d, 0x2c, 0xdf, 0x1f, 0xce, 0xff, 0xcc, 
+    0x75, 0x49, 0xa0, 0xef, 0x54, 0xd9, 0x32, 0x1b, 0x17, 0xb6, 0x7e, 0x7a, 0xa6, 0x5f, 0x7a, 0xff, 
+  },
+  {
+    0x81, 0x6b, 0x06, 0x73, 0x5b, 0x32, 0x0d, 0x37, 0xb4, 0x50, 0x63, 0x52, 0x25, 0x72, 0x5c, 0xf5, 
+    0x5d, 0x58, 0xa6, 0xbf, 0x08, 0xcc, 0x1d, 0x70, 0x2d, 0x12, 0x5d, 0xd7, 0xbd, 0xca, 0xe7, 0x10, 
+  },
+  {
+    0x8f, 0xfc, 0x57, 0x17, 0xce, 0x47, 0x10, 0x79, 0xae, 0x66, 0xa5, 0xcc, 0x98, 0x0b, 0x77, 0xe8, 
+    0xa2, 0x6e, 0xc1, 0x0d, 0x03, 0xe3, 0x5b, 0xed, 0x38, 0x0e, 0x31, 0x2d, 0x19, 0x4d, 0xd6, 0x2a, 
+  },
+  {
+    0xc6, 0x9d, 0x2d, 0x0b, 0xad, 0x6d, 0x0c, 0x61, 0x2d, 0x62, 0xe4, 0xce, 0x73, 0x47, 0x72, 0x20, 
+    0x0c, 0x8e, 0x8f, 0xe9, 0xe7, 0x67, 0x66, 0x2e, 0x17, 0x54, 0xec, 0x15, 0x3e, 0x1b, 0xf3, 0x04, 
+  },
+  {
+    0xf4, 0xee, 0x03, 0x30, 0x2e, 0x1e, 0xa4, 0xb1, 0x86, 0x6d, 0xc3, 0x54, 0xf3, 0xc5, 0x61, 0xa6, 
+    0xb9, 0x28, 0x29, 0x11, 0x91, 0xcb, 0xbd, 0xc9, 0x77, 0x62, 0x09, 0x8c, 0xa4, 0x40, 0x84, 0x97, 
+  },
+  {
+    0xff, 0x98, 0x9b, 0xbc, 0xc2, 0xf0, 0xf8, 0x7f, 0x5c, 0x86, 0x74, 0x33, 0xee, 0x42, 0x6e, 0xab, 
+    0xd4, 0xd2, 0x1a, 0x0d, 0x41, 0x2d, 0xac, 0xa1, 0x3e, 0x56, 0xed, 0x4b, 0x27, 0x5a, 0x65, 0xe4, 
+  },
+  {
+    0x2b, 0xb1, 0xe3, 0x64, 0xaa, 0x32, 0x17, 0x57, 0x7c, 0x67, 0xb8, 0x6b, 0x00, 0x53, 0xbe, 0x3e, 
+    0xec, 0xd1, 0x1b, 0xc4, 0xc3, 0x8d, 0xe6, 0x19, 0xe8, 0x3a, 0x25, 0x98, 0x4e, 0xe9, 0xd4, 0x60, 
+  },
+  {
+    0xa6, 0x2e, 0xb3, 0xc8, 0xcd, 0xc9, 0xc2, 0x8e, 0xe1, 0xf0, 0x8f, 0x96, 0x8e, 0xc6, 0x37, 0x11, 
+    0xbc, 0x6c, 0x0c, 0xf6, 0xb6, 0x83, 0x38, 0x96, 0x7a, 0x74, 0x5a, 0xa7, 0xe1, 0x11, 0x8d, 0x8b, 
+  },
+  {
+    0x90, 0xf2, 0x4d, 0xbd, 0x83, 0x39, 0xe6, 0x54, 0xf6, 0x75, 0xf6, 0x2c, 0x28, 0x3d, 0xd1, 0xcf, 
+    0xe1, 0xfb, 0x9f, 0x97, 0x19, 0xca, 0x4d, 0x2c, 0x38, 0x3d, 0x36, 0xed, 0x19, 0xe9, 0x4a, 0x0b, 
+  },
+  {
+    0x1a, 0x61, 0xb8, 0x19, 0x59, 0x16, 0x74, 0xec, 0xdb, 0x7b, 0xeb, 0xd6, 0xae, 0xcd, 0xe9, 0x55, 
+    0xdb, 0x45, 0xdc, 0xf2, 0x35, 0x84, 0xe9, 0xe6, 0x17, 0x48, 0xac, 0x38, 0x05, 0x21, 0x2c, 0x8e, 
+  },
+  {
+    0x41, 0xac, 0x17, 0x42, 0xcc, 0x17, 0x10, 0x02, 0x07, 0x7e, 0xfc, 0x4d, 0x77, 0x06, 0x70, 0xcb, 
+    0x40, 0x8b, 0x47, 0x47, 0x07, 0x29, 0x82, 0xca, 0x93, 0x69, 0x2f, 0x3a, 0x64, 0xc6, 0xcb, 0x23, 
+  },
+  {
+    0xa2, 0xcb, 0x2d, 0x02, 0x5d, 0x30, 0x9f, 0x32, 0xf5, 0xc5, 0x13, 0xff, 0xfc, 0xe2, 0xfb, 0x26, 
+    0x3b, 0x3b, 0xaf, 0xa4, 0x37, 0x6d, 0x45, 0xbf, 0xdb, 0xb9, 0xee, 0xec, 0x92, 0xa5, 0x1d, 0x0d, 
+  },
+  {
+    0xa4, 0xef, 0x08, 0xb7, 0xb4, 0x68, 0x74, 0x93, 0xb2, 0xda, 0xba, 0xe9, 0x05, 0xf5, 0x09, 0xb6, 
+    0x53, 0xdd, 0x17, 0x60, 0xbb, 0x1e, 0xb0, 0x71, 0xd8, 0x47, 0x85, 0x02, 0x13, 0xbe, 0xa2, 0x67, 
+  },
+  {
+    0x31, 0x50, 0x90, 0xb0, 0x83, 0x4a, 0xcf, 0x3f, 0xbe, 0x88, 0x90, 0x4b, 0xe1, 0x9f, 0xe6, 0xd0, 
+    0xfd, 0x01, 0x8e, 0xfc, 0xc0, 0x8c, 0x2f, 0x9b, 0x48, 0x70, 0x9d, 0x4e, 0x22, 0x21, 0x07, 0x09, 
+  },
+  {
+    0x15, 0x9f, 0x37, 0x45, 0x52, 0x99, 0x6e, 0xe9, 0x1a, 0x25, 0x56, 0x0b, 0x19, 0xf1, 0xca, 0x9f, 
+    0x29, 0xe5, 0x23, 0xa6, 0x0b, 0x94, 0x0a, 0xe3, 0x74, 0xaa, 0xd5, 0x35, 0xaf, 0x6e, 0xb2, 0x24, 
+  },
+  {
+    0x68, 0xab, 0xa6, 0x8b, 0x5f, 0xc7, 0x93, 0x1a, 0x06, 0x51, 0x2c, 0x3b, 0xad, 0x44, 0x6b, 0x69, 
+    0x1a, 0x1d, 0x41, 0xca, 0x8e, 0x59, 0x2c, 0x83, 0x71, 0x48, 0x8c, 0xaf, 0x50, 0x85, 0x00, 0xf3, 
+  },
+  {
+    0xe2, 0xa6, 0x38, 0x93, 0xca, 0xe3, 0xd0, 0x36, 0xf4, 0xe9, 0x53, 0xfb, 0xa0, 0xd0, 0x13, 0xd3, 
+    0x2b, 0x7d, 0x46, 0xc8, 0x8b, 0xc7, 0x8c, 0xca, 0x59, 0xec, 0x66, 0x17, 0x70, 0xbb, 0xf9, 0x92, 
+  },
+  {
+    0x89, 0xca, 0x92, 0x19, 0x01, 0xb8, 0x4b, 0x97, 0x06, 0x1a, 0x12, 0x91, 0x72, 0x11, 0xeb, 0x12, 
+    0x8b, 0x12, 0xd9, 0xdc, 0xc9, 0xb2, 0x37, 0xf8, 0x3e, 0x02, 0x03, 0xbe, 0x45, 0x45, 0xc9, 0x42, 
+  },
+  {
+    0x45, 0x2c, 0x80, 0xe1, 0x3a, 0x0a, 0xdf, 0xa3, 0xd2, 0x4a, 0x23, 0x0d, 0x47, 0x0d, 0x49, 0xad, 
+    0xdf, 0xb0, 0x42, 0xdf, 0x87, 0x85, 0xa6, 0x8f, 0x9f, 0x7e, 0x9b, 0xa4, 0x42, 0x64, 0xcb, 0xfb, 
+  },
+  {
+    0xc7, 0x39, 0x26, 0xb1, 0x90, 0x4d, 0xc1, 0x7a, 0xea, 0x31, 0x1b, 0xae, 0x1a, 0x5c, 0x1f, 0x4f, 
+    0x44, 0x2f, 0x87, 0x08, 0x5d, 0xa6, 0x74, 0xfd, 0xab, 0xb2, 0x4b, 0x01, 0xed, 0xd5, 0x4c, 0xe6, 
+  },
+  {
+    0x2a, 0xef, 0xfa, 0x25, 0x3c, 0xd2, 0xc8, 0x08, 0x9c, 0x33, 0x3c, 0x47, 0xb3, 0xb5, 0x44, 0x34, 
+    0x97, 0xee, 0xe8, 0x52, 0x1c, 0x15, 0xb3, 0xe0, 0xda, 0xef, 0x77, 0xde, 0x15, 0x39, 0x4b, 0x38, 
+  },
+  {
+    0x8a, 0x3a, 0x59, 0x61, 0x9d, 0x3e, 0x9b, 0x38, 0xc9, 0x84, 0x80, 0xaf, 0xb4, 0x37, 0x8a, 0x67, 
+    0x47, 0xc9, 0x6c, 0x72, 0xef, 0x39, 0x50, 0x28, 0x6c, 0x8f, 0xad, 0x09, 0x75, 0x26, 0xc9, 0xa9, 
+  },
+  {
+    0x27, 0x1e, 0x8b, 0xf6, 0x6b, 0x56, 0x5d, 0x17, 0x58, 0xac, 0xdf, 0x27, 0xd9, 0x3e, 0x5b, 0xdd, 
+    0xaf, 0xbc, 0xb7, 0xf9, 0x76, 0x3b, 0x40, 0x06, 0xbc, 0x6e, 0xec, 0xaa, 0xb2, 0xdc, 0x9a, 0x0c, 
+  },
+  {
+    0x09, 0x14, 0xef, 0x19, 0xc7, 0x7d, 0xc8, 0xa3, 0xd0, 0xaa, 0x7f, 0x09, 0x18, 0xaf, 0xd3, 0xde, 
+    0xbf, 0x05, 0xfc, 0xf9, 0xeb, 0xc0, 0x8e, 0xcf, 0xc7, 0x4c, 0x2f, 0x3f, 0xd4, 0x51, 0x41, 0xb2, 
+  },
+  {
+    0xb7, 0x33, 0xf3, 0x72, 0x6d, 0x12, 0xc1, 0x5c, 0x5d, 0x81, 0xb3, 0x63, 0x81, 0x50, 0x81, 0xc0, 
+    0x4a, 0xea, 0x18, 0x7c, 0xa6, 0x2d, 0x23, 0xba, 0x4b, 0xb9, 0x31, 0xd9, 0xab, 0x20, 0x60, 0x77, 
+  },
+  {
+    0xbb, 0x1c, 0x50, 0x2d, 0xdc, 0x18, 0x27, 0x98, 0x60, 0xde, 0x17, 0xd9, 0x17, 0x3c, 0xd5, 0x98, 
+    0xc3, 0x12, 0xfd, 0x8b, 0x25, 0x1e, 0xa5, 0xc1, 0xa8, 0xef, 0xec, 0x05, 0x3e, 0xfc, 0xd1, 0xfc, 
+  },
+  {
+    0xe6, 0x3b, 0x9f, 0x33, 0x85, 0xeb, 0x91, 0xd0, 0xad, 0x2f, 0xc2, 0x96, 0x61, 0x64, 0xbc, 0x12, 
+    0x15, 0x79, 0x65, 0x93, 0xc2, 0x63, 0xa3, 0x27, 0x88, 0x60, 0x39, 0x35, 0x21, 0x05, 0xe9, 0x49, 
+  },
+  {
+    0x9b, 0x68, 0xe7, 0xc8, 0xea, 0x0e, 0x5c, 0xa4, 0x03, 0x3a, 0x4e, 0x31, 0xb1, 0x92, 0xad, 0x9d, 
+    0x7e, 0xb5, 0x93, 0x81, 0xdd, 0x7a, 0xe9, 0xa1, 0x69, 0x28, 0x6a, 0xaf, 0x48, 0x05, 0x94, 0xc0, 
+  },
+  {
+    0x66, 0x3d, 0x87, 0xc1, 0x48, 0x9d, 0xdf, 0x99, 0x25, 0xd7, 0xb0, 0xfa, 0x03, 0x8b, 0x62, 0x60, 
+    0xc5, 0x07, 0x3f, 0xa2, 0xc8, 0xbd, 0x70, 0xdb, 0x40, 0x6c, 0x65, 0xbf, 0x15, 0xfc, 0x1e, 0xc9, 
+  },
+  {
+    0x38, 0x9c, 0x1a, 0x5b, 0x4f, 0x84, 0xca, 0xe1, 0x30, 0x6a, 0xf0, 0xb6, 0xf1, 0x61, 0xd3, 0xb0, 
+    0xa6, 0x6d, 0x0d, 0x11, 0x03, 0xe0, 0xcb, 0x9f, 0xbe, 0x7e, 0xc2, 0x7a, 0x53, 0x9d, 0x39, 0xcb, 
+  },
+  {
+    0xf3, 0x47, 0x4a, 0x37, 0xcd, 0x19, 0x27, 0x0f, 0xfb, 0x3f, 0xcb, 0x81, 0x1e, 0x0f, 0xfd, 0x1f, 
+    0x2e, 0x8d, 0xff, 0xe9, 0x52, 0x8b, 0x8e, 0x52, 0x3c, 0x82, 0xe6, 0x44, 0xf6, 0x92, 0xd4, 0xd4, 
+  },
+  {
+    0x3a, 0xe8, 0x4d, 0xcc, 0x4a, 0x0e, 0xaa, 0xf6, 0x32, 0x88, 0x4c, 0xee, 0xaa, 0x9c, 0xeb, 0x59, 
+    0xb5, 0xb8, 0x06, 0x89, 0x49, 0xc9, 0xa6, 0xf7, 0xa6, 0x14, 0x44, 0x55, 0x5e, 0x3e, 0x86, 0x08, 
+  },
+  {
+    0xca, 0x3d, 0x95, 0x21, 0xf3, 0xbb, 0x78, 0x29, 0x6a, 0x38, 0xd3, 0xe4, 0x48, 0x98, 0x6f, 0x0e, 
+    0xaf, 0x46, 0xa5, 0x02, 0xdd, 0xfb, 0x52, 0x42, 0x9b, 0x69, 0x97, 0xe6, 0x68, 0x21, 0x0d, 0x69, 
+  },
+  {
+    0x3a, 0x8a, 0x14, 0x6e, 0xa2, 0x24, 0x8f, 0x89, 0x5e, 0x99, 0x8a, 0x5b, 0x90, 0xb1, 0xf3, 0x64, 
+    0x4d, 0x10, 0xef, 0x45, 0xa9, 0xfb, 0xbb, 0xc0, 0xf5, 0x66, 0xdf, 0x15, 0xae, 0xd0, 0xd9, 0x56, 
+  },
+  {
+    0x62, 0x50, 0x52, 0xb5, 0xb9, 0x76, 0xa7, 0xcb, 0xe6, 0xf7, 0x3a, 0x9f, 0xa4, 0x1e, 0x0a, 0x4d, 
+    0x88, 0xa4, 0x1c, 0xea, 0x11, 0x8c, 0xfb, 0xbe, 0x70, 0x62, 0xec, 0x4e, 0x00, 0x56, 0x0e, 0xa9, 
+  },
+};
diff --git a/src/southbridge/intel/bd82x6x/Makefile.inc b/src/southbridge/intel/bd82x6x/Makefile.inc
index 64038e5..fa21277 100644
--- a/src/southbridge/intel/bd82x6x/Makefile.inc
+++ b/src/southbridge/intel/bd82x6x/Makefile.inc
@@ -45,10 +45,14 @@ smm-$(CONFIG_SPI_FLASH_SMM) += ../common/spi.c
 ramstage-$(CONFIG_HAVE_SMI_HANDLER) += smi.c
 smm-$(CONFIG_HAVE_SMI_HANDLER) += smihandler.c me.c me_8.x.c finalize.c pch.c
 
-romstage-y += early_usb.c early_smbus.c early_me.c me_status.c gpio.c
+romstage-y += early_usb.c early_smbus.c me_status.c gpio.c
 romstage-y += reset.c
 romstage-y += early_spi.c early_pch.c
 
+romstage-$(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE) += early_me.c
+romstage-$(CONFIG_NORTHBRIDGE_INTEL_SANDYBRIDGE) += early_me.c
+romstage-$(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE) += early_thermal.c early_pch_native.c early_me_native.c
+
 ifeq ($(CONFIG_BUILD_WITH_FAKE_IFD),y)
 IFD_BIN_PATH := $(objgenerated)/ifdfake.bin
 IFD_SECTIONS := $(addprefix -b ,$(CONFIG_IFD_BIOS_SECTION:"%"=%)) \
diff --git a/src/southbridge/intel/bd82x6x/early_me_native.c b/src/southbridge/intel/bd82x6x/early_me_native.c
new file mode 100644
index 0000000..ab54ffd
--- /dev/null
+++ b/src/southbridge/intel/bd82x6x/early_me_native.c
@@ -0,0 +1,272 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2011 The Chromium OS Authors. All rights reserved.
+ *
+ * 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 <arch/hlt.h>
+#include <arch/io.h>
+#include <console/console.h>
+#include <delay.h>
+#include <device/pci_ids.h>
+#include <string.h>
+#include "me.h"
+#include "pch.h"
+
+static const char *me_ack_values[] = {
+	[ME_HFS_ACK_NO_DID]	= "No DID Ack received",
+	[ME_HFS_ACK_RESET]	= "Non-power cycle reset",
+	[ME_HFS_ACK_PWR_CYCLE]	= "Power cycle reset",
+	[ME_HFS_ACK_S3]		= "Go to S3",
+	[ME_HFS_ACK_S4]		= "Go to S4",
+	[ME_HFS_ACK_S5]		= "Go to S5",
+	[ME_HFS_ACK_GBL_RESET]	= "Global Reset",
+	[ME_HFS_ACK_CONTINUE]	= "Continue to boot"
+};
+
+static inline void pci_read_dword_ptr(void *ptr, int offset)
+{
+	u32 dword = pci_read_config32(PCH_ME_DEV, offset);
+	memcpy(ptr, &dword, sizeof(dword));
+}
+
+static inline void pci_write_dword_ptr(void *ptr, int offset)
+{
+	u32 dword = 0;
+	memcpy(&dword, ptr, sizeof(dword));
+	pci_write_config32(PCH_ME_DEV, offset, dword);
+}
+
+void intel_early_me_status(void)
+{
+	struct me_hfs hfs;
+	struct me_gmes gmes;
+
+	pci_read_dword_ptr(&hfs, PCI_ME_HFS);
+	pci_read_dword_ptr(&gmes, PCI_ME_GMES);
+
+	intel_me_status(&hfs, &gmes);
+}
+
+int intel_early_me_init(void)
+{
+	int count;
+	struct me_uma uma;
+	struct me_hfs hfs;
+
+	printk(BIOS_INFO, "Intel ME early init\n");
+
+	/* Wait for ME UMA SIZE VALID bit to be set */
+	for (count = ME_RETRY; count > 0; --count) {
+		pci_read_dword_ptr(&uma, PCI_ME_UMA);
+		if (uma.valid)
+			break;
+		udelay(ME_DELAY);
+	}
+	if (!count) {
+		printk(BIOS_ERR, "ERROR: ME is not ready!\n");
+		return -1;
+	}
+
+	/* Check for valid firmware */
+	pci_read_dword_ptr(&hfs, PCI_ME_HFS);
+	if (hfs.fpt_bad) {
+		printk(BIOS_WARNING, "WARNING: ME has bad firmware\n");
+		return -1;
+	}
+
+	printk(BIOS_INFO, "Intel ME firmware is ready\n");
+	return 0;
+}
+
+int intel_early_me_uma_size(void)
+{
+	struct me_uma uma;
+
+	pci_read_dword_ptr(&uma, PCI_ME_UMA);
+	if (uma.valid) {
+		printk(BIOS_DEBUG, "ME: Requested %uMB UMA\n", uma.size);
+		return uma.size;
+	}
+
+	printk(BIOS_DEBUG, "ME: Invalid UMA size\n");
+	return 0;
+}
+
+static inline void set_global_reset(int enable)
+{
+	u32 etr3 = pci_read_config32(PCH_LPC_DEV, ETR3);
+
+	/* Clear CF9 Without Resume Well Reset Enable */
+	etr3 &= ~ETR3_CWORWRE;
+
+	/* CF9GR indicates a Global Reset */
+	if (enable)
+		etr3 |= ETR3_CF9GR;
+	else
+		etr3 &= ~ETR3_CF9GR;
+
+	pci_write_config32(PCH_LPC_DEV, ETR3, etr3);
+}
+
+int intel_early_me_init_done(u8 status)
+{
+	u8 reset, errorcode, opmode;
+	u16 reg16;
+	u32 mebase_l, mebase_h;
+	u32 millisec;
+	u32 hfs, me_fws2;
+	struct me_did did = {
+		.init_done = ME_INIT_DONE,
+		.status = status
+	};
+	u32 meDID;
+
+	hfs = (pci_read_config32(PCI_DEV(0, 0x16, 0), PCI_ME_HFS) & 0xff000) >> 12;
+	      
+	opmode = (hfs & 0xf0) >> 4;
+	errorcode = hfs & 0xf;
+
+	if (opmode != ME_HFS_MODE_NORMAL) {
+		printk(BIOS_NOTICE, "ME: Wrong mode : %d\n", opmode);
+		//return 0;
+	}
+	if (errorcode) {
+		printk(BIOS_NOTICE, "ME: HFS error : %d\n", errorcode);
+		//return 0;
+	}
+
+	me_fws2 = pci_read_config32(PCI_DEV(0, 0x16, 0), 0x48);
+	printk(BIOS_NOTICE, "ME: FWS2: 0x%x\n", me_fws2);
+	printk(BIOS_NOTICE, "ME:  Bist in progress: 0x%x\n", me_fws2 & 0x1);
+	printk(BIOS_NOTICE, "ME:  ICC Status      : 0x%x\n", (me_fws2 & 0x6) >> 1);
+	printk(BIOS_NOTICE, "ME:  Invoke MEBx     : 0x%x\n", (me_fws2 & 0x8) >> 3);
+	printk(BIOS_NOTICE, "ME:  CPU replaced    : 0x%x\n", (me_fws2 & 0x10) >> 4);
+	printk(BIOS_NOTICE, "ME:  MBP ready       : 0x%x\n", (me_fws2 & 0x20) >> 5);
+	printk(BIOS_NOTICE, "ME:  MFS failure     : 0x%x\n", (me_fws2 & 0x40) >> 6);
+	printk(BIOS_NOTICE, "ME:  Warm reset req  : 0x%x\n", (me_fws2 & 0x80) >> 7);
+	printk(BIOS_NOTICE, "ME:  CPU repl valid  : 0x%x\n", (me_fws2 & 0x100) >> 8);
+	printk(BIOS_NOTICE, "ME:  (Reserved)      : 0x%x\n", (me_fws2 & 0x600) >> 9);
+	printk(BIOS_NOTICE, "ME:  FW update req   : 0x%x\n", (me_fws2 & 0x800) >> 11);
+	printk(BIOS_NOTICE, "ME:  (Reserved)      : 0x%x\n", (me_fws2 & 0xf000) >> 12);
+	printk(BIOS_NOTICE, "ME:  Current state   : 0x%x\n", (me_fws2 & 0xff0000) >> 16);
+	printk(BIOS_NOTICE, "ME:  Current PM event: 0x%x\n", (me_fws2 & 0xf000000) >> 24);
+	printk(BIOS_NOTICE, "ME:  Progress code   : 0x%x\n", (me_fws2 & 0xf0000000) >> 28);
+	
+	// Poll cpu replaced for 50ms
+	millisec = 0;
+	while ((((me_fws2 & 0x100) >> 8) == 0) && millisec < 50) {
+		udelay(1000);
+		me_fws2 = pci_read_config32(PCI_DEV(0, 0x16, 0), 0x48);
+		millisec++;
+	}
+	if (millisec >= 50 || ((me_fws2 & 0x100) >> 8) == 0x0) {
+		printk(BIOS_NOTICE, "Waited long enough, or CPU was not replaced, continue...\n");
+	} else if ((me_fws2 & 0x100) == 0x100) {
+		if ((me_fws2 & 0x80) == 0x80) {		
+			printk(BIOS_NOTICE, "CPU was replaced & warm reset required...\n");
+			reg16 = pcie_read_config16(PCI_DEV(0, 31, 0), 0xa2) & ~0x80;
+			pcie_write_config16(PCI_DEV(0, 31, 0), 0xa2, reg16);
+			set_global_reset(0);
+			outb(0x6, 0xcf9);
+			hlt();
+		}
+
+		if (((me_fws2 & 0x10) == 0x10) && (me_fws2 & 0x80) == 0x00) {
+			printk(BIOS_NOTICE, "Full training required\n");
+		}
+	}
+
+	printk(BIOS_NOTICE, "PASSED! Tell ME that DRAM is ready\n");
+
+	/* MEBASE from MESEG_BASE[35:20] */
+	mebase_l = pci_read_config32(PCI_CPU_DEVICE, PCI_CPU_MEBASE_L);
+	mebase_h = pci_read_config32(PCI_CPU_DEVICE, PCI_CPU_MEBASE_H) & 0xf;
+	did.uma_base = (mebase_l >> 20) | (mebase_h << 12);
+
+	meDID = did.uma_base | (1 << 28);// | (1 << 23);
+	pci_write_config32(PCI_DEV(0, 0x16, 0), PCI_ME_H_GS, meDID);
+
+	udelay(1100);
+
+	/* Must wait for ME acknowledgement */
+	millisec = 0;
+	hfs = (pci_read_config32(PCI_DEV(0, 0x16, 0), PCI_ME_HFS) & 0xfe000000) >> 24;
+	while ((((hfs & 0xf0) >> 4) != ME_HFS_BIOS_DRAM_ACK) && (millisec < 5000)) {
+		udelay(1000);
+		hfs = (pci_read_config32(PCI_DEV(0, 0x16, 0), PCI_ME_HFS) & 0xfe000000) >> 24;
+		millisec++;
+	}
+
+	me_fws2 = pci_read_config32(PCI_DEV(0, 0x16, 0), 0x48);
+	printk(BIOS_NOTICE, "ME: FWS2: 0x%x\n", me_fws2);
+	printk(BIOS_NOTICE, "ME:  Bist in progress: 0x%x\n", me_fws2 & 0x1);
+	printk(BIOS_NOTICE, "ME:  ICC Status      : 0x%x\n", (me_fws2 & 0x6) >> 1);
+	printk(BIOS_NOTICE, "ME:  Invoke MEBx     : 0x%x\n", (me_fws2 & 0x8) >> 3);
+	printk(BIOS_NOTICE, "ME:  CPU replaced    : 0x%x\n", (me_fws2 & 0x10) >> 4);
+	printk(BIOS_NOTICE, "ME:  MBP ready       : 0x%x\n", (me_fws2 & 0x20) >> 5);
+	printk(BIOS_NOTICE, "ME:  MFS failure     : 0x%x\n", (me_fws2 & 0x40) >> 6);
+	printk(BIOS_NOTICE, "ME:  Warm reset req  : 0x%x\n", (me_fws2 & 0x80) >> 7);
+	printk(BIOS_NOTICE, "ME:  CPU repl valid  : 0x%x\n", (me_fws2 & 0x100) >> 8);
+	printk(BIOS_NOTICE, "ME:  (Reserved)      : 0x%x\n", (me_fws2 & 0x600) >> 9);
+	printk(BIOS_NOTICE, "ME:  FW update req   : 0x%x\n", (me_fws2 & 0x800) >> 11);
+	printk(BIOS_NOTICE, "ME:  (Reserved)      : 0x%x\n", (me_fws2 & 0xf000) >> 12);
+	printk(BIOS_NOTICE, "ME:  Current state   : 0x%x\n", (me_fws2 & 0xff0000) >> 16);
+	printk(BIOS_NOTICE, "ME:  Current PM event: 0x%x\n", (me_fws2 & 0xf000000) >> 24);
+	printk(BIOS_NOTICE, "ME:  Progress code   : 0x%x\n", (me_fws2 & 0xf0000000) >> 28);
+
+	
+	/* Return the requested BIOS action */
+	printk(BIOS_NOTICE, "ME: Requested BIOS Action: %s\n",
+		me_ack_values[(hfs & 0xe) >> 1]);
+
+	reset = inb(0xcf9);
+	reset &= 0xf1;
+	switch ((hfs & 0xe) >> 1) {
+	case ME_HFS_ACK_NO_DID:
+	case ME_HFS_ACK_CONTINUE:
+		/* Continue to boot */
+		return 0;
+	case ME_HFS_ACK_RESET:
+		/* Non-power cycle reset */
+		set_global_reset(0);
+		reset |= 0x06;
+		break;
+	case ME_HFS_ACK_PWR_CYCLE:
+		/* Power cycle reset */
+		set_global_reset(0);
+		reset |= 0x0e;
+		break;
+	case ME_HFS_ACK_GBL_RESET:
+		/* Global reset */
+		set_global_reset(1);
+		reset |= 0x0e;
+		break;
+	case ME_HFS_ACK_S3:
+	case ME_HFS_ACK_S4:
+	case ME_HFS_ACK_S5:
+		break;
+	}
+
+	/* Perform the requested reset */
+	if (reset) {
+		outb(reset, 0xcf9);
+		hlt();
+	}
+	return -1;
+}
diff --git a/src/southbridge/intel/bd82x6x/early_pch_native.c b/src/southbridge/intel/bd82x6x/early_pch_native.c
new file mode 100644
index 0000000..5cd6315
--- /dev/null
+++ b/src/southbridge/intel/bd82x6x/early_pch_native.c
@@ -0,0 +1,375 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2014 Vladimir Serbinenko <phcoder at 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; 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 <console/console.h>
+#include <string.h>
+#include <arch/hlt.h>
+#include <arch/io.h>
+#include <cbmem.h>
+#include <arch/cbfs.h>
+#include <cbfs.h>
+#include <ip_checksum.h>
+#include <pc80/mc146818rtc.h>
+#include <device/pci_def.h>
+#include <delay.h>
+
+#include "pch.h"
+/* For DMI bar.  */
+#include "northbridge/intel/sandybridge/sandybridge.h"
+
+#define SOUTHBRIDGE PCI_DEV(0, 0x1f, 0)
+
+static void
+wait_2338 (void)
+{
+	while (read8 (DEFAULT_RCBA | 0x2338) & 1);
+}
+
+static u32
+read_2338 (u32 edx)
+{
+	u32 ret;
+
+	write32 (DEFAULT_RCBA | 0x2330, edx);
+	write16 (DEFAULT_RCBA | 0x2338, (read16 (DEFAULT_RCBA | 0x2338)
+					 & 0x1ff) | 0x600);
+	wait_2338 ();
+	ret = read32 (DEFAULT_RCBA | 0x2334);
+	wait_2338 ();
+	read8 (DEFAULT_RCBA | 0x2338);
+	return ret;
+}
+
+static void
+write_2338 (u32 edx, u32 val)
+{
+	read_2338 (edx);
+	write16 (DEFAULT_RCBA | 0x2338, (read16 (DEFAULT_RCBA | 0x2338)
+					 & 0x1ff) | 0x600);
+	wait_2338 ();
+
+	write32 (DEFAULT_RCBA | 0x2334, val);
+	wait_2338 ();
+	write16 (DEFAULT_RCBA | 0x2338,
+		 (read16 (DEFAULT_RCBA | 0x2338) & 0x1ff) | 0x600);
+	read8 (DEFAULT_RCBA | 0x2338);
+}
+
+
+static void
+init_dmi (void)
+{
+	int i;
+
+	write32 (DEFAULT_DMIBAR | 0x0914,
+		 read32 (DEFAULT_DMIBAR | 0x0914) | 0x80000000);
+	write32 (DEFAULT_DMIBAR | 0x0934,
+		 read32 (DEFAULT_DMIBAR | 0x0934) | 0x80000000);
+	for (i = 0; i < 4; i++)
+	{
+		write32 (DEFAULT_DMIBAR | 0x0a00 | (i << 4),
+			 read32 (DEFAULT_DMIBAR | 0x0a00 | (i << 4)) & 0xf3ffffff);
+		write32 (DEFAULT_DMIBAR | 0x0a04 | (i << 4),
+			 read32 (DEFAULT_DMIBAR | 0x0a04 | (i << 4)) | 0x800);
+	}
+	write32 (DEFAULT_DMIBAR | 0x0c30, (read32 (DEFAULT_DMIBAR | 0x0c30)
+					   & 0xfffffff) | 0x40000000);
+	for (i = 0; i < 2; i++)
+	{
+		write32 (DEFAULT_DMIBAR | 0x0904 | (i << 5),
+			 read32 (DEFAULT_DMIBAR | 0x0904 | (i << 5)) & 0xfe3fffff);
+		write32 (DEFAULT_DMIBAR | 0x090c | (i << 5),
+			 read32 (DEFAULT_DMIBAR | 0x090c | (i << 5)) & 0xfff1ffff);
+	}
+	write32 (DEFAULT_DMIBAR | 0x090c,
+		 read32 (DEFAULT_DMIBAR | 0x090c) & 0xfe1fffff);
+	write32 (DEFAULT_DMIBAR | 0x092c,
+		 read32 (DEFAULT_DMIBAR | 0x092c) & 0xfe1fffff);
+	read32 (DEFAULT_DMIBAR | 0x0904);	// !!! = 0x7a1842ec
+	write32 (DEFAULT_DMIBAR | 0x0904, 0x7a1842ec);
+	read32 (DEFAULT_DMIBAR | 0x090c);	// !!! = 0x00000208
+	write32 (DEFAULT_DMIBAR | 0x090c, 0x00000128);
+	read32 (DEFAULT_DMIBAR | 0x0924);	// !!! = 0x7a1842ec
+	write32 (DEFAULT_DMIBAR | 0x0924, 0x7a1842ec);
+	read32 (DEFAULT_DMIBAR | 0x092c);	// !!! = 0x00000208
+	write32 (DEFAULT_DMIBAR | 0x092c, 0x00000128);
+	read32 (DEFAULT_DMIBAR | 0x0700);	// !!! = 0x46139008
+	write32 (DEFAULT_DMIBAR | 0x0700, 0x46139008);
+	read32 (DEFAULT_DMIBAR | 0x0720);	// !!! = 0x46139008
+	write32 (DEFAULT_DMIBAR | 0x0720, 0x46139008);
+	read32 (DEFAULT_DMIBAR | 0x0c04);	// !!! = 0x2e680008
+	write32 (DEFAULT_DMIBAR | 0x0c04, 0x2e680008);
+	read32 (DEFAULT_DMIBAR | 0x0904);	// !!! = 0x7a1842ec
+	write32 (DEFAULT_DMIBAR | 0x0904, 0x3a1842ec);
+	read32 (DEFAULT_DMIBAR | 0x0924);	// !!! = 0x7a1842ec
+	write32 (DEFAULT_DMIBAR | 0x0924, 0x3a1842ec);
+	read32 (DEFAULT_DMIBAR | 0x0910);	// !!! = 0x00006300
+	write32 (DEFAULT_DMIBAR | 0x0910, 0x00004300);
+	read32 (DEFAULT_DMIBAR | 0x0930);	// !!! = 0x00006300
+	write32 (DEFAULT_DMIBAR | 0x0930, 0x00004300);
+	read32 (DEFAULT_DMIBAR | 0x0a00);	// !!! = 0x03042010
+	write32 (DEFAULT_DMIBAR | 0x0a00, 0x03042018);
+	read32 (DEFAULT_DMIBAR | 0x0a10);	// !!! = 0x03042010
+	write32 (DEFAULT_DMIBAR | 0x0a10, 0x03042018);
+	read32 (DEFAULT_DMIBAR | 0x0a20);	// !!! = 0x03042010
+	write32 (DEFAULT_DMIBAR | 0x0a20, 0x03042018);
+	read32 (DEFAULT_DMIBAR | 0x0a30);	// !!! = 0x03042010
+	write32 (DEFAULT_DMIBAR | 0x0a30, 0x03042018);
+	read32 (DEFAULT_DMIBAR | 0x0c00);	// !!! = 0x29700c08
+	write32 (DEFAULT_DMIBAR | 0x0c00, 0x29700c08);
+	read32 (DEFAULT_DMIBAR | 0x0a04);	// !!! = 0x0c0708f0
+	write32 (DEFAULT_DMIBAR | 0x0a04, 0x0c0718f0);
+	read32 (DEFAULT_DMIBAR | 0x0a14);	// !!! = 0x0c0708f0
+	write32 (DEFAULT_DMIBAR | 0x0a14, 0x0c0718f0);
+	read32 (DEFAULT_DMIBAR | 0x0a24);	// !!! = 0x0c0708f0
+	write32 (DEFAULT_DMIBAR | 0x0a24, 0x0c0718f0);
+	read32 (DEFAULT_DMIBAR | 0x0a34);	// !!! = 0x0c0708f0
+	write32 (DEFAULT_DMIBAR | 0x0a34, 0x0c0718f0);
+	read32 (DEFAULT_DMIBAR | 0x0900);	// !!! = 0x50000000
+	write32 (DEFAULT_DMIBAR | 0x0900, 0x50000000);
+	read32 (DEFAULT_DMIBAR | 0x0920);	// !!! = 0x50000000
+	write32 (DEFAULT_DMIBAR | 0x0920, 0x50000000);
+	read32 (DEFAULT_DMIBAR | 0x0908);	// !!! = 0x51ffffff
+	write32 (DEFAULT_DMIBAR | 0x0908, 0x51ffffff);
+	read32 (DEFAULT_DMIBAR | 0x0928);	// !!! = 0x51ffffff
+	write32 (DEFAULT_DMIBAR | 0x0928, 0x51ffffff);
+	read32 (DEFAULT_DMIBAR | 0x0a00);	// !!! = 0x03042018
+	write32 (DEFAULT_DMIBAR | 0x0a00, 0x03042018);
+	read32 (DEFAULT_DMIBAR | 0x0a10);	// !!! = 0x03042018
+	write32 (DEFAULT_DMIBAR | 0x0a10, 0x03042018);
+	read32 (DEFAULT_DMIBAR | 0x0a20);	// !!! = 0x03042018
+	write32 (DEFAULT_DMIBAR | 0x0a20, 0x03042018);
+	read32 (DEFAULT_DMIBAR | 0x0a30);	// !!! = 0x03042018
+	write32 (DEFAULT_DMIBAR | 0x0a30, 0x03042018);
+	read32 (DEFAULT_DMIBAR | 0x0700);	// !!! = 0x46139008
+	write32 (DEFAULT_DMIBAR | 0x0700, 0x46139008);
+	read32 (DEFAULT_DMIBAR | 0x0720);	// !!! = 0x46139008
+	write32 (DEFAULT_DMIBAR | 0x0720, 0x46139008);
+	read32 (DEFAULT_DMIBAR | 0x0904);	// !!! = 0x3a1842ec
+	write32 (DEFAULT_DMIBAR | 0x0904, 0x3a1846ec);
+	read32 (DEFAULT_DMIBAR | 0x0924);	// !!! = 0x3a1842ec
+	write32 (DEFAULT_DMIBAR | 0x0924, 0x3a1846ec);
+	read32 (DEFAULT_DMIBAR | 0x0a00);	// !!! = 0x03042018
+	write32 (DEFAULT_DMIBAR | 0x0a00, 0x03042018);
+	read32 (DEFAULT_DMIBAR | 0x0a10);	// !!! = 0x03042018
+	write32 (DEFAULT_DMIBAR | 0x0a10, 0x03042018);
+	read32 (DEFAULT_DMIBAR | 0x0a20);	// !!! = 0x03042018
+	write32 (DEFAULT_DMIBAR | 0x0a20, 0x03042018);
+	read32 (DEFAULT_DMIBAR | 0x0a30);	// !!! = 0x03042018
+	write32 (DEFAULT_DMIBAR | 0x0a30, 0x03042018);
+	read32 (DEFAULT_DMIBAR | 0x0908);	// !!! = 0x51ffffff
+	write32 (DEFAULT_DMIBAR | 0x0908, 0x51ffffff);
+	read32 (DEFAULT_DMIBAR | 0x0928);	// !!! = 0x51ffffff
+	write32 (DEFAULT_DMIBAR | 0x0928, 0x51ffffff);
+	read32 (DEFAULT_DMIBAR | 0x0c00);	// !!! = 0x29700c08
+	write32 (DEFAULT_DMIBAR | 0x0c00, 0x29700c08);
+	read32 (DEFAULT_DMIBAR | 0x0c0c);	// !!! = 0x16063400
+	write32 (DEFAULT_DMIBAR | 0x0c0c, 0x00063400);
+	read32 (DEFAULT_DMIBAR | 0x0700);	// !!! = 0x46139008
+	write32 (DEFAULT_DMIBAR | 0x0700, 0x46339008);
+	read32 (DEFAULT_DMIBAR | 0x0720);	// !!! = 0x46139008
+	write32 (DEFAULT_DMIBAR | 0x0720, 0x46339008);
+	read32 (DEFAULT_DMIBAR | 0x0700);	// !!! = 0x46339008
+	write32 (DEFAULT_DMIBAR | 0x0700, 0x45339008);
+	read32 (DEFAULT_DMIBAR | 0x0720);	// !!! = 0x46339008
+	write32 (DEFAULT_DMIBAR | 0x0720, 0x45339008);
+	read32 (DEFAULT_DMIBAR | 0x0700);	// !!! = 0x45339008
+	write32 (DEFAULT_DMIBAR | 0x0700, 0x453b9008);
+	read32 (DEFAULT_DMIBAR | 0x0720);	// !!! = 0x45339008
+	write32 (DEFAULT_DMIBAR | 0x0720, 0x453b9008);
+	read32 (DEFAULT_DMIBAR | 0x0700);	// !!! = 0x453b9008
+	write32 (DEFAULT_DMIBAR | 0x0700, 0x45bb9008);
+	read32 (DEFAULT_DMIBAR | 0x0720);	// !!! = 0x453b9008
+	write32 (DEFAULT_DMIBAR | 0x0720, 0x45bb9008);
+	read32 (DEFAULT_DMIBAR | 0x0700);	// !!! = 0x45bb9008
+	write32 (DEFAULT_DMIBAR | 0x0700, 0x45fb9008);
+	read32 (DEFAULT_DMIBAR | 0x0720);	// !!! = 0x45bb9008
+	write32 (DEFAULT_DMIBAR | 0x0720, 0x45fb9008);
+	read32 (DEFAULT_DMIBAR | 0x0914);	// !!! = 0x9021a080
+	write32 (DEFAULT_DMIBAR | 0x0914, 0x9021a280);
+	read32 (DEFAULT_DMIBAR | 0x0934);	// !!! = 0x9021a080
+	write32 (DEFAULT_DMIBAR | 0x0934, 0x9021a280);
+	read32 (DEFAULT_DMIBAR | 0x0914);	// !!! = 0x9021a280
+	write32 (DEFAULT_DMIBAR | 0x0914, 0x9821a280);
+	read32 (DEFAULT_DMIBAR | 0x0934);	// !!! = 0x9021a280
+	write32 (DEFAULT_DMIBAR | 0x0934, 0x9821a280);
+	read32 (DEFAULT_DMIBAR | 0x0a00);	// !!! = 0x03042018
+	write32 (DEFAULT_DMIBAR | 0x0a00, 0x03242018);
+	read32 (DEFAULT_DMIBAR | 0x0a10);	// !!! = 0x03042018
+	write32 (DEFAULT_DMIBAR | 0x0a10, 0x03242018);
+	read32 (DEFAULT_DMIBAR | 0x0a20);	// !!! = 0x03042018
+	write32 (DEFAULT_DMIBAR | 0x0a20, 0x03242018);
+	read32 (DEFAULT_DMIBAR | 0x0a30);	// !!! = 0x03042018
+	write32 (DEFAULT_DMIBAR | 0x0a30, 0x03242018);
+	read32 (DEFAULT_DMIBAR | 0x0258);	// !!! = 0x40000600
+	write32 (DEFAULT_DMIBAR | 0x0258, 0x60000600);
+	read32 (DEFAULT_DMIBAR | 0x0904);	// !!! = 0x3a1846ec
+	write32 (DEFAULT_DMIBAR | 0x0904, 0x2a1846ec);
+	read32 (DEFAULT_DMIBAR | 0x0914);	// !!! = 0x9821a280
+	write32 (DEFAULT_DMIBAR | 0x0914, 0x98200280);
+	read32 (DEFAULT_DMIBAR | 0x0924);	// !!! = 0x3a1846ec
+	write32 (DEFAULT_DMIBAR | 0x0924, 0x2a1846ec);
+	read32 (DEFAULT_DMIBAR | 0x0934);	// !!! = 0x9821a280
+	write32 (DEFAULT_DMIBAR | 0x0934, 0x98200280);
+	read32 (DEFAULT_DMIBAR | 0x022c);	// !!! = 0x00c26460
+	write32 (DEFAULT_DMIBAR | 0x022c, 0x00c2403c);
+	read8 (DEFAULT_RCBA | 0x21a4);	// !!! = 0x42
+
+	read32 (DEFAULT_RCBA | 0x21a4);	// !!! = 0x00012c42
+	read32 (DEFAULT_RCBA | 0x2340);	// !!! = 0x0013001b
+	write32 (DEFAULT_RCBA | 0x2340, 0x003a001b);
+	read8 (DEFAULT_RCBA | 0x21b0);	// !!! = 0x01
+	write8 (DEFAULT_RCBA | 0x21b0, 0x02);
+	read32 (DEFAULT_DMIBAR | 0x0084);	// !!! = 0x0041ac41
+	write32 (DEFAULT_DMIBAR | 0x0084, 0x0041ac42);
+	read8 (DEFAULT_DMIBAR | 0x0088);	// !!! = 0x00
+	write8 (DEFAULT_DMIBAR | 0x0088, 0x20);
+	read16 (DEFAULT_DMIBAR | 0x008a);	// !!! = 0x0041
+	read8 (DEFAULT_DMIBAR | 0x0088);	// !!! = 0x00
+	write8 (DEFAULT_DMIBAR | 0x0088, 0x20);
+	read16 (DEFAULT_DMIBAR | 0x008a);	// !!! = 0x0042
+	read16 (DEFAULT_DMIBAR | 0x008a);	// !!! = 0x0042
+
+	read32 (DEFAULT_DMIBAR | 0x0014);	// !!! = 0x8000007f
+	write32 (DEFAULT_DMIBAR | 0x0014, 0x80000019);
+	read32 (DEFAULT_DMIBAR | 0x0020);	// !!! = 0x01000000
+	write32 (DEFAULT_DMIBAR | 0x0020, 0x81000022);
+	read32 (DEFAULT_DMIBAR | 0x002c);	// !!! = 0x02000000
+	write32 (DEFAULT_DMIBAR | 0x002c, 0x82000044);
+	read32 (DEFAULT_DMIBAR | 0x0038);	// !!! = 0x07000080
+	write32 (DEFAULT_DMIBAR | 0x0038, 0x87000080);
+	read8 (DEFAULT_DMIBAR | 0x0004);	// !!! = 0x00
+	write8 (DEFAULT_DMIBAR | 0x0004, 0x01);
+
+	read32 (DEFAULT_RCBA | 0x0050);	// !!! = 0x01200654
+	write32 (DEFAULT_RCBA | 0x0050, 0x01200654);
+	read32 (DEFAULT_RCBA | 0x0050);	// !!! = 0x01200654
+	write32 (DEFAULT_RCBA | 0x0050, 0x012a0654);
+	read32 (DEFAULT_RCBA | 0x0050);	// !!! = 0x012a0654
+	read8 (DEFAULT_RCBA | 0x1114);	// !!! = 0x00
+	write8 (DEFAULT_RCBA | 0x1114, 0x05);
+	read32 (DEFAULT_RCBA | 0x2014);	// !!! = 0x80000011
+	write32 (DEFAULT_RCBA | 0x2014, 0x80000019);
+	read32 (DEFAULT_RCBA | 0x2020);	// !!! = 0x00000000
+	write32 (DEFAULT_RCBA | 0x2020, 0x81000022);
+	read32 (DEFAULT_RCBA | 0x2020);	// !!! = 0x81000022
+	read32 (DEFAULT_RCBA | 0x2030);	// !!! = 0x00000000
+	write32 (DEFAULT_RCBA | 0x2030, 0x82000044);
+	read32 (DEFAULT_RCBA | 0x2030);	// !!! = 0x82000044
+	read32 (DEFAULT_RCBA | 0x2040);	// !!! = 0x00000000
+	write32 (DEFAULT_RCBA | 0x2040, 0x87000080);
+	read32 (DEFAULT_RCBA | 0x0050);	// !!! = 0x012a0654
+	write32 (DEFAULT_RCBA | 0x0050, 0x812a0654);
+	read32 (DEFAULT_RCBA | 0x0050);	// !!! = 0x812a0654
+	read16 (DEFAULT_RCBA | 0x201a);	// !!! = 0x0000
+	read16 (DEFAULT_RCBA | 0x2026);	// !!! = 0x0000
+	read16 (DEFAULT_RCBA | 0x2036);	// !!! = 0x0000
+	read16 (DEFAULT_RCBA | 0x2046);	// !!! = 0x0000
+	read16 (DEFAULT_DMIBAR | 0x001a);	// !!! = 0x0000
+	read16 (DEFAULT_DMIBAR | 0x0026);	// !!! = 0x0000
+	read16 (DEFAULT_DMIBAR | 0x0032);	// !!! = 0x0000
+	read16 (DEFAULT_DMIBAR | 0x003e);	// !!! = 0x0000
+}
+
+void
+early_pch_init_native (void)
+{
+	pcie_write_config8 (SOUTHBRIDGE, 0xa6,
+			    pcie_read_config8 (SOUTHBRIDGE, 0xa6) | 2);
+
+	write32 (DEFAULT_RCBA | 0x2088, 0x00109000);
+	read32 (DEFAULT_RCBA | 0x20ac);	// !!! = 0x00000000
+	write32 (DEFAULT_RCBA | 0x20ac, 0x40000000);
+	write32 (DEFAULT_RCBA | 0x100c, 0x01110000);
+	write8 (DEFAULT_RCBA | 0x2340, 0x1b);
+	read32 (DEFAULT_RCBA | 0x2314);	// !!! = 0x0a080000
+	write32 (DEFAULT_RCBA | 0x2314, 0x0a280000);
+	read32 (DEFAULT_RCBA | 0x2310);	// !!! = 0xc809605b
+	write32 (DEFAULT_RCBA | 0x2310, 0xa809605b);
+	write32 (DEFAULT_RCBA | 0x2324, 0x00854c74);
+	read8 (DEFAULT_RCBA | 0x0400);	// !!! = 0x00
+	read32 (DEFAULT_RCBA | 0x2310);	// !!! = 0xa809605b
+	write32 (DEFAULT_RCBA | 0x2310, 0xa809605b);
+	read32 (DEFAULT_RCBA | 0x2310);	// !!! = 0xa809605b
+	write32 (DEFAULT_RCBA | 0x2310, 0xa809605b);
+
+	write_2338 (0xea007f62, 0x00590133);
+	write_2338 (0xec007f62, 0x00590133);
+	write_2338 (0xec007f64, 0x59555588);
+	write_2338 (0xea0040b9, 0x0001051c);
+	write_2338 (0xeb0040a1, 0x800084ff);
+	write_2338 (0xec0040a1, 0x800084ff);
+	write_2338 (0xea004001, 0x00008400);
+	write_2338 (0xeb004002, 0x40201758);
+	write_2338 (0xec004002, 0x40201758);
+	write_2338 (0xea004002, 0x00601758);
+	write_2338 (0xea0040a1, 0x810084ff);
+	write_2338 (0xeb0040b1, 0x0001c598);
+	write_2338 (0xec0040b1, 0x0001c598);
+	write_2338 (0xeb0040b6, 0x0001c598);
+	write_2338 (0xea0000a9, 0x80ff969f);
+	write_2338 (0xea0001a9, 0x80ff969f);
+	write_2338 (0xeb0040b2, 0x0001c396);
+	write_2338 (0xeb0040b3, 0x0001c396);
+	write_2338 (0xec0040b2, 0x0001c396);
+	write_2338 (0xea0001a9, 0x80ff94ff);
+	write_2338 (0xea000151, 0x0088037f);
+	write_2338 (0xea0000a9, 0x80ff94ff);
+	write_2338 (0xea000051, 0x0088037f);
+
+	write_2338 (0xea007f05, 0x00010642);
+	write_2338 (0xea0040b7, 0x0001c91c);
+	write_2338 (0xea0040b8, 0x0001c91c);
+	write_2338 (0xeb0040a1, 0x820084ff);
+	write_2338 (0xec0040a1, 0x820084ff);
+	write_2338 (0xea007f0a, 0xc2480000);
+
+	write_2338 (0xec00404d, 0x1ff177f);
+	write_2338 (0xec000084, 0x5a600000);
+	write_2338 (0xec000184, 0x5a600000);
+	write_2338 (0xec000284, 0x5a600000);
+	write_2338 (0xec000384, 0x5a600000);
+	write_2338 (0xec000094, 0x000f0501);
+	write_2338 (0xec000194, 0x000f0501);
+	write_2338 (0xec000294, 0x000f0501);
+	write_2338 (0xec000394, 0x000f0501);
+	write_2338 (0xec000096, 0x00000001);
+	write_2338 (0xec000196, 0x00000001);
+	write_2338 (0xec000296, 0x00000001);
+	write_2338 (0xec000396, 0x00000001);
+	write_2338 (0xec000001, 0x00008c08);
+	write_2338 (0xec000101, 0x00008c08);
+	write_2338 (0xec000201, 0x00008c08);
+	write_2338 (0xec000301, 0x00008c08);
+	write_2338 (0xec0040b5, 0x0001c518);
+	write_2338 (0xec000087, 0x06077597);
+	write_2338 (0xec000187, 0x06077597);
+	write_2338 (0xec000287, 0x06077597);
+	write_2338 (0xec000387, 0x06077597);
+	write_2338 (0xea000050, 0x00bb0157);
+	write_2338 (0xea000150, 0x00bb0157);
+	write_2338 (0xec007f60, 0x77777d77);
+	write_2338 (0xea00008d, 0x01320000);
+	write_2338 (0xea00018d, 0x01320000);
+	write_2338 (0xec0007b2, 0x04514b5e);
+	write_2338 (0xec00078c, 0x40000200);
+	write_2338 (0xec000780, 0x02000020);
+
+	init_dmi();
+}
diff --git a/src/southbridge/intel/bd82x6x/early_thermal.c b/src/southbridge/intel/bd82x6x/early_thermal.c
new file mode 100644
index 0000000..02ec9a7
--- /dev/null
+++ b/src/southbridge/intel/bd82x6x/early_thermal.c
@@ -0,0 +1,70 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2014 Vladimir Serbinenko
+ *
+ * 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, 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
+ */
+
+#include <arch/io.h>
+#include "pch.h"
+#include "cpu/intel/model_206ax/model_206ax.h"
+#include <cpu/x86/msr.h>
+
+/* Early thermal init, must be done prior to giving ME its memory
+   which is done at the end of raminit.  */
+void early_thermal_init(void)
+{
+	device_t dev;
+	msr_t msr;
+
+	dev = PCI_DEV(0x0, 0x1f, 0x6);
+
+	/* Program address for temporary BAR.  */
+	pci_write_config32(dev, 0x40, 0x40000000);
+	pci_write_config32(dev, 0x44, 0x0);
+
+	/* Activate temporary BAR.  */
+	pci_write_config32(dev, 0x40,
+			   pci_read_config32(dev, 0x40) | 5);
+
+
+	write16 (0x40000004, 0x3a2b);
+	write8 (0x4000000c, 0xff);
+	write8 (0x4000000d, 0x00);
+	write8 (0x4000000e, 0x40);
+	write8 (0x40000082, 0x00);
+	write8 (0x40000001, 0xba);
+
+	/* Perform init.  */
+	/* Configure TJmax.  */
+	msr = rdmsr(MSR_TEMPERATURE_TARGET);
+	write16(0x40000012, ((msr.lo >> 16) & 0xff) << 6);
+	/* Northbridge temperature slope and offset.  */
+	write16(0x40000016, 0x808c);
+
+	write16 (0x40000014, 0xde87);
+
+	/* Enable thermal data reporting, processor, PCH and northbridge.  */
+	write16(0x4000001a, (read16(0x4000001a) & ~0xf) | 0x10f0);
+
+	/* Disable temporary BAR.  */
+	pci_write_config32(dev, 0x40,
+			   pci_read_config32(dev, 0x40) & ~1);
+	pci_write_config32(dev, 0x40, 0);
+
+	write32 (DEFAULT_RCBA | 0x38b0,
+		 (read32 (DEFAULT_RCBA | 0x38b0) & 0xffff8003) | 0x403c);
+}
diff --git a/src/southbridge/intel/bd82x6x/pch.h b/src/southbridge/intel/bd82x6x/pch.h
index 90de855..83128e2 100644
--- a/src/southbridge/intel/bd82x6x/pch.h
+++ b/src/southbridge/intel/bd82x6x/pch.h
@@ -74,6 +74,8 @@ void enable_smbus(void);
 void enable_usb_bar(void);
 int smbus_read_byte(unsigned device, unsigned address);
 int early_spi_read(u32 offset, u32 size, u8 *buffer);
+void early_thermal_init(void);
+void early_pch_init_native(void);
 #endif
 #endif
 
diff --git a/src/southbridge/intel/bd82x6x/smi.c b/src/southbridge/intel/bd82x6x/smi.c
index 0166edf..a20232e 100644
--- a/src/southbridge/intel/bd82x6x/smi.c
+++ b/src/southbridge/intel/bd82x6x/smi.c
@@ -29,10 +29,7 @@
 #include <cpu/x86/smm.h>
 #include <string.h>
 #include "pch.h"
-
-#if CONFIG_NORTHBRIDGE_INTEL_SANDYBRIDGE || CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE
 #include "northbridge/intel/sandybridge/sandybridge.h"
-#endif
 
 extern unsigned char _binary_smm_start;
 extern unsigned char _binary_smm_end;
diff --git a/src/southbridge/intel/bd82x6x/usb_ehci.c b/src/southbridge/intel/bd82x6x/usb_ehci.c
index 78f92d9..97f20bd 100644
--- a/src/southbridge/intel/bd82x6x/usb_ehci.c
+++ b/src/southbridge/intel/bd82x6x/usb_ehci.c
@@ -18,6 +18,7 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#include <kconfig.h>
 #include <console/console.h>
 #include <device/device.h>
 #include <device/pci.h>
@@ -36,11 +37,43 @@ static void usb_ehci_init(struct device *dev)
 	RCBA32(0x35b0) = reg32;
 
 	printk(BIOS_DEBUG, "EHCI: Setting up controller.. ");
+
+	/* For others, done in MRC.  */
+#if IS_ENABLED(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE)
+	pci_write_config32(dev, 0x84, 0x930c8811);
+	pci_write_config32(dev, 0x88, 0x24000d30);
+	pci_write_config32(dev, 0xf4, 0x80408588);
+	pci_write_config32(dev, 0xf4, 0x80808588);
+	pci_write_config32(dev, 0xf4, 0x00808588);
+	pci_write_config32(dev, 0xfc, 0x205b1708);
+#endif
+
 	reg32 = pci_read_config32(dev, PCI_COMMAND);
 	reg32 |= PCI_COMMAND_MASTER;
 	//reg32 |= PCI_COMMAND_SERR;
 	pci_write_config32(dev, PCI_COMMAND, reg32);
 
+	/* For others, done in MRC.  */
+#if IS_ENABLED(CONFIG_NORTHBRIDGE_INTEL_IVYBRIDGE_NATIVE)
+	struct resource *res;
+	u8 access_cntl;
+
+	access_cntl = pci_read_config8(dev, 0x80);
+
+	/* Enable writes to protected registers. */
+	pci_write_config8(dev, 0x80, access_cntl | 1);
+
+	res = find_resource(dev, PCI_BASE_ADDRESS_0);
+	if (res) {
+		/* Number of ports and companion controllers.  */
+		reg32 = read32(res->base + 4);
+		write32(res->base + 4, (reg32 & 0xfff00000) | 3);
+	}
+
+	/* Restore protection. */
+	pci_write_config8(dev, 0x80, access_cntl);
+#endif
+
 	printk(BIOS_DEBUG, "done.\n");
 }
 



More information about the coreboot-gerrit mailing list