diff options
author | Patrick Georgi <patrick.georgi@secunet.com> | 2012-11-06 11:03:53 +0100 |
---|---|---|
committer | Patrick Georgi <patrick@georgi-clan.de> | 2012-11-27 09:16:18 +0100 |
commit | 2efc8808b8bfaee0a0e8f3ee387ecd9a3f049705 (patch) | |
tree | c486b184f7609b42b3de3d5cd5d213226820a278 /src/northbridge/intel/gm45 | |
parent | acd7d952514485dbc41fa04b0d16be4002e31019 (diff) |
intel/gm45: new northbridge
The code supports DDR3 boards only. RAM init for DDR2 is sufficiently
different that it requires separate code, and we have no boards to
test that.
Change-Id: I9076546faf8a2033c89eb95f5eec524439ab9fe1
Signed-off-by: Patrick Georgi <patrick.georgi@secunet.com>
Signed-off-by: Nico Huber <nico.huber@secunet.com>
Reviewed-on: http://review.coreboot.org/1689
Tested-by: build bot (Jenkins)
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
Diffstat (limited to 'src/northbridge/intel/gm45')
24 files changed, 5949 insertions, 0 deletions
diff --git a/src/northbridge/intel/gm45/Kconfig b/src/northbridge/intel/gm45/Kconfig new file mode 100644 index 0000000000..798e4016d8 --- /dev/null +++ b/src/northbridge/intel/gm45/Kconfig @@ -0,0 +1,31 @@ +## +## This file is part of the coreboot project. +## +## Copyright (C) 2007-2009 coresystems GmbH +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; version 2 of the License. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +config NORTHBRIDGE_INTEL_GM45 + bool + +if NORTHBRIDGE_INTEL_GM45 + +config NORTHBRIDGE_SPECIFIC_OPTIONS # dummy + def_bool y + select HAVE_DEBUG_RAM_SETUP + select MMCONF_SUPPORT_DEFAULT + select IOMMU + +endif diff --git a/src/northbridge/intel/gm45/Makefile.inc b/src/northbridge/intel/gm45/Makefile.inc new file mode 100644 index 0000000000..054906823e --- /dev/null +++ b/src/northbridge/intel/gm45/Makefile.inc @@ -0,0 +1,39 @@ +# +# This file is part of the coreboot project. +# +# Copyright (C) 2012 secunet Security Networks AG +# +# 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 +# + +romstage-y += early_init.c +romstage-y += early_reset.c +romstage-y += delay.c +romstage-y += raminit.c +romstage-y += raminit_rcomp_calibration.c +romstage-y += raminit_receive_enable_calibration.c +romstage-y += raminit_read_write_training.c +romstage-y += pcie.c +romstage-y += thermal.c +romstage-y += igd.c +romstage-y += pm.c +romstage-y += ram_calc.c +romstage-$(CONFIG_IOMMU) += iommu.c + +ramstage-$(CONFIG_GENERATE_ACPI_TABLES) += acpi.c + +driver-y += ram_calc.c +driver-y += northbridge.c + +smm-$(CONFIG_HAVE_SMI_HANDLER) += delay.c diff --git a/src/northbridge/intel/gm45/acpi.c b/src/northbridge/intel/gm45/acpi.c new file mode 100644 index 0000000000..da9bfa8f90 --- /dev/null +++ b/src/northbridge/intel/gm45/acpi.c @@ -0,0 +1,107 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2007-2009 coresystems GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include <types.h> +#include <string.h> +#include <console/console.h> +#include <arch/acpi.h> +#include <arch/acpigen.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include "gm45.h" + +unsigned long acpi_fill_mcfg(unsigned long current) +{ + device_t dev; + u32 pciexbar = 0; + u32 pciexbar_reg; + int max_buses; + + dev = dev_find_device(0x8086, 0x2a40, 0); + if (!dev) + return current; + + pciexbar_reg = pci_read_config32(dev, D0F0_PCIEXBAR_LO); + + // MMCFG not supported or not enabled. + if (!(pciexbar_reg & (1 << 0))) + return current; + + switch ((pciexbar_reg >> 1) & 3) { + case 0: // 256MB + pciexbar = pciexbar_reg & ((1 << 31)|(1 << 30)|(1 << 29)|(1 << 28)); + max_buses = 256; + break; + case 1: // 128M + pciexbar = pciexbar_reg & ((1 << 31)|(1 << 30)|(1 << 29)|(1 << 28)|(1 << 27)); + max_buses = 128; + break; + case 2: // 64M + pciexbar = pciexbar_reg & ((1 << 31)|(1 << 30)|(1 << 29)|(1 << 28)|(1 << 27)|(1 << 26)); + max_buses = 64; + break; + default: // RSVD + return current; + } + + if (!pciexbar) + return current; + + current += acpi_create_mcfg_mmconfig((acpi_mcfg_mmconfig_t *) current, + pciexbar, 0x0, 0x0, max_buses - 1); + + return current; +} + +unsigned long acpi_fill_dmar(unsigned long current) +{ + int me_active = (dev_find_slot(0, PCI_DEVFN(3, 0)) != NULL); + int stepping = pci_read_config8(dev_find_slot(0, PCI_DEVFN(0, 0)), PCI_CLASS_REVISION); + + unsigned long tmp = current; + current += acpi_create_dmar_drhd(current, 0, 0, IOMMU_BASE1); + current += acpi_create_dmar_drhd_ds_pci(current, 0, 0x1b, 0); + acpi_dmar_drhd_fixup(tmp, current); + + if (stepping != STEPPING_B2) { + tmp = current; + current += acpi_create_dmar_drhd(current, 0, 0, IOMMU_BASE2); + current += acpi_create_dmar_drhd_ds_pci(current, 0, 0x2, 0); + current += acpi_create_dmar_drhd_ds_pci(current, 0, 0x2, 1); + acpi_dmar_drhd_fixup(tmp, current); + } + + if (me_active) { + tmp = current; + current += acpi_create_dmar_drhd(current, 0, 0, IOMMU_BASE3); + current += acpi_create_dmar_drhd_ds_pci(current, 0, 0x3, 0); + current += acpi_create_dmar_drhd_ds_pci(current, 0, 0x3, 1); + current += acpi_create_dmar_drhd_ds_pci(current, 0, 0x3, 2); + current += acpi_create_dmar_drhd_ds_pci(current, 0, 0x3, 3); + acpi_dmar_drhd_fixup(tmp, current); + } + + current += acpi_create_dmar_drhd(current, DRHD_INCLUDE_PCI_ALL, 0, IOMMU_BASE4); + + /* TODO: reserve GTT for 0.2.0 and 0.2.1? */ + return current; +} diff --git a/src/northbridge/intel/gm45/acpi/gm45.asl b/src/northbridge/intel/gm45/acpi/gm45.asl new file mode 100644 index 0000000000..fac194b47a --- /dev/null +++ b/src/northbridge/intel/gm45/acpi/gm45.asl @@ -0,0 +1,89 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2007-2009 coresystems GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "hostbridge.asl" +#include "../gm45.h" + +/* PCI Device Resource Consumption */ +Device (PDRC) +{ + Name (_HID, EISAID("PNP0C02")) + Name (_UID, 1) + + // This does not seem to work correctly yet - set values statically for + // now. + + //Name (PDRS, ResourceTemplate() { + // Memory32Fixed(ReadWrite, 0x00000000, 0x00004000, RCRB) // RCBA + // Memory32Fixed(ReadWrite, 0x00000000, 0x00004000, MCHB) // MCHBAR + // Memory32Fixed(ReadWrite, 0x00000000, 0x00001000, DMIB) // DMIBAR + // Memory32Fixed(ReadWrite, 0x00000000, 0x00001000, EGPB) // EPBAR + // Memory32Fixed(ReadWrite, 0x00000000, 0x00000000, PCIE) // PCIE BAR + // Memory32Fixed(ReadWrite, 0xfed20000, 0x00070000, ICHB) // Misc ICH + //}) + + Name (PDRS, ResourceTemplate() { + Memory32Fixed(ReadWrite, 0xfed1c000, 0x00004000) // RCBA + Memory32Fixed(ReadWrite, DEFAULT_MCHBAR, 0x00004000) + Memory32Fixed(ReadWrite, DEFAULT_DMIBAR, 0x00001000) + Memory32Fixed(ReadWrite, DEFAULT_EPBAR, 0x00001000) + Memory32Fixed(ReadWrite, DEFAULT_PCIEXBAR, 0x04000000) + Memory32Fixed(ReadWrite, 0xfed20000, 0x00020000) // Misc ICH + Memory32Fixed(ReadWrite, 0xfed40000, 0x00005000) // Misc ICH + Memory32Fixed(ReadWrite, 0xfed45000, 0x0004b000) // Misc ICH + }) + + // Current Resource Settings + Method (_CRS, 0, Serialized) + { + //CreateDwordField(PDRS, ^RCRB._BAS, RBR0) + //ShiftLeft(\_SB.PCI0.LPCB.RCBA, 14, RBR0) + + //CreateDwordField(PDRS, ^MCHB._BAS, MBR0) + //ShiftLeft(\_SB.PCI0.MCHC.MHBR, 14, MBR0) + + //CreateDwordField(PDRS, ^DMIB._BAS, DBR0) + //ShiftLeft(\_SB.PCI0.MCHC.DMBR, 12, DBR0) + + //CreateDwordField(PDRS, ^EGPB._BAS, EBR0) + //ShiftLeft(\_SB.PCI0.MCHC.EPBR, 12, EBR0) + + //CreateDwordField(PDRS, ^PCIE._BAS, PBR0) + //ShiftLeft(\_SB.PCI0.MCHC.PXBR, 26, PBR0) + + //CreateDwordField(PDRS, ^PCIE._LEN, PSZ0) + //ShiftLeft(0x10000000, \_SB.PCI0.MCHC.PXSZ, PSZ0) + + Return(PDRS) + } +} + +// PCIe graphics port 0:1.0 +#include "peg.asl" + +// Integrated graphics 0:2.0 +#include "igd.asl" + +Scope (\) +{ + // backlight control, display switching, lid + #include "acpi/video.asl" +} diff --git a/src/northbridge/intel/gm45/acpi/hostbridge.asl b/src/northbridge/intel/gm45/acpi/hostbridge.asl new file mode 100644 index 0000000000..3d70e54821 --- /dev/null +++ b/src/northbridge/intel/gm45/acpi/hostbridge.asl @@ -0,0 +1,241 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2007-2009 coresystems GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include <arch/ioapic.h> + +Name(_HID,EISAID("PNP0A08")) // PCIe +Name(_CID,EISAID("PNP0A03")) // PCI + +Name(_ADR, 0) +Name(_BBN, 0) + +Device (MCHC) +{ + Name(_ADR, 0x00000000) // 0:0.0 + + OperationRegion(MCHP, PCI_Config, 0x00, 0x100) + Field (MCHP, DWordAcc, NoLock, Preserve) + { + Offset (0x40), // EPBAR + EPEN, 1, // Enable + , 11, // + EPBR, 24, // EPBAR + + Offset (0x48), // MCHBAR + MHEN, 1, // Enable + , 13, // + MHBR, 22, // MCHBAR + + Offset (0x60), // PCIe BAR + PXEN, 1, // Enable + PXSZ, 2, // BAR size + , 23, // + PXBR, 10, // PCIe BAR + + Offset (0x68), // DMIBAR + DMEN, 1, // Enable + , 11, // + DMBR, 24, // DMIBAR + + // ... + + Offset (0x90), // PAM0 + , 4, + PM0H, 2, + , 2, + Offset (0x91), // PAM1 + PM1L, 2, + , 2, + PM1H, 2, + , 2, + Offset (0x92), // PAM2 + PM2L, 2, + , 2, + PM2H, 2, + , 2, + Offset (0x93), // PAM3 + PM3L, 2, + , 2, + PM3H, 2, + , 2, + Offset (0x94), // PAM4 + PM4L, 2, + , 2, + PM4H, 2, + , 2, + Offset (0x95), // PAM5 + PM5L, 2, + , 2, + PM5H, 2, + , 2, + Offset (0x96), // PAM6 + PM6L, 2, + , 2, + PM6H, 2, + , 2, + + Offset (0xa0), // Top of Used Memory + TOM, 8, + + Offset (0xb0), // Top of Low Used Memory + , 4, + TLUD, 12, + } + +} + + +// Current Resource Settings + +Method (_CRS, 0, Serialized) +{ + Name (MCRS, ResourceTemplate() + { + // Bus Numbers + WordBusNumber (ResourceProducer, MinFixed, MaxFixed, PosDecode, + 0x0000, 0x0000, 0x00ff, 0x0000, 0x0100,,, PB00) + + // IO Region 0 + DWordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange, + 0x0000, 0x0000, 0x0cf7, 0x0000, 0x0cf8,,, PI00) + + // PCI Config Space + Io (Decode16, 0x0cf8, 0x0cf8, 0x0001, 0x0008) + + // IO Region 1 + DWordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange, + 0x0000, 0x0d00, 0xffff, 0x0000, 0xf300,,, PI01) + + // VGA memory (0xa0000-0xbffff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000a0000, 0x000bffff, 0x00000000, + 0x00020000,,, ASEG) + + // OPROM reserved (0xc0000-0xc3fff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000c0000, 0x000c3fff, 0x00000000, + 0x00004000,,, OPR0) + + // OPROM reserved (0xc4000-0xc7fff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000c4000, 0x000c7fff, 0x00000000, + 0x00004000,,, OPR1) + + // OPROM reserved (0xc8000-0xcbfff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000c8000, 0x000cbfff, 0x00000000, + 0x00004000,,, OPR2) + + // OPROM reserved (0xcc000-0xcffff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000cc000, 0x000cffff, 0x00000000, + 0x00004000,,, OPR3) + + // OPROM reserved (0xd0000-0xd3fff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000d0000, 0x000d3fff, 0x00000000, + 0x00004000,,, OPR4) + + // OPROM reserved (0xd4000-0xd7fff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000d4000, 0x000d7fff, 0x00000000, + 0x00004000,,, OPR5) + + // OPROM reserved (0xd8000-0xdbfff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000d8000, 0x000dbfff, 0x00000000, + 0x00004000,,, OPR6) + + // OPROM reserved (0xdc000-0xdffff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000dc000, 0x000dffff, 0x00000000, + 0x00004000,,, OPR7) + + // BIOS Extension (0xe0000-0xe3fff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000e0000, 0x000e3fff, 0x00000000, + 0x00004000,,, ESG0) + + // BIOS Extension (0xe4000-0xe7fff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000e4000, 0x000e7fff, 0x00000000, + 0x00004000,,, ESG1) + + // BIOS Extension (0xe8000-0xebfff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000e8000, 0x000ebfff, 0x00000000, + 0x00004000,,, ESG2) + + // BIOS Extension (0xec000-0xeffff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000ec000, 0x000effff, 0x00000000, + 0x00004000,,, ESG3) + + // System BIOS (0xf0000-0xfffff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000f0000, 0x000fffff, 0x00000000, + 0x00010000,,, FSEG) + + // PCI Memory Region (Top of memory-0xfebfffff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x00000000, 0xfebfffff, 0x00000000, + IO_APIC_ADDR,,, PM01) + + // TPM Area (0xfed40000-0xfed44fff) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0xfed40000, 0xfed44fff, 0x00000000, + 0x00005000,,, TPMR) + }) + + // Find PCI resource area in MCRS + CreateDwordField(MCRS, PM01._MIN, PMIN) + CreateDwordField(MCRS, PM01._MAX, PMAX) + CreateDwordField(MCRS, PM01._LEN, PLEN) + + // Fix up PCI memory region: + // Enter actual TOLUD. The TOLUD register contains bits 20-31 of + // the top of memory address. + ShiftLeft (^MCHC.TLUD, 20, PMIN) + Add(Subtract(PMAX, PMIN), 1, PLEN) + + Return (MCRS) +} + +/* IRQ assignment is mainboard specific. Get it from mainboard ACPI code */ +#include "acpi/gm45_pci_irqs.asl" + + diff --git a/src/northbridge/intel/gm45/acpi/igd.asl b/src/northbridge/intel/gm45/acpi/igd.asl new file mode 100644 index 0000000000..ba01d230b6 --- /dev/null +++ b/src/northbridge/intel/gm45/acpi/igd.asl @@ -0,0 +1,324 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2007-2009 coresystems GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +Device (GFX0) +{ + Name (_ADR, 0x00020000) + + /* Display Output Switching */ + Method (_DOS, 1) + { + /* Windows 2000 and Windows XP call _DOS to enable/disable + * Display Output Switching during init and while a switch + * is already active + */ + Store (And(Arg0, 7), DSEN) + } + + /* We try to support as many GM45 systems as possible, + * so keep the number of DIDs flexible. + */ + Method (_DOD, 0) + { + If (LEqual(NDID, 1)) { + Name(DOD1, Package() { + 0xffffffff + }) + Store (Or(0x00010000, DID1), Index(DOD1, 0)) + Return(DOD1) + } + + If (LEqual(NDID, 2)) { + Name(DOD2, Package() { + 0xffffffff, + 0xffffffff + }) + Store (Or(0x00010000, DID1), Index(DOD2, 0)) + Store (Or(0x00010000, DID2), Index(DOD2, 1)) + Return(DOD2) + } + + If (LEqual(NDID, 3)) { + Name(DOD3, Package() { + 0xffffffff, + 0xffffffff, + 0xffffffff + }) + Store (Or(0x00010000, DID1), Index(DOD3, 0)) + Store (Or(0x00010000, DID2), Index(DOD3, 1)) + Store (Or(0x00010000, DID3), Index(DOD3, 2)) + Return(DOD3) + } + + If (LEqual(NDID, 4)) { + Name(DOD4, Package() { + 0xffffffff, + 0xffffffff, + 0xffffffff, + 0xffffffff + }) + Store (Or(0x00010000, DID1), Index(DOD4, 0)) + Store (Or(0x00010000, DID2), Index(DOD4, 1)) + Store (Or(0x00010000, DID3), Index(DOD4, 2)) + Store (Or(0x00010000, DID4), Index(DOD4, 3)) + Return(DOD4) + } + + If (LGreater(NDID, 4)) { + Name(DOD5, Package() { + 0xffffffff, + 0xffffffff, + 0xffffffff, + 0xffffffff, + 0xffffffff + }) + Store (Or(0x00010000, DID1), Index(DOD5, 0)) + Store (Or(0x00010000, DID2), Index(DOD5, 1)) + Store (Or(0x00010000, DID3), Index(DOD5, 2)) + Store (Or(0x00010000, DID4), Index(DOD5, 3)) + Store (Or(0x00010000, DID5), Index(DOD5, 4)) + Return(DOD5) + } + + /* Some error happened, but we have to return something */ + Return (Package() {0x00000400}) + } + + Device(DD01) + { + /* Device Unique ID */ + Method(_ADR, 0, Serialized) + { + If(LEqual(DID1, 0)) { + Return (1) + } Else { + Return (And(0xffff, DID1)) + } + } + + /* Device Current Status */ + Method(_DCS, 0) + { + TRAP(1) + If (And(CSTE, 1)) { + Return (0x1f) + } + Return(0x1d) + } + + /* Query Device Graphics State */ + Method(_DGS, 0) + { + If (And(NSTE, 1)) { + Return(1) + } + Return(0) + } + + /* Device Set State */ + Method(_DSS, 1) + { + /* If Parameter Arg0 is (1 << 31) | (1 << 30), the + * display switch was completed + */ + If (LEqual(And(Arg0, 0xc0000000), 0xc0000000)) { + Store (NSTE, CSTE) + } + } + } + + Device(DD02) + { + /* Device Unique ID */ + Method(_ADR, 0, Serialized) + { + If(LEqual(DID2, 0)) { + Return (2) + } Else { + Return (And(0xffff, DID2)) + } + } + + /* Device Current Status */ + Method(_DCS, 0) + { + TRAP(1) + If (And(CSTE, 2)) { + Return (0x1f) + } + Return(0x1d) + } + + /* Query Device Graphics State */ + Method(_DGS, 0) + { + If (And(NSTE, 2)) { + Return(1) + } + Return(0) + } + + /* Device Set State */ + Method(_DSS, 1) + { + /* If Parameter Arg0 is (1 << 31) | (1 << 30), the + * display switch was completed + */ + If (LEqual(And(Arg0, 0xc0000000), 0xc0000000)) { + Store (NSTE, CSTE) + } + } + } + + + Device(DD03) + { + /* Device Unique ID */ + Method(_ADR, 0, Serialized) + { + If(LEqual(DID3, 0)) { + Return (3) + } Else { + Return (And(0xffff, DID3)) + } + } + + /* Device Current Status */ + Method(_DCS, 0) + { + TRAP(1) + If (And(CSTE, 4)) { + Return (0x1f) + } + Return(0x1d) + } + + /* Query Device Graphics State */ + Method(_DGS, 0) + { + If (And(NSTE, 4)) { + Return(1) + } + Return(0) + } + + /* Device Set State */ + Method(_DSS, 1) + { + /* If Parameter Arg0 is (1 << 31) | (1 << 30), the + * display switch was completed + */ + If (LEqual(And(Arg0, 0xc0000000), 0xc0000000)) { + Store (NSTE, CSTE) + } + } + } + + + Device(DD04) + { + /* Device Unique ID */ + Method(_ADR, 0, Serialized) + { + If(LEqual(DID4, 0)) { + Return (4) + } Else { + Return (And(0xffff, DID4)) + } + } + + /* Device Current Status */ + Method(_DCS, 0) + { + TRAP(1) + If (And(CSTE, 8)) { + Return (0x1f) + } + Return(0x1d) + } + + /* Query Device Graphics State */ + Method(_DGS, 0) + { + If (And(NSTE, 4)) { + Return(1) + } + Return(0) + } + + /* Device Set State */ + Method(_DSS, 1) + { + /* If Parameter Arg0 is (1 << 31) | (1 << 30), the + * display switch was completed + */ + If (LEqual(And(Arg0, 0xc0000000), 0xc0000000)) { + Store (NSTE, CSTE) + } + } + } + + + Device(DD05) + { + /* Device Unique ID */ + Method(_ADR, 0, Serialized) + { + If(LEqual(DID5, 0)) { + Return (5) + } Else { + Return (And(0xffff, DID5)) + } + } + + /* Device Current Status */ + Method(_DCS, 0) + { + TRAP(1) + If (And(CSTE, 16)) { + Return (0x1f) + } + Return(0x1d) + } + + /* Query Device Graphics State */ + Method(_DGS, 0) + { + If (And(NSTE, 4)) { + Return(1) + } + Return(0) + } + + /* Device Set State */ + Method(_DSS, 1) + { + /* If Parameter Arg0 is (1 << 31) | (1 << 30), the + * display switch was completed + */ + If (LEqual(And(Arg0, 0xc0000000), 0xc0000000)) { + Store (NSTE, CSTE) + } + } + } + +} + diff --git a/src/northbridge/intel/gm45/acpi/peg.asl b/src/northbridge/intel/gm45/acpi/peg.asl new file mode 100644 index 0000000000..bc7f8f7578 --- /dev/null +++ b/src/northbridge/intel/gm45/acpi/peg.asl @@ -0,0 +1,47 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2007-2009 coresystems GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +Device (PEGP) +{ + Name (_ADR, 0x00010000) + + // PCI Interrupt Routing. + Method (_PRT) + { + If (PICM) { + Return (Package() { + Package() { 0x0000ffff, 0, 0, 16 }, + Package() { 0x0000ffff, 1, 0, 17 }, + Package() { 0x0000ffff, 2, 0, 18 }, + Package() { 0x0000ffff, 3, 0, 19 } + }) + } Else { + Return (Package() { + Package() { 0x0000ffff, 0, \_SB.PCI0.LPCB.LNKA, 0 }, + Package() { 0x0000ffff, 1, \_SB.PCI0.LPCB.LNKB, 0 }, + Package() { 0x0000ffff, 2, \_SB.PCI0.LPCB.LNKC, 0 }, + Package() { 0x0000ffff, 3, \_SB.PCI0.LPCB.LNKD, 0 } + }) + } + + } +} + diff --git a/src/northbridge/intel/gm45/chip.h b/src/northbridge/intel/gm45/chip.h new file mode 100644 index 0000000000..474506afb6 --- /dev/null +++ b/src/northbridge/intel/gm45/chip.h @@ -0,0 +1,27 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2007-2008 coresystems GmbH + * 2012 secunet Security Networks AG + * + * 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 NORTHBRIDGE_INTEL_GM45_CHIP_H +#define NORTHBRIDGE_INTEL_GM45_CHIP_H + +struct northbridge_intel_gm45_config { +}; + +#endif /* NORTHBRIDGE_INTEL_GM45_CHIP_H */ diff --git a/src/northbridge/intel/gm45/delay.c b/src/northbridge/intel/gm45/delay.c new file mode 100644 index 0000000000..50bea2c1c8 --- /dev/null +++ b/src/northbridge/intel/gm45/delay.c @@ -0,0 +1,112 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2007-2008 coresystems GmbH + * 2012 secunet Security Networks AG + * + * 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 <stdint.h> +#include <cpu/x86/tsc.h> +#include <cpu/x86/msr.h> +#include "delay.h" + +/* Simple 32- to 64-bit multiplication. Uses 16-bit words to avoid overflow. */ +static inline void multiply_to_tsc(tsc_t *const tsc, const u32 a, const u32 b) +{ + tsc->lo = (a & 0xffff) * (b & 0xffff); + tsc->hi = ((tsc->lo >> 16) + + ((a & 0xffff) * (b >> 16)) + + ((b & 0xffff) * (a >> 16))); + tsc->lo = ((tsc->hi & 0xffff) << 16) | (tsc->lo & 0xffff); + tsc->hi = ((a >> 16) * (b >> 16)) + (tsc->hi >> 16); +} + +/** + * Intel Core(tm) cpus always run the TSC at the maximum possible CPU clock + */ +static void _udelay(const u32 us, const u32 numerator, const int total) +{ + u32 dword; + tsc_t tsc, tsc1, tscd; + msr_t msr; + u32 fsb = 0, divisor; + u32 d; /* ticks per us */ + + msr = rdmsr(0xcd); + switch (msr.lo & 0x07) { + case 5: + fsb = 400; + break; + case 1: + fsb = 533; + break; + case 3: + fsb = 667; + break; + case 2: + fsb = 800; + break; + case 0: + fsb = 1067; + break; + case 4: + fsb = 1333; + break; + case 6: + fsb = 1600; + break; + } + + msr = rdmsr(0x198); + divisor = (msr.hi >> 8) & 0x1f; + + /* CPU clock is always a quarter. */ + d = ((fsb * divisor) / numerator) / 4; + + multiply_to_tsc(&tscd, us, d); + + if (!total) { + tsc1 = rdtsc(); + dword = tsc1.lo + tscd.lo; + if ((dword < tsc1.lo) || (dword < tscd.lo)) { + tsc1.hi++; + } + tsc1.lo = dword; + tsc1.hi += tscd.hi; + } else { + tsc1 = tscd; + } + + do { + tsc = rdtsc(); + } while ((tsc.hi < tsc1.hi) + || ((tsc.hi == tsc1.hi) && (tsc.lo < tsc1.lo))); +} + +void udelay(const u32 us) +{ + _udelay(us, 1, 0); +} + +void ns100delay(const u32 ns100) +{ + _udelay(ns100, 10, 0); +} + +void udelay_from_reset(const u32 us) +{ + _udelay(us, 1, 1); +} diff --git a/src/northbridge/intel/gm45/delay.h b/src/northbridge/intel/gm45/delay.h new file mode 100644 index 0000000000..34ebdefaa6 --- /dev/null +++ b/src/northbridge/intel/gm45/delay.h @@ -0,0 +1,28 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * + * 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 __NORTHBRIDGE_INTEL_GM45_DELAY_H__ +#define __NORTHBRIDGE_INTEL_GM45_DELAY_H__ 1 + +#include <delay.h> + +void ns100delay(u32); +void udelay_from_reset(u32); + +#endif diff --git a/src/northbridge/intel/gm45/early_init.c b/src/northbridge/intel/gm45/early_init.c new file mode 100644 index 0000000000..1dfe72d4dd --- /dev/null +++ b/src/northbridge/intel/gm45/early_init.c @@ -0,0 +1,54 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * + * 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 <stdint.h> +#include <arch/io.h> +#include <arch/romcc_io.h> +#include "gm45.h" + +void gm45_early_init(void) +{ + const device_t d0f0 = PCI_DEV(0, 0, 0); + + /* Setup PCIEXBAR. */ + pci_io_write_config32(d0f0, D0F0_PCIEXBAR_LO, + /* 64MB, enable */ + DEFAULT_PCIEXBAR | (2 << 1) | 1); + + /* Setup MCHBAR. */ + pci_write_config32(d0f0, D0F0_MCHBAR_LO, DEFAULT_MCHBAR | 1); + + /* Setup DMIBAR. */ + pci_write_config32(d0f0, D0F0_DMIBAR_LO, DEFAULT_DMIBAR | 1); + + /* Setup EPBAR. */ + pci_write_config32(d0f0, D0F0_EPBAR_LO, DEFAULT_EPBAR | 1); + + pci_write_config32(d0f0, D0F0_PMBASE, DEFAULT_PMBASE | 1); + + /* Set C0000-FFFFF to access RAM on both reads and writes */ + pci_write_config8(d0f0, D0F0_PAM(0), 0x30); + pci_write_config8(d0f0, D0F0_PAM(1), 0x33); + pci_write_config8(d0f0, D0F0_PAM(2), 0x33); + pci_write_config8(d0f0, D0F0_PAM(3), 0x33); + pci_write_config8(d0f0, D0F0_PAM(4), 0x33); + pci_write_config8(d0f0, D0F0_PAM(5), 0x33); + pci_write_config8(d0f0, D0F0_PAM(6), 0x33); +} + diff --git a/src/northbridge/intel/gm45/early_reset.c b/src/northbridge/intel/gm45/early_reset.c new file mode 100644 index 0000000000..f3902beb5d --- /dev/null +++ b/src/northbridge/intel/gm45/early_reset.c @@ -0,0 +1,74 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * + * 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 <types.h> +#include <arch/io.h> +#include <arch/romcc_io.h> +#include "gm45.h" + +void gm45_early_reset(void/*const timings_t *const timings*/) +{ + int ch, r; + + /* Reset DRAM power-up settings in CLKCFG (they are not + affected by system reset but may disrupt raminit). */ + MCHBAR32(CLKCFG_MCHBAR) = + (MCHBAR32(CLKCFG_MCHBAR) & ~(3 << 21)) | (1 << 3); + + /*\ Next settings are the real purpose of this function: + If these steps are not performed, reset results in power off. \*/ + + /* Initialize some DRAM settings to 1 populated rank of 128MB. */ + FOR_EACH_CHANNEL(ch) { + /* Configure DRAM control mode. */ + MCHBAR32(CxDRC0_MCHBAR(ch)) = + (MCHBAR32(CxDRC0_MCHBAR(ch)) & ~CxDRC0_RANKEN_MASK) | + (ch ? 0 : CxDRC0_RANKEN(0)); + MCHBAR32(CxDRC1_MCHBAR(ch)) = + (MCHBAR32(CxDRC1_MCHBAR(ch)) | CxDRC1_NOTPOP_MASK) & + ~(ch ? 0 : CxDRC1_NOTPOP(0)); + MCHBAR32(CxDRC2_MCHBAR(ch)) = + (MCHBAR32(CxDRC2_MCHBAR(ch)) | CxDRC2_NOTPOP_MASK) & + ~(ch ? 0 : CxDRC2_NOTPOP(0)); + /*if (timings && (timings->mem_clock == MEM_CLOCK_1067MT)) + MCHBAR32(CxDRC2_MCHBAR(ch)) |= CxDRC2_CLK1067MT;*/ + + /* Program rank boundaries (CxDRBy). */ + for (r = 0; r < RANKS_PER_CHANNEL; r += 2) + MCHBAR32(CxDRBy_MCHBAR(ch, r)) = + CxDRBy_BOUND_MB(r, 128) | + CxDRBy_BOUND_MB(r+1, 128); + } + /* Set DCC mode to no operation and do magic 0xf0 thing. */ + MCHBAR32(DCC_MCHBAR) = + (MCHBAR32(DCC_MCHBAR) & ~DCC_CMD_MASK) | DCC_CMD_NOP; + u8 reg8 = pci_read_config8(PCI_DEV(0, 0, 0), 0xf0); + pci_write_config8(PCI_DEV(0, 0, 0), 0xf0, reg8 & ~(1 << 2)); + reg8 = pci_read_config8(PCI_DEV(0, 0, 0), 0xf0); + pci_write_config8(PCI_DEV(0, 0, 0), 0xf0, reg8 | (1 << 2)); + /* Normally, we would set this after successful raminit. */ + MCHBAR32(DCC_MCHBAR) |= (1 << 19); + + /* Perform system reset through CF9 interface. */ + outb(0x02, 0xcf9); /* Set system reset bit. */ + outb(0x06, 0xcf9); /* Set cpu reset bit, too. */ + while (1) asm("hlt"); +} diff --git a/src/northbridge/intel/gm45/gm45.h b/src/northbridge/intel/gm45/gm45.h new file mode 100644 index 0000000000..2b1026c067 --- /dev/null +++ b/src/northbridge/intel/gm45/gm45.h @@ -0,0 +1,430 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2007-2008 coresystems GmbH + * 2012 secunet Security Networks AG + * + * 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 __NORTHBRIDGE_INTEL_GM45_GM45_H__ +#define __NORTHBRIDGE_INTEL_GM45_GM45_H__ 1 + +#include "southbridge/intel/i82801ix/i82801ix.h" + +#ifndef __ACPI__ + +#include <stdint.h> + +typedef enum { + FSB_CLOCK_1067MHz = 0, + FSB_CLOCK_800MHz = 1, + FSB_CLOCK_667MHz = 2, +} fsb_clock_t; + +typedef enum { /* Steppings below B1 were pre-production, + conversion stepping A1 is... ? + We'll support B1, B2, B3, and conversion stepping A1. */ + STEPPING_A0 = 0, + STEPPING_A1 = 1, + STEPPING_A2 = 2, + STEPPING_A3 = 3, + STEPPING_B0 = 4, + STEPPING_B1 = 5, + STEPPING_B2 = 6, + STEPPING_B3 = 7, + STEPPING_CONVERSION_A1 = 9, +} stepping_t; + +typedef enum { + GMCH_GM45 = 0, + GMCH_GM47, + GMCH_GM49, + GMCH_GE45, + GMCH_GL40, + GMCH_GL43, + GMCH_GS40, + GMCH_GS45, + GMCH_PM45, + GMCH_UNKNOWN +} gmch_gfx_t; + +typedef enum { + MEM_CLOCK_533MHz = 0, + MEM_CLOCK_400MHz = 1, + MEM_CLOCK_333MHz = 2, + MEM_CLOCK_1067MT = 0, + MEM_CLOCK_800MT = 1, + MEM_CLOCK_667MT = 2, +} mem_clock_t; + +typedef enum { + DDR1 = 1, + DDR2 = 2, + DDR3 = 3, +} ddr_t; + +typedef enum { + CHANNEL_MODE_SINGLE, + CHANNEL_MODE_DUAL_ASYNC, + CHANNEL_MODE_DUAL_INTERLEAVED, +} channel_mode_t; + +typedef enum { /* as in DDR3 spd */ + CHIP_WIDTH_x4 = 0, + CHIP_WIDTH_x8 = 1, + CHIP_WIDTH_x16 = 2, + CHIP_WIDTH_x32 = 3, +} chip_width_t; + +typedef enum { /* as in DDR3 spd */ + CHIP_CAP_256M = 0, + CHIP_CAP_512M = 1, + CHIP_CAP_1G = 2, + CHIP_CAP_2G = 3, + CHIP_CAP_4G = 4, + CHIP_CAP_8G = 5, + CHIP_CAP_16G = 6, +} chip_capacity_t; + +typedef struct { + unsigned int CAS; + fsb_clock_t fsb_clock; + mem_clock_t mem_clock; + channel_mode_t channel_mode; + unsigned int tRAS; + unsigned int tRP; + unsigned int tRCD; + unsigned int tRFC; + unsigned int tWR; + unsigned int tRD; + unsigned int tRRD; + unsigned int tFAW; + unsigned int tWL; +} timings_t; + +typedef struct { + unsigned int card_type; /* 0x0: unpopulated, + 0xa - 0xf: raw card type A - F */ + chip_width_t chip_width; + chip_capacity_t chip_capacity; + unsigned int page_size; /* of whole DIMM in Bytes (4096 or 8192) */ + unsigned int banks; + unsigned int ranks; + unsigned int rank_capacity_mb; /* per rank in Mega Bytes */ +} dimminfo_t; + +/* The setup is one DIMM per channel, so there's no need to find a + common timing setup between multiple chips (but chip and controller + still need to be coordinated */ +typedef struct { + stepping_t stepping; + int txt_enabled; + int cores; + gmch_gfx_t gfx_type; + int gs45_low_power_mode; /* low power mode of GMCH_GS45 */ + int max_ddr2_mhz; + int max_ddr3_mt; + fsb_clock_t max_fsb; + int max_fsb_mhz; + int max_render_mhz; + + int spd_type; + timings_t selected_timings; + dimminfo_t dimms[2]; +} sysinfo_t; +#define TOTAL_CHANNELS 2 +#define CHANNEL_IS_POPULATED(dimms, idx) (dimms[idx].card_type != 0) +#define CHANNEL_IS_CARDF(dimms, idx) (dimms[idx].card_type == 0xf) +#define IF_CHANNEL_POPULATED(dimms, idx) if (dimms[idx].card_type != 0) +#define FOR_EACH_CHANNEL(idx) \ + for (idx = 0; idx < TOTAL_CHANNELS; ++idx) +#define FOR_EACH_POPULATED_CHANNEL(dimms, idx) \ + FOR_EACH_CHANNEL(idx) IF_CHANNEL_POPULATED(dimms, idx) + +#define RANKS_PER_CHANNEL 4 /* Only two may be populated */ +#define IF_RANK_POPULATED(dimms, ch, r) \ + if (dimms[ch].card_type && ((r) < dimms[ch].ranks)) +#define FOR_EACH_RANK_IN_CHANNEL(r) \ + for (r = 0; r < RANKS_PER_CHANNEL; ++r) +#define FOR_EACH_POPULATED_RANK_IN_CHANNEL(dimms, ch, r) \ + FOR_EACH_RANK_IN_CHANNEL(r) IF_RANK_POPULATED(dimms, ch, r) +#define FOR_EACH_RANK(ch, r) \ + FOR_EACH_CHANNEL(ch) FOR_EACH_RANK_IN_CHANNEL(r) +#define FOR_EACH_POPULATED_RANK(dimms, ch, r) \ + FOR_EACH_RANK(ch, r) IF_RANK_POPULATED(dimms, ch, r) + +#define DDR3_MAX_CAS 18 + +enum { + VCO_2666 = 4, + VCO_3200 = 0, + VCO_4000 = 1, + VCO_5333 = 2, +}; + +#endif + +/* Offsets of read/write training results in CMOS. + They will be restored upon S3 resumes. */ +#define CMOS_READ_TRAINING 0x80 /* 16 bytes */ +#define CMOS_WRITE_TRAINING 0x90 /* 16 bytes + (could be reduced to 10 bytes) */ + + +#define DEFAULT_MCHBAR 0xfed14000 +#define DEFAULT_DMIBAR 0xfed18000 +#define DEFAULT_EPBAR 0xfed19000 +#define DEFAULT_HECIBAR 0xfed1a000 + + /* 4 KB per PCIe device */ +#define DEFAULT_PCIEXBAR CONFIG_MMCONF_BASE_ADDRESS + +#define IOMMU_BASE1 0xfed90000 +#define IOMMU_BASE2 0xfed91000 +#define IOMMU_BASE3 0xfed92000 +#define IOMMU_BASE4 0xfed93000 + +/* + * D0:F0 + */ +#define D0F0_EPBAR_LO 0x40 +#define D0F0_EPBAR_HI 0x44 +#define D0F0_MCHBAR_LO 0x48 +#define D0F0_MCHBAR_HI 0x4c +#define D0F0_GGC 0x52 +#define D0F0_DEVEN 0x54 +#define D0F0_PCIEXBAR_LO 0x60 +#define D0F0_PCIEXBAR_HI 0x64 +#define D0F0_DMIBAR_LO 0x68 +#define D0F0_DMIBAR_HI 0x6c +#define D0F0_PMBASE 0x78 +#define D0F0_PAM(x) (0x90+(x)) /* 0-6*/ +#define D0F0_REMAPBASE 0x98 +#define D0F0_REMAPLIMIT 0x9a +#define D0F0_SMRAM 0x9d +#define D0F0_ESMRAMC 0x9e +#define D0F0_TOM 0xa0 +#define D0F0_TOUUD 0xa2 +#define D0F0_TOLUD 0xb0 +#define D0F0_SKPD 0xdc /* Scratchpad Data */ +#define D0F0_CAPID0 0xe0 + +/* + * D1:F0 PEG + */ +#define PEG_CAP 0xa2 +#define SLOTCAP 0xb4 +#define PEGLC 0xec +#define D1F0_VCCAP 0x104 +#define D1F0_VC0RCTL 0x114 + +/* + * Graphics frequencies + */ +#define GCFGC_PCIDEV PCI_DEV(0, 2, 0) +#define GCFGC_OFFSET 0xf0 +#define GCFGC_CR_SHIFT 0 +#define GCFGC_CR_MASK (0xf << GCFGC_CR_SHIFT) +#define GCFGC_CS_SHIFT 8 +#define GCFGC_CS_MASK (0xf << GCFGC_CS_SHIFT) +#define GCFGC_CD_SHIFT 12 +#define GCFGC_CD_MASK (0x1 << GCFGC_CD_SHIFT) +#define GCFGC_UPDATE_SHIFT 5 +#define GCFGC_UPDATE (0x1 << GCFGC_UPDATE_SHIFT) + +/* + * MCHBAR + */ + +#define MCHBAR8(x) *((volatile u8 *)(DEFAULT_MCHBAR + x)) +#define MCHBAR16(x) *((volatile u16 *)(DEFAULT_MCHBAR + x)) +#define MCHBAR32(x) *((volatile u32 *)(DEFAULT_MCHBAR + x)) + +#define PMSTS_MCHBAR 0x0f14 /* Self refresh channel status */ +#define PMSTS_WARM_RESET (1 << 1) +#define PMSTS_BOTH_SELFREFRESH (1 << 0) + +#define CLKCFG_MCHBAR 0x0c00 +#define CLKCFG_FSBCLK_SHIFT 0 +#define CLKCFG_FSBCLK_MASK (7 << CLKCFG_FSBCLK_SHIFT) +#define CLKCFG_MEMCLK_SHIFT 4 +#define CLKCFG_MEMCLK_MASK (7 << CLKCFG_MEMCLK_SHIFT) +#define CLKCFG_UPDATE (1 << 12) + +#define SSKPD_MCHBAR 0x0c1c +#define SSKPD_CLK_SHIFT 0 +#define SSKPD_CLK_MASK (7 << SSKPD_CLK_SHIFT) + +#define DCC_MCHBAR 0x200 +#define DCC_NO_CHANXOR (1 << 10) +#define DCC_INTERLEAVED (1 << 1) +#define DCC_CMD_SHIFT 16 +#define DCC_CMD_MASK (7 << DCC_CMD_SHIFT) +#define DCC_CMD_NOP (1 << DCC_CMD_SHIFT) + /* For mode register mr0: */ +#define DCC_SET_MREG (3 << DCC_CMD_SHIFT) + /* For extended mode registers mr1 to mr3: */ +#define DCC_SET_EREG (4 << DCC_CMD_SHIFT) +#define DCC_SET_EREG_SHIFT 21 +#define DCC_SET_EREG_MASK (DCC_CMD_MASK | (3 << DCC_SET_EREG_SHIFT)) +#define DCC_SET_EREGx(x) ((DCC_SET_EREG | \ + ((x - 1) << DCC_SET_EREG_SHIFT)) & \ + DCC_SET_EREG_MASK) + +/* Per channel DRAM Row Attribute registers (32-bit) */ +#define CxDRA_MCHBAR(x) (0x1208 + (x * 0x0100)) +#define CxDRA_PAGESIZE_SHIFT(r) (r * 4) /* Per rank r */ +#define CxDRA_PAGESIZE_MASKr(r) (0x7 << CxDRA_PAGESIZE_SHIFT(r)) +#define CxDRA_PAGESIZE_MASK 0x0000ffff +#define CxDRA_PAGESIZE(r, p) /* for log2(dimm page size in bytes) p */ \ + (((p - 10) << CxDRA_PAGESIZE_SHIFT(r)) & CxDRA_PAGESIZE_MASKr(r)) +#define CxDRA_BANKS_SHIFT(r) ((r * 3) + 16) +#define CxDRA_BANKS_MASKr(r) (0x3 << CxDRA_BANKS_SHIFT(r)) +#define CxDRA_BANKS_MASK 0x07ff0000 +#define CxDRA_BANKS(r, b) /* for number of banks b */ \ + ((b << (CxDRA_BANKS_SHIFT(r) - 3)) & CxDRA_BANKS_MASKr(r)) + +/* + * Per channel DRAM Row Boundary registers (32-bit) + * Every two ranks share one register and must be programmed at the same time. + * All registers (4 ranks per channel) have to be set. + */ +#define CxDRBy_MCHBAR(x, r) (0x1200 + (x * 0x0100) + ((r/2) * 4)) +#define CxDRBy_BOUND_SHIFT(r) ((r % 2) * 16) +#define CxDRBy_BOUND_MASK(r) (0x1fc << CxDRBy_BOUND_SHIFT(r)) +#define CxDRBy_BOUND_MB(r, b) /* for boundary in MB b */ \ + (((b >> 5) << CxDRBy_BOUND_SHIFT(r)) & CxDRBy_BOUND_MASK(r)) + +#define CxDRC0_MCHBAR(x) (0x1230 + (x * 0x0100)) +#define CxDRC0_RANKEN0 (1 << 24) /* Rank Enable */ +#define CxDRC0_RANKEN1 (1 << 25) +#define CxDRC0_RANKEN2 (1 << 26) +#define CxDRC0_RANKEN3 (1 << 27) +#define CxDRC0_RANKEN(r) (1 << (24 + r)) +#define CxDRC0_RANKEN_MASK (0xf << 24) +#define CxDRC0_RMS_SHIFT 8 /* Refresh Mode Select */ +#define CxDRC0_RMS_MASK (7 << CxDRC0_RMS_SHIFT) +#define CxDRC0_RMS_78US (2 << CxDRC0_RMS_SHIFT) +#define CxDRC0_RMS_39US (3 << CxDRC0_RMS_SHIFT) + +#define CxDRC1_MCHBAR(x) (0x1234 + (x * 0x0100)) +#define CxDRC1_SSDS_SHIFT 24 +#define CxDRC1_SSDS_MASK (0xff << CxDRC1_SSDS_SHIFT) +#define CxDRC1_DS (0x91 << CxDRC1_SSDS_SHIFT) +#define CxDRC1_SS (0xb1 << CxDRC1_SSDS_SHIFT) +#define CxDRC1_NOTPOP(r) (1 << (16 + r)) /* Write 1 for Not Populated */ +#define CxDRC1_NOTPOP_MASK (0xf << 16) +#define CxDRC1_MUSTWR (3 << 11) + +#define CxDRC2_MCHBAR(x) (0x1238 + (x * 0x0100)) +#define CxDRC2_NOTPOP(r) (1 << (24 + r)) /* Write 1 for Not Populated */ +#define CxDRC2_NOTPOP_MASK (0xf << 24) +#define CxDRC2_MUSTWR (1 << 12) +#define CxDRC2_CLK1067MT (1 << 0) + +/* DRAM Timing registers (32-bit each) */ +#define CxDRT0_MCHBAR(x) (0x1210 + (x * 0x0100)) +#define CxDRT0_BtB_WtP_SHIFT 26 +#define CxDRT0_BtB_WtP_MASK (0x1f << CxDRT0_BtB_WtP_SHIFT) +#define CxDRT0_BtB_WtR_SHIFT 20 +#define CxDRT0_BtB_WtR_MASK (0x1f << CxDRT0_BtB_WtR_SHIFT) +#define CxDRT1_MCHBAR(x) (0x1214 + (x * 0x0100)) +#define CxDRT2_MCHBAR(x) (0x1218 + (x * 0x0100)) +#define CxDRT3_MCHBAR(x) (0x121c + (x * 0x0100)) +#define CxDRT4_MCHBAR(x) (0x1220 + (x * 0x0100)) +#define CxDRT5_MCHBAR(x) (0x1224 + (x * 0x0100)) +#define CxDRT6_MCHBAR(x) (0x1228 + (x * 0x0100)) + +/* Clock disable registers (32-bit each) */ +#define CxDCLKDIS_MCHBAR(x) (0x120c + (x * 0x0100)) +#define CxDCLKDIS_MASK 3 +#define CxDCLKDIS_ENABLE 3 /* Always enable both clock pairs. */ + +/* On-Die-Termination registers (2x 32-bit per channel) */ +#define CxODT_HIGH(x) (0x124c + (x * 0x0100)) +#define CxODT_LOW(x) (0x1248 + (x * 0x0100)) + +/* Write Training registers. */ +#define CxWRTy_MCHBAR(ch, s) (0x1470 + (ch * 0x0100) + ((3 - s) * 4)) + +#define CxGTEW(x) (0x1270+(x*0x100)) +#define CxGTC(x) (0x1274+(x*0x100)) +#define CxDTPEW(x) (0x1278+(x*0x100)) +#define CxDTAEW(x) (0x1280+(x*0x100)) +#define CxDTC(x) (0x1288+(x*0x100)) + + +/* + * DMIBAR + */ + +#define DMIBAR8(x) *((volatile u8 *)(DEFAULT_DMIBAR + x)) +#define DMIBAR16(x) *((volatile u16 *)(DEFAULT_DMIBAR + x)) +#define DMIBAR32(x) *((volatile u32 *)(DEFAULT_DMIBAR + x)) + +#define DMIVC0RCTL 0x14 +#define DMIVC1RCTL 0x20 +#define DMIVC1RSTS 0x26 +#define DMIESD 0x44 +#define DMILE1D 0x50 +#define DMILE1A 0x58 +#define DMILE2D 0x60 +#define DMILE2A 0x68 + + +/* + * EPBAR + */ + +#define EPBAR8(x) *((volatile u8 *)(DEFAULT_EPBAR + x)) +#define EPBAR16(x) *((volatile u16 *)(DEFAULT_EPBAR + x)) +#define EPBAR32(x) *((volatile u32 *)(DEFAULT_EPBAR + x)) + +#define EPESD 0x44 +#define EPLE1D 0x50 +#define EPLE1A 0x58 +#define EPLE2D 0x60 + + +#ifndef __ACPI__ +void gm45_early_init(void); +void gm45_early_reset(void); + +void enter_raminit_or_reset(void); +void get_gmch_info(sysinfo_t *); +void raminit(sysinfo_t *, int s3resume); +void raminit_thermal(const sysinfo_t *); +void init_igd(const sysinfo_t *, int no_igd, int no_peg); +void init_pm(const sysinfo_t *); + +int raminit_read_vco_index(void); +u32 raminit_get_rank_addr(unsigned int channel, unsigned int rank); + +void raminit_rcomp_calibration(stepping_t stepping); +void raminit_reset_readwrite_pointers(void); +void raminit_receive_enable_calibration(const timings_t *, const dimminfo_t *); +void raminit_write_training(const mem_clock_t, const dimminfo_t *, int s3resume); +void raminit_read_training(const dimminfo_t *, int s3resume); + +void gm45_late_init(stepping_t); + +u32 decode_igd_memory_size(u32 gms); +u32 decode_igd_gtt_size(u32 gsm); +u32 get_top_of_ram(void); + +void init_iommu(void); +#endif + +#endif diff --git a/src/northbridge/intel/gm45/igd.c b/src/northbridge/intel/gm45/igd.c new file mode 100644 index 0000000000..c07fdfc26b --- /dev/null +++ b/src/northbridge/intel/gm45/igd.c @@ -0,0 +1,154 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * + * 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 <stdint.h> +#include <stddef.h> +#include <arch/io.h> +#include <arch/romcc_io.h> +#include <device/pci_def.h> +#include <console/console.h> + +#include "gm45.h" + +/* TODO: Chipset supports Protected Audio Video Path (PAVP) */ + +/* TODO: We could pass DVMT structure in GetBIOSData() SCI interface */ + +/* The PEG settings have to be set before ASPM is setup on DMI. */ +static void enable_igd(const sysinfo_t *const sysinfo, const int no_peg) +{ + const device_t mch_dev = PCI_DEV(0, 0, 0); + const device_t peg_dev = PCI_DEV(0, 1, 0); + const device_t igd_dev = PCI_DEV(0, 2, 0); + + u16 reg16; + u32 reg32; + + printk(BIOS_DEBUG, "Enabling IGD.\n"); + + /* HSync/VSync */ + MCHBAR8(0xbd0 + 3) = 0x5a; + MCHBAR8(0xbd0 + 4) = 0x5a; + + static const u16 display_clock_from_f0_and_vco[][4] = { + /* VCO 2666 VCO 3200 VCO 4000 VCO 5333 */ + { 222, 228, 222, 222, }, + { 333, 320, 333, 333, }, + }; + const int f0_12 = (pci_read_config16(igd_dev, 0xf0) >> 12) & 1; + const int vco = raminit_read_vco_index(); + reg16 = pci_read_config16(igd_dev, 0xcc); + reg16 &= 0xfc00; + reg16 |= display_clock_from_f0_and_vco[f0_12][vco]; + pci_write_config16(igd_dev, 0xcc, reg16); + + /* Graphics Stolen Memory: 2MB GTT (0x0300) when VT-d disabled, + 2MB GTT + 2MB shadow GTT (0x0b00) else. */ + /* Graphics Mode Select: 32MB framebuffer (0x0050) */ + /* TODO: We could switch to 64MB (0x0070), config flag? */ + const u32 capid = pci_read_config32(mch_dev, D0F0_CAPID0 + 4); + reg16 = pci_read_config16(mch_dev, D0F0_GGC); + reg16 &= 0xf00f; + reg16 |= 0x0350; + if (capid & (1 << (48 - 32))) + reg16 |= 0x0800; + pci_write_config16(mch_dev, D0F0_GGC, reg16); + + if ((sysinfo->gfx_type != GMCH_GL40) && + (sysinfo->gfx_type != GMCH_GS40) && + (sysinfo->gfx_type != GMCH_GL43)) { + const u32 deven = pci_read_config32(mch_dev, D0F0_DEVEN); + if (!(deven & 2)) + /* Enable PEG temporarily to access D1:F0. */ + pci_write_config32(mch_dev, D0F0_DEVEN, deven | 2); + + /* Some IGD related settings on D1:F0. */ + reg16 = pci_read_config16(peg_dev, 0xa08); + reg16 &= ~(1 << 15); + pci_write_config16(peg_dev, 0xa08, reg16); + + reg16 = pci_read_config16(peg_dev, 0xa84); + reg16 |= (1 << 8); + pci_write_config16(peg_dev, 0xa84, reg16); + + reg16 = pci_read_config16(peg_dev, 0xb00); + reg16 |= (3 << 8) | (7 << 3); + pci_write_config16(peg_dev, 0xb00, reg16); + + reg32 = pci_read_config32(peg_dev, 0xb14); + reg32 &= ~(1 << 17); + pci_write_config32(peg_dev, 0xb14, reg32); + + if (!(deven & 2) || no_peg) { + /* Disable PEG finally. */ + printk(BIOS_DEBUG, "Finally disabling " + "PEG in favor of IGD.\n"); + MCHBAR8(0xc14) |= (1 << 5) | (1 << 0); + + reg32 = pci_read_config32(peg_dev, 0x200); + reg32 |= (1 << 18); + pci_write_config32(peg_dev, 0x200, reg32); + reg16 = pci_read_config16(peg_dev, 0x224); + reg16 |= (1 << 8); + pci_write_config16(peg_dev, 0x224, reg16); + reg32 = pci_read_config32(peg_dev, 0x200); + reg32 &= ~(1 << 18); + pci_write_config32(peg_dev, 0x200, reg32); + while (pci_read_config32(peg_dev, 0x214) & 0x000f0000); + + pci_write_config32(mch_dev, D0F0_DEVEN, deven & ~2); + MCHBAR8(0xc14) &= ~((1 << 5) | (1 << 0)); + } + } +} + +static void disable_igd(const sysinfo_t *const sysinfo) +{ + const device_t mch_dev = PCI_DEV(0, 0, 0); + + printk(BIOS_DEBUG, "Disabling IGD.\n"); + + u16 reg16; + + reg16 = pci_read_config16(mch_dev, D0F0_GGC); + reg16 &= 0xff0f; /* Disable Graphics Stolen Memory. */ + reg16 |= 0x0002; /* Disable IGD. */ + pci_write_config16(mch_dev, D0F0_GGC, reg16); + MCHBAR8(0xf10) |= (1 << 0); + + if (!(pci_read_config8(mch_dev, D0F0_CAPID0 + 4) & (1 << (33 - 32)))) { + MCHBAR16(0x1190) |= (1 << 14); + MCHBAR16(0x119e) = (MCHBAR16(0x119e) & ~(7 << 13)) | (4 << 13); + MCHBAR16(0x119e) |= (1 << 12); + } +} + +void init_igd(const sysinfo_t *const sysinfo, + const int no_igd, const int no_peg) +{ + const device_t mch_dev = PCI_DEV(0, 0, 0); + + const u8 capid = pci_read_config8(mch_dev, D0F0_CAPID0 + 4); + if (no_igd || (capid & (1 << (33 - 32)))) + disable_igd(sysinfo); + else + enable_igd(sysinfo, no_peg); +} diff --git a/src/northbridge/intel/gm45/iommu.c b/src/northbridge/intel/gm45/iommu.c new file mode 100644 index 0000000000..89770ee71b --- /dev/null +++ b/src/northbridge/intel/gm45/iommu.c @@ -0,0 +1,84 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * + * 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 <stdint.h> +#include <string.h> + +#include <arch/io.h> +#include <arch/romcc_io.h> +#include <device/pci_def.h> +#include <arch/acpi.h> + +#include "gm45.h" + +void init_iommu() +{ + /* FIXME: proper test? */ + int me_active = pci_read_config8(PCI_DEV(0, 3, 0), PCI_CLASS_REVISION) != 0xff; + int stepping = pci_read_config8(PCI_DEV(0, 0, 0), PCI_CLASS_REVISION); + + MCHBAR32(0x28) = IOMMU_BASE1 | 1; /* HDA @ 0:1b.0 */ + if (stepping != STEPPING_B2) { + /* The official workaround is to run SMM every 64ms. + The only winning move is not to play. */ + MCHBAR32(0x18) = IOMMU_BASE2 | 1; /* IGD @ 0:2.0-1 */ + } else { + /* write-once, so lock it down */ + MCHBAR32(0x18) = 0; /* disable IOMMU for IGD @ 0:2.0-1 */ + } + if (me_active) { + MCHBAR32(0x10) = IOMMU_BASE3 | 1; /* ME @ 0:3.0-3 */ + } + MCHBAR32(0x20) = IOMMU_BASE4 | 1; /* all other DMA sources */ + + /* clear GTT */ + u32 gtt = pci_read_config16(PCI_DEV(0, 0, 0), 0x52); + if (gtt & 0x400) { /* VT mode */ + device_t igd = PCI_DEV(0, 2, 0); + + /* setup somewhere */ + u8 cmd = pci_read_config8(igd, PCI_COMMAND); + cmd |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY; + pci_write_config8(igd, PCI_COMMAND, cmd); + void* bar = (void*)pci_read_config32(igd, PCI_BASE_ADDRESS_0); + + /* clear GTT, 2MB is enough (and should be safe) */ + memset(bar, 0, 2<<20); + + /* and now disable again */ + cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + pci_write_config8(igd, PCI_COMMAND, cmd); + pci_write_config32(igd, PCI_BASE_ADDRESS_0, 0); + } + + if (stepping == STEPPING_B3) { + MCHBAR8(0xffc) |= 1 << 4; + device_t peg = PCI_DEV(0, 1, 0); + /* FIXME: proper test? */ + if (pci_read_config8(peg, PCI_CLASS_REVISION) != 0xff) { + int val = pci_read_config32(peg, 0xfc) | (1 << 15); + pci_write_config32(peg, 0xfc, val); + } + } + + /* final */ + MCHBAR8(0x94) |= 1 << 3; +} diff --git a/src/northbridge/intel/gm45/northbridge.c b/src/northbridge/intel/gm45/northbridge.c new file mode 100644 index 0000000000..8c80f86d1b --- /dev/null +++ b/src/northbridge/intel/gm45/northbridge.c @@ -0,0 +1,285 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2007-2009 coresystems GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <console/console.h> +#include <arch/io.h> +#include <stdint.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include <device/hypertransport.h> +#include <stdlib.h> +#include <string.h> +#include <bitops.h> +#include <cpu/cpu.h> +#include <boot/tables.h> +#include <arch/acpi.h> +#include <cbmem.h> +#include "chip.h" +#include "gm45.h" + +/* Reserve everything between A segment and 1MB: + * + * 0xa0000 - 0xbffff: legacy VGA + * 0xc0000 - 0xcffff: VGA OPROM (needed by kernel) + * 0xe0000 - 0xfffff: SeaBIOS, if used, otherwise DMI + */ +static const int legacy_hole_base_k = 0xa0000 / 1024; +static const int legacy_hole_size_k = 384; + +static int decode_pcie_bar(u32 *const base, u32 *const len) +{ + *base = 0; + *len = 0; + + const device_t dev = dev_find_slot(0, PCI_DEVFN(0, 0)); + if (!dev) + return 0; + + const u32 pciexbar_reg = pci_read_config32(dev, D0F0_PCIEXBAR_LO); + + if (!(pciexbar_reg & (1 << 0))) + return 0; + + switch ((pciexbar_reg >> 1) & 3) { + case 0: /* 256MB */ + *base = pciexbar_reg & (0x0f << 28); + *len = 256 * 1024 * 1024; + return 1; + case 1: /* 128M */ + *base = pciexbar_reg & (0x1f << 27); + *len = 128 * 1024 * 1024; + return 1; + case 2: /* 64M */ + *base = pciexbar_reg & (0x3f << 26); + *len = 64 * 1024 * 1024; + return 1; + } + + return 0; +} + +static void mch_domain_read_resources(device_t dev) +{ + u64 tom, touud; + u32 tomk, tolud, uma_sizek = 0; + u32 pcie_config_base, pcie_config_size; + + /* Total Memory 2GB example: + * + * 00000000 0000MB-2014MB 2014MB RAM (writeback) + * 7de00000 2014MB-2016MB 2MB GFX GTT (uncached) + * 7e000000 2016MB-2048MB 32MB GFX UMA (uncached) + * 80000000 2048MB TOLUD + * 80000000 2048MB TOM + * + * Total Memory 4GB example: + * + * 00000000 0000MB-3038MB 3038MB RAM (writeback) + * bde00000 3038MB-3040MB 2MB GFX GTT (uncached) + * be000000 3040MB-3072MB 32MB GFX UMA (uncached) + * be000000 3072MB TOLUD + * 100000000 4096MB TOM + * 100000000 4096MB-5120MB 1024MB RAM (writeback) + * 140000000 5120MB TOUUD + */ + + pci_domain_read_resources(dev); + + /* Top of Upper Usable DRAM, including remap */ + touud = pci_read_config16(dev, D0F0_TOUUD); + touud <<= 20; + + /* Top of Lower Usable DRAM */ + tolud = pci_read_config16(dev, D0F0_TOLUD) & 0xfff0; + tolud <<= 16; + + /* Top of Memory - does not account for any UMA */ + tom = pci_read_config16(dev, D0F0_TOM) & 0x1ff; + tom <<= 27; + + printk(BIOS_DEBUG, "TOUUD 0x%llx TOLUD 0x%08x TOM 0x%llx\n", + touud, tolud, tom); + + tomk = tolud >> 10; + + /* Graphics memory comes next */ + const u16 ggc = pci_read_config16(dev, D0F0_GGC); + if (!(ggc & 2)) { + printk(BIOS_DEBUG, "IGD decoded, subtracting "); + + /* Graphics memory */ + const u32 gms_sizek = decode_igd_memory_size((ggc >> 4) & 0xf); + printk(BIOS_DEBUG, "%uM UMA", gms_sizek >> 10); + tomk -= gms_sizek; + + /* GTT Graphics Stolen Memory Size (GGMS) */ + const u32 gsm_sizek = decode_igd_gtt_size((ggc >> 8) & 0xf); + printk(BIOS_DEBUG, " and %uM GTT\n", gsm_sizek >> 10); + tomk -= gsm_sizek; + + uma_sizek = gms_sizek + gsm_sizek; + } + + printk(BIOS_INFO, "Available memory below 4GB: %uM\n", tomk >> 10); + + /* Report the memory regions */ + ram_resource(dev, 3, 0, legacy_hole_base_k); + ram_resource(dev, 4, legacy_hole_base_k + legacy_hole_size_k, + (tomk - (legacy_hole_base_k + legacy_hole_size_k))); + + /* + * If >= 4GB installed then memory from TOLUD to 4GB + * is remapped above TOM, TOUUD will account for both + */ + touud >>= 10; /* Convert to KB */ + if (touud > 4096 * 1024) { + ram_resource(dev, 5, 4096 * 1024, touud - (4096 * 1024)); + printk(BIOS_INFO, "Available memory above 4GB: %lluM\n", + (touud >> 10) - 4096); + } + + printk(BIOS_DEBUG, "Adding UMA memory area base=0x%llx " + "size=0x%llx\n", ((u64)tomk) << 10, ((u64)uma_sizek) << 10); + /* Don't use uma_resource() as our UMA touches the PCI hole. */ + fixed_mem_resource(dev, 6, tomk, uma_sizek, IORESOURCE_RESERVE); + + if (decode_pcie_bar(&pcie_config_base, &pcie_config_size)) { + printk(BIOS_DEBUG, "Adding PCIe config bar base=0x%08x " + "size=0x%x\n", pcie_config_base, pcie_config_size); + fixed_mem_resource(dev, 7, pcie_config_base >> 10, + pcie_config_size >> 10, IORESOURCE_RESERVE); + } + +#if CONFIG_WRITE_HIGH_TABLES + /* Leave some space for ACPI, PIRQ and MP tables */ + high_tables_base = (tomk << 10) - HIGH_MEMORY_SIZE; + high_tables_size = HIGH_MEMORY_SIZE; +#endif +} + +static void mch_domain_set_resources(device_t dev) +{ + struct resource *resource; + int i; + + for (i = 3; i < 8; ++i) { + /* Report read resources. */ + resource = find_resource(dev, i); + if (resource) + report_resource_stored(dev, resource, ""); + } + + assign_resources(dev->link_list); +} + +static void mch_domain_init(device_t dev) +{ + u32 reg32; + + /* Enable SERR */ + reg32 = pci_read_config32(dev, PCI_COMMAND); + reg32 |= PCI_COMMAND_SERR; + pci_write_config32(dev, PCI_COMMAND, reg32); +} + +static struct device_operations pci_domain_ops = { + .read_resources = mch_domain_read_resources, + .set_resources = mch_domain_set_resources, + .enable_resources = NULL, + .init = mch_domain_init, + .scan_bus = pci_domain_scan_bus, +#if CONFIG_MMCONF_SUPPORT_DEFAULT + .ops_pci_bus = &pci_ops_mmconf, +#else + .ops_pci_bus = &pci_cf8_conf1, +#endif +}; + + +static void cpu_bus_init(device_t dev) +{ + initialize_cpus(dev->link_list); +} + +static void cpu_bus_noop(device_t dev) +{ +} + +static struct device_operations cpu_bus_ops = { + .read_resources = cpu_bus_noop, + .set_resources = cpu_bus_noop, + .enable_resources = cpu_bus_noop, + .init = cpu_bus_init, + .scan_bus = 0, +}; + + +static void enable_dev(device_t dev) +{ + /* Set the operations if it is a special bus type */ + if (dev->path.type == DEVICE_PATH_PCI_DOMAIN) { + dev->ops = &pci_domain_ops; + } else if (dev->path.type == DEVICE_PATH_APIC_CLUSTER) { + dev->ops = &cpu_bus_ops; + } +} + +static void gm45_init(void *const chip_info) +{ + int dev, fn, bit_base; + + struct device *const d0f0 = dev_find_slot(0, 0); + + /* Hide internal functions based on devicetree info. */ + for (dev = 3; dev > 0; --dev) { + switch (dev) { + case 3: /* ME */ + fn = 3; + bit_base = 6; + break; + case 2: /* IGD */ + fn = 1; + bit_base = 3; + break; + case 1: /* PEG */ + fn = 0; + bit_base = 1; + break; + } + for (; fn >= 0; --fn) { + const struct device *const d = + dev_find_slot(0, PCI_DEVFN(dev, fn)); + if (!d || d->enabled) continue; + const u32 deven = pci_read_config32(d0f0, D0F0_DEVEN); + pci_write_config32(d0f0, D0F0_DEVEN, + deven & ~(1 << (bit_base + fn))); + } + } + + const u32 deven = pci_read_config32(d0f0, D0F0_DEVEN); + if (!(deven & (0xf << 6))) + pci_write_config32(d0f0, D0F0_DEVEN, deven & ~(1 << 14)); +} + +struct chip_operations northbridge_intel_gm45_ops = { + CHIP_NAME("Intel GM45 Northbridge") + .enable_dev = enable_dev, + .init = gm45_init, +}; diff --git a/src/northbridge/intel/gm45/pcie.c b/src/northbridge/intel/gm45/pcie.c new file mode 100644 index 0000000000..6b42e15d0c --- /dev/null +++ b/src/northbridge/intel/gm45/pcie.c @@ -0,0 +1,347 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * + * 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 <stdint.h> +#include <stddef.h> +#include <string.h> +#include <arch/io.h> +#include <arch/romcc_io.h> +#include <device/pci_def.h> +#include <device/pnp_def.h> +#include <console/console.h> + +#include "gm45.h" + +static void init_egress(void) +{ + /* VC0: TC0 only */ + EPBAR8(0x14) &= 1; + EPBAR8(0x4) = (EPBAR8(0x4) & ~7) | 1; + + /* VC1: isoch */ + EPBAR32(0x28) = 0x0a0a0a0a; + EPBAR32(0x1c) = (EPBAR32(0x1c) & ~(127 << 16)) | (0x0a << 16); + + /* VC1: ID1, TC7 */ + EPBAR32(0x20) = (EPBAR32(0x20) & ~(7 << 24)) | (1 << 24); + EPBAR8(0x20) = (EPBAR8(0x20) & 1) | (1 << 7); + + /* VC1 ARB table: setup and enable */ + EPBAR32(0x100) = 0x55555555; + EPBAR32(0x104) = 0x55555555; + EPBAR32(0x108) = 0x55555555; + EPBAR32(0x10c) = 0x55555555; + EPBAR32(0x110) = 0x55555555; + EPBAR32(0x114) = 0x55555555; + EPBAR32(0x118) = 0x55555555; + EPBAR32(0x11c) = 0x00005555; + EPBAR32(0x20) |= 1 << 16; + + while ((EPBAR8(0x26) & 1) != 0) ; + + /* VC1: enable */ + EPBAR32(0x20) |= 1 << 31; + + while ((EPBAR8(0x26) & 2) != 0) ; +} + +/* MCH side */ +/* b2step: b2 stepping or higher */ +static void init_dmi(int b2step) +{ + /* VC0: TC0 only */ + DMIBAR8(DMIVC0RCTL) &= 1; + DMIBAR8(0x4) = (DMIBAR8(0x4) & ~7) | 1; + + /* VC1: ID1, TC7 */ + DMIBAR32(0x20) = (DMIBAR32(0x20) & ~(7 << 24)) | (1 << 24); + DMIBAR8(0x20) = (DMIBAR8(0x20) & 1) | (1 << 7); + + /* VC1: enable */ + DMIBAR32(0x20) |= 1 << 31; + + while ((DMIBAR8(0x26) & 2) != 0) ; + + /* additional configuration. */ + DMIBAR32(0x200) |= 3 << 13; + DMIBAR32(0x200) &= ~(1 << 21); + DMIBAR32(0x200) = (DMIBAR32(0x200) & ~(3 << 26)) | (2 << 26); + DMIBAR32(0x2c) = 0x86000040; + DMIBAR32(0xfc) |= 1 << 0; + DMIBAR32(0xfc) |= 1 << 1; + DMIBAR32(0xfc) |= 1 << 4; + if (!b2step) { + DMIBAR32(0xfc) |= 1 << 11; + } else { + DMIBAR32(0xfc) &= ~(1 << 11); + } + DMIBAR32(0x204) &= ~(3 << 10); + DMIBAR32(0xf4) &= ~(1 << 4); + DMIBAR32(0xf0) |= 3 << 24; + DMIBAR32(0xf04) = 0x07050880; + DMIBAR32(0xf44) = 0x07050880; + DMIBAR32(0xf84) = 0x07050880; + DMIBAR32(0xfc4) = 0x07050880; + + /* lock down write-once registers + DMIBAR32(0x84) will be set in setup_aspm(). */ + DMIBAR32(0x308) = DMIBAR32(0x308); + DMIBAR32(0x314) = DMIBAR32(0x314); + DMIBAR32(0x324) = DMIBAR32(0x324); + DMIBAR32(0x328) = DMIBAR32(0x328); + DMIBAR32(0x334) = DMIBAR32(0x334); + DMIBAR32(0x338) = DMIBAR32(0x338); +} + +static void init_pcie(const int peg_enabled, + const int sdvo_enabled, + const int peg_x16) +{ + u8 tmp8; + u16 tmp16; + u32 tmp; + const device_t mch = PCI_DEV(0, 0, 0); + const device_t pciex = PCI_DEV(0, 1, 0); + + printk(BIOS_DEBUG, "PEG x%d %s, SDVO %s\n", peg_x16?16:1, + peg_enabled?"enabled":"disabled", + sdvo_enabled?"enabled":"disabled"); + + if (peg_enabled) { + tmp8 = pci_read_config8(mch, D0F0_DEVEN) | (1 << 1); + pci_write_config8(mch, D0F0_DEVEN, tmp8); + + tmp8 = pci_read_config8(pciex, 0x224) & ~31; + pci_write_config8(pciex, 0x224, tmp8 | (peg_x16?16:0) | 1); + + tmp16 = pci_read_config16(pciex, 0x224) & ~(1 << 8); + pci_write_config16(pciex, 0x224, tmp16); + + /* FIXME: fill in: slot or fixed? -> devicetree */ + int peg_is_slot = 0; + if (peg_is_slot) { + tmp16 = pci_read_config16(pciex, PEG_CAP) | (1 << 8); + pci_write_config16(pciex, PEG_CAP, tmp16); + } + + /* FIXME: fill in: slot number, slot power -> devicetree */ + /* Use slot number 0 by now, slots on sb count from 1. */ + int peg_slot = 0; /* unique within chassis */ + /* peg_power := val * 10^-exp */ + int peg_power_val = 75; + int peg_power_exp = 0; /* 0..3 */ + tmp = (peg_slot << 17) | (peg_power_exp << 15) | + (peg_power_val << 7); + pci_write_config32(pciex, SLOTCAP, tmp); + + /* GPEs */ + tmp8 = pci_read_config8(pciex, PEGLC) | 7; + pci_write_config8(pciex, PEGLC, tmp8); + + /* VC0: TC0 only, VC0 only */ + tmp8 = pci_read_config8(pciex, D1F0_VC0RCTL); + pci_write_config8(pciex, D1F0_VC0RCTL, tmp8 & 1); + + tmp8 = pci_read_config8(pciex, D1F0_VCCAP); + pci_write_config8(pciex, D1F0_VCCAP, tmp8 & ~7); + } +} + +static void setup_aspm(const stepping_t stepping, const int peg_enabled) +{ + u32 tmp32; + const device_t pciex = PCI_DEV(0, 1, 0); + + /* Prerequisites for ASPM: */ + if (peg_enabled) { + tmp32 = pci_read_config32(pciex, 0x200) | (3 << 13); + pci_write_config32(pciex, 0x200, tmp32); + + tmp32 = pci_read_config32(pciex, 0x0f0); + tmp32 &= ~((1 << 27) | (1 << 26)); + pci_write_config32(pciex, 0x0f0, tmp32); + + tmp32 = pci_read_config32(pciex, 0x0f0) | (3 << 24); + pci_write_config32(pciex, 0x0f0, tmp32); + + tmp32 = pci_read_config32(pciex, 0x0f4) & ~(1 << 4); + pci_write_config32(pciex, 0x0f4, tmp32); + + tmp32 = pci_read_config32(pciex, 0x0fc) | (1 << 0); + pci_write_config32(pciex, 0x0fc, tmp32); + + tmp32 = pci_read_config32(pciex, 0x0fc) | (1 << 1); + pci_write_config32(pciex, 0x0fc, tmp32); + + tmp32 = pci_read_config32(pciex, 0x0fc) | (1 << 4); + pci_write_config32(pciex, 0x0fc, tmp32); + + tmp32 = pci_read_config32(pciex, 0x0fc) & ~(7 << 5); + pci_write_config32(pciex, 0x0fc, tmp32); + + /* Set L0s, L1 supported in LCTL? */ + tmp32 = pci_read_config32(pciex, 0x0b0) | (3 << 0); + pci_write_config32(pciex, 0x0b0, tmp32); + + tmp32 = pci_read_config32(pciex, 0x0f0) | (3 << 24); + pci_write_config32(pciex, 0x0f0, tmp32); + + tmp32 = pci_read_config32(pciex, 0x0f0); + if ((stepping >= STEPPING_B0) && (stepping <= STEPPING_B1)) + tmp32 |= (1 << 31); + else if (stepping >= STEPPING_B2) + tmp32 &= ~(1 << 31); + pci_write_config32(pciex, 0x0f0, tmp32); + + tmp32 = pci_read_config32(pciex, 0x0fc); + if ((stepping >= STEPPING_B0) && (stepping <= STEPPING_B1)) + tmp32 |= (1 << 10); + else if (stepping >= STEPPING_B2) + tmp32 &= ~(1 << 10); + pci_write_config32(pciex, 0x0fc, tmp32); + + tmp32 = pci_read_config32(pciex, 0x0fc); + if (stepping >= STEPPING_B2) + tmp32 |= (1 << 14); + pci_write_config32(pciex, 0x0fc, tmp32); + + tmp32 = pci_read_config32(pciex, 0x0fc); + if (stepping >= STEPPING_B1) + tmp32 &= ~(1 << 13); + pci_write_config32(pciex, 0x0fc, tmp32); + } + DMIBAR8 (0x0e1c) |= (1 << 0); + DMIBAR16(0x0f00) |= (3 << 8); + DMIBAR16(0x0f00) |= (7 << 3); + DMIBAR32(0x0f14) &= ~(1 << 17); + DMIBAR16(0x0e1c) &= ~(1 << 8); + if (stepping >= STEPPING_B0) { + DMIBAR32(0x0e28 + 4) = (DMIBAR32(0x0e28 + 4) & + ~(0xf << (52 - 32))) | + (0xd << (52 - 32)); + DMIBAR32(0x0e2c) = 0x88d07333; + } + if (peg_enabled) { + tmp32 = pci_read_config32(pciex, 0xa08) & ~(1 << 15); + pci_write_config32(pciex, 0xa08, tmp32); + + tmp32 = pci_read_config32(pciex, 0xa84) | (1 << 8); + pci_write_config32(pciex, 0xa84, tmp32); + + tmp32 = pci_read_config32(pciex, 0xb14) & ~(1 << 17); + pci_write_config32(pciex, 0xb14, tmp32); + + tmp32 = pci_read_config32(pciex, 0xb00) | (3 << 8); + pci_write_config32(pciex, 0xb00, tmp32); + + tmp32 = pci_read_config32(pciex, 0xb00) | (7 << 3); + pci_write_config32(pciex, 0xb00, tmp32); + + tmp32 = pci_read_config32(pciex, 0xa84) & ~(1 << 8); + pci_write_config32(pciex, 0xa84, tmp32); + + tmp32 = pci_read_config32(pciex, 0xa84) | (1 << 8); + pci_write_config32(pciex, 0xa84, tmp32); + + tmp32 = pci_read_config32(pciex, 0xb04); + tmp32 = (tmp32 & ~(0x1f << 23)) | (0xe << 23); + pci_write_config32(pciex, 0xb04, tmp32); + + tmp32 = pci_read_config32(pciex, 0xb04); + tmp32 |= (1 << 31); + pci_write_config32(pciex, 0xb04, tmp32); + + tmp32 = pci_read_config32(pciex, 0xb04); + tmp32 = (tmp32 & ~(0x03 << 29)) | (0x1 << 29); + pci_write_config32(pciex, 0xb04, tmp32); + } + + + /*\ Setup ASPM on DMI \*/ + + /* Exit latencies should be checked to be supported by + the endpoint (ICH), but ICH doesn't give any limits. */ + + if (LPC_IS_MOBILE(PCI_DEV(0, 0x1f, 0))) + DMIBAR8(0x88) |= (3 << 0); // enable ASPM L0s, L1 (write-once) + else + DMIBAR8(0x88) |= (1 << 0); // enable ASPM L0s (write-once) + /* timing */ + DMIBAR32(0x84) = (DMIBAR32(0x84) & ~(63 << 12)) | (2 << 12) | (2 << 15); + DMIBAR8(0x208 + 3) = 0; + DMIBAR32(0x208) &= ~(3 << 20); + + + /*\ Setup ASPM on PEG \*/ + /* + * Maybe we just have to advertise ASPM through LCAP[11:10] + * (LCAP[17:15] == 010b is the default, will be locked, as it's R/WO), + * set 0x208[31:24,23:22] to zero, 0x224[24:21] = 1 and let the + * generic ASPM code do the rest? – Nico + */ + /* TODO: Prepare PEG for ASPM. */ +} + +static void setup_rcrb(const int peg_enabled) +{ + /*\ RCRB setup: Egress Port \*/ + + /* Set component ID of MCH (1). */ + EPBAR8(EPESD + 2) = 1; + + /* Link1: component ID 1, link valid. */ + EPBAR32(EPLE1D) = (EPBAR32(EPLE1D) & 0xff000000) | (1 << 16) | (1 << 0); + EPBAR32(EPLE1A) = DEFAULT_DMIBAR; + + if (peg_enabled) + /* Link2: link_valid. */ + EPBAR8(EPLE2D) |= (1 << 0); /* link valid */ + + + /*\ RCRB setup: DMI Port \*/ + + /* Set component ID of MCH (1). */ + DMIBAR8(DMIESD + 2) = 1; + + /* Link1: target port 0, component id 2 (ICH), link valid. */ + DMIBAR32(DMILE1D) = (0 << 24) | (2 << 16) | (1 << 0); + DMIBAR32(DMILE1A) = DEFAULT_RCBA; + + /* Link2: component ID 1 (MCH), link valid */ + DMIBAR32(DMILE2D) = + (DMIBAR32(DMILE2D) & 0xff000000) | (1 << 16) | (1 << 0); + DMIBAR32(DMILE2A) = DEFAULT_MCHBAR; +} + +void gm45_late_init(const stepping_t stepping) +{ + const device_t mch = PCI_DEV(0, 0, 0); + const int peg_enabled = (pci_read_config8(mch, D0F0_DEVEN) >> 1) & 1; + const int sdvo_enabled = (MCHBAR16(0x40) >> 8) & 1; + const int peg_x16 = (peg_enabled && !sdvo_enabled); + + init_egress(); + init_dmi(stepping >= STEPPING_B2); + init_pcie(peg_enabled, sdvo_enabled, peg_x16); + + setup_aspm(stepping, peg_enabled); + setup_rcrb(peg_enabled); +} diff --git a/src/northbridge/intel/gm45/pm.c b/src/northbridge/intel/gm45/pm.c new file mode 100644 index 0000000000..32a5ba7b96 --- /dev/null +++ b/src/northbridge/intel/gm45/pm.c @@ -0,0 +1,295 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * + * 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 <stdint.h> +#include <stddef.h> +#include <string.h> +#include <arch/io.h> +#include <arch/romcc_io.h> +#include <device/pci_def.h> +#include <device/pnp_def.h> +#include <console/console.h> +#include <cpu/x86/msr.h> + +#include "gm45.h" + +static int sku_freq_index(const gmch_gfx_t sku, const int low_power_mode) +{ + if (low_power_mode) + return 1; + switch (sku) { + case GMCH_GM45: + case GMCH_GE45: + case GMCH_GS45: + return 0; + case GMCH_GM47: + return 2; + case GMCH_GM49: + return 3; + default: + return 0; + } +} +static void init_freq_scaling(const gmch_gfx_t sku, const int low_power_mode) +{ + int i; + + MCHBAR32(0x11cc) = (MCHBAR32(0x11cc) & ~(0x1f)) | 0x17; + switch (sku) { + case GMCH_GM45: + case GMCH_GE45: + case GMCH_GS45: + case GMCH_GM47: + case GMCH_GM49: + break; + default: + /* No more to be done for the others. */ + return; + } + + static const u32 voltage_mask = + (0x1f << 24) | (0x1f << 16) | (0x1f << 8) | 0x1f; + MCHBAR32(0x1120) = (MCHBAR32(0x1120) & ~voltage_mask) | 0x10111213; + MCHBAR32(0x1124) = (MCHBAR32(0x1124) & ~voltage_mask) | 0x14151617; + MCHBAR32(0x1128) = (MCHBAR32(0x1128) & ~voltage_mask) | 0x18191a1b; + MCHBAR32(0x112c) = (MCHBAR32(0x112c) & ~voltage_mask) | 0x1c1d1e1f; + MCHBAR32(0x1130) = (MCHBAR32(0x1130) & ~voltage_mask) | 0x00010203; + MCHBAR32(0x1134) = (MCHBAR32(0x1134) & ~voltage_mask) | 0x04050607; + MCHBAR32(0x1138) = (MCHBAR32(0x1138) & ~voltage_mask) | 0x08090a0b; + MCHBAR32(0x113c) = (MCHBAR32(0x113c) & ~voltage_mask) | 0x0c0d0e0f; + + /* Program frequencies. */ + static const u32 frequencies_from_sku_vco[][4][8] = { + /* GM45/GE45/GS45_perf */ { + /* VCO 2666 */ { 0xcd, 0xbc, 0x9b, 0x8a, 0x79, 0x78, 0x67, 0x56 }, + /* VCO 3200 */ { 0xcd, 0xac, 0x9b, 0x8a, 0x89, 0x78, 0x67, 0x56 }, + /* VCO 4000 */ { 0xac, 0x9b, 0x9a, 0x89, 0x89, 0x68, 0x56, 0x45 }, + /* VCO 5333 */ { 0xab, 0x9a, 0x79, 0x68, 0x57, 0x56, 0x45, 0x34 }, + }, + /* GS45_low_power */ { + /* VCO 2666 */ { 0xcd, 0x8a }, + /* VCO 3200 */ { 0xcd, 0x89 }, + /* VCO 4000 */ { 0xac, 0x89 }, + /* VCO 5333 */ { 0xab, 0x68 }, + }, + /* GM47 */ { + /* VCO 2666 */ { 0xcd, 0xcd, 0xbc, 0x9b, 0x79, 0x78, 0x67, 0x56 }, + /* VCO 3200 */ { 0xde, 0xcd, 0xac, 0x9b, 0x89, 0x78, 0x67, 0x56 }, + /* VCO 4000 */ { 0xcd, 0xac, 0x9b, 0x9a, 0x89, 0x68, 0x56, 0x45 }, + /* VCO 5333 */ { 0xac, 0xab, 0x9a, 0x79, 0x68, 0x56, 0x45, 0x34 }, + }, + /* GM49 */ { + /* VCO 2666 */ { }, + /* VCO 3200 */ { 0xef, 0xde, 0xcd, 0xac, 0x89, 0x78, 0x67, 0x56 }, + /* VCO 4000 */ { 0xef, 0xde, 0xac, 0x9b, 0x89, 0x68, 0x56, 0x45 }, + /* VCO 5333 */ { 0xce, 0xbd, 0xab, 0x9a, 0x68, 0x57, 0x45, 0x34 }, + }}; + const int sku_index = sku_freq_index(sku, low_power_mode); + const int vco_index = raminit_read_vco_index(); + const int reg_limit = low_power_mode ? 1 : 4; + if (sku == GMCH_GM49) + MCHBAR8(0x1110+3) = 0x1b; + else + MCHBAR8(0x1110+3) = 0x17; + MCHBAR8(0x1110+1) = 0x17; + if (!low_power_mode) { + MCHBAR8(0x1114+3) = 0x17; + MCHBAR8(0x1114+1) = 0x17; + MCHBAR8(0x1118+3) = 0x17; + MCHBAR8(0x1118+1) = 0x17; + MCHBAR8(0x111c+3) = 0x17; + MCHBAR8(0x111c+1) = 0x17; + } + for (i = 0; i < reg_limit; ++i) { + const int mchbar = 0x1110 + (i * 4); + MCHBAR8(mchbar + 2) = frequencies_from_sku_vco + [sku_index][vco_index][i * 2 + 0]; + MCHBAR8(mchbar + 0) = frequencies_from_sku_vco + [sku_index][vco_index][i * 2 + 1]; + } + + if (low_power_mode) { + MCHBAR16(0x1190) = + (MCHBAR16(0x1190) & ~((7 << 8) | (7 << 4) | 7)) | + (1 << 8) | (1 << 4) | 1; + } else { + MCHBAR16(0x1190) = + (MCHBAR16(0x1190) & ~((7 << 8) | (7 << 4))) | 7; + if (sku == GMCH_GS45) /* performance mode */ + MCHBAR32(0x0ffc) &= ~(1 << 31); + } + + MCHBAR16(0x0fc0) |= (1 << 11); + MCHBAR16(0x11b8) = 0x333c; + MCHBAR16(0x11c0 + 2) = 0x0303; + MCHBAR32(0x11c4) = 0x0a030a03; + MCHBAR16(0x1100) = (MCHBAR16(0x1100) & ~(0x1f << 8)) | (3 << 8); + MCHBAR16(0x11b8 + 2) = 0x4000; +} + +void init_pm(const sysinfo_t *const sysinfo) +{ + const stepping_t stepping = sysinfo->stepping; + const fsb_clock_t fsb = sysinfo->selected_timings.fsb_clock; + const mem_clock_t memclk = sysinfo->selected_timings.mem_clock; + + MCHBAR16(0xc14) = 0; + MCHBAR16(0xc20) = 0; + MCHBAR32(0xfc0) = 0x001f00fd; + MCHBAR32(0xfc0) |= 3 << 25; + MCHBAR32(0xfc0) |= 1 << 11; + MCHBAR8(0xfb0) = 3; + MCHBAR8(0xf10) |= 1 << 1; + if (fsb == FSB_CLOCK_667MHz) { + MCHBAR16(0xc3a) = 0xea6; + MCHBAR8(0xc16) = (MCHBAR8(0xc16) & 0x80) | 0x0e; + } else if (fsb == FSB_CLOCK_800MHz) { + MCHBAR16(0xc3a) = 0x1194; + MCHBAR8(0xc16) = (MCHBAR8(0xc16) & 0x80) | 0x10; + } else if (fsb == FSB_CLOCK_1067MHz) { + MCHBAR16(0xc3a) = 0x1777; + MCHBAR8(0xc16) = (MCHBAR8(0xc16) & 0x80) | 0x15; + } + MCHBAR8(0xfb8) = 3; + if (fsb == FSB_CLOCK_667MHz) + MCHBAR16(0xc38) = 0x0ea6; + else if (fsb == FSB_CLOCK_800MHz) + MCHBAR16(0xc38) = 0x1194; + else if (fsb == FSB_CLOCK_1067MHz) + MCHBAR16(0xc38) = 0x1777; + MCHBAR8(0xf10) |= 1 << 5; + MCHBAR16(0xc16) |= 3 << 12; + MCHBAR32(0xf60) = 0x01030419; + if (fsb == FSB_CLOCK_667MHz) { + MCHBAR32(0xf00) = 0x00000600; + MCHBAR32(0xf04) = 0x00001d80; + } else if (fsb == FSB_CLOCK_800MHz) { + MCHBAR32(0xf00) = 0x00000700; + MCHBAR32(0xf04) = 0x00002380; + } else if (fsb == FSB_CLOCK_1067MHz) { + MCHBAR32(0xf00) = 0x00000900; + MCHBAR32(0xf04) = 0x00002e80; + } + MCHBAR16(0xf08) = 0x730f; + if (fsb == FSB_CLOCK_667MHz) + MCHBAR16(0xf0c) = 0x0b96; + else if (fsb == FSB_CLOCK_800MHz) + MCHBAR16(0xf0c) = 0x0c99; + else if (fsb == FSB_CLOCK_1067MHz) + MCHBAR16(0xf0c) = 0x10a4; + MCHBAR32(0xf80) |= 1 << 31; + + MCHBAR32(0x40) = (MCHBAR32(0x40) & ~(0x3f << 24)) | + (sysinfo->cores == 4) ? (1 << 24) : 0; + + MCHBAR32(0x40) &= ~(1 << 19); + MCHBAR32(0x40) |= 1 << 13; + MCHBAR32(0x40) |= 1 << 21; + MCHBAR32(0x40) |= 1 << 9; + if (stepping > STEPPING_B1) { + if (fsb != FSB_CLOCK_1067MHz) { + MCHBAR32(0x70) |= 1 << 30; + } else { + MCHBAR32(0x70) &= ~(1 << 30); + } + } + if (stepping < STEPPING_B1) + MCHBAR32(0x70) |= 1 << 29; + else + MCHBAR32(0x70) &= ~(1 << 29); + if (stepping > STEPPING_B1) { + MCHBAR32(0x70) |= 1 << 28; + MCHBAR32(0x70) |= 1 << 25; + } + if (stepping > STEPPING_B0) { + if (fsb != FSB_CLOCK_667MHz) + MCHBAR32(0x70) = (MCHBAR32(0x70) & ~(3<<21)) | (1 << 21); + else + MCHBAR32(0x70) = (MCHBAR32(0x70) & ~(3<<21)); + } + if (stepping > STEPPING_B2) + MCHBAR32(0x44) |= 1 << 30; + MCHBAR32(0x44) |= 1 << 31; + if (sysinfo->cores == 2) + MCHBAR32(0x44) |= 1 << 26; + MCHBAR32(0x44) |= 1 << 21; + MCHBAR32(0x44) = (MCHBAR32(0x44) & ~(3 << 24)) | (2 << 24); + MCHBAR32(0x44) |= 1 << 5; + MCHBAR32(0x44) |= 1 << 4; + MCHBAR32(0x90) = (MCHBAR32(0x90) & ~7) | 4; + MCHBAR32(0x94) |= 1 << 29; + MCHBAR32(0x94) |= 1 << 11; + if (stepping < STEPPING_B0) + MCHBAR32(0x94) = (MCHBAR32(0x94) & ~(3 << 19)) | (2 << 19); + if (stepping > STEPPING_B2) + MCHBAR32(0x94) |= 1 << 21; + MCHBAR8(0xb00) &= ~1; + MCHBAR8(0xb00) |= 1 << 7; + if (fsb != FSB_CLOCK_1067MHz) + MCHBAR8(0x75) |= 1 << 6; + else + MCHBAR8(0x75) &= 1 << 1; + MCHBAR8(0x77) |= 3; + if (stepping >= STEPPING_B1) + MCHBAR8(0x77) |= 1 << 2; + if (stepping > STEPPING_B2) + MCHBAR8(0x77) |= 1 << 4; + if (MCHBAR16(0x90) & 0x100) + MCHBAR8(0x90) &= ~(7 << 4); + if (stepping >= STEPPING_B0) + MCHBAR8(0xd0) |= 1 << 1; + MCHBAR8(0xbd8) |= 3 << 2; + if (stepping >= STEPPING_B3) + MCHBAR32(0x70) |= 1 << 0; + MCHBAR32(0x70) |= 1 << 3; + if (stepping >= STEPPING_B0) + MCHBAR32(0x70) &= ~(1 << 16); + else + MCHBAR32(0x70) |= 1 << 16; + if (stepping >= STEPPING_B3) + MCHBAR8(0xc14) |= 1 << 1; + if (stepping >= STEPPING_B1) + MCHBAR16(0xffc) = (MCHBAR16(0xffc) & ~0x7ff) | 0x7c0; + MCHBAR16(0x48) = (MCHBAR16(0x48) & ~(0xff << 2)) | (0xaa << 2); + if (stepping == STEPPING_CONVERSION_A1) { + MCHBAR16(0x40) |= 1 << 12; + MCHBAR32(0x94) |= 3 << 22; + } + + const int cpu_supports_super_lfm = rdmsr(0xee).lo & (1 << 27); + if ((stepping >= STEPPING_B0) && cpu_supports_super_lfm) { + MCHBAR16(CLKCFG_MCHBAR) &= ~(1 << 7); + MCHBAR16(CLKCFG_MCHBAR) |= 1 << 14; + } else { + MCHBAR16(CLKCFG_MCHBAR) &= ~(1 << 14); + MCHBAR16(CLKCFG_MCHBAR) |= 1 << 7; + MCHBAR32(0x44) &= ~(1 << 31); /* Was set above. */ + } + + if ((sysinfo->gfx_type != GMCH_PM45) && + (sysinfo->gfx_type != GMCH_UNKNOWN)) + init_freq_scaling(sysinfo->gfx_type, + sysinfo->gs45_low_power_mode); + + /* This has to be the last write to CLKCFG. */ + if ((fsb == FSB_CLOCK_1067MHz) && (memclk == MEM_CLOCK_667MT)) + MCHBAR32(CLKCFG_MCHBAR) &= ~(1 << 17); +} diff --git a/src/northbridge/intel/gm45/ram_calc.c b/src/northbridge/intel/gm45/ram_calc.c new file mode 100644 index 0000000000..1da9e87ed1 --- /dev/null +++ b/src/northbridge/intel/gm45/ram_calc.c @@ -0,0 +1,102 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * + * 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 <stdint.h> +#include <arch/io.h> +#include <arch/romcc_io.h> +#include <device/pci_def.h> +#include <console/console.h> +#include "gm45.h" + +/** Decodes used Graphics Mode Select (GMS) to kilobytes. */ +u32 decode_igd_memory_size(const u32 gms) +{ + switch (gms) { + case 1: + return 1 << 10; + case 2: + return 4 << 10; /* guessed */ + case 3: + return 8 << 10; /* guessed */ + case 4: + return 16 << 10; + case 5: + return 32 << 10; + case 7: + return 64 << 10; + case 8: + return 128 << 10; + case 9: + return 256 << 10; + case 10: + return 96 << 10; + case 11: + return 160 << 10; + case 12: + return 224 << 10; + case 13: + return 352 << 10; + default: + die("Bad Graphics Mode Select (GMS) setting.\n"); + return 0; + } +} + +/** Decodes used Graphics Stolen Memory (GSM) to kilobytes. */ +u32 decode_igd_gtt_size(const u32 gsm) +{ + switch (gsm) { + case 0: + return 0 << 10; + case 1: + return 1 << 10; + case 3: + case 9: + return 2 << 10; + case 10: + return 3 << 10; + case 11: + return 4 << 10; + default: + die("Bad Graphics Stolen Memory (GSM) setting.\n"); + return 0; + } +} + +u32 get_top_of_ram(void) +{ + const device_t dev = PCI_DEV(0, 0, 0); + + u32 tor; + + /* Top of Lower Usable DRAM */ + tor = (pci_read_config16(dev, D0F0_TOLUD) & 0xfff0) << 16; + + /* Graphics memory comes next */ + const u32 ggc = pci_read_config16(dev, D0F0_GGC); + if (!(ggc & 2)) { + /* Graphics memory */ + tor -= decode_igd_memory_size((ggc >> 4) & 0xf) << 10; + /* GTT Graphics Stolen Memory Size (GGMS) */ + tor -= decode_igd_gtt_size((ggc >> 8) & 0xf) << 10; + } + return tor; +} diff --git a/src/northbridge/intel/gm45/raminit.c b/src/northbridge/intel/gm45/raminit.c new file mode 100644 index 0000000000..68c81206e1 --- /dev/null +++ b/src/northbridge/intel/gm45/raminit.c @@ -0,0 +1,1779 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * + * 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 <stdint.h> +#include <arch/cpu.h> +#include <arch/io.h> +#include <arch/romcc_io.h> +#include <device/pci_def.h> +#include <device/pnp_def.h> +#include <spd.h> +#include <console/console.h> +#include <lib.h> +#include "delay.h" +#include "gm45.h" + +static const gmch_gfx_t gmch_gfx_types[][5] = { +/* MAX_667MHz MAX_533MHz MAX_400MHz MAX_333MHz MAX_800MHz */ + { GMCH_UNKNOWN, GMCH_UNKNOWN, GMCH_UNKNOWN, GMCH_UNKNOWN, GMCH_UNKNOWN }, + { GMCH_GM47, GMCH_GM45, GMCH_UNKNOWN, GMCH_UNKNOWN, GMCH_GM49 }, + { GMCH_GE45, GMCH_GE45, GMCH_GE45, GMCH_GE45, GMCH_GE45 }, + { GMCH_UNKNOWN, GMCH_GL43, GMCH_GL40, GMCH_UNKNOWN, GMCH_UNKNOWN }, + { GMCH_UNKNOWN, GMCH_GS45, GMCH_GS40, GMCH_UNKNOWN, GMCH_UNKNOWN }, + { GMCH_UNKNOWN, GMCH_UNKNOWN, GMCH_UNKNOWN, GMCH_UNKNOWN, GMCH_UNKNOWN }, + { GMCH_UNKNOWN, GMCH_UNKNOWN, GMCH_UNKNOWN, GMCH_UNKNOWN, GMCH_UNKNOWN }, + { GMCH_PM45, GMCH_PM45, GMCH_PM45, GMCH_PM45, GMCH_PM45 }, +}; + +void get_gmch_info(sysinfo_t *sysinfo) +{ + sysinfo->stepping = pci_read_config8(PCI_DEV(0, 0, 0), PCI_CLASS_REVISION); + if ((sysinfo->stepping > STEPPING_B3) && + (sysinfo->stepping != STEPPING_CONVERSION_A1)) + die("Unknown stepping.\n"); + if (sysinfo->stepping <= STEPPING_B3) + printk(BIOS_DEBUG, "Stepping %c%d\n", 'A' + sysinfo->stepping / 4, sysinfo->stepping % 4); + else + printk(BIOS_DEBUG, "Conversion stepping A1\n"); + + const u32 eax = cpuid_ext(0x04, 0).eax; + sysinfo->cores = ((eax >> 26) & 0x3f) + 1; + printk(BIOS_SPEW, "%d CPU cores\n", sysinfo->cores); + + u32 capid = pci_read_config16(PCI_DEV(0, 0, 0), D0F0_CAPID0+8); + if (!(capid & (1<<(79-64)))) { + printk(BIOS_SPEW, "iTPM enabled\n"); + } + + capid = pci_read_config32(PCI_DEV(0, 0, 0), D0F0_CAPID0+4); + if (!(capid & (1<<(57-32)))) { + printk(BIOS_SPEW, "ME enabled\n"); + } + + if (!(capid & (1<<(56-32)))) { + printk(BIOS_SPEW, "AMT enabled\n"); + } + + sysinfo->max_ddr2_mhz = (capid & (1<<(53-32)))?667:800; + printk(BIOS_SPEW, "capable of DDR2 of %d MHz or lower\n", sysinfo->max_ddr2_mhz); + + if (!(capid & (1<<(48-32)))) { + printk(BIOS_SPEW, "VT-d enabled\n"); + } + + const u32 gfx_variant = (capid>>(42-32)) & 0x7; + const u32 render_freq = ((capid>>(50-32) & 0x1) << 2) | ((capid>>(35-32)) & 0x3); + if (render_freq <= 4) + sysinfo->gfx_type = gmch_gfx_types[gfx_variant][render_freq]; + else + sysinfo->gfx_type = GMCH_UNKNOWN; + sysinfo->gs45_low_power_mode = 0; + switch (sysinfo->gfx_type) { + case GMCH_GM45: + printk(BIOS_SPEW, "GMCH: GM45\n"); + break; + case GMCH_GM47: + printk(BIOS_SPEW, "GMCH: GM47\n"); + break; + case GMCH_GM49: + printk(BIOS_SPEW, "GMCH: GM49\n"); + break; + case GMCH_GE45: + printk(BIOS_SPEW, "GMCH: GE45\n"); + break; + case GMCH_GL40: + printk(BIOS_SPEW, "GMCH: GL40\n"); + break; + case GMCH_GL43: + printk(BIOS_SPEW, "GMCH: GL43\n"); + break; + case GMCH_GS40: + printk(BIOS_SPEW, "GMCH: GS40\n"); + break; + case GMCH_GS45: + printk(BIOS_SPEW, "GMCH: GS45, using low power mode by default\n"); + sysinfo->gs45_low_power_mode = 1; + break; + case GMCH_PM45: + printk(BIOS_SPEW, "GMCH: PM45\n"); + break; + case GMCH_UNKNOWN: + printk(BIOS_SPEW, "unknown GMCH\n"); + break; + } + + sysinfo->txt_enabled = !(capid & (1 << (37-32))); + if (sysinfo->txt_enabled) { + printk(BIOS_SPEW, "TXT enabled\n"); + } + + switch (render_freq) { + case 4: + sysinfo->max_render_mhz = 800; + break; + case 0: + sysinfo->max_render_mhz = 667; + break; + case 1: + sysinfo->max_render_mhz = 533; + break; + case 2: + sysinfo->max_render_mhz = 400; + break; + case 3: + sysinfo->max_render_mhz = 333; + break; + default: + printk(BIOS_SPEW, "Unknown render frequency\n"); + sysinfo->max_render_mhz = 0; + break; + } + if (sysinfo->max_render_mhz != 0) { + printk(BIOS_SPEW, "Render frequency: %d MHz\n", sysinfo->max_render_mhz); + } + + if (!(capid & (1<<(33-32)))) { + printk(BIOS_SPEW, "IGD enabled\n"); + } + + if (!(capid & (1<<(32-32)))) { + printk(BIOS_SPEW, "PCIe-to-GMCH enabled\n"); + } + + capid = pci_read_config32(PCI_DEV(0, 0, 0), D0F0_CAPID0); + + u32 ddr_cap = capid>>30 & 0x3; + switch (ddr_cap) { + case 0: + sysinfo->max_ddr3_mt = 1067; + break; + case 1: + sysinfo->max_ddr3_mt = 800; + break; + case 2: + case 3: + printk(BIOS_SPEW, "GMCH not DDR3 capable\n"); + sysinfo->max_ddr3_mt = 0; + break; + } + if (sysinfo->max_ddr3_mt != 0) { + printk(BIOS_SPEW, "GMCH supports DDR3 with %d MT or less\n", sysinfo->max_ddr3_mt); + } + + const unsigned max_fsb = (capid >> 28) & 0x3; + switch (max_fsb) { + case 1: + sysinfo->max_fsb_mhz = 1067; + break; + case 2: + sysinfo->max_fsb_mhz = 800; + break; + case 3: + sysinfo->max_fsb_mhz = 667; + break; + default: + die("unknown FSB capability\n"); + break; + } + if (sysinfo->max_fsb_mhz != 0) { + printk(BIOS_SPEW, "GMCH supports FSB with up to %d MHz\n", sysinfo->max_fsb_mhz); + } + sysinfo->max_fsb = max_fsb - 1; +} + +/* + * Detect if the system went through an interrupted RAM init or is incon- + * sistent. If so, initiate a cold reboot. Otherwise mark the system to be + * in RAM init, so this function would detect it on an erreneous reboot. + */ +void enter_raminit_or_reset(void) +{ + /* Interrupted RAM init or inconsistent system? */ + u8 reg8 = pci_read_config8(PCI_DEV(0, 0x1f, 0), 0xa2); + + if (reg8 & (1 << 2)) { /* S4-assertion-width violation */ + /* Ignore S4-assertion-width violation like original BIOS. */ + printk(BIOS_WARNING, + "WARNING: Ignoring S4-assertion-width violation.\n"); + /* Bit2 is R/WC, so it will clear itself below. */ + } + + if (reg8 & (1 << 7)) { /* interrupted RAM init */ + /* Don't enable S4-assertion stretch. Makes trouble on roda/rk9. + reg8 = pci_read_config8(PCI_DEV(0, 0x1f, 0), 0xa4); + pci_write_config8(PCI_DEV(0, 0x1f, 0), 0xa4, reg8 | 0x08); + */ + + /* Clear bit7. */ + pci_write_config8(PCI_DEV(0, 0x1f, 0), 0xa2, reg8 & ~(1 << 7)); + + printk(BIOS_INFO, "Interrupted RAM init, reset required.\n"); + gm45_early_reset(); + } + /* Mark system to be in RAM init. */ + pci_write_config8(PCI_DEV(0, 0x1f, 0), 0xa2, reg8 | (1 << 7)); +} + + +/* For a detected DIMM, test the value of an SPD byte to + match the expected value after masking some bits. */ +static int test_dimm(int dimm, int addr, int bitmask, int expected) +{ + return (smbus_read_byte(DIMM0 + dimm, addr) & bitmask) == expected; +} + +/* This function dies if dimm is unsuitable for the chipset. */ +static void verify_ddr3_dimm(int dimm) +{ + if (!test_dimm(dimm, 3, 15, 3)) + die("Chipset only supports SO-DIMM\n"); + + if (!test_dimm(dimm, 8, 0x18, 0)) + die("Chipset doesn't support ECC RAM\n"); + + if (!test_dimm(dimm, 7, 0x38, 0) && + !test_dimm(dimm, 7, 0x38, 8)) + die("Chipset wants single or double sided DIMMs\n"); + + if (!test_dimm(dimm, 7, 7, 1) && + !test_dimm(dimm, 7, 7, 2)) + die("Chipset requires x8 or x16 width\n"); + + if (!test_dimm(dimm, 4, 0x0f, 0) && + !test_dimm(dimm, 4, 0x0f, 1) && + !test_dimm(dimm, 4, 0x0f, 2) && + !test_dimm(dimm, 4, 0x0f, 3)) + die("Chipset requires 256Mb, 512Mb, 1Gb or 2Gb chips."); + + if (!test_dimm(dimm, 4, 0x70, 0)) + die("Chipset requires 8 banks on DDR3\n"); + + /* How to check if burst length is 8? + Other values are not supported, are they even possible? */ + + if (!test_dimm(dimm, 10, 0xff, 1)) + die("Code assumes 1/8ns MTB\n"); + + if (!test_dimm(dimm, 11, 0xff, 8)) + die("Code assumes 1/8ns MTB\n"); + + if (!test_dimm(dimm, 62, 0x9f, 0) && + !test_dimm(dimm, 62, 0x9f, 1) && + !test_dimm(dimm, 62, 0x9f, 2) && + !test_dimm(dimm, 62, 0x9f, 3) && + !test_dimm(dimm, 62, 0x9f, 5)) + die("Only raw card types A, B, C, D and F are supported.\n"); +} + +/* For every detected DIMM, test if it's suitable for the chipset. */ +static void verify_ddr3(int mask) +{ + int cur = 0; + while (mask) { + if (mask & 1) { + verify_ddr3_dimm(cur); + } + mask >>= 1; + cur++; + } +} + + +typedef struct { + int dimm_mask; + struct { + unsigned int rows; + unsigned int cols; + unsigned int chip_capacity; + unsigned int banks; + unsigned int ranks; + unsigned int cas_latencies; + unsigned int tAAmin; + unsigned int tCKmin; + unsigned int width; + unsigned int tRAS; + unsigned int tRP; + unsigned int tRCD; + unsigned int tWR; + unsigned int page_size; + unsigned int raw_card; + } channel[2]; +} spdinfo_t; +/* + * This function collects RAM characteristics from SPD, assuming that RAM + * is generally within chipset's requirements, since verify_ddr3() passed. + */ +static void collect_ddr3(spdinfo_t *const config) +{ + int mask = config->dimm_mask; + int cur = 0; + while (mask != 0) { + if (mask & 1) { + int tmp; + const int smb_addr = DIMM0 + cur*2; + + config->channel[cur].rows = ((smbus_read_byte(smb_addr, 5) >> 3) & 7) + 12; + config->channel[cur].cols = (smbus_read_byte(smb_addr, 5) & 7) + 9; + + config->channel[cur].chip_capacity = smbus_read_byte(smb_addr, 4) & 0xf; + + config->channel[cur].banks = 8; /* GM45 only accepts this for DDR3. + verify_ddr3() fails for other values. */ + config->channel[cur].ranks = ((smbus_read_byte(smb_addr, 7) >> 3) & 7) + 1; + + config->channel[cur].cas_latencies = + ((smbus_read_byte(smb_addr, 15) << 8) | smbus_read_byte(smb_addr, 14)) + << 4; /* so bit x is CAS x */ + config->channel[cur].tAAmin = smbus_read_byte(smb_addr, 16); /* in MTB */ + config->channel[cur].tCKmin = smbus_read_byte(smb_addr, 12); /* in MTB */ + + config->channel[cur].width = smbus_read_byte(smb_addr, 7) & 7; + config->channel[cur].page_size = config->channel[cur].width * + (1 << config->channel[cur].cols); /* in Bytes */ + + tmp = smbus_read_byte(smb_addr, 21); + config->channel[cur].tRAS = smbus_read_byte(smb_addr, 22) | ((tmp & 0xf) << 8); + config->channel[cur].tRP = smbus_read_byte(smb_addr, 20); + config->channel[cur].tRCD = smbus_read_byte(smb_addr, 18); + config->channel[cur].tWR = smbus_read_byte(smb_addr, 17); + + config->channel[cur].raw_card = smbus_read_byte(smb_addr, 62) & 0x1f; + } + cur++; + mask >>= 2; /* Only every other address is used. */ + } +} + +#define min(a, b) ((a) < (b))?(a):(b) +#define ROUNDUP_DIV(val, by) (((val) + (by) - 1) / (by)) +#define ROUNDUP_DIV_THIS(val, by) val = ROUNDUP_DIV(val, by) +static fsb_clock_t read_fsb_clock(void) +{ + switch (MCHBAR32(CLKCFG_MCHBAR) & CLKCFG_FSBCLK_MASK) { + case 6: + return FSB_CLOCK_1067MHz; + case 2: + return FSB_CLOCK_800MHz; + case 3: + return FSB_CLOCK_667MHz; + default: + die("Unsupported FSB clock.\n"); + } +} +static mem_clock_t clock_index(const unsigned int clock) +{ + switch (clock) { + case 533: return MEM_CLOCK_533MHz; + case 400: return MEM_CLOCK_400MHz; + case 333: return MEM_CLOCK_333MHz; + default: die("Unknown clock value.\n"); + } + return -1; /* Won't be reached. */ +} +static void normalize_clock(unsigned int *const clock) +{ + if (*clock >= 533) + *clock = 533; + else if (*clock >= 400) + *clock = 400; + else if (*clock >= 333) + *clock = 333; + else + *clock = 0; +} +static void lower_clock(unsigned int *const clock) +{ + --*clock; + normalize_clock(clock); +} +static unsigned int find_common_clock_cas(sysinfo_t *const sysinfo, + const spdinfo_t *const spdinfo) +{ + /* various constraints must be fulfilled: + CAS * tCK < 20ns == 160MTB + tCK_max >= tCK >= tCK_min + CAS >= roundup(tAA_min/tCK) + CAS supported + Clock(MHz) = 1000 / tCK(ns) + Clock(MHz) = 8000 / tCK(MTB) + AND BTW: Clock(MT) = 2000 / tCK(ns) - intel uses MTs but calls them MHz + */ + int i; + + /* Calculate common cas_latencies mask, tCKmin and tAAmin. */ + unsigned int cas_latencies = (unsigned int)-1; + unsigned int tCKmin = 0, tAAmin = 0; + FOR_EACH_POPULATED_CHANNEL(sysinfo->dimms, i) { + cas_latencies &= spdinfo->channel[i].cas_latencies; + if (spdinfo->channel[i].tCKmin > tCKmin) + tCKmin = spdinfo->channel[i].tCKmin; + if (spdinfo->channel[i].tAAmin > tAAmin) + tAAmin = spdinfo->channel[i].tAAmin; + } + + /* Get actual value of fsb clock. */ + sysinfo->selected_timings.fsb_clock = read_fsb_clock(); + unsigned int fsb_mhz = 0; + switch (sysinfo->selected_timings.fsb_clock) { + case FSB_CLOCK_1067MHz: fsb_mhz = 1067; break; + case FSB_CLOCK_800MHz: fsb_mhz = 800; break; + case FSB_CLOCK_667MHz: fsb_mhz = 667; break; + } + + unsigned int clock = 8000 / tCKmin; + if ((clock > sysinfo->max_ddr3_mt / 2) || (clock > fsb_mhz / 2)) { + int new_clock = min(sysinfo->max_ddr3_mt / 2, fsb_mhz / 2); + printk(BIOS_SPEW, "DIMMs support %d MHz, but chipset only runs at up to %d. Limiting...\n", + clock, new_clock); + clock = new_clock; + } + normalize_clock(&clock); + + /* Find compatible clock / CAS pair. */ + unsigned int tCKproposed; + unsigned int CAS; + while (1) { + if (!clock) + die("Couldn't find compatible clock / CAS settings.\n"); + tCKproposed = 8000 / clock; + CAS = ROUNDUP_DIV(tAAmin, tCKproposed); + printk(BIOS_SPEW, "Trying CAS %u, tCK %u.\n", CAS, tCKproposed); + for (; CAS <= DDR3_MAX_CAS; ++CAS) + if (cas_latencies & (1 << CAS)) + break; + if ((CAS <= DDR3_MAX_CAS) && (CAS * tCKproposed < 160)) { + /* Found good CAS. */ + printk(BIOS_SPEW, "Found compatible clock / CAS pair: %u / %u.\n", clock, CAS); + break; + } + lower_clock(&clock); + } + sysinfo->selected_timings.CAS = CAS; + sysinfo->selected_timings.mem_clock = clock_index(clock); + + return tCKproposed; +} + +static void calculate_derived_timings(sysinfo_t *const sysinfo, + const unsigned int tCLK, + const spdinfo_t *const spdinfo) +{ + int i; + + /* Calculate common tRASmin, tRPmin, tRCDmin and tWRmin. */ + unsigned int tRASmin = 0, tRPmin = 0, tRCDmin = 0, tWRmin = 0; + FOR_EACH_POPULATED_CHANNEL(sysinfo->dimms, i) { + if (spdinfo->channel[i].tRAS > tRASmin) + tRASmin = spdinfo->channel[i].tRAS; + if (spdinfo->channel[i].tRP > tRPmin) + tRPmin = spdinfo->channel[i].tRP; + if (spdinfo->channel[i].tRCD > tRCDmin) + tRCDmin = spdinfo->channel[i].tRCD; + if (spdinfo->channel[i].tWR > tWRmin) + tWRmin = spdinfo->channel[i].tWR; + } + ROUNDUP_DIV_THIS(tRASmin, tCLK); + ROUNDUP_DIV_THIS(tRPmin, tCLK); + ROUNDUP_DIV_THIS(tRCDmin, tCLK); + ROUNDUP_DIV_THIS(tWRmin, tCLK); + + /* Lookup tRFC and calculate common tRFCmin. */ + const unsigned int tRFC_from_clock_and_cap[][4] = { + /* CAP_256M CAP_512M CAP_1G CAP_2G */ + /* 533MHz */ { 40, 56, 68, 104 }, + /* 400MHz */ { 30, 42, 51, 78 }, + /* 333MHz */ { 25, 35, 43, 65 }, + }; + unsigned int tRFCmin = 0; + FOR_EACH_POPULATED_CHANNEL(sysinfo->dimms, i) { + const unsigned int tRFC = tRFC_from_clock_and_cap + [sysinfo->selected_timings.mem_clock][spdinfo->channel[i].chip_capacity]; + if (tRFC > tRFCmin) + tRFCmin = tRFC; + } + + /* Calculate common tRD from CAS and FSB and DRAM clocks. */ + unsigned int tRDmin = sysinfo->selected_timings.CAS; + switch (sysinfo->selected_timings.fsb_clock) { + case FSB_CLOCK_667MHz: + tRDmin += 1; + break; + case FSB_CLOCK_800MHz: + tRDmin += 2; + break; + case FSB_CLOCK_1067MHz: + tRDmin += 3; + if (sysinfo->selected_timings.mem_clock == MEM_CLOCK_1067MT) + tRDmin += 1; + break; + } + + /* Calculate common tRRDmin. */ + unsigned int tRRDmin = 0; + FOR_EACH_POPULATED_CHANNEL(sysinfo->dimms, i) { + unsigned int tRRD = 2 + (spdinfo->channel[i].page_size / 1024); + if (sysinfo->selected_timings.mem_clock == MEM_CLOCK_1067MT) + tRRD += (spdinfo->channel[i].page_size / 1024); + if (tRRD > tRRDmin) + tRRDmin = tRRD; + } + + /* Lookup and calculate common tFAWmin. */ + unsigned int tFAW_from_pagesize_and_clock[][3] = { + /* 533MHz 400MHz 333MHz */ + /* 1K */ { 20, 15, 13 }, + /* 2K */ { 27, 20, 17 }, + }; + unsigned int tFAWmin = 0; + FOR_EACH_POPULATED_CHANNEL(sysinfo->dimms, i) { + const unsigned int tFAW = tFAW_from_pagesize_and_clock + [spdinfo->channel[i].page_size / 1024 - 1] + [sysinfo->selected_timings.mem_clock]; + if (tFAW > tFAWmin) + tFAWmin = tFAW; + } + + /* Refresh rate is fixed. */ + unsigned int tWL; + if (sysinfo->selected_timings.mem_clock == MEM_CLOCK_1067MT) { + tWL = 6; + } else { + tWL = 5; + } + + printk(BIOS_SPEW, "Timing values:\n" + " tCLK: %3u\n" + " tRAS: %3u\n" + " tRP: %3u\n" + " tRCD: %3u\n" + " tRFC: %3u\n" + " tWR: %3u\n" + " tRD: %3u\n" + " tRRD: %3u\n" + " tFAW: %3u\n" + " tWL: %3u\n", + tCLK, tRASmin, tRPmin, tRCDmin, tRFCmin, tWRmin, tRDmin, tRRDmin, tFAWmin, tWL); + + sysinfo->selected_timings.tRAS = tRASmin; + sysinfo->selected_timings.tRP = tRPmin; + sysinfo->selected_timings.tRCD = tRCDmin; + sysinfo->selected_timings.tRFC = tRFCmin; + sysinfo->selected_timings.tWR = tWRmin; + sysinfo->selected_timings.tRD = tRDmin; + sysinfo->selected_timings.tRRD = tRRDmin; + sysinfo->selected_timings.tFAW = tFAWmin; + sysinfo->selected_timings.tWL = tWL; +} + +static void collect_dimm_config(sysinfo_t *const sysinfo) +{ + int i; + spdinfo_t spdinfo; + + spdinfo.dimm_mask = 0; + sysinfo->spd_type = 0; + + /* at most 2 dimms, on even slots */ + for (i = 0; i < 4; i += 2) { + const u8 spd = smbus_read_byte(DIMM0 + i, 2); + if ((spd == 7) || (spd == 8) || (spd == 0xb)) { + spdinfo.dimm_mask |= 1 << i; + if (sysinfo->spd_type && sysinfo->spd_type != spd) { + die("Multiple types of DIMM installed in the system, don't do that!\n"); + } + sysinfo->spd_type = spd; + } + } + if (spdinfo.dimm_mask == 0) { + die("Could not find any DIMM.\n"); + } + + /* Normalize spd_type to 1, 2, 3. */ + sysinfo->spd_type = (sysinfo->spd_type & 1) | ((sysinfo->spd_type & 8) >> 2); + printk(BIOS_SPEW, "DDR mask %x, DDR %d\n", spdinfo.dimm_mask, sysinfo->spd_type); + + if (sysinfo->spd_type == DDR2) { + die("DDR2 not supported at this time.\n"); + } else if (sysinfo->spd_type == DDR3) { + verify_ddr3(spdinfo.dimm_mask); + collect_ddr3(&spdinfo); + } else { + die("Will never support DDR1.\n"); + } + + for (i = 0; i < 2; i++) { + if ((spdinfo.dimm_mask >> (i*2)) & 1) { + printk(BIOS_SPEW, "Bank %d populated:\n" + " Raw card type: %4c\n" + " Row addr bits: %4u\n" + " Col addr bits: %4u\n" + " byte width: %4u\n" + " page size: %4u\n" + " banks: %4u\n" + " ranks: %4u\n" + " tAAmin: %3u\n" + " tCKmin: %3u\n" + " Max clock: %3u MHz\n" + " CAS: 0x%04x\n", + i, spdinfo.channel[i].raw_card + 'A', + spdinfo.channel[i].rows, spdinfo.channel[i].cols, + spdinfo.channel[i].width, spdinfo.channel[i].page_size, + spdinfo.channel[i].banks, spdinfo.channel[i].ranks, + spdinfo.channel[i].tAAmin, spdinfo.channel[i].tCKmin, + 8000 / spdinfo.channel[i].tCKmin, spdinfo.channel[i].cas_latencies); + } + } + + FOR_EACH_CHANNEL(i) { + sysinfo->dimms[i].card_type = + (spdinfo.dimm_mask & (1 << (i * 2))) ? spdinfo.channel[i].raw_card + 0xa : 0; + } + + /* Find common memory clock and CAS. */ + const unsigned int tCLK = find_common_clock_cas(sysinfo, &spdinfo); + + /* Calculate other timings from clock and CAS. */ + calculate_derived_timings(sysinfo, tCLK, &spdinfo); + + /* Initialize DIMM infos. */ + /* Always prefer interleaved over async channel mode. */ + FOR_EACH_CHANNEL(i) { + IF_CHANNEL_POPULATED(sysinfo->dimms, i) { + sysinfo->dimms[i].banks = spdinfo.channel[i].banks; + sysinfo->dimms[i].ranks = spdinfo.channel[i].ranks; + + /* .width is 1 for x8 or 2 for x16, bus width is 8 bytes. */ + const unsigned int chips_per_rank = 8 / spdinfo.channel[i].width; + + sysinfo->dimms[i].chip_width = spdinfo.channel[i].width; + sysinfo->dimms[i].chip_capacity = spdinfo.channel[i].chip_capacity; + sysinfo->dimms[i].page_size = spdinfo.channel[i].page_size * chips_per_rank; + sysinfo->dimms[i].rank_capacity_mb = + /* offset of chip_capacity is 8 (256M), therefore, add 8 + chip_capacity is in Mbit, we want MByte, therefore, subtract 3 */ + (1 << (spdinfo.channel[i].chip_capacity + 8 - 3)) * chips_per_rank; + } + } + if (CHANNEL_IS_POPULATED(sysinfo->dimms, 0) && + CHANNEL_IS_POPULATED(sysinfo->dimms, 1)) + sysinfo->selected_timings.channel_mode = CHANNEL_MODE_DUAL_INTERLEAVED; + else + sysinfo->selected_timings.channel_mode = CHANNEL_MODE_SINGLE; +} + +static void reset_on_bad_warmboot(void) +{ + /* Check self refresh channel status. */ + const u32 reg = MCHBAR32(PMSTS_MCHBAR); + /* Clear status bits. R/WC */ + MCHBAR32(PMSTS_MCHBAR) = reg; + if ((reg & PMSTS_WARM_RESET) && !(reg & PMSTS_BOTH_SELFREFRESH)) { + printk(BIOS_INFO, "DRAM was not in self refresh " + "during warm boot, reset required.\n"); + gm45_early_reset(); + } +} + +static void set_system_memory_frequency(const timings_t *const timings) +{ + MCHBAR16(CLKCFG_MCHBAR + 0x60) &= ~(1 << 15); + MCHBAR16(CLKCFG_MCHBAR + 0x48) &= ~(1 << 15); + + /* Calculate wanted frequency setting. */ + const int want_freq = 6 - timings->mem_clock; + + /* Read current memory frequency. */ + const u32 clkcfg = MCHBAR32(CLKCFG_MCHBAR); + int cur_freq = (clkcfg & CLKCFG_MEMCLK_MASK) >> CLKCFG_MEMCLK_SHIFT; + if (0 == cur_freq) { + /* Try memory frequency from scratchpad. */ + printk(BIOS_DEBUG, "Reading current memory frequency from scratchpad.\n"); + cur_freq = (MCHBAR16(SSKPD_MCHBAR) & SSKPD_CLK_MASK) >> SSKPD_CLK_SHIFT; + } + + if (cur_freq != want_freq) { + printk(BIOS_DEBUG, "Changing memory frequency: old %x, new %x.\n", cur_freq, want_freq); + /* When writing new frequency setting, reset, then set update bit. */ + MCHBAR32(CLKCFG_MCHBAR) = (MCHBAR32(CLKCFG_MCHBAR) & ~(CLKCFG_UPDATE | CLKCFG_MEMCLK_MASK)) | + (want_freq << CLKCFG_MEMCLK_SHIFT); + MCHBAR32(CLKCFG_MCHBAR) = (MCHBAR32(CLKCFG_MCHBAR) & ~CLKCFG_MEMCLK_MASK) | + (want_freq << CLKCFG_MEMCLK_SHIFT) | CLKCFG_UPDATE; + /* Reset update bit. */ + MCHBAR32(CLKCFG_MCHBAR) &= ~CLKCFG_UPDATE; + } + + if ((timings->fsb_clock == FSB_CLOCK_1067MHz) && (timings->mem_clock == MEM_CLOCK_667MT)) { + MCHBAR32(CLKCFG_MCHBAR + 0x16) = 0x000030f0; + MCHBAR32(CLKCFG_MCHBAR + 0x64) = 0x000050c1; + + MCHBAR32(CLKCFG_MCHBAR) = (MCHBAR32(CLKCFG_MCHBAR) & ~(1 << 12)) | (1 << 17); + MCHBAR32(CLKCFG_MCHBAR) |= (1 << 17) | (1 << 12); + MCHBAR32(CLKCFG_MCHBAR) &= ~(1 << 12); + + MCHBAR32(CLKCFG_MCHBAR + 0x04) = 0x9bad1f1f; + MCHBAR8(CLKCFG_MCHBAR + 0x08) = 0xf4; + MCHBAR8(CLKCFG_MCHBAR + 0x0a) = 0x43; + MCHBAR8(CLKCFG_MCHBAR + 0x0c) = 0x10; + MCHBAR8(CLKCFG_MCHBAR + 0x0d) = 0x80; + MCHBAR32(CLKCFG_MCHBAR + 0x50) = 0x0b0e151b; + MCHBAR8(CLKCFG_MCHBAR + 0x54) = 0xb4; + MCHBAR8(CLKCFG_MCHBAR + 0x55) = 0x10; + MCHBAR8(CLKCFG_MCHBAR + 0x56) = 0x08; + + MCHBAR32(CLKCFG_MCHBAR) |= (1 << 10); + MCHBAR32(CLKCFG_MCHBAR) |= (1 << 11); + MCHBAR32(CLKCFG_MCHBAR) &= ~(1 << 10); + MCHBAR32(CLKCFG_MCHBAR) &= ~(1 << 11); + } + + MCHBAR32(CLKCFG_MCHBAR + 0x48) |= 0x3f << 24; +} + +int raminit_read_vco_index(void) +{ + switch (MCHBAR8(0x0c0f) & 0x7) { + case VCO_2666: + return 0; + case VCO_3200: + return 1; + case VCO_4000: + return 2; + case VCO_5333: + return 3; + default: + die("Unknown VCO frequency.\n"); + return 0; + } +} +static void set_igd_memory_frequencies(const sysinfo_t *const sysinfo) +{ + const int gfx_idx = ((sysinfo->gfx_type == GMCH_GS45) && + !sysinfo->gs45_low_power_mode) + ? (GMCH_GS45 + 1) : sysinfo->gfx_type; + + /* Render and sampler frequency values seem to be some kind of factor. */ + const u16 render_freq_from_vco_and_gfxtype[][10] = { + /* GM45 GM47 GM49 GE45 GL40 GL43 GS40 GS45 (perf) */ + /* VCO 2666 */ { 0xd, 0xd, 0xe, 0xd, 0xb, 0xd, 0xb, 0xa, 0xd }, + /* VCO 3200 */ { 0xd, 0xe, 0xf, 0xd, 0xb, 0xd, 0xb, 0x9, 0xd }, + /* VCO 4000 */ { 0xc, 0xd, 0xf, 0xc, 0xa, 0xc, 0xa, 0x9, 0xc }, + /* VCO 5333 */ { 0xb, 0xc, 0xe, 0xb, 0x9, 0xb, 0x9, 0x8, 0xb }, + }; + const u16 sampler_freq_from_vco_and_gfxtype[][10] = { + /* GM45 GM47 GM49 GE45 GL40 GL43 GS40 GS45 (perf) */ + /* VCO 2666 */ { 0xc, 0xc, 0xd, 0xc, 0x9, 0xc, 0x9, 0x8, 0xc }, + /* VCO 3200 */ { 0xc, 0xd, 0xe, 0xc, 0x9, 0xc, 0x9, 0x8, 0xc }, + /* VCO 4000 */ { 0xa, 0xc, 0xd, 0xa, 0x8, 0xa, 0x8, 0x8, 0xa }, + /* VCO 5333 */ { 0xa, 0xa, 0xc, 0xa, 0x7, 0xa, 0x7, 0x6, 0xa }, + }; + const u16 display_clock_select_from_gfxtype[] = { + /* GM45 GM47 GM49 GE45 GL40 GL43 GS40 GS45 (perf) */ + 1, 1, 1, 1, 1, 1, 1, 0, 1 + }; + + if (pci_read_config16(GCFGC_PCIDEV, 0) != 0x8086) { + printk(BIOS_DEBUG, "Skipping IGD memory frequency setting.\n"); + return; + } + + MCHBAR16(0x119e) = 0xa800; + MCHBAR16(0x11c0) = (MCHBAR16(0x11c0) & ~0xff00) | (0x01 << 8); + MCHBAR16(0x119e) = 0xb800; + MCHBAR8(0x0f10) |= 1 << 7; + + /* Read VCO. */ + const int vco_idx = raminit_read_vco_index(); + printk(BIOS_DEBUG, "Setting IGD memory frequencies for VCO #%d.\n", vco_idx); + + const u32 freqcfg = + ((render_freq_from_vco_and_gfxtype[vco_idx][gfx_idx] + << GCFGC_CR_SHIFT) & GCFGC_CR_MASK) | + ((sampler_freq_from_vco_and_gfxtype[vco_idx][gfx_idx] + << GCFGC_CS_SHIFT) & GCFGC_CS_MASK); + + /* Set frequencies, clear update bit. */ + u32 gcfgc = pci_read_config16(GCFGC_PCIDEV, GCFGC_OFFSET); + gcfgc &= ~(GCFGC_CS_MASK | GCFGC_UPDATE | GCFGC_CR_MASK); + gcfgc |= freqcfg; + pci_write_config16(GCFGC_PCIDEV, GCFGC_OFFSET, gcfgc); + + /* Set frequencies, set update bit. */ + gcfgc = pci_read_config16(GCFGC_PCIDEV, GCFGC_OFFSET); + gcfgc &= ~(GCFGC_CS_MASK | GCFGC_CR_MASK); + gcfgc |= freqcfg | GCFGC_UPDATE; + pci_write_config16(GCFGC_PCIDEV, GCFGC_OFFSET, gcfgc); + + /* Clear update bit. */ + pci_write_config16(GCFGC_PCIDEV, GCFGC_OFFSET, + pci_read_config16(GCFGC_PCIDEV, GCFGC_OFFSET) & ~GCFGC_UPDATE); + + /* Set display clock select bit. */ + pci_write_config16(GCFGC_PCIDEV, GCFGC_OFFSET, + (pci_read_config16(GCFGC_PCIDEV, GCFGC_OFFSET) & ~GCFGC_CD_MASK) | + (display_clock_select_from_gfxtype[gfx_idx] << GCFGC_CD_SHIFT)); +} + +static void configure_dram_control_mode(const timings_t *const timings, const dimminfo_t *const dimms) +{ + int ch, r; + + FOR_EACH_CHANNEL(ch) { + unsigned int mchbar = CxDRC0_MCHBAR(ch); + u32 cxdrc = MCHBAR32(mchbar); + cxdrc &= ~CxDRC0_RANKEN_MASK; + FOR_EACH_POPULATED_RANK_IN_CHANNEL(dimms, ch, r) + cxdrc |= CxDRC0_RANKEN(r); + cxdrc = (cxdrc & ~CxDRC0_RMS_MASK) | + /* Always 7.8us for DDR3: */ + CxDRC0_RMS_78US; + MCHBAR32(mchbar) = cxdrc; + + mchbar = CxDRC1_MCHBAR(ch); + cxdrc = MCHBAR32(mchbar); + cxdrc |= CxDRC1_NOTPOP_MASK; + FOR_EACH_POPULATED_RANK_IN_CHANNEL(dimms, ch, r) + cxdrc &= ~CxDRC1_NOTPOP(r); + cxdrc |= CxDRC1_MUSTWR; + MCHBAR32(mchbar) = cxdrc; + + mchbar = CxDRC2_MCHBAR(ch); + cxdrc = MCHBAR32(mchbar); + cxdrc |= CxDRC2_NOTPOP_MASK; + FOR_EACH_POPULATED_RANK_IN_CHANNEL(dimms, ch, r) + cxdrc &= ~CxDRC2_NOTPOP(r); + cxdrc |= CxDRC2_MUSTWR; + if (timings->mem_clock == MEM_CLOCK_1067MT) + cxdrc |= CxDRC2_CLK1067MT; + MCHBAR32(mchbar) = cxdrc; + } +} + +static void rcomp_initialization(const stepping_t stepping, const int sff) +{ + /* Programm RCOMP codes. */ + if (sff) + die("SFF platform unsupported in RCOMP initialization.\n"); + /* Values are for DDR3. */ + MCHBAR8(0x6ac) &= ~0x0f; + MCHBAR8(0x6b0) = 0x55; + MCHBAR8(0x6ec) &= ~0x0f; + MCHBAR8(0x6f0) = 0x66; + MCHBAR8(0x72c) &= ~0x0f; + MCHBAR8(0x730) = 0x66; + MCHBAR8(0x76c) &= ~0x0f; + MCHBAR8(0x770) = 0x66; + MCHBAR8(0x7ac) &= ~0x0f; + MCHBAR8(0x7b0) = 0x66; + MCHBAR8(0x7ec) &= ~0x0f; + MCHBAR8(0x7f0) = 0x66; + MCHBAR8(0x86c) &= ~0x0f; + MCHBAR8(0x870) = 0x55; + MCHBAR8(0x8ac) &= ~0x0f; + MCHBAR8(0x8b0) = 0x66; + /* ODT multiplier bits. */ + MCHBAR32(0x04d0) = (MCHBAR32(0x04d0) & ~((7 << 3) | (7 << 0))) | (2 << 3) | (2 << 0); + + /* Perform RCOMP calibration for DDR3. */ + raminit_rcomp_calibration(stepping); + + /* Run initial RCOMP. */ + MCHBAR32(0x418) |= 1 << 17; + MCHBAR32(0x40c) &= ~(1 << 23); + MCHBAR32(0x41c) &= ~((1 << 7) | (1 << 3)); + MCHBAR32(0x400) |= 1; + while (MCHBAR32(0x400) & 1) {} + + /* Run second RCOMP. */ + MCHBAR32(0x40c) |= 1 << 19; + MCHBAR32(0x400) |= 1; + while (MCHBAR32(0x400) & 1) {} + + /* Cleanup and start periodic RCOMP. */ + MCHBAR32(0x40c) &= ~(1 << 19); + MCHBAR32(0x40c) |= 1 << 23; + MCHBAR32(0x418) &= ~(1 << 17); + MCHBAR32(0x41c) |= (1 << 7) | (1 << 3); + MCHBAR32(0x400) |= (1 << 1); +} + +static void dram_powerup(const int resume) +{ + udelay_from_reset(200); + MCHBAR32(CLKCFG_MCHBAR) = (MCHBAR32(CLKCFG_MCHBAR) & ~(1 << 3)) | (3 << 21); + if (!resume) { + MCHBAR32(0x1434) |= (1 << 10); + ns100delay(2); + } + MCHBAR32(0x1434) |= (1 << 6); + if (!resume) { + ns100delay(1); + MCHBAR32(0x1434) |= (1 << 9); + MCHBAR32(0x1434) &= ~(1 << 10); + udelay(500); + } +} +static void dram_program_timings(const timings_t *const timings) +{ + /* Values are for DDR3. */ + const int burst_length = 8; + const int tWTR = 4, tRTP = 1; + int i; + + FOR_EACH_CHANNEL(i) { + u32 reg = MCHBAR32(CxDRT0_MCHBAR(i)); + const int btb_wtp = timings->tWL + burst_length/2 + timings->tWR; + const int btb_wtr = timings->tWL + burst_length/2 + tWTR; + reg = (reg & ~(CxDRT0_BtB_WtP_MASK | CxDRT0_BtB_WtR_MASK)) | + ((btb_wtp << CxDRT0_BtB_WtP_SHIFT) & CxDRT0_BtB_WtP_MASK) | + ((btb_wtr << CxDRT0_BtB_WtR_SHIFT) & CxDRT0_BtB_WtR_MASK); + if (timings->mem_clock != MEM_CLOCK_1067MT) { + reg = (reg & ~(0x7 << 15)) | ((9 - timings->CAS) << 15); + reg = (reg & ~(0xf << 10)) | ((timings->CAS - 3) << 10); + } else { + reg = (reg & ~(0x7 << 15)) | ((10 - timings->CAS) << 15); + reg = (reg & ~(0xf << 10)) | ((timings->CAS - 4) << 10); + } + reg = (reg & ~(0x7 << 5)) | (3 << 5); + reg = (reg & ~(0x7 << 0)) | (1 << 0); + MCHBAR32(CxDRT0_MCHBAR(i)) = reg; + + reg = MCHBAR32(CxDRT1_MCHBAR(i)); + reg = (reg & ~(0x03 << 28)) | ((tRTP & 0x03) << 28); + reg = (reg & ~(0x1f << 21)) | ((timings->tRAS & 0x1f) << 21); + reg = (reg & ~(0x07 << 10)) | (((timings->tRRD - 2) & 0x07) << 10); + reg = (reg & ~(0x07 << 5)) | (((timings->tRCD - 2) & 0x07) << 5); + reg = (reg & ~(0x07 << 0)) | (((timings->tRP - 2) & 0x07) << 0); + MCHBAR32(CxDRT1_MCHBAR(i)) = reg; + + reg = MCHBAR32(CxDRT2_MCHBAR(i)); + reg = (reg & ~(0x1f << 17)) | ((timings->tFAW & 0x1f) << 17); + if (timings->mem_clock != MEM_CLOCK_1067MT) { + reg = (reg & ~(0x7 << 12)) | (0x2 << 12); + reg = (reg & ~(0xf << 6)) | (0x9 << 6); + } else { + reg = (reg & ~(0x7 << 12)) | (0x3 << 12); + reg = (reg & ~(0xf << 6)) | (0xc << 6); + } + reg = (reg & ~(0x1f << 0)) | (0x13 << 0); + MCHBAR32(CxDRT2_MCHBAR(i)) = reg; + + reg = MCHBAR32(CxDRT3_MCHBAR(i)); + reg |= 0x3 << 28; + reg = (reg & ~(0x03 << 26)); + reg = (reg & ~(0x07 << 23)) | (((timings->CAS - 3) & 0x07) << 23); + reg = (reg & ~(0xff << 13)) | ((timings->tRFC & 0xff) << 13); + reg = (reg & ~(0x07 << 0)) | (((timings->tWL - 2) & 0x07) << 0); + MCHBAR32(CxDRT3_MCHBAR(i)) = reg; + + reg = MCHBAR32(CxDRT4_MCHBAR(i)); + static const u8 timings_by_clock[4][3] = { + /* 333MHz 400MHz 533MHz + 667MT 800MT 1067MT */ + { 0x07, 0x0a, 0x0d }, + { 0x3a, 0x46, 0x5d }, + { 0x0c, 0x0e, 0x18 }, + { 0x21, 0x28, 0x35 }, + }; + const int clk_idx = 2 - timings->mem_clock; + reg = (reg & ~(0x01f << 27)) | (timings_by_clock[0][clk_idx] << 27); + reg = (reg & ~(0x3ff << 17)) | (timings_by_clock[1][clk_idx] << 17); + reg = (reg & ~(0x03f << 10)) | (timings_by_clock[2][clk_idx] << 10); + reg = (reg & ~(0x1ff << 0)) | (timings_by_clock[3][clk_idx] << 0); + MCHBAR32(CxDRT4_MCHBAR(i)) = reg; + + reg = MCHBAR32(CxDRT5_MCHBAR(i)); + if (timings->mem_clock == MEM_CLOCK_1067MT) + reg = (reg & ~(0xf << 28)) | (0x8 << 28); + reg = (reg & ~(0x00f << 22)) | ((burst_length/2 + timings->CAS + 2) << 22); + reg = (reg & ~(0x3ff << 12)) | (0x190 << 12); + reg = (reg & ~(0x00f << 4)) | ((timings->CAS - 2) << 4); + reg = (reg & ~(0x003 << 2)) | (0x001 << 2); + reg = (reg & ~(0x003 << 0)); + MCHBAR32(CxDRT5_MCHBAR(i)) = reg; + + reg = MCHBAR32(CxDRT6_MCHBAR(i)); + reg = (reg & ~(0xffff << 16)) | (0x066a << 16); /* always 7.8us refresh rate for DDR3 */ + reg |= (1 << 2); + MCHBAR32(CxDRT6_MCHBAR(i)) = reg; + } +} + +static void dram_program_banks(const dimminfo_t *const dimms) +{ + int ch, r; + + FOR_EACH_CHANNEL(ch) { + const int tRPALL = dimms[ch].banks == 8; + + u32 reg = MCHBAR32(CxDRT1_MCHBAR(ch)) & ~(0x01 << 15); + IF_CHANNEL_POPULATED(dimms, ch) + reg |= tRPALL << 15; + MCHBAR32(CxDRT1_MCHBAR(ch)) = reg; + + reg = MCHBAR32(CxDRA_MCHBAR(ch)) & ~CxDRA_BANKS_MASK; + FOR_EACH_POPULATED_RANK_IN_CHANNEL(dimms, ch, r) { + reg |= CxDRA_BANKS(r, dimms[ch].banks); + } + MCHBAR32(CxDRA_MCHBAR(ch)) = reg; + } +} + +static void odt_setup(const timings_t *const timings, const int sff) +{ + /* Values are for DDR3. */ + int ch; + + FOR_EACH_CHANNEL(ch) { + u32 reg = MCHBAR32(CxODT_HIGH(ch)); + if (sff && (timings->mem_clock != MEM_CLOCK_1067MT)) + reg &= ~(0x3 << (61 - 32)); + else + reg |= 0x3 << (61 - 32); + reg = (reg & ~(0x3 << (52 - 32))) | (0x2 << (52 - 32)); + reg = (reg & ~(0x7 << (48 - 32))) | ((timings->CAS - 3) << (48 - 32)); + reg = (reg & ~(0xf << (44 - 32))) | (0x7 << (44 - 32)); + if (timings->mem_clock != MEM_CLOCK_1067MT) { + reg = (reg & ~(0xf << (40 - 32))) | ((12 - timings->CAS) << (40 - 32)); + reg = (reg & ~(0xf << (36 - 32))) | (( 2 + timings->CAS) << (36 - 32)); + } else { + reg = (reg & ~(0xf << (40 - 32))) | ((13 - timings->CAS) << (40 - 32)); + reg = (reg & ~(0xf << (36 - 32))) | (( 1 + timings->CAS) << (36 - 32)); + } + reg = (reg & ~(0xf << (32 - 32))) | (0x7 << (32 - 32)); + MCHBAR32(CxODT_HIGH(ch)) = reg; + + reg = MCHBAR32(CxODT_LOW(ch)); + reg = (reg & ~(0x7 << 28)) | (0x2 << 28); + reg = (reg & ~(0x3 << 22)) | (0x2 << 22); + reg = (reg & ~(0x7 << 12)) | (0x2 << 12); + reg = (reg & ~(0x7 << 4)) | (0x2 << 4); + switch (timings->mem_clock) { + case MEM_CLOCK_667MT: + reg = (reg & ~0x7); + break; + case MEM_CLOCK_800MT: + reg = (reg & ~0x7) | 0x2; + break; + case MEM_CLOCK_1067MT: + reg = (reg & ~0x7) | 0x5; + break; + } + MCHBAR32(CxODT_LOW(ch)) = reg; + } +} + +static void misc_settings(const timings_t *const timings, + const stepping_t stepping) +{ + MCHBAR32(0x1260) = (MCHBAR32(0x1260) & ~((1 << 24) | 0x1f)) | timings->tRD; + MCHBAR32(0x1360) = (MCHBAR32(0x1360) & ~((1 << 24) | 0x1f)) | timings->tRD; + + MCHBAR8(0x1268) = (MCHBAR8(0x1268) & ~(0xf)) | timings->tWL; + MCHBAR8(0x1368) = (MCHBAR8(0x1368) & ~(0xf)) | timings->tWL; + MCHBAR8(0x12a0) = (MCHBAR8(0x12a0) & ~(0xf)) | 0xa; + MCHBAR8(0x13a0) = (MCHBAR8(0x13a0) & ~(0xf)) | 0xa; + + MCHBAR32(0x218) = (MCHBAR32(0x218) & ~((7 << 29) | (7 << 25) | (3 << 22) | (3 << 10))) | + (4 << 29) | (3 << 25) | (0 << 22) | (1 << 10); + MCHBAR32(0x220) = (MCHBAR32(0x220) & ~(7 << 16)) | (1 << 21) | (1 << 16); + MCHBAR32(0x224) = (MCHBAR32(0x224) & ~(7 << 8)) | (3 << 8); + if (stepping >= STEPPING_B1) + MCHBAR8(0x234) |= (1 << 3); +} + +static void clock_crossing_setup(const fsb_clock_t fsb, + const mem_clock_t ddr3clock, + const dimminfo_t *const dimms) +{ + int ch; + + static const u32 values_from_fsb_and_mem[][3][4] = { + /* FSB 1067MHz */{ + /* DDR3-1067 */ { 0x00000000, 0x00000000, 0x00180006, 0x00810060 }, + /* DDR3-800 */ { 0x00000000, 0x00000000, 0x0000001c, 0x000300e0 }, + /* DDR3-667 */ { 0x00000000, 0x00001c00, 0x03c00038, 0x0007e000 }, + }, + /* FSB 800MHz */{ + /* DDR3-1067 */ { 0, 0, 0, 0 }, + /* DDR3-800 */ { 0x00000000, 0x00000000, 0x0030000c, 0x000300c0 }, + /* DDR3-667 */ { 0x00000000, 0x00000380, 0x0060001c, 0x00030c00 }, + }, + /* FSB 667MHz */{ + /* DDR3-1067 */ { 0, 0, 0, 0 }, + /* DDR3-800 */ { 0, 0, 0, 0 }, + /* DDR3-667 */ { 0x00000000, 0x00000000, 0x0030000c, 0x000300c0 }, + }, + }; + + const u32 *data = values_from_fsb_and_mem[fsb][ddr3clock]; + MCHBAR32(0x0208) = data[3]; + MCHBAR32(0x020c) = data[2]; + if (((fsb == FSB_CLOCK_1067MHz) || (fsb == FSB_CLOCK_800MHz)) && (ddr3clock == MEM_CLOCK_667MT)) + MCHBAR32(0x0210) = data[1]; + + static const u32 from_fsb_and_mem[][3] = { + /* DDR3-1067 DDR3-800 DDR3-667 */ + /* FSB 1067MHz */{ 0x40100401, 0x10040220, 0x08040110, }, + /* FSB 800MHz */{ 0x00000000, 0x40100401, 0x00080201, }, + /* FSB 667MHz */{ 0x00000000, 0x00000000, 0x40100401, }, + }; + FOR_EACH_CHANNEL(ch) { + const unsigned int mchbar = 0x1258 + (ch * 0x0100); + if ((fsb == FSB_CLOCK_1067MHz) && (ddr3clock == MEM_CLOCK_800MT) && CHANNEL_IS_CARDF(dimms, ch)) + MCHBAR32(mchbar) = 0x08040120; + else + MCHBAR32(mchbar) = from_fsb_and_mem[fsb][ddr3clock]; + MCHBAR32(mchbar + 4) = 0x00000000; + } +} + +/* Program egress VC1 timings. */ +static void vc1_program_timings(const fsb_clock_t fsb) +{ + const u32 timings_by_fsb[][2] = { + /* FSB 1067MHz */ { 0x1a, 0x01380138 }, + /* FSB 800MHz */ { 0x14, 0x00f000f0 }, + /* FSB 667MHz */ { 0x10, 0x00c000c0 }, + }; + EPBAR8(0x2c) = timings_by_fsb[fsb][0]; + EPBAR32(0x38) = timings_by_fsb[fsb][1]; + EPBAR32(0x3c) = timings_by_fsb[fsb][1]; +} + +/* @prejedec if not zero, set rank size to 128MB and page size to 4KB. */ +static void program_memory_map(const dimminfo_t *const dimms, const channel_mode_t mode, const int prejedec) +{ + int ch, r; + + /* Program rank boundaries (CxDRBy). */ + unsigned int base = 0; /* start of next rank in MB */ + unsigned int total_mb[2] = { 0, 0 }; /* total memory per channel in MB */ + FOR_EACH_CHANNEL(ch) { + if (mode == CHANNEL_MODE_DUAL_INTERLEAVED) + /* In interleaved mode, start every channel from 0. */ + base = 0; + for (r = 0; r < RANKS_PER_CHANNEL; r += 2) { + /* Fixed capacity for pre-jedec config. */ + const unsigned int rank_capacity_mb = + prejedec ? 128 : dimms[ch].rank_capacity_mb; + u32 reg = 0; + + /* Program bounds in CxDRBy. */ + IF_RANK_POPULATED(dimms, ch, r) { + base += rank_capacity_mb; + total_mb[ch] += rank_capacity_mb; + } + reg |= CxDRBy_BOUND_MB(r, base); + IF_RANK_POPULATED(dimms, ch, r+1) { + base += rank_capacity_mb; + total_mb[ch] += rank_capacity_mb; + } + reg |= CxDRBy_BOUND_MB(r+1, base); + + MCHBAR32(CxDRBy_MCHBAR(ch, r)) = reg; + } + } + + /* Program page size (CxDRA). */ + FOR_EACH_CHANNEL(ch) { + u32 reg = MCHBAR32(CxDRA_MCHBAR(ch)) & ~CxDRA_PAGESIZE_MASK; + FOR_EACH_POPULATED_RANK_IN_CHANNEL(dimms, ch, r) { + /* Fixed page size for pre-jedec config. */ + const unsigned int page_size = /* dimm page size in bytes */ + prejedec ? 4096 : dimms[ch].page_size; + reg |= CxDRA_PAGESIZE(r, log2(page_size)); + /* deferred to f5_27: reg |= CxDRA_BANKS(r, dimms[ch].banks); */ + } + MCHBAR32(CxDRA_MCHBAR(ch)) = reg; + } + + /* Calculate memory mapping, all values in MB. */ + const unsigned int MMIOstart = 0x0c00; /* 3GB, makes MTRR configuration small. */ + const unsigned int ME_SIZE = 0; + const unsigned int usedMEsize = (total_mb[0] != total_mb[1]) ? ME_SIZE : 2 * ME_SIZE; + const unsigned int claimCapable = + !(pci_read_config32(PCI_DEV(0, 0, 0), D0F0_CAPID0 + 4) & (1 << (47 - 32))); + + const unsigned int TOM = total_mb[0] + total_mb[1]; + unsigned int TOMminusME = TOM - usedMEsize; + unsigned int TOLUD = (TOMminusME < MMIOstart) ? TOMminusME : MMIOstart; + unsigned int TOUUD = TOMminusME; + unsigned int REMAPbase = 0xffff, REMAPlimit = 0; + + if (claimCapable && (TOMminusME >= (MMIOstart + 64))) { + /* 64MB alignment: We'll lose some MBs here, if ME is on. */ + TOMminusME &= ~(64 - 1); + /* 64MB alignment: Loss will be reclaimed. */ + TOLUD &= ~(64 - 1); + if (TOMminusME > 4096) { + REMAPbase = TOMminusME; + REMAPlimit = REMAPbase + (4096 - TOLUD); + } else { + REMAPbase = 4096; + REMAPlimit = REMAPbase + (TOMminusME - TOLUD); + } + TOUUD = REMAPlimit; + /* REMAPlimit is an inclusive bound, all others exclusive. */ + REMAPlimit -= 64; + } + + pci_write_config16(PCI_DEV(0, 0, 0), D0F0_TOM, (TOM >> 7) & 0x1ff); + pci_write_config16(PCI_DEV(0, 0, 0), D0F0_TOLUD, TOLUD << 4); + pci_write_config16(PCI_DEV(0, 0, 0), D0F0_TOUUD, TOUUD); + pci_write_config16(PCI_DEV(0, 0, 0), D0F0_REMAPBASE, (REMAPbase >> 6) & 0x03ff); + pci_write_config16(PCI_DEV(0, 0, 0), D0F0_REMAPLIMIT, (REMAPlimit >> 6) & 0x03ff); + + /* Program channel mode. */ + switch (mode) { + case CHANNEL_MODE_SINGLE: + printk(BIOS_DEBUG, "Memory configured in single-channel mode.\n"); + MCHBAR32(DCC_MCHBAR) &= ~DCC_INTERLEAVED; + break; + case CHANNEL_MODE_DUAL_ASYNC: + printk(BIOS_DEBUG, "Memory configured in dual-channel assymetric mode.\n"); + MCHBAR32(DCC_MCHBAR) &= ~DCC_INTERLEAVED; + break; + case CHANNEL_MODE_DUAL_INTERLEAVED: + printk(BIOS_DEBUG, "Memory configured in dual-channel interleaved mode.\n"); + MCHBAR32(DCC_MCHBAR) &= ~(DCC_NO_CHANXOR | (1 << 9)); + MCHBAR32(DCC_MCHBAR) |= DCC_INTERLEAVED; + break; + } + + printk(BIOS_SPEW, "Memory map:\n" + "TOM = %5uMB\n" + "TOLUD = %5uMB\n" + "TOUUD = %5uMB\n" + "REMAP:\t base = %5uMB\n" + "\t limit = %5uMB\n", + TOM, TOLUD, TOUUD, REMAPbase, REMAPlimit); +} +static void prejedec_memory_map(const dimminfo_t *const dimms, channel_mode_t mode) +{ + /* Never use dual-interleaved mode in pre-jedec config. */ + if (CHANNEL_MODE_DUAL_INTERLEAVED == mode) + mode = CHANNEL_MODE_DUAL_ASYNC; + + program_memory_map(dimms, mode, 1); + MCHBAR32(DCC_MCHBAR) |= DCC_NO_CHANXOR; +} + +static void ddr3_select_clock_mux(const mem_clock_t ddr3clock, + const dimminfo_t *const dimms, + const stepping_t stepping) +{ + const int clk1067 = (ddr3clock == MEM_CLOCK_1067MT); + const int cardF[] = { CHANNEL_IS_CARDF(dimms, 0), CHANNEL_IS_CARDF(dimms, 1) }; + + int ch; + + if (stepping < STEPPING_B1) + die("Stepping <B1 unsupported in clock-multiplexer selection.\n"); + + FOR_EACH_POPULATED_CHANNEL(dimms, ch) { + int mixed = 0; + if ((1 == ch) && (!CHANNEL_IS_POPULATED(dimms, 0) || (cardF[0] != cardF[1]))) + mixed = 4 << 11; + const unsigned int b = 0x14b0 + (ch * 0x0100); + MCHBAR32(b+0x1c) = (MCHBAR32(b+0x1c) & ~(7 << 11)) | + ((( cardF[ch])?1:0) << 11) | mixed; + MCHBAR32(b+0x18) = (MCHBAR32(b+0x18) & ~(7 << 11)) | mixed; + MCHBAR32(b+0x14) = (MCHBAR32(b+0x14) & ~(7 << 11)) | + (((!clk1067 && !cardF[ch])?0:1) << 11) | mixed; + MCHBAR32(b+0x10) = (MCHBAR32(b+0x10) & ~(7 << 11)) | + ((( clk1067 && !cardF[ch])?1:0) << 11) | mixed; + MCHBAR32(b+0x0c) = (MCHBAR32(b+0x0c) & ~(7 << 11)) | + ((( cardF[ch])?3:2) << 11) | mixed; + MCHBAR32(b+0x08) = (MCHBAR32(b+0x08) & ~(7 << 11)) | + (2 << 11) | mixed; + MCHBAR32(b+0x04) = (MCHBAR32(b+0x04) & ~(7 << 11)) | + (((!clk1067 && !cardF[ch])?2:3) << 11) | mixed; + MCHBAR32(b+0x00) = (MCHBAR32(b+0x00) & ~(7 << 11)) | + ((( clk1067 && !cardF[ch])?3:2) << 11) | mixed; + } +} +static void ddr3_write_io_init(const mem_clock_t ddr3clock, + const dimminfo_t *const dimms, + const stepping_t stepping, + const int sff) +{ + const int a1step = stepping >= STEPPING_CONVERSION_A1; + const int cardF[] = { CHANNEL_IS_CARDF(dimms, 0), CHANNEL_IS_CARDF(dimms, 1) }; + + int ch; + + if (stepping < STEPPING_B1) + die("Stepping <B1 unsupported in write i/o initialization.\n"); + if (sff) + die("SFF platform unsupported in write i/o initialization.\n"); + + static const u32 ddr3_667_800_by_stepping_ddr3_and_card[][2][2][4] = { + { /* Stepping B3 and below */ + { /* 667 MHz */ + { 0xa3255008, 0x26888209, 0x26288208, 0x6188040f }, + { 0x7524240b, 0xa5255608, 0x232b8508, 0x5528040f }, + }, + { /* 800 MHz */ + { 0xa6255308, 0x26888209, 0x212b7508, 0x6188040f }, + { 0x7524240b, 0xa6255708, 0x132b7508, 0x5528040f }, + }, + }, + { /* Conversion stepping A1 and above */ + { /* 667 MHz */ + { 0xc5257208, 0x26888209, 0x26288208, 0x6188040f }, + { 0x7524240b, 0xc5257608, 0x232b8508, 0x5528040f }, + }, + { /* 800 MHz */ + { 0xb6256308, 0x26888209, 0x212b7508, 0x6188040f }, + { 0x7524240b, 0xb6256708, 0x132b7508, 0x5528040f }, + } + }}; + + static const u32 ddr3_1067_by_channel_and_card[][2][4] = { + { /* Channel A */ + { 0xb2254708, 0x002b7408, 0x132b8008, 0x7228060f }, + { 0xb0255008, 0xa4254108, 0x4528b409, 0x9428230f }, + }, + { /* Channel B */ + { 0xa4254208, 0x022b6108, 0x132b8208, 0x9228210f }, + { 0x6024140b, 0x92244408, 0x252ba409, 0x9328360c }, + }, + }; + + FOR_EACH_POPULATED_CHANNEL(dimms, ch) { + if ((1 == ch) && CHANNEL_IS_POPULATED(dimms, 0) && (cardF[0] == cardF[1])) + /* Only write if second channel population differs. */ + continue; + const u32 *const data = (ddr3clock != MEM_CLOCK_1067MT) + ? ddr3_667_800_by_stepping_ddr3_and_card[a1step][2 - ddr3clock][cardF[ch]] + : ddr3_1067_by_channel_and_card[ch][cardF[ch]]; + MCHBAR32(CxWRTy_MCHBAR(ch, 0)) = data[0]; + MCHBAR32(CxWRTy_MCHBAR(ch, 1)) = data[1]; + MCHBAR32(CxWRTy_MCHBAR(ch, 2)) = data[2]; + MCHBAR32(CxWRTy_MCHBAR(ch, 3)) = data[3]; + } + + MCHBAR32(0x1490) = 0x00e70067; + MCHBAR32(0x1494) = 0x000d8000; + MCHBAR32(0x1590) = 0x00e70067; + MCHBAR32(0x1594) = 0x000d8000; +} +static void ddr3_read_io_init(const mem_clock_t ddr3clock, + const dimminfo_t *const dimms, + const int sff) +{ + int ch; + + FOR_EACH_POPULATED_CHANNEL(dimms, ch) { + u32 addr, tmp; + const unsigned int base = 0x14b0 + (ch * 0x0100); + for (addr = base + 0x1c; addr >= base; addr -= 4) { + tmp = MCHBAR32(addr); + tmp &= ~((3 << 25) | (1 << 8) | (7 << 16) | (0xf << 20) | (1 << 27)); + tmp |= (1 << 27); + switch (ddr3clock) { + case MEM_CLOCK_667MT: + tmp |= (1 << 16) | (4 << 20); + break; + case MEM_CLOCK_800MT: + tmp |= (2 << 16) | (3 << 20); + break; + case MEM_CLOCK_1067MT: + if (!sff) + tmp |= (2 << 16) | (1 << 20); + else + tmp |= (2 << 16) | (2 << 20); + break; + default: + die("Wrong clock"); + } + MCHBAR32(addr) = tmp; + } + } +} + +static void memory_io_init(const mem_clock_t ddr3clock, + const dimminfo_t *const dimms, + const stepping_t stepping, + const int sff) +{ + u32 tmp; + + if (stepping < STEPPING_B1) + die("Stepping <B1 unsupported in " + "system-memory i/o initialization.\n"); + + tmp = MCHBAR32(0x1400); + tmp &= ~(3<<13); + tmp |= (1<<9) | (1<<13); + MCHBAR32(0x1400) = tmp; + + tmp = MCHBAR32(0x140c); + tmp &= ~(0xff | (1<<11) | (1<<12) | + (1<<16) | (1<<18) | (1<<27) | (0xf<<28)); + tmp |= (1<<7) | (1<<11) | (1<<16); + switch (ddr3clock) { + case MEM_CLOCK_667MT: + tmp |= 9 << 28; + break; + case MEM_CLOCK_800MT: + tmp |= 7 << 28; + break; + case MEM_CLOCK_1067MT: + tmp |= 8 << 28; + break; + } + MCHBAR32(0x140c) = tmp; + + MCHBAR32(0x1440) &= ~1; + + tmp = MCHBAR32(0x1414); + tmp &= ~((1<<20) | (7<<11) | (0xf << 24) | (0xf << 16)); + tmp |= (3<<11); + switch (ddr3clock) { + case MEM_CLOCK_667MT: + tmp |= (2 << 24) | (10 << 16); + break; + case MEM_CLOCK_800MT: + tmp |= (3 << 24) | (7 << 16); + break; + case MEM_CLOCK_1067MT: + tmp |= (4 << 24) | (4 << 16); + break; + } + MCHBAR32(0x1414) = tmp; + + MCHBAR32(0x1418) &= ~((1<<3) | (1<<11) | (1<<19) | (1<<27)); + + MCHBAR32(0x141c) &= ~((1<<3) | (1<<11) | (1<<19) | (1<<27)); + + MCHBAR32(0x1428) |= 1<<14; + + tmp = MCHBAR32(0x142c); + tmp &= ~((0xf << 8) | (0x7 << 20) | 0xf | (0xf << 24)); + tmp |= (0x3 << 20) | (5 << 24); + switch (ddr3clock) { + case MEM_CLOCK_667MT: + tmp |= (2 << 8) | 0xc; + break; + case MEM_CLOCK_800MT: + tmp |= (3 << 8) | 0xa; + break; + case MEM_CLOCK_1067MT: + tmp |= (4 << 8) | 0x7; + break; + } + MCHBAR32(0x142c) = tmp; + + tmp = MCHBAR32(0x400); + tmp &= ~((3 << 4) | (3 << 16) | (3 << 30)); + tmp |= (2 << 4) | (2 << 16); + MCHBAR32(0x400) = tmp; + + MCHBAR32(0x404) &= ~(0xf << 20); + + MCHBAR32(0x40c) &= ~(1 << 6); + + tmp = MCHBAR32(0x410); + tmp &= ~(7 << 28); + tmp |= 2 << 28; + MCHBAR32(0x410) = tmp; + + tmp = MCHBAR32(0x41c); + tmp &= ~0x77; + tmp |= 0x11; + MCHBAR32(0x41c) = tmp; + + ddr3_select_clock_mux(ddr3clock, dimms, stepping); + + ddr3_write_io_init(ddr3clock, dimms, stepping, sff); + + ddr3_read_io_init(ddr3clock, dimms, sff); +} + +static void jedec_init(const timings_t *const timings, + const dimminfo_t *const dimms) +{ + if ((timings->tWR < 5) || (timings->tWR > 12)) + die("tWR value unsupported in Jedec initialization.\n"); + + /* Pre-jedec settings */ + MCHBAR32(0x40) |= (1 << 1); + MCHBAR32(0x230) |= (3 << 1); + MCHBAR32(0x238) |= (3 << 24); + MCHBAR32(0x23c) |= (3 << 24); + + /* Normal write pointer operation */ + MCHBAR32(0x14f0) |= (1 << 9); + MCHBAR32(0x15f0) |= (1 << 9); + + MCHBAR32(DCC_MCHBAR) = (MCHBAR32(DCC_MCHBAR) & ~DCC_CMD_MASK) | DCC_CMD_NOP; + + u8 reg8 = pci_read_config8(PCI_DEV(0, 0, 0), 0xf0); + pci_write_config8(PCI_DEV(0, 0, 0), 0xf0, reg8 & ~(1 << 2)); + reg8 = pci_read_config8(PCI_DEV(0, 0, 0), 0xf0); + pci_write_config8(PCI_DEV(0, 0, 0), 0xf0, reg8 | (1 << 2)); + udelay(2); + + /* 5 6 7 8 9 10 11 12 */ + static const u8 wr_lut[] = { 1, 2, 3, 4, 5, 5, 6, 6 }; + + const int WL = ((timings->tWL - 5) & 7) << 6; + const int ODT_120OHMS = (1 << 9); + const int ODS_34OHMS = (1 << 4); + const int WR = (wr_lut[timings->tWR - 5] & 7) << 12; + const int DLL1 = 1 << 11; + const int CAS = ((timings->CAS - 4) & 7) << 7; + const int INTERLEAVED = 1 << 6;/* This is READ Burst Type == interleaved. */ + + int ch, r; + FOR_EACH_POPULATED_RANK(dimms, ch, r) { + /* We won't do this in dual-interleaved mode, + so don't care about the offset. */ + const u32 rankaddr = raminit_get_rank_addr(ch, r); + printk(BIOS_DEBUG, "Performing Jedec initialization at address 0x%08x.\n", rankaddr); + MCHBAR32(DCC_MCHBAR) = (MCHBAR32(DCC_MCHBAR) & ~DCC_SET_EREG_MASK) | DCC_SET_EREGx(2); + read32(rankaddr | WL); + MCHBAR32(DCC_MCHBAR) = (MCHBAR32(DCC_MCHBAR) & ~DCC_SET_EREG_MASK) | DCC_SET_EREGx(3); + read32(rankaddr); + MCHBAR32(DCC_MCHBAR) = (MCHBAR32(DCC_MCHBAR) & ~DCC_SET_EREG_MASK) | DCC_SET_EREGx(1); + read32(rankaddr | ODT_120OHMS | ODS_34OHMS); + MCHBAR32(DCC_MCHBAR) = (MCHBAR32(DCC_MCHBAR) & ~DCC_CMD_MASK) | DCC_SET_MREG; + read32(rankaddr | WR | DLL1 | CAS | INTERLEAVED); + MCHBAR32(DCC_MCHBAR) = (MCHBAR32(DCC_MCHBAR) & ~DCC_CMD_MASK) | DCC_SET_MREG; + read32(rankaddr | WR | CAS | INTERLEAVED); + } +} + +static void ddr3_calibrate_zq(void) { + udelay(2); + + u32 tmp = MCHBAR32(DCC_MCHBAR); + tmp &= ~(7 << 16); + tmp |= (5 << 16); /* ZQ calibration mode */ + MCHBAR32(DCC_MCHBAR) = tmp; + + MCHBAR32(CxDRT6_MCHBAR(0)) |= (1 << 3); + MCHBAR32(CxDRT6_MCHBAR(1)) |= (1 << 3); + + udelay(1); + + MCHBAR32(CxDRT6_MCHBAR(0)) &= ~(1 << 3); + MCHBAR32(CxDRT6_MCHBAR(1)) &= ~(1 << 3); + + MCHBAR32(DCC_MCHBAR) |= (7 << 16); /* Normal operation */ +} + +static void post_jedec_sequence(const int cores) { + const int quadcore = cores == 4; + + MCHBAR32(0x0040) &= ~(1 << 1); + MCHBAR32(0x0230) &= ~(3 << 1); + MCHBAR32(0x0230) |= 1 << 15; + MCHBAR32(0x0230) &= ~(1 << 19); + MCHBAR32(0x1250) = 0x6c4; + MCHBAR32(0x1350) = 0x6c4; + MCHBAR32(0x1254) = 0x871a066d; + MCHBAR32(0x1354) = 0x871a066d; + MCHBAR32(0x0238) |= 1 << 26; + MCHBAR32(0x0238) &= ~(3 << 24); + MCHBAR32(0x0238) |= 1 << 23; + MCHBAR32(0x0238) = (MCHBAR32(0x238) & ~(7 << 20)) | (3 << 20); + MCHBAR32(0x0238) = (MCHBAR32(0x238) & ~(7 << 17)) | (6 << 17); + MCHBAR32(0x0238) = (MCHBAR32(0x238) & ~(7 << 14)) | (6 << 14); + MCHBAR32(0x0238) = (MCHBAR32(0x238) & ~(7 << 11)) | (6 << 11); + MCHBAR32(0x0238) = (MCHBAR32(0x238) & ~(7 << 8)) | (6 << 8); + MCHBAR32(0x023c) &= ~(3 << 24); + MCHBAR32(0x023c) &= ~(1 << 23); + MCHBAR32(0x023c) = (MCHBAR32(0x23c) & ~(7 << 20)) | (3 << 20); + MCHBAR32(0x023c) = (MCHBAR32(0x23c) & ~(7 << 17)) | (6 << 17); + MCHBAR32(0x023c) = (MCHBAR32(0x23c) & ~(7 << 14)) | (6 << 14); + MCHBAR32(0x023c) = (MCHBAR32(0x23c) & ~(7 << 11)) | (6 << 11); + MCHBAR32(0x023c) = (MCHBAR32(0x23c) & ~(7 << 8)) | (6 << 8); + + if (quadcore) { + MCHBAR32(0xb14) |= (0xbfbf << 16); + } +} + +static void dram_optimizations(const timings_t *const timings, + const dimminfo_t *const dimms) +{ + int ch; + + FOR_EACH_POPULATED_CHANNEL(dimms, ch) { + const unsigned int mchbar = CxDRC1_MCHBAR(ch); + u32 cxdrc1 = MCHBAR32(mchbar); + cxdrc1 &= ~CxDRC1_SSDS_MASK; + if (dimms[ch].ranks == 1) + cxdrc1 |= CxDRC1_SS; + else + cxdrc1 |= CxDRC1_DS; + MCHBAR32(mchbar) = cxdrc1; + } +} + +u32 raminit_get_rank_addr(unsigned int channel, unsigned int rank) +{ + if (!channel && !rank) + return 0; /* Address of first rank */ + + /* Read the bound of the previous rank. */ + if (rank > 0) { + rank--; + } else { + rank = 3; /* Highest rank per channel */ + channel--; + } + const u32 reg = MCHBAR32(CxDRBy_MCHBAR(channel, rank)); + /* Bound is in 32MB. */ + return ((reg & CxDRBy_BOUND_MASK(rank)) >> CxDRBy_BOUND_SHIFT(rank)) << 25; +} + +void raminit_reset_readwrite_pointers(void) { + MCHBAR32(0x1234) |= (1 << 6); + MCHBAR32(0x1234) &= ~(1 << 6); + MCHBAR32(0x1334) |= (1 << 6); + MCHBAR32(0x1334) &= ~(1 << 6); + MCHBAR32(0x14f0) &= ~(1 << 9); + MCHBAR32(0x14f0) |= (1 << 9); + MCHBAR32(0x14f0) |= (1 << 10); + MCHBAR32(0x15f0) &= ~(1 << 9); + MCHBAR32(0x15f0) |= (1 << 9); + MCHBAR32(0x15f0) |= (1 << 10); +} + +void raminit(sysinfo_t *const sysinfo, const int s3resume) +{ + const dimminfo_t *const dimms = sysinfo->dimms; + const timings_t *const timings = &sysinfo->selected_timings; + const int sff = sysinfo->gfx_type == GMCH_GS45; + + int ch; + u8 reg8; + + + /* Wait for some bit, maybe TXT clear. */ + if (sysinfo->txt_enabled) { + while (!(read8(0xfed40000) & (1 << 7))) {} + } + + /* Enable SMBUS. */ + enable_smbus(); + + /* Collect information about DIMMs and find common settings. */ + collect_dimm_config(sysinfo); + + /* Check for bad warm boot. */ + reset_on_bad_warmboot(); + + + /***** From now on, program according to collected infos: *****/ + + /* Program DRAM type. */ + switch (sysinfo->spd_type) { + case DDR2: + MCHBAR8(0x1434) |= (1 << 7); + break; + case DDR3: + MCHBAR8(0x1434) |= (3 << 0); + break; + } + + /* Program system memory frequency. */ + set_system_memory_frequency(timings); + /* Program IGD memory frequency. */ + set_igd_memory_frequencies(sysinfo); + + /* Configure DRAM control mode for populated channels. */ + configure_dram_control_mode(timings, dimms); + + /* Initialize RCOMP. */ + rcomp_initialization(sysinfo->stepping, sff); + + /* Power-up DRAM. */ + dram_powerup(s3resume); + /* Program DRAM timings. */ + dram_program_timings(timings); + /* Program number of banks. */ + dram_program_banks(dimms); + /* Enable DRAM clock pairs for populated DIMMs. */ + FOR_EACH_POPULATED_CHANNEL(dimms, ch) + MCHBAR32(CxDCLKDIS_MCHBAR(ch)) |= CxDCLKDIS_ENABLE; + + /* Enable On-Die Termination. */ + odt_setup(timings, sff); + /* Miscellaneous settings. */ + misc_settings(timings, sysinfo->stepping); + /* Program clock crossing registers. */ + clock_crossing_setup(timings->fsb_clock, timings->mem_clock, dimms); + /* Program egress VC1 timings. */ + vc1_program_timings(timings->fsb_clock); + /* Perform system-memory i/o initialization. */ + memory_io_init(timings->mem_clock, dimms, sysinfo->stepping, sff); + + /* Initialize memory map with dummy values of 128MB per rank with a + page size of 4KB. This makes the JEDEC initialization code easier. */ + prejedec_memory_map(dimms, timings->channel_mode); + if (!s3resume) + /* Perform JEDEC initialization of DIMMS. */ + jedec_init(timings, dimms); + /* Some programming steps after JEDEC initialization. */ + post_jedec_sequence(sysinfo->cores); + + /* Announce normal operation, initialization completed. */ + MCHBAR32(DCC_MCHBAR) |= (0x7 << 16) | (0x1 << 19); + reg8 = pci_read_config8(PCI_DEV(0, 0, 0), 0xf0); + pci_write_config8(PCI_DEV(0, 0, 0), 0xf0, reg8 | (1 << 2)); + reg8 = pci_read_config8(PCI_DEV(0, 0, 0), 0xf0); + pci_write_config8(PCI_DEV(0, 0, 0), 0xf0, reg8 & ~(1 << 2)); + + + /* Take a breath (the reader). */ + + + /* Perform ZQ calibration for DDR3. */ + ddr3_calibrate_zq(); + + /* Perform receive-enable calibration. */ + raminit_receive_enable_calibration(timings, dimms); + /* Lend clock values from receive-enable calibration. */ + MCHBAR32(0x1224) = (MCHBAR32(0x1224) & ~(0xf0)) | + ((((MCHBAR32(0x121c) >> 7) - 1) & 0xf) << 4); + MCHBAR32(0x1324) = (MCHBAR32(0x1324) & ~(0xf0)) | + ((((MCHBAR32(0x131c) >> 7) - 1) & 0xf) << 4); + + /* Perform read/write training for high clock rate. */ + if (timings->mem_clock == MEM_CLOCK_1067MT) { + raminit_read_training(dimms, s3resume); + raminit_write_training(timings->mem_clock, dimms, s3resume); + } + + /* Program final memory map (with real values). */ + program_memory_map(dimms, timings->channel_mode, 0); + + /* Some last optimizations. */ + dram_optimizations(timings, dimms); + + /* Mark raminit beeing finished. :-) */ + u8 tmp8 = pci_read_config8(PCI_DEV(0, 0x1f, 0), 0xa2) & ~(1 << 7); + pci_write_config8(PCI_DEV(0, 0x1f, 0), 0xa2, tmp8); +} diff --git a/src/northbridge/intel/gm45/raminit_rcomp_calibration.c b/src/northbridge/intel/gm45/raminit_rcomp_calibration.c new file mode 100644 index 0000000000..5a560ac85a --- /dev/null +++ b/src/northbridge/intel/gm45/raminit_rcomp_calibration.c @@ -0,0 +1,252 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * (Written by Nico Huber <nico.huber@secunet.com> for secunet) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdint.h> +#include <delay.h> +#include <console/console.h> +#include "gm45.h" + +static const int ddr3_lookup_schedule[6][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 4, 5 }, { 6, 7 }, { 6, 7 } +}; +/* Look-up table: + * a1step X idx X (group || pull-up/-down) + */ +static const u8 ddr3_lut[2][64][8] = { + { /* Stepping B3 and below. */ + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 8, 8, 3, 3, 3, 3, 5, 7 }, + { 9, 9, 3, 3, 3, 3, 6, 7 }, + { 9, 9, 3, 3, 3, 3, 6, 7 }, + { 10, 10, 3, 3, 3, 3, 7, 8 }, + { 11, 10, 3, 3, 3, 3, 7, 8 }, + { 12, 11, 3, 3, 3, 3, 8, 9 }, + { 13, 11, 3, 3, 3, 3, 9, 9 }, + { 14, 12, 3, 3, 3, 3, 9, 10 }, + { 15, 13, 3, 3, 3, 3, 9, 10 }, + { 16, 14, 3, 3, 3, 3, 9, 11 }, + { 18, 16, 3, 3, 3, 3, 10, 12 }, + { 20, 18, 4, 3, 4, 4, 10, 12 }, + { 22, 22, 4, 4, 4, 4, 11, 12 }, + { 24, 24, 4, 4, 4, 4, 11, 12 }, + { 28, 26, 4, 4, 4, 4, 12, 12 }, + { 32, 28, 5, 4, 5, 5, 12, 12 }, + { 36, 32, 5, 5, 5, 5, 13, 13 }, + { 40, 36, 5, 5, 5, 5, 14, 13 }, + { 43, 40, 5, 5, 5, 5, 15, 14 }, + { 43, 43, 5, 5, 6, 5, 15, 14 }, + { 43, 43, 6, 5, 6, 5, 15, 15 }, + { 43, 43, 6, 5, 6, 6, 15, 15 }, + { 43, 43, 6, 6, 6, 6, 15, 15 }, + { 43, 43, 6, 6, 7, 6, 15, 15 }, + { 43, 43, 7, 6, 7, 6, 15, 15 }, + { 43, 43, 7, 7, 7, 7, 15, 15 }, + { 43, 43, 7, 7, 7, 7, 15, 15 }, + { 43, 43, 7, 7, 7, 7, 15, 15 }, + { 43, 43, 8, 7, 8, 7, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + }, + { /* Stepping A1 and above. */ + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 8, 8, 3, 3, 3, 3, 5, 5 }, + { 9, 9, 3, 3, 3, 3, 6, 6 }, + { 9, 9, 3, 3, 3, 3, 6, 6 }, + { 10, 10, 3, 3, 3, 3, 7, 7 }, + { 10, 10, 3, 3, 3, 3, 7, 7 }, + { 12, 11, 3, 3, 3, 3, 8, 8 }, + { 13, 11, 3, 3, 3, 3, 9, 9 }, + { 14, 12, 3, 3, 3, 3, 9, 9 }, + { 15, 13, 3, 3, 3, 3, 9, 9 }, + { 16, 14, 3, 3, 3, 3, 9, 9 }, + { 18, 16, 3, 3, 3, 3, 10, 10 }, + { 20, 18, 4, 3, 4, 4, 10, 10 }, + { 22, 22, 4, 4, 4, 4, 11, 11 }, + { 24, 24, 4, 4, 4, 4, 11, 11 }, + { 28, 26, 4, 4, 4, 4, 12, 12 }, + { 32, 28, 5, 4, 5, 5, 12, 12 }, + { 36, 32, 5, 5, 5, 5, 13, 13 }, + { 40, 36, 5, 5, 5, 5, 14, 14 }, + { 43, 40, 5, 5, 5, 5, 15, 15 }, + { 43, 43, 5, 5, 6, 5, 15, 15 }, + { 43, 43, 6, 5, 6, 5, 15, 15 }, + { 43, 43, 6, 5, 6, 6, 15, 15 }, + { 43, 43, 6, 6, 6, 6, 15, 15 }, + { 43, 43, 6, 6, 7, 6, 15, 15 }, + { 43, 43, 7, 6, 7, 6, 15, 15 }, + { 43, 43, 7, 7, 7, 7, 15, 15 }, + { 43, 43, 7, 7, 7, 7, 15, 15 }, + { 43, 43, 7, 7, 7, 7, 15, 15 }, + { 43, 43, 8, 7, 8, 7, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + { 43, 43, 8, 8, 8, 8, 15, 15 }, + } +}; +static void lookup_and_write(const int a1step, + const int row, const int col, + unsigned int mchbar) +{ + int i; + + /* Write 4 32-bit registers, 4 values each. */ + for (i = row; i < row + 16; i += 4) { + MCHBAR32(mchbar) = + ((ddr3_lut[a1step][i + 0][col] & 0x3f) << 0) | + ((ddr3_lut[a1step][i + 1][col] & 0x3f) << 8) | + ((ddr3_lut[a1step][i + 2][col] & 0x3f) << 16) | + ((ddr3_lut[a1step][i + 3][col] & 0x3f) << 24); + mchbar += 4; + } +} +void raminit_rcomp_calibration(const stepping_t stepping) { + const int a1step = stepping >= STEPPING_CONVERSION_A1; + + int i; + + enum { + PULL_UP = 0, + PULL_DOWN = 1, + }; + /* channel X group X pull-up/-down */ + char lut_idx[2][6][2]; + for (i = 0; i < 2 * 6 * 2; ++i) + ((char *)lut_idx)[i] = -1; + + MCHBAR32(0x400) |= (1 << 2); + MCHBAR32(0x418) |= (1 << 17); + MCHBAR32(0x40c) &= ~(1 << 23); + MCHBAR32(0x41c) &= ~((1 << 7) | (1 << 3)); + MCHBAR32(0x400) |= 1; + + /* Read lookup indices. */ + for (i = 0; i < 12; ++i) { + do { + MCHBAR32(0x400) |= (1 << 3); + udelay(10); + MCHBAR32(0x400) &= ~(1 << 3); + } while ((MCHBAR32(0x530) & 0x7) != 0x4); + u32 reg = MCHBAR32(0x400); + const unsigned int group = (reg >> 13) & 0x7; + const unsigned int channel = (reg >> 12) & 0x1; + if (group > 5) + break; + reg = MCHBAR32(0x518); + lut_idx[channel][group][PULL_UP] = (reg >> 24) & 0x7f; + lut_idx[channel][group][PULL_DOWN] = (reg >> 16) & 0x7f; + } + /* Cleanup? */ + MCHBAR32(0x400) |= (1 << 3); + udelay(10); + MCHBAR32(0x400) &= ~(1 << 3); + MCHBAR32(0x400) &= ~(1 << 2); + + /* Check for consistency. */ + for (i = 0; i < 2 * 6 * 2; ++i) { + const char idx = ((char *)lut_idx)[i]; + if ((idx < 7) || (idx > 55)) + die("Bad RCOMP calibration lookup index.\n"); + } + + /* Lookup values and fill registers. */ + int channel, group, pu_pd; + unsigned int mchbar = 0x0680; + for (channel = 0; channel < 2; ++channel) { + for (group = 0; group < 6; ++group) { + for (pu_pd = PULL_DOWN; pu_pd >= PULL_UP; --pu_pd) { + lookup_and_write( + a1step, + lut_idx[channel][group][pu_pd] - 7, + ddr3_lookup_schedule[group][pu_pd], + mchbar); + mchbar += 0x0018; + } + mchbar += 0x0010; + /* Channel B knows only the first two groups. */ + if ((1 == channel) && (1 == group)) + break; + } + mchbar += 0x0040; + } +} + diff --git a/src/northbridge/intel/gm45/raminit_read_write_training.c b/src/northbridge/intel/gm45/raminit_read_write_training.c new file mode 100644 index 0000000000..80cdcdc813 --- /dev/null +++ b/src/northbridge/intel/gm45/raminit_read_write_training.c @@ -0,0 +1,539 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * (Written by Nico Huber <nico.huber@secunet.com> for secunet) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdint.h> +#include <arch/io.h> +#include <pc80/mc146818rtc.h> +#include <console/console.h> +#include "gm45.h" + +typedef struct { + u32 addr[RANKS_PER_CHANNEL]; + unsigned count; +} address_bunch_t; + +/* Read Training. */ +#define CxRDTy_MCHBAR(ch, bl) (0x14b0 + (ch * 0x0100) + ((7 - bl) * 4)) +#define CxRDTy_T_SHIFT 20 +#define CxRDTy_T_MASK (0xf << CxRDTy_T_SHIFT) +#define CxRDTy_T(t) ((t << CxRDTy_T_SHIFT) & CxRDTy_T_MASK) +#define CxRDTy_P_SHIFT 16 +#define CxRDTy_P_MASK (0x7 << CxRDTy_P_SHIFT) +#define CxRDTy_P(p) ((p << CxRDTy_P_SHIFT) & CxRDTy_P_MASK) +static const u32 read_training_schedule[] = { + 0xfefefefe, 0x7f7f7f7f, 0xbebebebe, 0xdfdfdfdf, + 0xeeeeeeee, 0xf7f7f7f7, 0xfafafafa, 0xfdfdfdfd, + 0x00000000, 0x81818181, 0x40404040, 0x21212121, + 0x10101010, 0x09090909, 0x04040404, 0x03030303, + 0x10101010, 0x11111111, 0xeeeeeeee, 0xefefefef, + 0x10101010, 0x11111111, 0xeeeeeeee, 0xefefefef, + 0x10101010, 0xefefefef, 0x10101010, 0xefefefef, + 0x10101010, 0xefefefef, 0x10101010, 0xefefefef, + 0x00000000, 0xffffffff, 0x00000000, 0xffffffff, + 0x00000000, 0xffffffff, 0x00000000, 0x00000000, +}; +#define READ_TIMING_P_SHIFT 3 +#define READ_TIMING_P_BOUND (1 << READ_TIMING_P_SHIFT) +#define READ_TIMING_T_BOUND 14 +typedef struct { + int t; + int p; +} read_timing_t; +static void normalize_read_timing(read_timing_t *const timing) +{ + while (timing->p >= READ_TIMING_P_BOUND) { + timing->t++; + timing->p -= READ_TIMING_P_BOUND; + } + while (timing->p < 0) { + timing->t--; + timing->p += READ_TIMING_P_BOUND; + } + if ((timing->t < 0) || (timing->t >= READ_TIMING_T_BOUND)) + die("Timing under-/overflow during read training.\n"); +} +static void program_read_timing(const int ch, const int lane, + read_timing_t *const timing) +{ + normalize_read_timing(timing); + + u32 reg = MCHBAR32(CxRDTy_MCHBAR(ch, lane)); + reg &= ~(CxRDTy_T_MASK | CxRDTy_P_MASK); + reg |= CxRDTy_T(timing->t) | CxRDTy_P(timing->p); + MCHBAR32(CxRDTy_MCHBAR(ch, lane)) = reg; +} +/* Returns 1 on success, 0 on failure. */ +static int read_training_test(const int channel, const int lane, + const address_bunch_t *const addresses) +{ + int i; + + const int lane_offset = lane & 4; + const int lane_mask = 0xff << ((lane & ~4) << 3); + + for (i = 0; i < addresses->count; ++i) { + unsigned int offset; + for (offset = lane_offset; offset < 320; offset += 8) { + const u32 read = read32(addresses->addr[i] + offset); + const u32 good = read_training_schedule[offset >> 3]; + if ((read & lane_mask) != (good & lane_mask)) + return 0; + } + } + return 1; +} +static void read_training_per_lane(const int channel, const int lane, + const address_bunch_t *const addresses) +{ + read_timing_t lower, upper; + + MCHBAR32(CxRDTy_MCHBAR(channel, lane)) |= 3 << 25; + + /* Search lower bound. */ + lower.t = 0; + lower.p = 0; + program_read_timing(channel, lane, &lower); + /* Coarse search for good t. */ + while (!read_training_test(channel, lane, addresses)) { + ++lower.t; + program_read_timing(channel, lane, &lower); + } + /* Step back, then fine search for good p. */ + if (lower.t > 0) { + --lower.t; + program_read_timing(channel, lane, &lower); + while (!read_training_test(channel, lane, addresses)) { + ++lower.p; + program_read_timing(channel, lane, &lower); + } + } + + /* Search upper bound. */ + upper.t = lower.t + 1; + upper.p = lower.p; + program_read_timing(channel, lane, &upper); + if (!read_training_test(channel, lane, addresses)) + die("Read training failed: limits too narrow.\n"); + /* Coarse search for bad t. */ + do { + ++upper.t; + program_read_timing(channel, lane, &upper); + } while (read_training_test(channel, lane, addresses)); + /* Fine search for bad p. */ + --upper.t; + program_read_timing(channel, lane, &upper); + while (read_training_test(channel, lane, addresses)) { + ++upper.p; + program_read_timing(channel, lane, &upper); + } + + /* Calculate and program mean value. */ + lower.p += lower.t << READ_TIMING_P_SHIFT; + upper.p += upper.t << READ_TIMING_P_SHIFT; + const int mean_p = (lower.p + upper.p) >> 1; + /* lower becomes the mean value. */ + lower.t = mean_p >> READ_TIMING_P_SHIFT; + lower.p = mean_p & (READ_TIMING_P_BOUND - 1); + program_read_timing(channel, lane, &lower); + printk(BIOS_DEBUG, "Final timings for byte lane %d on channel %d: " + "%d.%d\n", lane, channel, lower.t, lower.p); +} +static void perform_read_training(const dimminfo_t *const dimms) +{ + int ch, i; + + FOR_EACH_POPULATED_CHANNEL(dimms, ch) { + address_bunch_t addresses = { { 0, }, 0 }; + FOR_EACH_POPULATED_RANK_IN_CHANNEL(dimms, ch, i) + addresses.addr[addresses.count++] = + raminit_get_rank_addr(ch, i); + + for (i = 0; i < addresses.count; ++i) { + /* Write test pattern. */ + unsigned int offset; + for (offset = 0; offset < 320; offset += 4) + write32(addresses.addr[i] + offset, + read_training_schedule[offset >> 3]); + } + + for (i = 0; i < 8; ++i) + read_training_per_lane(ch, i, &addresses); + } +} +static void read_training_store_results(void) +{ + u8 bytes[TOTAL_CHANNELS * 8]; + int ch, i; + + /* Store one timing pair in one byte each. */ + FOR_EACH_CHANNEL(ch) { + for (i = 0; i < 8; ++i) { + const u32 bl_reg = MCHBAR32(CxRDTy_MCHBAR(ch, i)); + bytes[(ch * 8) + i] = + (((bl_reg & CxRDTy_T_MASK) >> CxRDTy_T_SHIFT) + << 4) | + ((bl_reg & CxRDTy_P_MASK) >> CxRDTy_P_SHIFT); + } + } + + /* Store everything in CMOS above 128 bytes. */ + for (i = 0; i < (TOTAL_CHANNELS * 8); ++i) + cmos_write(bytes[i], CMOS_READ_TRAINING + i); +} +static void read_training_restore_results(void) +{ + u8 bytes[TOTAL_CHANNELS * 8]; + int ch, i; + + /* Read from CMOS. */ + for (i = 0; i < (TOTAL_CHANNELS * 8); ++i) + bytes[i] = cmos_read(CMOS_READ_TRAINING + i); + + /* Program restored results. */ + FOR_EACH_CHANNEL(ch) { + for (i = 0; i < 8; ++i) { + const int t = bytes[(ch * 8) + i] >> 4; + const int p = bytes[(ch * 8) + i] & 7; + u32 bl_reg = MCHBAR32(CxRDTy_MCHBAR(ch, i)); + bl_reg &= ~(CxRDTy_T_MASK | CxRDTy_P_MASK); + bl_reg |= (3 << 25) | CxRDTy_T(t) | CxRDTy_P(p); + MCHBAR32(CxRDTy_MCHBAR(ch, i)) = bl_reg; + printk(BIOS_DEBUG, "Restored timings for byte lane " + "%d on channel %d: %d.%d\n", i, ch, t, p); + } + } +} +void raminit_read_training(const dimminfo_t *const dimms, const int s3resume) +{ + if (!s3resume) { + perform_read_training(dimms); + read_training_store_results(); + } else { + read_training_restore_results(); + } + raminit_reset_readwrite_pointers(); +} + +/* Write Training. */ +#define CxWRTy_T_SHIFT 28 +#define CxWRTy_T_MASK (0xf << CxWRTy_T_SHIFT) +#define CxWRTy_T(t) ((t << CxWRTy_T_SHIFT) & CxWRTy_T_MASK) +#define CxWRTy_P_SHIFT 24 +#define CxWRTy_P_MASK (0x7 << CxWRTy_P_SHIFT) +#define CxWRTy_P(p) ((p << CxWRTy_P_SHIFT) & CxWRTy_P_MASK) +#define CxWRTy_F_SHIFT 18 +#define CxWRTy_F_MASK (0x3 << CxWRTy_F_SHIFT) +#define CxWRTy_F(f) ((f << CxWRTy_F_SHIFT) & CxWRTy_F_MASK) +#define CxWRTy_D_SHIFT 16 +#define CxWRTy_D_MASK (0x3 << CxWRTy_D_SHIFT) +#define CxWRTy_BELOW_D (0x3 << CxWRTy_D_SHIFT) +#define CxWRTy_ABOVE_D (0x1 << CxWRTy_D_SHIFT) +static const u32 write_training_schedule[] = { + 0xffffffff, 0x00000000, 0xffffffff, 0x00000000, + 0xffffffff, 0x00000000, 0xffffffff, 0x00000000, + 0xffffffff, 0x00000000, 0xffffffff, 0x00000000, + 0xffffffff, 0x00000000, 0xffffffff, 0x00000000, + 0xefefefef, 0x10101010, 0xefefefef, 0x10101010, + 0xefefefef, 0x10101010, 0xefefefef, 0x10101010, + 0xefefefef, 0x10101010, 0xefefefef, 0x10101010, + 0xefefefef, 0x10101010, 0xefefefef, 0x10101010, + 0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010, + 0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010, + 0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010, + 0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010, + 0x03030303, 0x04040404, 0x09090909, 0x10101010, + 0x21212121, 0x40404040, 0x81818181, 0x00000000, + 0x03030303, 0x04040404, 0x09090909, 0x10101010, + 0x21212121, 0x40404040, 0x81818181, 0x00000000, + 0xfdfdfdfd, 0xfafafafa, 0xf7f7f7f7, 0xeeeeeeee, + 0xdfdfdfdf, 0xbebebebe, 0x7f7f7f7f, 0xfefefefe, + 0xfdfdfdfd, 0xfafafafa, 0xf7f7f7f7, 0xeeeeeeee, + 0xdfdfdfdf, 0xbebebebe, 0x7f7f7f7f, 0xfefefefe, +}; +/* for raw card types A, B and C: MEM_CLOCK_1067MT? X group X lower/upper */ +static const u32 write_training_bytelane_masks_abc[2][4][2] = { + { /* clock < MEM_CLOCK_1067MT */ + { 0xffffffff, 0x00000000 }, { 0x00000000, 0x00000000 }, + { 0x00000000, 0xffffffff }, { 0x00000000, 0x00000000 }, + }, + { /* clock == MEM_CLOCK_1067MT */ + { 0x0000ffff, 0x00000000 }, { 0xffff0000, 0x00000000 }, + { 0x00000000, 0x0000ffff }, { 0x00000000, 0xffff0000 }, + }, +}; +/* for raw card type F: group X lower/upper */ +static const u32 write_training_bytelane_masks_f[4][2] = { + { 0xff00ff00, 0x00000000 }, { 0x00ff00ff, 0x00000000 }, + { 0x00000000, 0xff00ff00 }, { 0x00000000, 0x00ff00ff }, +}; +#define WRITE_TIMING_P_SHIFT 3 +#define WRITE_TIMING_P_BOUND (1 << WRITE_TIMING_P_SHIFT) +#define WRITE_TIMING_F_BOUND 4 +typedef struct { + int f; + int t; + const int t_bound; + int p; +} write_timing_t; +static void normalize_write_timing(write_timing_t *const timing) +{ + while (timing->p >= WRITE_TIMING_P_BOUND) { + timing->t++; + timing->p -= WRITE_TIMING_P_BOUND; + } + while (timing->p < 0) { + timing->t--; + timing->p += WRITE_TIMING_P_BOUND; + } + while (timing->t >= timing->t_bound) { + timing->f++; + timing->t -= timing->t_bound; + } + while (timing->t < 0) { + timing->f--; + timing->t += timing->t_bound; + } + if ((timing->f < 0) || (timing->f >= WRITE_TIMING_F_BOUND)) + die("Timing under-/overflow during write training.\n"); +} +static void program_write_timing(const int ch, const int group, + write_timing_t *const timing, int memclk1067) +{ + /* MEM_CLOCK_1067MT? X lower/upper */ + const u32 d_bounds[2][2] = { { 1, 6 }, { 2, 9 } }; + + normalize_write_timing(timing); + + const int f = timing->f; + const int t = timing->t; + const int p = (memclk1067 && (((t == 9) && (timing->p >= 4)) || + ((t == 10) && (timing->p < 4)))) + ? 4 : timing->p; + const int d = + (t <= d_bounds[memclk1067][0]) ? CxWRTy_BELOW_D : + ((t > d_bounds[memclk1067][1]) ? CxWRTy_ABOVE_D : 0); + + u32 reg = MCHBAR32(CxWRTy_MCHBAR(ch, group)); + reg &= ~(CxWRTy_T_MASK | CxWRTy_P_MASK | CxWRTy_F_MASK); + reg &= ~CxWRTy_D_MASK; + reg |= CxWRTy_T(t) | CxWRTy_P(p) | CxWRTy_F(f) | d; + MCHBAR32(CxWRTy_MCHBAR(ch, group)) = reg; +} +/* Returns 1 on success, 0 on failure. */ +static int write_training_test(const address_bunch_t *const addresses, + const u32 *const masks) +{ + int i, ret = 0; + + const u32 mmarb0 = MCHBAR32(0x0220); + const u8 wrcctl = MCHBAR8(0x0218); + MCHBAR32(0x0220) |= 0xf << 28; + MCHBAR8(0x0218) |= 0x1 << 4; + + for (i = 0; i < addresses->count; ++i) { + const unsigned int addr = addresses->addr[i]; + unsigned int off; + for (off = 0; off < 640; off += 8) { + const u32 pattern = write_training_schedule[off >> 3]; + write32(addr + off, pattern); + write32(addr + off + 4, pattern); + } + + MCHBAR8(0x78) |= 1; + + for (off = 0; off < 640; off += 8) { + const u32 good = write_training_schedule[off >> 3]; + const u32 read1 = read32(addr + off); + if ((read1 & masks[0]) != (good & masks[0])) + goto _bad_timing_out; + const u32 read2 = read32(addr + off + 4); + if ((read2 & masks[1]) != (good & masks[1])) + goto _bad_timing_out; + } + } + ret = 1; + +_bad_timing_out: + MCHBAR32(0x0220) = mmarb0; + MCHBAR8(0x0218) = wrcctl; + + return ret; +} +static void write_training_per_group(const int ch, const int group, + const address_bunch_t *const addresses, + const u32 masks[][2], const int memclk1067) +{ + const int t_bound = memclk1067 ? 12 : 11; + write_timing_t lower = { 0, 0, t_bound, 0 }, + upper = { 0, 0, t_bound, 0 }; + + /* Search lower bound. */ + const u32 reg = MCHBAR32(CxWRTy_MCHBAR(ch, group)); + lower.t = (reg >> 12) & 0xf; + lower.p = (reg >> 8) & 0x7; + lower.f = ((reg >> 2) & 0x3) - 1; + program_write_timing(ch, group, &lower, memclk1067); + /* Coarse search for good t. */ + while (!write_training_test(addresses, masks[group])) { + ++lower.t; + program_write_timing(ch, group, &lower, memclk1067); + } + /* Fine search for good p. */ + --lower.t; + program_write_timing(ch, group, &lower, memclk1067); + while (!write_training_test(addresses, masks[group])) { + ++lower.p; + program_write_timing(ch, group, &lower, memclk1067); + } + + /* Search upper bound. */ + upper.t = lower.t + 3; + upper.p = lower.p; + upper.f = lower.f; + program_write_timing(ch, group, &upper, memclk1067); + if (!write_training_test(addresses, masks[group])) + die("Write training failed; limits too narrow.\n"); + /* Coarse search for good t. */ + while (write_training_test(addresses, masks[group])) { + ++upper.t; + program_write_timing(ch, group, &upper, memclk1067); + } + /* Fine search for good p. */ + --upper.t; + program_write_timing(ch, group, &upper, memclk1067); + while (write_training_test(addresses, masks[group])) { + ++upper.p; + program_write_timing(ch, group, &upper, memclk1067); + } + + /* Calculate and program mean value. */ + lower.t += lower.f * lower.t_bound; + lower.p += lower.t << WRITE_TIMING_P_SHIFT; + upper.t += upper.f * upper.t_bound; + upper.p += upper.t << WRITE_TIMING_P_SHIFT; + /* lower becomes the mean value. */ + const int mean_p = (lower.p + upper.p) >> 1; + lower.f = mean_p / (lower.t_bound << WRITE_TIMING_P_SHIFT); + lower.t = (mean_p >> WRITE_TIMING_P_SHIFT) % lower.t_bound; + lower.p = mean_p & (WRITE_TIMING_P_BOUND - 1); + program_write_timing(ch, group, &lower, memclk1067); + + printk(BIOS_DEBUG, "Final timings for group %d" + " on channel %d: %d.%d.%d\n", + group, ch, lower.f, lower.t, lower.p); +} +static void perform_write_training(const int memclk1067, + const dimminfo_t *const dimms) +{ + const int cardF[] = { dimms[0].card_type == 0xf, + dimms[1].card_type == 0xf }; + int ch, r, group; + + address_bunch_t addr[2] = { { { 0, }, 0 }, { { 0, }, 0 }, }; + /* Add check if channel A is populated, i.e. if cardF[0] is valid. + * Otherwise we would write channel A registers when DIMM in channel B + * is of raw card type A, B or C (cardF[1] == 0) even if channel A is + * not populated. + * Needs raw card type A, B or C for testing. */ + if ((dimms[0].card_type != 0) && (cardF[0] == cardF[1])) { + /* Common path for both channels. */ + FOR_EACH_POPULATED_RANK(dimms, ch, r) + addr[0].addr[addr[0].count++] = + raminit_get_rank_addr(ch, r); + } else { + FOR_EACH_POPULATED_RANK(dimms, ch, r) + addr[ch].addr[addr[ch].count++] = + raminit_get_rank_addr(ch, r); + } + + FOR_EACH_CHANNEL(ch) if (addr[ch].count > 0) { + const u32 (*const masks)[2] = (!cardF[ch]) + ? write_training_bytelane_masks_abc[memclk1067] + : write_training_bytelane_masks_f; + for (group = 0; group < 4; ++group) { + if (!masks[group][0] && !masks[group][1]) + continue; + write_training_per_group( + ch, group, &addr[ch], masks, memclk1067); + } + } +} +static void write_training_store_results(void) +{ + u8 bytes[TOTAL_CHANNELS * 4 * 2]; /* two bytes per group */ + int ch, i; + + /* Store one T/P pair in one, F in the other byte. */ + /* We could save six bytes by putting all F values in two bytes. */ + FOR_EACH_CHANNEL(ch) { + for (i = 0; i < 4; ++i) { + const u32 reg = MCHBAR32(CxWRTy_MCHBAR(ch, i)); + bytes[(ch * 8) + (i * 2)] = + (((reg & CxWRTy_T_MASK) + >> CxWRTy_T_SHIFT) << 4) | + ((reg & CxWRTy_P_MASK) >> CxWRTy_P_SHIFT); + bytes[(ch * 8) + (i * 2) + 1] = + ((reg & CxWRTy_F_MASK) >> CxWRTy_F_SHIFT); + } + } + + /* Store everything in CMOS above 128 bytes. */ + for (i = 0; i < (TOTAL_CHANNELS * 4 * 2); ++i) + cmos_write(bytes[i], CMOS_WRITE_TRAINING + i); +} +static void write_training_restore_results(const int memclk1067) +{ + const int t_bound = memclk1067 ? 12 : 11; + + u8 bytes[TOTAL_CHANNELS * 4 * 2]; /* two bytes per group */ + int ch, i; + + /* Read from CMOS. */ + for (i = 0; i < (TOTAL_CHANNELS * 4 * 2); ++i) + bytes[i] = cmos_read(CMOS_WRITE_TRAINING + i); + + /* Program with original program_write_timing(). */ + FOR_EACH_CHANNEL(ch) { + for (i = 0; i < 4; ++i) { + write_timing_t timing = { 0, 0, t_bound, 0 }; + timing.f = bytes[(ch * 8) + (i * 2) + 1] & 3; + timing.t = bytes[(ch * 8) + (i * 2)] >> 4; + timing.p = bytes[(ch * 8) + (i * 2)] & 7; + program_write_timing(ch, i, &timing, memclk1067); + printk(BIOS_DEBUG, "Restored timings for group %d " + "on channel %d: %d.%d.%d\n", + i, ch, timing.f, timing.t, timing.p); + } + } +} +void raminit_write_training(const mem_clock_t ddr3clock, + const dimminfo_t *const dimms, + const int s3resume) +{ + const int memclk1067 = ddr3clock == MEM_CLOCK_1067MT; + + if (!s3resume) { + perform_write_training(memclk1067, dimms); + write_training_store_results(); + } else { + write_training_restore_results(memclk1067); + } + raminit_reset_readwrite_pointers(); +} diff --git a/src/northbridge/intel/gm45/raminit_receive_enable_calibration.c b/src/northbridge/intel/gm45/raminit_receive_enable_calibration.c new file mode 100644 index 0000000000..49d22f29e1 --- /dev/null +++ b/src/northbridge/intel/gm45/raminit_receive_enable_calibration.c @@ -0,0 +1,308 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * (Written by Nico Huber <nico.huber@secunet.com> for secunet) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdint.h> +#include <arch/io.h> +#include <console/console.h> +#include "gm45.h" + +#define CxRECy_MCHBAR(x, y) (0x14a0 + (x * 0x0100) + ((3 - y) * 4)) +#define CxRECy_SHIFT_L 0 +#define CxRECy_MASK_L (3 << CxRECy_SHIFT_L) +#define CxRECy_SHIFT_H 16 +#define CxRECy_MASK_H (3 << CxRECy_SHIFT_H) +#define CxRECy_T_SHIFT 28 +#define CxRECy_T_MASK (0xf << CxRECy_T_SHIFT) +#define CxRECy_T(t) ((t << CxRECy_T_SHIFT) & CxRECy_T_MASK) +#define CxRECy_P_SHIFT 24 +#define CxRECy_P_MASK (0x7 << CxRECy_P_SHIFT) +#define CxRECy_P(p) ((p << CxRECy_P_SHIFT) & CxRECy_P_MASK) +#define CxRECy_PH_SHIFT 22 +#define CxRECy_PH_MASK (0x3 << CxRECy_PH_SHIFT) +#define CxRECy_PH(p) ((p << CxRECy_PH_SHIFT) & CxRECy_PH_MASK) +#define CxRECy_PM_SHIFT 20 +#define CxRECy_PM_MASK (0x3 << CxRECy_PM_SHIFT) +#define CxRECy_PM(p) ((p << CxRECy_PM_SHIFT) & CxRECy_PM_MASK) +#define CxRECy_TIMING_MASK (CxRECy_T_MASK | CxRECy_P_MASK | \ + CxRECy_PH_MASK | CxRECy_PM_MASK) + +#define CxDRT3_C_SHIFT 7 +#define CxDRT3_C_MASK (0xf << CxDRT3_C_SHIFT) +#define CxDRT3_C(c) ((c << CxDRT3_C_SHIFT) & CxDRT3_C_MASK) +/* group to byte-lane mapping: (cardF X group X 2 per group) */ +static const char bytelane_map[2][4][2] = { +/* A,B,C */{ { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 } }, +/* F */{ { 0, 2 }, { 1, 3 }, { 4, 6 }, { 5, 7 } }, +}; + +#define PH_BOUND 4 +#define PH_STEP 2 +#define PM_BOUND 3 +#define C_BOUND 16 +typedef struct { + int c; + int pre; + int ph; + int t; + const int t_bound; + int p; + const int p_bound; +} rec_timing_t; +static void normalize_rec_timing(rec_timing_t *const timing) +{ + while (timing->p >= timing->p_bound) { + timing->t++; + timing->p -= timing->p_bound; + } + while (timing->p < 0) { + timing->t--; + timing->p += timing->p_bound; + } + while (timing->t >= timing->t_bound) { + timing->ph += PH_STEP; + timing->t -= timing->t_bound; + } + while (timing->t < 0) { + timing->ph -= PH_STEP; + timing->t += timing->t_bound; + } + while (timing->ph >= PH_BOUND) { + timing->c++; + timing->ph -= PH_BOUND; + } + while (timing->ph < 0) { + timing->c--; + timing->ph += PH_BOUND; + } + if (timing->c < 0 || timing->c >= C_BOUND) + die("Timing under-/overflow during " + "receive-enable calibration.\n"); +} + +static void rec_full_backstep(rec_timing_t *const timing) +{ + timing->c--; +} +static void rec_half_backstep(rec_timing_t *const timing) +{ + timing->ph -= PH_STEP; +} +static void rec_quarter_step(rec_timing_t *const timing) +{ + timing->t += (timing->t_bound) >> 1; + timing->p += (timing->t_bound & 1) * (timing->p_bound >> 1); +} +static void rec_quarter_backstep(rec_timing_t *const timing) +{ + timing->t -= (timing->t_bound) >> 1; + timing->p -= (timing->t_bound & 1) * (timing->p_bound >> 1); +} +static void rec_smallest_step(rec_timing_t *const timing) +{ + timing->p++; +} + +static void program_timing(int channel, int group, + rec_timing_t timings[][4]) +{ + rec_timing_t *const timing = &timings[channel][group]; + + normalize_rec_timing(timing); + + /* C value is per channel. */ + unsigned int mchbar = CxDRT3_MCHBAR(channel); + MCHBAR32(mchbar) = (MCHBAR32(mchbar) & ~CxDRT3_C_MASK) | + CxDRT3_C(timing->c); + + /* All other per group. */ + mchbar = CxRECy_MCHBAR(channel, group); + u32 reg = MCHBAR32(mchbar); + reg &= ~CxRECy_TIMING_MASK; + reg |= CxRECy_T(timing->t) | CxRECy_P(timing->p) | + CxRECy_PH(timing->ph) | CxRECy_PM(timing->pre); + MCHBAR32(mchbar) = reg; +} + +static int read_dqs_level(const int channel, const int lane) +{ + unsigned int mchbar = 0x14f0 + (channel * 0x0100); + MCHBAR32(mchbar) &= ~(1 << 9); + MCHBAR32(mchbar) |= (1 << 9); + + /* Read from this channel. */ + read32(raminit_get_rank_addr(channel, 0)); + + mchbar = 0x14b0 + (channel * 0x0100) + ((7 - lane) * 4); + return MCHBAR32(mchbar) & (1 << 30); +} + +static void find_dqs_low(const int channel, const int group, + rec_timing_t timings[][4], const char lane_map[][2]) +{ + /* Look for DQS low, using quarter steps. */ + while (read_dqs_level(channel, lane_map[group][0]) || + read_dqs_level(channel, lane_map[group][1])) { + rec_quarter_step(&timings[channel][group]); + program_timing(channel, group, timings); + } +} +static void find_dqs_high(const int channel, const int group, + rec_timing_t timings[][4], const char lane_map[][2]) +{ + /* Look for _any_ DQS high, using quarter steps. */ + while (!read_dqs_level(channel, lane_map[group][0]) && + !read_dqs_level(channel, lane_map[group][1])) { + rec_quarter_step(&timings[channel][group]); + program_timing(channel, group, timings); + } +} +static void find_dqs_edge_lowhigh(const int channel, const int group, + rec_timing_t timings[][4], + const char lane_map[][2]) +{ + /* Advance beyond previous high to low transition. */ + timings[channel][group].t += 2; + program_timing(channel, group, timings); + + /* Coarsely look for DQS high. */ + find_dqs_high(channel, group, timings, lane_map); + + /* Go back and perform finer search. */ + rec_quarter_backstep(&timings[channel][group]); + program_timing(channel, group, timings); + while (!read_dqs_level(channel, lane_map[group][0]) || + !read_dqs_level(channel, lane_map[group][1])) { + rec_smallest_step(&timings[channel][group]); + program_timing(channel, group, timings); + } +} +static void find_preamble(const int channel, const int group, + rec_timing_t timings[][4], const char lane_map[][2]) +{ + /* Look for DQS low, backstepping. */ + while (read_dqs_level(channel, lane_map[group][0]) || + read_dqs_level(channel, lane_map[group][1])) { + rec_full_backstep(&timings[channel][group]); + program_timing(channel, group, timings); + } +} + +static void receive_enable_calibration(const timings_t *const timings, + const dimminfo_t *const dimms) +{ + /* Override group to byte-lane mapping for raw card type F DIMMS. */ + static const char over_bytelane_map[2][4][2] = { + /* A,B,C */{ { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 } }, + /* F */{ { 0, 0 }, { 3, 3 }, { 6, 6 }, { 5, 5 } }, + }; + + const int cardF[] = + { dimms[0].card_type == 0xf, dimms[0].card_type == 0xf }; + const unsigned t_bound = + (timings->mem_clock == MEM_CLOCK_1067MT) ? 9 : 12; + const unsigned p_bound = + (timings->mem_clock == MEM_CLOCK_1067MT) ? 8 : 1; + + rec_timing_t rec_timings[2][4] = { + { + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound }, + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound }, + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound }, + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound } + }, { + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound }, + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound }, + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound }, + { timings->CAS + 1, 0, 0, 0, t_bound, 0, p_bound } + } + }; + + int ch, group; + FOR_EACH_POPULATED_CHANNEL(dimms, ch) { + const char (*const map)[2] = over_bytelane_map[cardF[ch]]; + for (group = 0; group < 4; ++group) { + program_timing(ch, group, rec_timings); + find_dqs_low(ch, group, rec_timings, map); + find_dqs_edge_lowhigh(ch, group, rec_timings, map); + + rec_quarter_step(&rec_timings[ch][group]); + program_timing(ch, group, rec_timings); + find_preamble(ch, group, rec_timings, map); + find_dqs_edge_lowhigh(ch, group, rec_timings, map); + rec_half_backstep(&rec_timings[ch][group]); + normalize_rec_timing(&rec_timings[ch][group]); + if (cardF[ch]) { + rec_timings[ch][group].t++; + program_timing(ch, group, rec_timings); + } + } + int c_min = C_BOUND; + for (group = 0; group < 4; ++group) { + if (rec_timings[ch][group].c < c_min) + c_min = rec_timings[ch][group].c; + } + for (group = 0; group < 4; ++group) { + rec_timings[ch][group].pre = + rec_timings[ch][group].c - c_min; + rec_timings[ch][group].c = c_min; + program_timing(ch, group, rec_timings); + printk(BIOS_SPEW, "Final timings for group %d " + "on channel %d: %d.%d.%d.%d.%d\n", + group, ch, + rec_timings[ch][group].c, + rec_timings[ch][group].pre, + rec_timings[ch][group].ph, + rec_timings[ch][group].t, + rec_timings[ch][group].p); + } + } +} + +void raminit_receive_enable_calibration(const timings_t *const timings, + const dimminfo_t *const dimms) +{ + int ch; + + /* Setup group to byte-lane mapping. */ + FOR_EACH_POPULATED_CHANNEL(dimms, ch) { + const char (*const map)[2] = + bytelane_map[dimms[ch].card_type == 0xf]; + unsigned int group; + for (group = 0; group < 4; ++group) { + const unsigned int mchbar = CxRECy_MCHBAR(ch, group); + u32 reg = MCHBAR32(mchbar); + reg &= ~((3 << 16) | (1 << 8) | 3); + reg |= (map[group][0] - group); + reg |= (map[group][1] - group - 1) << 16; + MCHBAR32(mchbar) = reg; + } + } + + MCHBAR32(0x12a4) |= 1 << 31; + MCHBAR32(0x13a4) |= 1 << 31; + MCHBAR32(0x14f0) = (MCHBAR32(0x14f0) & ~(3 << 9)) | (1 << 9); + MCHBAR32(0x15f0) = (MCHBAR32(0x15f0) & ~(3 << 9)) | (1 << 9); + + receive_enable_calibration(timings, dimms); + + MCHBAR32(0x12a4) &= ~(1 << 31); + MCHBAR32(0x13a4) &= ~(1 << 31); + raminit_reset_readwrite_pointers(); +} diff --git a/src/northbridge/intel/gm45/thermal.c b/src/northbridge/intel/gm45/thermal.c new file mode 100644 index 0000000000..a74bcc5e61 --- /dev/null +++ b/src/northbridge/intel/gm45/thermal.c @@ -0,0 +1,201 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 secunet Security Networks AG + * + * 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 <stdint.h> +#include <stddef.h> +#include <string.h> +#include <arch/io.h> +#include <arch/romcc_io.h> +#include <device/pci_def.h> +#include <device/pnp_def.h> +#include <spd.h> +#include <console/console.h> +#include "delay.h" + +#include "gm45.h" + +void raminit_thermal(const sysinfo_t *sysinfo) +{ + const mem_clock_t freq = sysinfo->selected_timings.mem_clock; + int x; + FOR_EACH_POPULATED_CHANNEL(sysinfo->dimms, x) { + const chip_width_t width = sysinfo->dimms[x].chip_width; + const chip_capacity_t size = sysinfo->dimms[x].chip_capacity; + if ((freq == MEM_CLOCK_1067MT) && (width == CHIP_WIDTH_x16)) { + MCHBAR32(CxDTPEW(x)) = 0x0d0b0403; + MCHBAR32(CxDTPEW(x)+4) = 0x060d; + MCHBAR32(CxDTAEW(x)) = 0x2d0b221a; + MCHBAR32(CxDTAEW(x)+4) = 0xc779956e; + } else + if ((freq == MEM_CLOCK_1067MT) && (width == CHIP_WIDTH_x8)) { + MCHBAR32(CxDTPEW(x)) = 0x06040101; + MCHBAR32(CxDTPEW(x)+4) = 0x0506; + if (size == CHIP_CAP_2G) + MCHBAR32(CxDTAEW(x)) = 0xa1071416; + else + MCHBAR32(CxDTAEW(x)) = 0x1a071416; + MCHBAR32(CxDTAEW(x)+4) = 0x7246643f; + } else + if ((freq == MEM_CLOCK_800MT) && (width == CHIP_WIDTH_x16)) { + MCHBAR32(CxDTPEW(x)) = 0x06030100; + MCHBAR32(CxDTPEW(x)+4) = 0x0506; + MCHBAR32(CxDTAEW(x)) = 0x3e081714; + MCHBAR32(CxDTAEW(x)+4) = 0xbb79a171; + } else + if ((freq == MEM_CLOCK_800MT) && (width == CHIP_WIDTH_x8)) { + if (size <= CHIP_CAP_512M) + MCHBAR32(CxDTPEW(x)) = 0x05050101; + else + MCHBAR32(CxDTPEW(x)) = 0x05060101; + MCHBAR32(CxDTPEW(x)+4) = 0x0503; + if (size == CHIP_CAP_2G) { + MCHBAR32(CxDTAEW(x)) = 0x57051010; + MCHBAR32(CxDTAEW(x)+4) = 0x5fd15dde; + } else + if (size == CHIP_CAP_1G) { + MCHBAR32(CxDTAEW(x)) = 0x3306130e; + MCHBAR32(CxDTAEW(x)+4) = 0x5763485d; + } else + if (size <= CHIP_CAP_512M) { + MCHBAR32(CxDTAEW(x)) = 0x1e08170d; + MCHBAR32(CxDTAEW(x)+4) = 0x502f3827; + } + } else + if ((freq == MEM_CLOCK_667MT) && (width == CHIP_WIDTH_x16)) { + MCHBAR32(CxDTPEW(x)) = 0x02000000; + MCHBAR32(CxDTPEW(x)+4) = 0x0402; + MCHBAR32(CxDTAEW(x)) = 0x46061111; + MCHBAR32(CxDTAEW(x)+4) = 0xb579a772; + } else + if ((freq == MEM_CLOCK_667MT) && (width == CHIP_WIDTH_x8)) { + MCHBAR32(CxDTPEW(x)) = 0x04070101; + MCHBAR32(CxDTPEW(x)+4) = 0x0501; + if (size == CHIP_CAP_2G) { + MCHBAR32(CxDTAEW(x)) = 0x32040e0d; + MCHBAR32(CxDTAEW(x)+4) = 0x55ff59ff; + } else + if (size == CHIP_CAP_1G) { + MCHBAR32(CxDTAEW(x)) = 0x3f05120a; + MCHBAR32(CxDTAEW(x)+4) = 0x49713a6c; + } else + if (size <= CHIP_CAP_512M) { + MCHBAR32(CxDTAEW(x)) = 0x20081808; + MCHBAR32(CxDTAEW(x)+4) = 0x3f23221b; + } + } + + /* also L-Shaped */ + if (sysinfo->selected_timings.channel_mode == + CHANNEL_MODE_DUAL_INTERLEAVED) { + if (freq == MEM_CLOCK_1067MT) { + MCHBAR32(CxGTEW(x)) = 0xc8f81717; + } else + if (freq == MEM_CLOCK_800MT) { + MCHBAR32(CxGTEW(x)) = 0x96ba1717; + } else + if (freq == MEM_CLOCK_667MT) { + MCHBAR32(CxGTEW(x)) = 0x7d9b1717; + } + } else { + if (freq == MEM_CLOCK_1067MT) { + MCHBAR32(CxGTEW(x)) = 0x53661717; + } else + if (freq == MEM_CLOCK_800MT) { + MCHBAR32(CxGTEW(x)) = 0x886e1717; + } else + if (freq == MEM_CLOCK_667MT) { + MCHBAR32(CxGTEW(x)) = 0x38621717; + } + } + } + + // always? + MCHBAR32(CxDTC(0)) = 0x00004020; + MCHBAR32(CxDTC(1)) = 0x00004020; + MCHBAR32(CxGTC(0)) = 0x00304848; + MCHBAR32(CxGTC(1)) = 0x00304848; + + /* enable thermal sensors */ + u32 tmp; + tmp = MCHBAR32(0x1290) & 0xfff8; + MCHBAR32(0x1290) = tmp | 0xa4810007; + tmp = MCHBAR32(0x1390) & 0xfff8; + MCHBAR32(0x1390) = tmp | 0xa4810007; + tmp = MCHBAR32(0x12b4) & 0xfff8; + MCHBAR32(0x12b4) = tmp | 0xa2810007; + tmp = MCHBAR32(0x13b4) & 0xfff8; + MCHBAR32(0x13b4) = tmp | 0xa2810007; + MCHBAR8(0x1070) = 1; + MCHBAR8(0x1080) = 6; + if (sysinfo->gfx_type == GMCH_PM45) { + MCHBAR16(0x1001) = 0; + MCHBAR8(0x1007) = 0; + MCHBAR32(0x1010) = 0; + MCHBAR32(0x1014) = 0; + MCHBAR8(0x101c) = 0x98; + MCHBAR16(0x1041) = 0x9200; + MCHBAR8(0x1047) = 0; + MCHBAR32(0x1050) = 0x2309; + MCHBAR32(0x1054) = 0; + MCHBAR8(0x105c) = 0x98; + } else { + MCHBAR16(0x1001) = 0x9200; + MCHBAR8(0x1007) = 0; + MCHBAR32(0x1010) = 0x2309; + MCHBAR32(0x1014) = 0; + MCHBAR8(0x101c) = 0x98; + MCHBAR16(0x1041) = 0; + MCHBAR8(0x1047) = 0; + MCHBAR32(0x1050) = 0; + MCHBAR32(0x1054) = 0; + MCHBAR8(0x105c) = 0x98; + } + + MCHBAR32(0x1010) |= 1 << 31; + MCHBAR32(0x1050) |= 1 << 31; + MCHBAR32(CxGTC(0)) |= 1 << 31; + MCHBAR32(CxGTC(1)) |= 1 << 31; + + if (sysinfo->gs45_low_power_mode) { + MCHBAR32(0x11b0) = 0xa000083a; + } else if (sysinfo->gfx_type == GMCH_GM49) { + MCHBAR32(0x11b0) = 0x2000383a; + MCHBAR16(0x1190) &= ~(1 << 15); + } else if ((sysinfo->gfx_type != GMCH_PM45) && + (sysinfo->gfx_type != GMCH_UNKNOWN)) { + MCHBAR32(0x11b0) = 0xa000383a; + } + + switch (sysinfo->selected_timings.fsb_clock) { + case FSB_CLOCK_667MHz: + MCHBAR32(0x11d0) = 0x0fd88000; + break; + case FSB_CLOCK_800MHz: + MCHBAR32(0x11d0) = 0x1303c000; + break; + case FSB_CLOCK_1067MHz: + MCHBAR32(0x11d0) = 0x194a0000; + break; + } + tmp = MCHBAR32(0x11d4) & ~0x1f; + MCHBAR32(0x11d4) = tmp | 4; +} + |