diff options
Diffstat (limited to 'src/southbridge/intel')
-rw-r--r-- | src/southbridge/intel/i82801dx/Kconfig | 1 | ||||
-rw-r--r-- | src/southbridge/intel/i82801dx/Makefile.inc | 3 | ||||
-rw-r--r-- | src/southbridge/intel/i82801dx/i82801dx_ac97.c | 278 | ||||
-rw-r--r-- | src/southbridge/intel/i82801dx/i82801dx_nvs.h | 138 | ||||
-rw-r--r-- | src/southbridge/intel/i82801dx/i82801dx_smi.c | 365 | ||||
-rw-r--r-- | src/southbridge/intel/i82801dx/i82801dx_smihandler.c | 669 | ||||
-rw-r--r-- | src/southbridge/intel/i82801dx/i82801dx_tco_timer.c | 37 |
7 files changed, 1474 insertions, 17 deletions
diff --git a/src/southbridge/intel/i82801dx/Kconfig b/src/southbridge/intel/i82801dx/Kconfig index 6a35691b5d..62da5cf856 100644 --- a/src/southbridge/intel/i82801dx/Kconfig +++ b/src/southbridge/intel/i82801dx/Kconfig @@ -1,2 +1,3 @@ config SOUTHBRIDGE_INTEL_I82801DX bool + select IOAPIC diff --git a/src/southbridge/intel/i82801dx/Makefile.inc b/src/southbridge/intel/i82801dx/Makefile.inc index 7167e1d391..562e80afe0 100644 --- a/src/southbridge/intel/i82801dx/Makefile.inc +++ b/src/southbridge/intel/i82801dx/Makefile.inc @@ -7,3 +7,6 @@ driver-y += i82801dx_ac97.o #driver-y += i82801dx_nic.o #driver-y += i82801dx_pci.o obj-y += i82801dx_reset.o + +obj-$(CONFIG_HAVE_SMI_HANDLER) += i82801dx_smi.o +smmobj-$(CONFIG_HAVE_SMI_HANDLER) += i82801dx_smihandler.o diff --git a/src/southbridge/intel/i82801dx/i82801dx_ac97.c b/src/southbridge/intel/i82801dx/i82801dx_ac97.c index 752449865d..10b100beee 100644 --- a/src/southbridge/intel/i82801dx/i82801dx_ac97.c +++ b/src/southbridge/intel/i82801dx/i82801dx_ac97.c @@ -1,41 +1,285 @@ /* - * (C) 2003 Linux Networx + * This file is part of the coreboot project. + * + * Copyright (C) 2008-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 <device/device.h> #include <device/pci.h> #include <device/pci_ids.h> -#include <device/pci_ops.h> +#include <arch/io.h> +#include <delay.h> #include "i82801dx.h" +#define NAMBAR 0x10 +#define MASTER_VOL 0x02 +#define PAGING 0x24 +#define EXT_AUDIO 0x28 +#define FUNC_SEL 0x66 +#define INFO_IO 0x68 +#define CONNECTOR 0x6a +#define VENDOR_ID1 0x7c +#define VENDOR_ID2 0x7e +#define SEC_VENDOR_ID1 0xfc +#define SEC_VENDOR_ID2 0xfe + +#define NABMBAR 0x14 +#define GLOB_CNT 0x2c +#define GLOB_STA 0x30 +#define CAS 0x34 + +#define MMBAR 0x10 +#define EXT_MODEM_ID1 0x3c +#define EXT_MODEM_ID2 0xbc + +#define MBAR 0x14 +#define SEC_CODEC 0x40 + + +/* FIXME. This table is probably mainboard specific */ +static u16 ac97_function[16*2][4] = { + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) }, + { (1 << 5), (2 << 11), (1 << 10), (3 << 13) } +}; + +static u16 nabmbar; +static u16 nambar; + +static int ac97_semaphore(void) +{ + int timeout; + u8 reg8; + + timeout = 0xffff; + do { + reg8 = inb(nabmbar + CAS); + timeout--; + } while ((reg8 & 1) && timeout); + if (! timeout) { + printk_debug("Timeout!\n"); + } + + return (!timeout); +} + +static void init_cnr(void) +{ + // TODO +} + +static void program_sigid(struct device *dev, u32 id) +{ + pci_write_config32(dev, 0x2c, id); +} + +static void ac97_audio_init(struct device *dev) +{ + u16 reg16; + u32 reg32; + int i; + + printk_debug("Initializing AC'97 Audio.\n"); + + /* top 16 bits are zero, so don't read them */ + nabmbar = pci_read_config16(dev, NABMBAR) & 0xfffe; + nambar = pci_read_config16(dev, NAMBAR) & 0xfffe; + + reg16 = inw(nabmbar + GLOB_CNT); + reg16 |= (1 << 1); /* Remove AC_RESET# */ + outw(reg16, nabmbar + GLOB_CNT); + + /* Wait 600ms. Ouch. */ + udelay(600 * 1000); + + init_cnr(); + + /* Detect Primary AC'97 Codec */ + reg32 = inl(nabmbar + GLOB_STA); + if ((reg32 & ((1 << 28) | (1 << 9) | (1 << 8))) == 0) { + /* Primary Codec not found */ + printk_debug("No primary codec. Disabling AC'97 Audio.\n"); + return; + } + + ac97_semaphore(); + + /* Detect if codec is programmable */ + outw(0x8000, nambar + MASTER_VOL); + ac97_semaphore(); + if (inw(nambar + MASTER_VOL) != 0x8000) { + printk_debug("Codec not programmable. Disabling AC'97 Audio.\n"); + return; + } + + /* Program Vendor IDs */ + reg32 = inw(nambar + VENDOR_ID1); + reg32 <<= 16; + reg32 |= (u16)inw(nambar + VENDOR_ID2); + + program_sigid(dev, reg32); -static struct device_operations ac97audio_ops = { + /* Is Codec AC'97 2.3 compliant? */ + reg16 = inw(nambar + EXT_AUDIO); + /* [11:10] = 10b -> AC'97 2.3 */ + if ((reg16 & 0x0c00) != 0x0800) { + /* No 2.3 Codec. We're done */ + return; + } + + /* Select Page 1 */ + reg16 = inw(nambar + PAGING); + reg16 &= 0xfff0; + reg16 |= 0x0001; + outw(reg16, nambar + PAGING); + + for (i = 0x0a * 2; i > 0; i--) { + outw(i, nambar + FUNC_SEL); + + /* Function could not be selected. Next one */ + if (inw(nambar + FUNC_SEL) != i) + continue; + + reg16 = inw(nambar + INFO_IO); + + /* Function Information present? */ + if (!(reg16 & (1 << 0))) + continue; + + /* Function Information valid? */ + if (!(reg16 & (1 << 4))) + continue; + + /* Program Buffer Delay [9:5] */ + reg16 &= 0x03e0; + reg16 |= ac97_function[i][0]; + + /* Program Gain [15:11] */ + reg16 |= ac97_function[i][1]; + + /* Program Inversion [10] */ + reg16 |= ac97_function[i][2]; + + outw(reg16, nambar + INFO_IO); + + /* Program Connector / Jack Location */ + reg16 = inw(nambar + CONNECTOR); + reg16 &= 0x1fff; + reg16 |= ac97_function[i][3]; + outw(reg16, nambar + CONNECTOR); + } +} + +static void ac97_modem_init(struct device *dev) +{ + u16 reg16; + u32 reg32; + u16 mmbar, mbar; + + mmbar = pci_read_config16(dev, MMBAR) & 0xfffe; + mbar = pci_read_config16(dev, MBAR) & 0xfffe; + + reg16 = inw(mmbar + EXT_MODEM_ID1); + if ((reg16 & 0xc000) != 0xc000 ) { + if (reg16 & (1 << 0)) { + reg32 = inw(mmbar + VENDOR_ID2); + reg32 <<= 16; + reg32 |= (u16)inw(mmbar + VENDOR_ID1); + program_sigid(dev, reg32); + return; + } + } + + /* Secondary codec? */ + reg16 = inw(mbar + SEC_CODEC); + if ((reg16 & (1 << 9)) == 0) + return; + + reg16 = inw(mmbar + EXT_MODEM_ID2); + if ((reg16 & 0xc000) == 0x4000) { + if (reg16 & (1 << 0)) { + reg32 = inw(mmbar + SEC_VENDOR_ID2); + reg32 <<= 16; + reg32 |= (u16)inw(mmbar + SEC_VENDOR_ID1); + program_sigid(dev, reg32); + return; + } + } +} + +static struct device_operations ac97_audio_ops = { .read_resources = pci_dev_read_resources, .set_resources = pci_dev_set_resources, .enable_resources = pci_dev_enable_resources, .enable = i82801dx_enable, - .init = 0, + .init = ac97_audio_init, .scan_bus = 0, }; -static const struct pci_driver ac97audio_driver __pci_driver = { - .ops = &ac97audio_ops, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_82801DBM_AC97_AUDIO, -}; - - -static struct device_operations ac97modem_ops = { +static struct device_operations ac97_modem_ops = { .read_resources = pci_dev_read_resources, .set_resources = pci_dev_set_resources, .enable_resources = pci_dev_enable_resources, .enable = i82801dx_enable, - .init = 0, + .init = ac97_modem_init, .scan_bus = 0, }; -static const struct pci_driver ac97modem_driver __pci_driver = { - .ops = &ac97modem_ops, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_82801DBM_AC97_MODEM, +/* 82801DB/DBL/DBM (ICH4/ICH4-L/ICH4-M) */ +static const struct pci_driver i82801db_ac97_audio __pci_driver = { + .ops = &ac97_audio_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_82801DB_AC97_AUDIO, +}; + +static const struct pci_driver i82801db_ac97_modem __pci_driver = { + .ops = &ac97_modem_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_82801DB_AC97_MODEM, }; + + diff --git a/src/southbridge/intel/i82801dx/i82801dx_nvs.h b/src/southbridge/intel/i82801dx/i82801dx_nvs.h new file mode 100644 index 0000000000..03f8de74ea --- /dev/null +++ b/src/southbridge/intel/i82801dx/i82801dx_nvs.h @@ -0,0 +1,138 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2008-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 + */ + +typedef struct { + /* Miscellaneous */ + u16 osys; /* 0x00 - Operating System */ + u8 smif; /* 0x02 - SMI function call ("TRAP") */ + u8 prm0; /* 0x03 - SMI function call parameter */ + u8 prm1; /* 0x04 - SMI function call parameter */ + u8 scif; /* 0x05 - SCI function call (via _L00) */ + u8 prm2; /* 0x06 - SCI function call parameter */ + u8 prm3; /* 0x07 - SCI function call parameter */ + u8 lckf; /* 0x08 - Global Lock function for EC */ + u8 prm4; /* 0x09 - Lock function parameter */ + u8 prm5; /* 0x0a - Lock function parameter */ + u32 p80d; /* 0x0b - Debug port (IO 0x80) value */ + u8 lids; /* 0x0f - LID state (open = 1) */ + u8 pwrs; /* 0x10 - Power state (AC = 1) */ + u8 dbgs; /* 0x11 - Debug state */ + u8 linx; /* 0x12 - Linux OS */ + u8 dckn; /* 0x13 - PCIe docking state */ + /* Thermal policy */ + u8 actt; /* 0x14 - active trip point */ + u8 psvt; /* 0x15 - passive trip point */ + u8 tc1v; /* 0x16 - passive trip point TC1 */ + u8 tc2v; /* 0x17 - passive trip point TC2 */ + u8 tspv; /* 0x18 - passive trip point TSP */ + u8 crtt; /* 0x19 - critical trip point */ + u8 dtse; /* 0x1a - Digital Thermal Sensor enable */ + u8 dts1; /* 0x1b - DT sensor 1 */ + u8 dts2; /* 0x1c - DT sensor 2 */ + u8 rsvd2; + /* Battery Support */ + u8 bnum; /* 0x1e - number of batteries */ + u8 b0sc, b1sc, b2sc; /* 0x1f-0x21 - stored capacity */ + u8 b0ss, b1ss, b2ss; /* 0x22-0x24 - stored status */ + u8 rsvd3[3]; + /* Processor Identification */ + u8 apic; /* 0x28 - APIC enabled */ + u8 mpen; /* 0x29 - MP capable/enabled */ + u8 pcp0; /* 0x2a - PDC CPU/CORE 0 */ + u8 pcp1; /* 0x2b - PDC CPU/CORE 1 */ + u8 ppcm; /* 0x2c - Max. PPC state */ + u8 rsvd4[5]; + /* Super I/O & CMOS config */ + u8 natp; /* 0x32 - SIO type */ + u8 cmap; /* 0x33 - */ + u8 cmbp; /* 0x34 - */ + u8 lptp; /* 0x35 - LPT port */ + u8 fdcp; /* 0x36 - Floppy Disk Controller */ + u8 rfdv; /* 0x37 - */ + u8 hotk; /* 0x38 - Hot Key */ + u8 rtcf; + u8 util; + u8 acin; + /* Integrated Graphics Device */ + u8 igds; /* 0x3c - IGD state */ + u8 tlst; /* 0x3d - Display Toggle List Pointer */ + u8 cadl; /* 0x3e - currently attached devices */ + u8 padl; /* 0x3f - previously attached devices */ + u16 cste; /* 0x40 - current display state */ + u16 nste; /* 0x42 - next display state */ + u16 sste; /* 0x44 - set display state */ + u8 ndid; /* 0x46 - number of device ids */ + u32 did[5]; /* 0x47 - 5b device id 1..5 */ + u8 rsvd5[0x9]; + /* Backlight Control */ + u8 blcs; /* 0x64 - Backlight Control possible */ + u8 brtl; + u8 odds; + u8 rsvd6[0x7]; + /* Ambient Light Sensors*/ + u8 alse; /* 0x6e - ALS enable */ + u8 alaf; + u8 llow; + u8 lhih; + u8 rsvd7[0x6]; + /* EMA */ + u8 emae; /* 0x78 - EMA enable */ + u16 emap; + u16 emal; + u8 rsvd8[0x5]; + /* MEF */ + u8 mefe; /* 0x82 - MEF enable */ + u8 rsvd9[0x9]; + /* TPM support */ + u8 tpmp; /* 0x8c - TPM */ + u8 tpme; + u8 rsvd10[8]; + /* SATA */ + u8 gtf0[7]; /* 0x96 - GTF task file buffer for port 0 */ + u8 gtf1[7]; + u8 gtf2[7]; + u8 idem; + u8 idet; + u8 rsvd11[7]; + /* IGD OpRegion (not implemented yet) */ + u32 aslb; /* 0xb4 - IGD OpRegion Base Address */ + u8 ibtt; + u8 ipat; + u8 itvf; + u8 itvm; + u8 ipsc; + u8 iblc; + u8 ibia; + u8 issc; + u8 i409; + u8 i509; + u8 i609; + u8 i709; + u8 idmm; + u8 idms; + u8 if1e; + u8 hvco; + u32 nxd[8]; + u8 rsvd12[8]; + /* Mainboard specific */ + u8 dock; /* 0xf0 - Docking Status */ + u8 bten; + u8 rsvd13[14]; +} __attribute__((packed)) global_nvs_t; + diff --git a/src/southbridge/intel/i82801dx/i82801dx_smi.c b/src/southbridge/intel/i82801dx/i82801dx_smi.c new file mode 100644 index 0000000000..b934dcf2bd --- /dev/null +++ b/src/southbridge/intel/i82801dx/i82801dx_smi.c @@ -0,0 +1,365 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2008-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 <device/device.h> +#include <device/pci.h> +#include <console/console.h> +#include <arch/io.h> +#include <cpu/x86/cache.h> +#include <cpu/x86/smm.h> +#include <string.h> +#include "i82801dx.h" + +extern unsigned char smm[]; +extern unsigned int smm_len; + +/* I945 */ +#define SMRAM 0x90 +#define D_OPEN (1 << 6) +#define D_CLS (1 << 5) +#define D_LCK (1 << 4) +#define G_SMRAME (1 << 3) +#define C_BASE_SEG ((0 << 2) | (1 << 1) | (0 << 0)) + +/* While we read PMBASE dynamically in case it changed, let's + * initialize it with a sane value + */ +static u16 pmbase = PMBASE_ADDR; + +/** + * @brief read and clear PM1_STS + * @return PM1_STS register + */ +static u16 reset_pm1_status(void) +{ + u16 reg16; + + reg16 = inw(pmbase + PM1_STS); + /* set status bits are cleared by writing 1 to them */ + outw(reg16, pmbase + PM1_STS); + + return reg16; +} + +static void dump_pm1_status(u16 pm1_sts) +{ + printk_debug("PM1_STS: "); + if (pm1_sts & (1 << 15)) printk_debug("WAK "); + if (pm1_sts & (1 << 14)) printk_debug("PCIEXPWAK "); + if (pm1_sts & (1 << 11)) printk_debug("PRBTNOR "); + if (pm1_sts & (1 << 10)) printk_debug("RTC "); + if (pm1_sts & (1 << 8)) printk_debug("PWRBTN "); + if (pm1_sts & (1 << 5)) printk_debug("GBL "); + if (pm1_sts & (1 << 4)) printk_debug("BM "); + if (pm1_sts & (1 << 0)) printk_debug("TMROF "); + printk_debug("\n"); +} + +/** + * @brief read and clear SMI_STS + * @return SMI_STS register + */ +static u32 reset_smi_status(void) +{ + u32 reg32; + + reg32 = inl(pmbase + SMI_STS); + /* set status bits are cleared by writing 1 to them */ + outl(reg32, pmbase + SMI_STS); + + return reg32; +} + +static void dump_smi_status(u32 smi_sts) +{ + printk_debug("SMI_STS: "); + if (smi_sts & (1 << 26)) printk_debug("SPI "); + if (smi_sts & (1 << 25)) printk_debug("EL_SMI "); + if (smi_sts & (1 << 21)) printk_debug("MONITOR "); + if (smi_sts & (1 << 20)) printk_debug("PCI_EXP_SMI "); + if (smi_sts & (1 << 18)) printk_debug("INTEL_USB2 "); + if (smi_sts & (1 << 17)) printk_debug("LEGACY_USB2 "); + if (smi_sts & (1 << 16)) printk_debug("SMBUS_SMI "); + if (smi_sts & (1 << 15)) printk_debug("SERIRQ_SMI "); + if (smi_sts & (1 << 14)) printk_debug("PERIODIC "); + if (smi_sts & (1 << 13)) printk_debug("TCO "); + if (smi_sts & (1 << 12)) printk_debug("DEVMON "); + if (smi_sts & (1 << 11)) printk_debug("MCSMI "); + if (smi_sts & (1 << 10)) printk_debug("GPI "); + if (smi_sts & (1 << 9)) printk_debug("GPE0 "); + if (smi_sts & (1 << 8)) printk_debug("PM1 "); + if (smi_sts & (1 << 6)) printk_debug("SWSMI_TMR "); + if (smi_sts & (1 << 5)) printk_debug("APM "); + if (smi_sts & (1 << 4)) printk_debug("SLP_SMI "); + if (smi_sts & (1 << 3)) printk_debug("LEGACY_USB "); + if (smi_sts & (1 << 2)) printk_debug("BIOS "); + printk_debug("\n"); +} + + +/** + * @brief read and clear GPE0_STS + * @return GPE0_STS register + */ +static u32 reset_gpe0_status(void) +{ + u32 reg32; + + reg32 = inl(pmbase + GPE0_STS); + /* set status bits are cleared by writing 1 to them */ + outl(reg32, pmbase + GPE0_STS); + + return reg32; +} + +static void dump_gpe0_status(u32 gpe0_sts) +{ + int i; + printk_debug("GPE0_STS: "); + for (i=31; i<= 16; i--) { + if (gpe0_sts & (1 << i)) printk_debug("GPIO%d ", (i-16)); + } + if (gpe0_sts & (1 << 14)) printk_debug("USB4 "); + if (gpe0_sts & (1 << 13)) printk_debug("PME_B0 "); + if (gpe0_sts & (1 << 12)) printk_debug("USB3 "); + if (gpe0_sts & (1 << 11)) printk_debug("PME "); + if (gpe0_sts & (1 << 10)) printk_debug("EL_SCI/BATLOW "); + if (gpe0_sts & (1 << 9)) printk_debug("PCI_EXP "); + if (gpe0_sts & (1 << 8)) printk_debug("RI "); + if (gpe0_sts & (1 << 7)) printk_debug("SMB_WAK "); + if (gpe0_sts & (1 << 6)) printk_debug("TCO_SCI "); + if (gpe0_sts & (1 << 5)) printk_debug("AC97 "); + if (gpe0_sts & (1 << 4)) printk_debug("USB2 "); + if (gpe0_sts & (1 << 3)) printk_debug("USB1 "); + if (gpe0_sts & (1 << 2)) printk_debug("HOT_PLUG "); + if (gpe0_sts & (1 << 0)) printk_debug("THRM "); + printk_debug("\n"); +} + + +/** + * @brief read and clear ALT_GP_SMI_STS + * @return ALT_GP_SMI_STS register + */ +static u16 reset_alt_gp_smi_status(void) +{ + u16 reg16; + + reg16 = inl(pmbase + ALT_GP_SMI_STS); + /* set status bits are cleared by writing 1 to them */ + outl(reg16, pmbase + ALT_GP_SMI_STS); + + return reg16; +} + +static void dump_alt_gp_smi_status(u16 alt_gp_smi_sts) +{ + int i; + printk_debug("ALT_GP_SMI_STS: "); + for (i=15; i<= 0; i--) { + if (alt_gp_smi_sts & (1 << i)) printk_debug("GPI%d ", (i-16)); + } + printk_debug("\n"); +} + + + +/** + * @brief read and clear TCOx_STS + * @return TCOx_STS registers + */ +static u32 reset_tco_status(void) +{ + u32 tcobase = pmbase + 0x60; + u32 reg32; + + reg32 = inl(tcobase + 0x04); + /* set status bits are cleared by writing 1 to them */ + outl(reg32 & ~(1<<18), tcobase + 0x04); // Don't clear BOOT_STS before SECOND_TO_STS + if (reg32 & (1 << 18)) + outl(reg32 & (1<<18), tcobase + 0x04); // clear BOOT_STS + + return reg32; +} + + +static void dump_tco_status(u32 tco_sts) +{ + printk_debug("TCO_STS: "); + if (tco_sts & (1 << 20)) printk_debug("SMLINK_SLV "); + if (tco_sts & (1 << 18)) printk_debug("BOOT "); + if (tco_sts & (1 << 17)) printk_debug("SECOND_TO "); + if (tco_sts & (1 << 16)) printk_debug("INTRD_DET "); + if (tco_sts & (1 << 12)) printk_debug("DMISERR "); + if (tco_sts & (1 << 10)) printk_debug("DMISMI "); + if (tco_sts & (1 << 9)) printk_debug("DMISCI "); + if (tco_sts & (1 << 8)) printk_debug("BIOSWR "); + if (tco_sts & (1 << 7)) printk_debug("NEWCENTURY "); + if (tco_sts & (1 << 3)) printk_debug("TIMEOUT "); + if (tco_sts & (1 << 2)) printk_debug("TCO_INT "); + if (tco_sts & (1 << 1)) printk_debug("SW_TCO "); + if (tco_sts & (1 << 0)) printk_debug("NMI2SMI "); + printk_debug("\n"); +} + + + +/** + * @brief Set the EOS bit + */ +static void smi_set_eos(void) +{ + u8 reg8; + + reg8 = inb(pmbase + SMI_EN); + reg8 |= EOS; + outb(reg8, pmbase + SMI_EN); +} + +extern uint8_t smm_relocation_start, smm_relocation_end; + +void smm_relocate(void) +{ + u32 smi_en; + u16 pm1_en; + + printk_debug("Initializing SMM handler..."); + + pmbase = pci_read_config16(dev_find_slot(0, PCI_DEVFN(0x1f, 0)), 0x40) & 0xfffc; + printk_spew(" ... pmbase = 0x%04x\n", pmbase); + + smi_en = inl(pmbase + SMI_EN); + if (smi_en & APMC_EN) { + printk_info("SMI# handler already enabled?\n"); + return; + } + + /* copy the SMM relocation code */ + memcpy((void *)0x38000, &smm_relocation_start, + &smm_relocation_end - &smm_relocation_start); + + printk_debug("\n"); + dump_smi_status(reset_smi_status()); + dump_pm1_status(reset_pm1_status()); + dump_gpe0_status(reset_gpe0_status()); + dump_alt_gp_smi_status(reset_alt_gp_smi_status()); + dump_tco_status(reset_tco_status()); + + /* Enable SMI generation: + * - on TCO events + * - on APMC writes (io 0xb2) + * - on writes to SLP_EN (sleep states) + * - on writes to GBL_RLS (bios commands) + * No SMIs: + * - on microcontroller writes (io 0x62/0x66) + */ + + smi_en = 0; /* reset SMI enables */ + +#if 0 + smi_en |= LEGACY_USB2_EN | LEGACY_USB_EN; +#endif + smi_en |= TCO_EN; + smi_en |= APMC_EN; +#if DEBUG_PERIODIC_SMIS + /* Set DEBUG_PERIODIC_SMIS in i82801gx.h to debug using + * periodic SMIs. + */ + smi_en |= PERIODIC_EN; +#endif + smi_en |= SLP_SMI_EN; + smi_en |= BIOS_EN; + + /* The following need to be on for SMIs to happen */ + smi_en |= EOS | GBL_SMI_EN; + + outl(smi_en, pmbase + SMI_EN); + + pm1_en = 0; + pm1_en |= PWRBTN_EN; + pm1_en |= GBL_EN; + outw(pm1_en, pmbase + PM1_EN); + + /** + * There are several methods of raising a controlled SMI# via + * software, among them: + * - Writes to io 0xb2 (APMC) + * - Writes to the Local Apic ICR with Delivery mode SMI. + * + * Using the local apic is a bit more tricky. According to + * AMD Family 11 Processor BKDG no destination shorthand must be + * used. + * The whole SMM initialization is quite a bit hardware specific, so + * I'm not too worried about the better of the methods at the moment + */ + + /* raise an SMI interrupt */ + printk_spew(" ... raise SMI#\n"); + outb(0x00, 0xb2); +} + +void smm_install(void) +{ + /* enable the SMM memory window */ + pci_write_config8(dev_find_slot(0, PCI_DEVFN(0, 0)), SMRAM, + D_OPEN | G_SMRAME | C_BASE_SEG); + + /* copy the real SMM handler */ + memcpy((void *)0xa0000, smm, smm_len); + wbinvd(); + + /* close the SMM memory window and enable normal SMM */ + pci_write_config8(dev_find_slot(0, PCI_DEVFN(0, 0)), SMRAM, + G_SMRAME | C_BASE_SEG); +} + +void smm_init(void) +{ + // FIXME is this a race condition? + smm_relocate(); + smm_install(); + + // We're done. Make sure SMIs can happen! + smi_set_eos(); +} + +void smm_lock(void) +{ + /* LOCK the SMM memory window and enable normal SMM. + * After running this function, only a full reset can + * make the SMM registers writable again. + */ + printk_debug("Locking SMM.\n"); + pci_write_config8(dev_find_slot(0, PCI_DEVFN(0, 0)), SMRAM, + D_LCK | G_SMRAME | C_BASE_SEG); +} + +void smm_setup_structures(void *gnvs, void *tcg, void *smi1) +{ + /* The GDT or coreboot table is going to live here. But a long time + * after we relocated the GNVS, so this is not troublesome. + */ + *(u32 *)0x500 = (u32)gnvs; + *(u32 *)0x504 = (u32)tcg; + *(u32 *)0x508 = (u32)smi1; + outb(0xea, 0xb2); +} diff --git a/src/southbridge/intel/i82801dx/i82801dx_smihandler.c b/src/southbridge/intel/i82801dx/i82801dx_smihandler.c new file mode 100644 index 0000000000..9994429a2f --- /dev/null +++ b/src/southbridge/intel/i82801dx/i82801dx_smihandler.c @@ -0,0 +1,669 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2008-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 <arch/io.h> +#include <arch/romcc_io.h> +#include <console/console.h> +#include <cpu/x86/cache.h> +#include <cpu/x86/smm.h> +#include <device/pci_def.h> +#include "i82801dx.h" + +#define DEBUG_SMI + +#define APM_CNT 0xb2 +#define CST_CONTROL 0x85 +#define PST_CONTROL 0x80 +#define ACPI_DISABLE 0x1e +#define ACPI_ENABLE 0xe1 +#define GNVS_UPDATE 0xea +#define MBI_UPDATE 0xeb +#define APM_STS 0xb3 + +/* I830M */ +#define SMRAM 0x90 +#define D_OPEN (1 << 6) +#define D_CLS (1 << 5) +#define D_LCK (1 << 4) +#define G_SMRANE (1 << 3) +#define C_BASE_SEG ((0 << 2) | (1 << 1) | (0 << 0)) + +#include "i82801dx_nvs.h" + +/* While we read PMBASE dynamically in case it changed, let's + * initialize it with a sane value + */ +u16 pmbase = PMBASE_ADDR; +u8 smm_initialized = 0; + +unsigned char *mbi = NULL; +u32 mbi_len; +u8 mbi_initialized = 0; + +/* GNVS needs to be updated by an 0xEA PM Trap (B2) after it has been located + * by coreboot. + */ +global_nvs_t *gnvs = (global_nvs_t *)0x0; +void *tcg = (void *)0x0; +void *smi1 = (void *)0x0; + +/** + * @brief read and clear PM1_STS + * @return PM1_STS register + */ +static u16 reset_pm1_status(void) +{ + u16 reg16; + + reg16 = inw(pmbase + PM1_STS); + /* set status bits are cleared by writing 1 to them */ + outw(reg16, pmbase + PM1_STS); + + return reg16; +} + +static void dump_pm1_status(u16 pm1_sts) +{ + printk_spew("PM1_STS: "); + if (pm1_sts & (1 << 15)) printk_spew("WAK "); + if (pm1_sts & (1 << 14)) printk_spew("PCIEXPWAK "); + if (pm1_sts & (1 << 11)) printk_spew("PRBTNOR "); + if (pm1_sts & (1 << 10)) printk_spew("RTC "); + if (pm1_sts & (1 << 8)) printk_spew("PWRBTN "); + if (pm1_sts & (1 << 5)) printk_spew("GBL "); + if (pm1_sts & (1 << 4)) printk_spew("BM "); + if (pm1_sts & (1 << 0)) printk_spew("TMROF "); + printk_spew("\n"); + int reg16 = inw(pmbase + PM1_EN); + printk_spew("PM1_EN: %x\n", reg16); +} + +/** + * @brief read and clear SMI_STS + * @return SMI_STS register + */ +static u32 reset_smi_status(void) +{ + u32 reg32; + + reg32 = inl(pmbase + SMI_STS); + /* set status bits are cleared by writing 1 to them */ + outl(reg32, pmbase + SMI_STS); + + return reg32; +} + +static void dump_smi_status(u32 smi_sts) +{ + printk_debug("SMI_STS: "); + if (smi_sts & (1 << 26)) printk_debug("SPI "); + if (smi_sts & (1 << 25)) printk_debug("EL_SMI "); + if (smi_sts & (1 << 21)) printk_debug("MONITOR "); + if (smi_sts & (1 << 20)) printk_debug("PCI_EXP_SMI "); + if (smi_sts & (1 << 18)) printk_debug("INTEL_USB2 "); + if (smi_sts & (1 << 17)) printk_debug("LEGACY_USB2 "); + if (smi_sts & (1 << 16)) printk_debug("SMBUS_SMI "); + if (smi_sts & (1 << 15)) printk_debug("SERIRQ_SMI "); + if (smi_sts & (1 << 14)) printk_debug("PERIODIC "); + if (smi_sts & (1 << 13)) printk_debug("TCO "); + if (smi_sts & (1 << 12)) printk_debug("DEVMON "); + if (smi_sts & (1 << 11)) printk_debug("MCSMI "); + if (smi_sts & (1 << 10)) printk_debug("GPI "); + if (smi_sts & (1 << 9)) printk_debug("GPE0 "); + if (smi_sts & (1 << 8)) printk_debug("PM1 "); + if (smi_sts & (1 << 6)) printk_debug("SWSMI_TMR "); + if (smi_sts & (1 << 5)) printk_debug("APM "); + if (smi_sts & (1 << 4)) printk_debug("SLP_SMI "); + if (smi_sts & (1 << 3)) printk_debug("LEGACY_USB "); + if (smi_sts & (1 << 2)) printk_debug("BIOS "); + printk_debug("\n"); +} + + +/** + * @brief read and clear GPE0_STS + * @return GPE0_STS register + */ +static u32 reset_gpe0_status(void) +{ + u32 reg32; + + reg32 = inl(pmbase + GPE0_STS); + /* set status bits are cleared by writing 1 to them */ + outl(reg32, pmbase + GPE0_STS); + + return reg32; +} + +static void dump_gpe0_status(u32 gpe0_sts) +{ + int i; + printk_debug("GPE0_STS: "); + for (i=31; i<= 16; i--) { + if (gpe0_sts & (1 << i)) printk_debug("GPIO%d ", (i-16)); + } + if (gpe0_sts & (1 << 14)) printk_debug("USB4 "); + if (gpe0_sts & (1 << 13)) printk_debug("PME_B0 "); + if (gpe0_sts & (1 << 12)) printk_debug("USB3 "); + if (gpe0_sts & (1 << 11)) printk_debug("PME "); + if (gpe0_sts & (1 << 10)) printk_debug("EL_SCI/BATLOW "); + if (gpe0_sts & (1 << 9)) printk_debug("PCI_EXP "); + if (gpe0_sts & (1 << 8)) printk_debug("RI "); + if (gpe0_sts & (1 << 7)) printk_debug("SMB_WAK "); + if (gpe0_sts & (1 << 6)) printk_debug("TCO_SCI "); + if (gpe0_sts & (1 << 5)) printk_debug("AC97 "); + if (gpe0_sts & (1 << 4)) printk_debug("USB2 "); + if (gpe0_sts & (1 << 3)) printk_debug("USB1 "); + if (gpe0_sts & (1 << 2)) printk_debug("HOT_PLUG "); + if (gpe0_sts & (1 << 0)) printk_debug("THRM "); + printk_debug("\n"); +} + + +/** + * @brief read and clear TCOx_STS + * @return TCOx_STS registers + */ +static u32 reset_tco_status(void) +{ + u32 tcobase = pmbase + 0x60; + u32 reg32; + + reg32 = inl(tcobase + 0x04); + /* set status bits are cleared by writing 1 to them */ + outl(reg32 & ~(1<<18), tcobase + 0x04); // Don't clear BOOT_STS before SECOND_TO_STS + if (reg32 & (1 << 18)) + outl(reg32 & (1<<18), tcobase + 0x04); // clear BOOT_STS + + return reg32; +} + + +static void dump_tco_status(u32 tco_sts) +{ + printk_debug("TCO_STS: "); + if (tco_sts & (1 << 20)) printk_debug("SMLINK_SLV "); + if (tco_sts & (1 << 18)) printk_debug("BOOT "); + if (tco_sts & (1 << 17)) printk_debug("SECOND_TO "); + if (tco_sts & (1 << 16)) printk_debug("INTRD_DET "); + if (tco_sts & (1 << 12)) printk_debug("DMISERR "); + if (tco_sts & (1 << 10)) printk_debug("DMISMI "); + if (tco_sts & (1 << 9)) printk_debug("DMISCI "); + if (tco_sts & (1 << 8)) printk_debug("BIOSWR "); + if (tco_sts & (1 << 7)) printk_debug("NEWCENTURY "); + if (tco_sts & (1 << 3)) printk_debug("TIMEOUT "); + if (tco_sts & (1 << 2)) printk_debug("TCO_INT "); + if (tco_sts & (1 << 1)) printk_debug("SW_TCO "); + if (tco_sts & (1 << 0)) printk_debug("NMI2SMI "); + printk_debug("\n"); +} + +/* We are using PCIe accesses for now + * 1. the chipset can do it + * 2. we don't need to worry about how we leave 0xcf8/0xcfc behind + */ +// #include "../../../northbridge/intel/i945/pcie_config.c" + +int southbridge_io_trap_handler(int smif) +{ + switch (smif) { + case 0x32: + printk_debug("OS Init\n"); + /* gnvs->smif: + * On success, the IO Trap Handler returns 0 + * On failure, the IO Trap Handler returns a value != 0 + */ + gnvs->smif = 0; + return 1; /* IO trap handled */ + } + + /* Not handled */ + return 0; +} + +/** + * @brief Set the EOS bit + */ +void southbridge_smi_set_eos(void) +{ + u8 reg8; + + reg8 = inb(pmbase + SMI_EN); + reg8 |= EOS; + outb(reg8, pmbase + SMI_EN); +} + +static void busmaster_disable_on_bus(int bus) +{ + int slot, func; + unsigned int val; + unsigned char hdr; + + for (slot = 0; slot < 0x20; slot++) { + for (func = 0; func < 8; func++) { + u32 reg32; + device_t dev = PCI_DEV(bus, slot, func); + + val = pci_read_config32(dev, PCI_VENDOR_ID); + + if (val == 0xffffffff || val == 0x00000000 || + val == 0x0000ffff || val == 0xffff0000) + continue; + + /* Disable Bus Mastering for this one device */ + reg32 = pci_read_config32(dev, PCI_COMMAND); + reg32 &= ~PCI_COMMAND_MASTER; + pci_write_config32(dev, PCI_COMMAND, reg32); + + /* If this is a bridge, then follow it. */ + hdr = pci_read_config8(dev, PCI_HEADER_TYPE); + hdr &= 0x7f; + if (hdr == PCI_HEADER_TYPE_BRIDGE || + hdr == PCI_HEADER_TYPE_CARDBUS) { + unsigned int buses; + buses = pci_read_config32(dev, PCI_PRIMARY_BUS); + busmaster_disable_on_bus((buses >> 8) & 0xff); + } + } + } +} + + +static void southbridge_smi_sleep(unsigned int node, smm_state_save_area_t *state_save) +{ + u8 reg8; + u32 reg32; + u8 slp_typ; + /* FIXME: the power state on boot should be read from + * CMOS or even better from GNVS. Right now it's hard + * coded at compile time. + */ + u8 s5pwr = CONFIG_MAINBOARD_POWER_ON_AFTER_POWER_FAIL; + + /* First, disable further SMIs */ + reg8 = inb(pmbase + SMI_EN); + reg8 &= ~SLP_SMI_EN; + outb(reg8, pmbase + SMI_EN); + + /* Figure out SLP_TYP */ + reg32 = inl(pmbase + PM1_CNT); + printk_spew("SMI#: SLP = 0x%08x\n", reg32); + slp_typ = (reg32 >> 10) & 7; + + /* Next, do the deed. + */ + + switch (slp_typ) { + case 0: printk_debug("SMI#: Entering S0 (On)\n"); break; + case 1: printk_debug("SMI#: Entering S1 (Assert STPCLK#)\n"); break; + case 5: + printk_debug("SMI#: Entering S3 (Suspend-To-RAM)\n"); + /* Invalidate the cache before going to S3 */ + wbinvd(); + break; + case 6: printk_debug("SMI#: Entering S4 (Suspend-To-Disk)\n"); break; + case 7: + printk_debug("SMI#: Entering S5 (Soft Power off)\n"); + + outl(0, pmbase + GPE0_EN); + + /* Should we keep the power state after a power loss? + * In case the setting is "ON" or "OFF" we don't have + * to do anything. But if it's "KEEP" we have to switch + * to "OFF" before entering S5. + */ + if (s5pwr == MAINBOARD_POWER_KEEP) { + reg8 = pci_read_config8(PCI_DEV(0, 0x1f, 0), GEN_PMCON_3); + reg8 |= 1; + pci_write_config8(PCI_DEV(0, 0x1f, 0), GEN_PMCON_3, reg8); + } + + /* also iterates over all bridges on bus 0 */ + busmaster_disable_on_bus(0); + break; + default: printk_debug("SMI#: ERROR: SLP_TYP reserved\n"); break; + } + + /* Write back to the SLP register to cause the originally intended + * event again. We need to set BIT13 (SLP_EN) though to make the + * sleep happen. + */ + outl(reg32 | SLP_EN, pmbase + PM1_CNT); + + /* In most sleep states, the code flow of this function ends at + * the line above. However, if we entered sleep state S1 and wake + * up again, we will continue to execute code in this function. + */ + reg32 = inl(pmbase + PM1_CNT); + if (reg32 & SCI_EN) { + /* The OS is not an ACPI OS, so we set the state to S0 */ + reg32 &= ~(SLP_EN | SLP_TYP); + outl(reg32, pmbase + PM1_CNT); + } +} + +static void southbridge_smi_apmc(unsigned int node, smm_state_save_area_t *state_save) +{ + u32 pmctrl; + u8 reg8; + + /* Emulate B2 register as the FADT / Linux expects it */ + + reg8 = inb(APM_CNT); + switch (reg8) { + case CST_CONTROL: + /* Calling this function seems to cause + * some kind of race condition in Linux + * and causes a kernel oops + */ + printk_debug("C-state control\n"); + break; + case PST_CONTROL: + /* Calling this function seems to cause + * some kind of race condition in Linux + * and causes a kernel oops + */ + printk_debug("P-state control\n"); + break; + case ACPI_DISABLE: + pmctrl = inl(pmbase + PM1_CNT); + pmctrl &= ~SCI_EN; + outl(pmctrl, pmbase + PM1_CNT); + printk_debug("SMI#: ACPI disabled.\n"); + break; + case ACPI_ENABLE: + pmctrl = inl(pmbase + PM1_CNT); + pmctrl |= SCI_EN; + outl(pmctrl, pmbase + PM1_CNT); + printk_debug("SMI#: ACPI enabled.\n"); + break; + case GNVS_UPDATE: + if (smm_initialized) { + printk_debug("SMI#: SMM structures already initialized!\n"); + return; + } + gnvs = *(global_nvs_t **)0x500; + tcg = *(void **)0x504; + smi1 = *(void **)0x508; + smm_initialized = 1; + printk_debug("SMI#: Setting up structures to %p, %p, %p\n", gnvs, tcg, smi1); + break; + case MBI_UPDATE: // FIXME + if (mbi_initialized) { + printk_debug("SMI#: mbi already registered!\n"); + return; + } + mbi = *(void **)0x500; + mbi_len = *(u32 *)0x504; + mbi_initialized = 1; + printk_debug("SMI#: Registered MBI at %p (%d bytes)\n", mbi, mbi_len); + break; + + default: + printk_debug("SMI#: Unknown function APM_CNT=%02x\n", reg8); + } +} + +static void southbridge_smi_pm1(unsigned int node, smm_state_save_area_t *state_save) +{ + u16 pm1_sts; + + pm1_sts = reset_pm1_status(); + dump_pm1_status(pm1_sts); + + /* While OSPM is not active, poweroff immediately + * on a power button event. + */ + if (pm1_sts & PWRBTN_STS) { + // power button pressed + u32 reg32; + reg32 = (7 << 10) | (1 << 13); + outl(reg32, pmbase + PM1_CNT); + } +} + +static void southbridge_smi_gpe0(unsigned int node, smm_state_save_area_t *state_save) +{ + u32 gpe0_sts; + + gpe0_sts = reset_gpe0_status(); + dump_gpe0_status(gpe0_sts); +} + +void __attribute__((weak)) mainboard_smi_gpi(u16 gpi_sts); + +static void southbridge_smi_gpi(unsigned int node, smm_state_save_area_t *state_save) +{ + u16 reg16; + reg16 = inw(pmbase + ALT_GP_SMI_STS); + outl(reg16, pmbase + ALT_GP_SMI_STS); + + reg16 &= inw(pmbase + ALT_GP_SMI_EN); + + if (mainboard_smi_gpi) { + mainboard_smi_gpi(reg16); + } else { + if (reg16) + printk_debug("GPI (mask %04x)\n",reg16); + } +} + +static void southbridge_smi_mc(unsigned int node, smm_state_save_area_t *state_save) +{ + u32 reg32; + + reg32 = inl(pmbase + SMI_EN); + + /* Are periodic SMIs enabled? */ + if ((reg32 & MCSMI_EN) == 0) + return; + + printk_debug("Microcontroller SMI.\n"); +} + + + +static void southbridge_smi_tco(unsigned int node, smm_state_save_area_t *state_save) +{ + u32 tco_sts; + + tco_sts = reset_tco_status(); + + /* Any TCO event? */ + if (!tco_sts) + return; + + if (tco_sts & (1 << 8)) { // BIOSWR + u8 bios_cntl; + + bios_cntl = pci_read_config16(PCI_DEV(0, 0x1f, 0), 0xdc); + + if (bios_cntl & 1) { + /* BWE is RW, so the SMI was caused by a + * write to BWE, not by a write to the BIOS + */ + + /* This is the place where we notice someone + * is trying to tinker with the BIOS. We are + * trying to be nice and just ignore it. A more + * resolute answer would be to power down the + * box. + */ + printk_debug("Switching back to RO\n"); + pci_write_config32(PCI_DEV(0, 0x1f, 0), 0xdc, (bios_cntl & ~1)); + } /* No else for now? */ + } else if (tco_sts & (1 << 3)) { /* TIMEOUT */ + /* Handle TCO timeout */ + printk_debug("TCO Timeout.\n"); + } else if (!tco_sts) { + dump_tco_status(tco_sts); + } +} + +static void southbridge_smi_periodic(unsigned int node, smm_state_save_area_t *state_save) +{ + u32 reg32; + + reg32 = inl(pmbase + SMI_EN); + + /* Are periodic SMIs enabled? */ + if ((reg32 & PERIODIC_EN) == 0) + return; + + printk_debug("Periodic SMI.\n"); +} + +static void southbridge_smi_monitor(unsigned int node, smm_state_save_area_t *state_save) +{ +#define IOTRAP(x) (trap_sts & (1 << x)) +#if 0 + u32 trap_sts, trap_cycle; + u32 data, mask = 0; + int i; + + trap_sts = RCBA32(0x1e00); // TRSR - Trap Status Register + RCBA32(0x1e00) = trap_sts; // Clear trap(s) in TRSR + + trap_cycle = RCBA32(0x1e10); + for (i=16; i<20; i++) { + if (trap_cycle & (1 << i)) + mask |= (0xff << ((i - 16) << 2)); + } + + + /* IOTRAP(3) SMI function call */ + if (IOTRAP(3)) { + if (gnvs && gnvs->smif) + io_trap_handler(gnvs->smif); // call function smif + return; + } + + /* IOTRAP(2) currently unused + * IOTRAP(1) currently unused */ + + /* IOTRAP(0) SMIC */ + if (IOTRAP(0)) { + if (!(trap_cycle & (1 << 24))) { // It's a write + printk_debug("SMI1 command\n"); + data = RCBA32(0x1e18); + data &= mask; + // if (smi1) + // southbridge_smi_command(data); + // return; + } + // Fall through to debug + } + + printk_debug(" trapped io address = 0x%x\n", trap_cycle & 0xfffc); + for (i=0; i < 4; i++) if(IOTRAP(i)) printk_debug(" TRAPĀ = %d\n", i); + printk_debug(" AHBE = %x\n", (trap_cycle >> 16) & 0xf); + printk_debug(" MASK = 0x%08x\n", mask); + printk_debug(" read/write: %s\n", (trap_cycle & (1 << 24)) ? "read" : "write"); + + if (!(trap_cycle & (1 << 24))) { + /* Write Cycle */ + data = RCBA32(0x1e18); + printk_debug(" iotrap written data = 0x%08x\n", data); + } +#endif +#undef IOTRAP +} + +typedef void (*smi_handler)(unsigned int node, + smm_state_save_area_t *state_save); + +smi_handler southbridge_smi[32] = { + NULL, // [0] reserved + NULL, // [1] reserved + NULL, // [2] BIOS_STS + NULL, // [3] LEGACY_USB_STS + southbridge_smi_sleep, // [4] SLP_SMI_STS + southbridge_smi_apmc, // [5] APM_STS + NULL, // [6] SWSMI_TMR_STS + NULL, // [7] reserved + southbridge_smi_pm1, // [8] PM1_STS + southbridge_smi_gpe0, // [9] GPE0_STS + southbridge_smi_gpi, // [10] GPI_STS + southbridge_smi_mc, // [11] MCSMI_STS + NULL, // [12] DEVMON_STS + southbridge_smi_tco, // [13] TCO_STS + southbridge_smi_periodic, // [14] PERIODIC_STS + NULL, // [15] SERIRQ_SMI_STS + NULL, // [16] SMBUS_SMI_STS + NULL, // [17] LEGACY_USB2_STS + NULL, // [18] INTEL_USB2_STS + NULL, // [19] reserved + NULL, // [20] PCI_EXP_SMI_STS + southbridge_smi_monitor, // [21] MONITOR_STS + NULL, // [22] reserved + NULL, // [23] reserved + NULL, // [24] reserved + NULL, // [25] EL_SMI_STS + NULL, // [26] SPI_STS + NULL, // [27] reserved + NULL, // [28] reserved + NULL, // [29] reserved + NULL, // [30] reserved + NULL // [31] reserved +}; + +/** + * @brief Interrupt handler for SMI# + * + * @param smm_revision revision of the smm state save map + */ + +void southbridge_smi_handler(unsigned int node, smm_state_save_area_t *state_save) +{ + int i, dump = 0; + u32 smi_sts; + + /* Update global variable pmbase */ + pmbase = pci_read_config16(PCI_DEV(0, 0x1f, 0), 0x40) & 0xfffc; + + /* We need to clear the SMI status registers, or we won't see what's + * happening in the following calls. + */ + smi_sts = reset_smi_status(); + + /* Filter all non-enabled SMI events */ + // FIXME Double check, this clears MONITOR + // smi_sts &= inl(pmbase + SMI_EN); + + /* Call SMI sub handler for each of the status bits */ + for (i = 0; i < 31; i++) { + if (smi_sts & (1 << i)) { + if (southbridge_smi[i]) + southbridge_smi[i](node, state_save); + else { + printk_debug("SMI_STS[%d] occured, but no " + "handler available.\n", i); + dump = 1; + } + } + } + + if(dump) { + dump_smi_status(smi_sts); + } + +} diff --git a/src/southbridge/intel/i82801dx/i82801dx_tco_timer.c b/src/southbridge/intel/i82801dx/i82801dx_tco_timer.c new file mode 100644 index 0000000000..ea5485f799 --- /dev/null +++ b/src/southbridge/intel/i82801dx/i82801dx_tco_timer.c @@ -0,0 +1,37 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2008 Joseph Smith <joe@settoplinux.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 + * + */ + +static void i82801dx_halt_tco_timer(void) +{ + device_t dev; + uint16_t halt_tco_timer; + + /* Set the LPC device statically. */ + dev = PCI_DEV(0x0, 0x1f, 0x0); + + /* Temporarily set ACPI base address (I/O space). */ + pci_write_config32(dev, PMBASE, (PMBASE_ADDR | 1)); + + /* Enable ACPI I/O. */ + pci_write_config8(dev, ACPI_CNTL, 0x10); + + /* Halt the TCO timer, preventing SMI and automatic reboot */ + outw(inw(PMBASE_ADDR + TCOBASE + TCO1_CNT) | (1 << 11), PMBASE_ADDR + TCOBASE + TCO1_CNT); +} |