diff options
author | des <des@FreeBSD.org> | 2007-08-13 18:52:37 +0000 |
---|---|---|
committer | des <des@FreeBSD.org> | 2007-08-13 18:52:37 +0000 |
commit | 4720b5aa389174b60e267cefb9ab1851a4420a66 (patch) | |
tree | 24b5d8b90229b34ff66d0f0587cf95ea4181c9ad /sys | |
parent | 47d488244c8d1ac0d8f9233b24cf663131829cc0 (diff) | |
download | FreeBSD-src-4720b5aa389174b60e267cefb9ab1851a4420a66.zip FreeBSD-src-4720b5aa389174b60e267cefb9ab1851a4420a66.tar.gz |
Update to support ICH[678] chipsets (based on a patch by Takeharu KATO)
Fix a resource allocation bug (explained by jhb on -acpi)
Thanks for Mike Tancsa for testing and helping track down the bug.
Approved by: re (kensmith)
MFC after: 3 weeks
Diffstat (limited to 'sys')
-rw-r--r-- | sys/dev/ichwd/ichwd.c | 261 | ||||
-rw-r--r-- | sys/dev/ichwd/ichwd.h | 44 |
2 files changed, 229 insertions, 76 deletions
diff --git a/sys/dev/ichwd/ichwd.c b/sys/dev/ichwd/ichwd.c index 86d9ad3..f6152dc 100644 --- a/sys/dev/ichwd/ichwd.c +++ b/sys/dev/ichwd/ichwd.c @@ -51,6 +51,8 @@ * (document no. 292273-001). The WDT is also described in the individual * chipset datasheets, e.g. Intel82801EB ICH5 / 82801ER ICH5R Datasheet * (document no. 252516-001) sections 9.10 and 9.11. + * + * ICH6/7/8 support by Takeharu KATO <takeharu1219@ybb.ne.jp> */ #include <sys/cdefs.h> @@ -71,20 +73,27 @@ __FBSDID("$FreeBSD$"); #include <dev/ichwd/ichwd.h> static struct ichwd_device ichwd_devices[] = { - { VENDORID_INTEL, DEVICEID_82801AA, "Intel 82801AA watchdog timer" }, - { VENDORID_INTEL, DEVICEID_82801AB, "Intel 82801AB watchdog timer" }, - { VENDORID_INTEL, DEVICEID_82801BA, "Intel 82801BA watchdog timer" }, - { VENDORID_INTEL, DEVICEID_82801BAM, "Intel 82801BAM watchdog timer" }, - { VENDORID_INTEL, DEVICEID_82801CA, "Intel 82801CA watchdog timer" }, - { VENDORID_INTEL, DEVICEID_82801CAM, "Intel 82801CAM watchdog timer" }, - { VENDORID_INTEL, DEVICEID_82801DB, "Intel 82801DB watchdog timer" }, - { VENDORID_INTEL, DEVICEID_82801DBM, "Intel 82801DBM watchdog timer" }, - { VENDORID_INTEL, DEVICEID_82801E, "Intel 82801E watchdog timer" }, - { VENDORID_INTEL, DEVICEID_82801EBR, "Intel 82801EB/ER watchdog timer" }, - { VENDORID_INTEL, DEVICEID_82801FBR, "Intel 82801FB/FR watchdog timer" }, - { VENDORID_INTEL, DEVICEID_ICH5, "Intel ICH5 watchdog timer"}, - { VENDORID_INTEL, DEVICEID_6300ESB, "Intel 6300ESB watchdog timer"}, - { 0, 0, NULL }, + { DEVICEID_82801AA, "Intel 82801AA watchdog timer", 1 }, + { DEVICEID_82801AB, "Intel 82801AB watchdog timer", 1 }, + { DEVICEID_82801BA, "Intel 82801BA watchdog timer", 2 }, + { DEVICEID_82801BAM, "Intel 82801BAM watchdog timer", 2 }, + { DEVICEID_82801CA, "Intel 82801CA watchdog timer", 3 }, + { DEVICEID_82801CAM, "Intel 82801CAM watchdog timer", 3 }, + { DEVICEID_82801DB, "Intel 82801DB watchdog timer", 4 }, + { DEVICEID_82801DBM, "Intel 82801DBM watchdog timer", 4 }, + { DEVICEID_82801E, "Intel 82801E watchdog timer", 5 }, + { DEVICEID_82801EBR, "Intel 82801EB/ER watchdog timer", 5 }, + { DEVICEID_6300ESB, "Intel 6300ESB watchdog timer", 5 }, + { DEVICEID_82801FBR, "Intel 82801FB/FR watchdog timer", 6 }, + { DEVICEID_ICH6M, "Intel ICH6M watchdog timer", 6 }, + { DEVICEID_ICH6W, "Intel ICH6W watchdog timer", 6 }, + { DEVICEID_ICH7, "Intel ICH7 watchdog timer", 7 }, + { DEVICEID_ICH7M, "Intel ICH7M watchdog timer", 7 }, + { DEVICEID_ICH7MDH, "Intel ICH7MDH watchdog timer", 7 }, + { DEVICEID_ICH8, "Intel ICH8 watchdog timer", 8 }, + { DEVICEID_ICH8DH, "Intel ICH8DH watchdog timer", 8 }, + { DEVICEID_ICH8DO, "Intel ICH8DO watchdog timer", 8 }, + { 0, NULL, 0 }, }; static devclass_t ichwd_devclass; @@ -95,6 +104,10 @@ static devclass_t ichwd_devclass; bus_space_read_2((sc)->tco_bst, (sc)->tco_bsh, (off)) #define ichwd_read_tco_4(sc, off) \ bus_space_read_4((sc)->tco_bst, (sc)->tco_bsh, (off)) +#define ichwd_read_smi_4(sc, off) \ + bus_space_read_4((sc)->smi_bst, (sc)->smi_bsh, (off)) +#define ichwd_read_gcs_4(sc, off) \ + bus_space_read_4((sc)->gcs_bst, (sc)->gcs_bsh, (off)) #define ichwd_write_tco_1(sc, off, val) \ bus_space_write_1((sc)->tco_bst, (sc)->tco_bsh, (off), (val)) @@ -102,11 +115,16 @@ static devclass_t ichwd_devclass; bus_space_write_2((sc)->tco_bst, (sc)->tco_bsh, (off), (val)) #define ichwd_write_tco_4(sc, off, val) \ bus_space_write_4((sc)->tco_bst, (sc)->tco_bsh, (off), (val)) - -#define ichwd_read_smi_4(sc, off) \ - bus_space_read_4((sc)->smi_bst, (sc)->smi_bsh, (off)) #define ichwd_write_smi_4(sc, off, val) \ bus_space_write_4((sc)->smi_bst, (sc)->smi_bsh, (off), (val)) +#define ichwd_write_gcs_4(sc, off, val) \ + bus_space_write_4((sc)->gcs_bst, (sc)->gcs_bsh, (off), (val)) + +#define ichwd_verbose_printf(dev, ...) \ + do { \ + if (bootverbose) \ + device_printf(dev, __VA_ARGS__);\ + } while (0) static __inline void ichwd_intr_enable(struct ichwd_softc *sc) @@ -136,8 +154,7 @@ ichwd_tmr_enable(struct ichwd_softc *sc) cnt = ichwd_read_tco_2(sc, TCO1_CNT) & TCO_CNT_PRESERVE; ichwd_write_tco_2(sc, TCO1_CNT, cnt & ~TCO_TMR_HALT); sc->active = 1; - if (bootverbose) - device_printf(sc->device, "timer enabled\n"); + ichwd_verbose_printf(sc->device, "timer enabled\n"); } static __inline void @@ -148,25 +165,85 @@ ichwd_tmr_disable(struct ichwd_softc *sc) cnt = ichwd_read_tco_2(sc, TCO1_CNT) & TCO_CNT_PRESERVE; ichwd_write_tco_2(sc, TCO1_CNT, cnt | TCO_TMR_HALT); sc->active = 0; - if (bootverbose) - device_printf(sc->device, "timer disabled\n"); + ichwd_verbose_printf(sc->device, "timer disabled\n"); } static __inline void ichwd_tmr_reload(struct ichwd_softc *sc) { - ichwd_write_tco_1(sc, TCO_RLD, 1); - if (bootverbose) - device_printf(sc->device, "timer reloaded\n"); + if (sc->ich_version <= 5) + ichwd_write_tco_1(sc, TCO_RLD, 1); + else + ichwd_write_tco_2(sc, TCO_RLD, 1); + + ichwd_verbose_printf(sc->device, "timer reloaded\n"); } static __inline void -ichwd_tmr_set(struct ichwd_softc *sc, uint8_t timeout) +ichwd_tmr_set(struct ichwd_softc *sc, unsigned int timeout) { - ichwd_write_tco_1(sc, TCO_TMR, timeout); + + /* + * If the datasheets are to be believed, the minimum value + * actually varies from chipset to chipset - 4 for ICH5 and 2 for + * all other chipsets. I suspect this is a bug in the ICH5 + * datasheet and that the minimum is uniformly 2, but I'd rather + * err on the side of caution. + */ + if (timeout < 4) + timeout = 4; + + if (sc->ich_version <= 5) { + uint8_t tmr_val8 = ichwd_read_tco_1(sc, TCO_TMR1); + + tmr_val8 &= 0xc0; + if (timeout > 0xbf) + timeout = 0xbf; + tmr_val8 |= timeout; + ichwd_write_tco_1(sc, TCO_TMR1, tmr_val8); + } else { + uint16_t tmr_val16 = ichwd_read_tco_2(sc, TCO_TMR2); + + tmr_val16 &= 0xfc00; + if (timeout > 0x0bff) + timeout = 0x0bff; + tmr_val16 |= timeout; + ichwd_write_tco_2(sc, TCO_TMR2, tmr_val16); + } + sc->timeout = timeout; - if (bootverbose) - device_printf(sc->device, "timeout set to %u ticks\n", timeout); + + ichwd_verbose_printf(sc->device, "timeout set to %u ticks\n", timeout); +} + +static __inline int +ichwd_clear_noreboot(struct ichwd_softc *sc) +{ + uint32_t status; + int rc = 0; + + /* try to clear the NO_REBOOT bit */ + if (sc->ich_version <= 5) { + status = pci_read_config(sc->ich, ICH_GEN_STA, 1); + status &= ~ICH_GEN_STA_NO_REBOOT; + pci_write_config(sc->ich, ICH_GEN_STA, status, 1); + status = pci_read_config(sc->ich, ICH_GEN_STA, 1); + if (status & ICH_GEN_STA_NO_REBOOT) + rc = EIO; + } else { + status = ichwd_read_gcs_4(sc, 0); + status &= ~ICH_GCS_NO_REBOOT; + ichwd_write_gcs_4(sc, 0, status); + status = ichwd_read_gcs_4(sc, 0); + if (status & ICH_GCS_NO_REBOOT) + rc = EIO; + } + + if (rc) + device_printf(sc->device, + "ICH WDT present but disabled in BIOS or hardware\n"); + + return (rc); } /* @@ -181,14 +258,12 @@ ichwd_event(void *arg, unsigned int cmd, int *error) /* convert from power-of-two-ns to WDT ticks */ cmd &= WD_INTERVAL; timeout = ((uint64_t)1 << cmd) / ICHWD_TICK; - if (cmd > 0 && cmd <= 63 - && timeout >= ICHWD_MIN_TIMEOUT && timeout <= ICHWD_MAX_TIMEOUT) { + if (cmd) { if (timeout != sc->timeout) { if (!sc->active) ichwd_tmr_enable(sc); ichwd_tmr_set(sc, timeout); } - ichwd_tmr_reload(sc); *error = 0; } else { @@ -197,7 +272,28 @@ ichwd_event(void *arg, unsigned int cmd, int *error) } } -static unsigned int pmbase = 0; +static device_t +ichwd_find_ich_lpc_bridge(struct ichwd_device **id_p) +{ + struct ichwd_device *id; + device_t ich = NULL; + + /* look for an ICH LPC interface bridge */ + for (id = ichwd_devices; id->desc != NULL; ++id) + if ((ich = pci_find_device(VENDORID_INTEL, id->device)) != NULL) + break; + + if (ich == NULL) + return (NULL); + + ichwd_verbose_printf(ich, "found ICH%d or equivalent chipset: %s\n", + id->version, id->desc); + + if (id_p) + *id_p = id; + + return (ich); +} /* * Look for an ICH LPC interface bridge. If one is found, register an @@ -206,49 +302,40 @@ static unsigned int pmbase = 0; static void ichwd_identify(driver_t *driver, device_t parent) { - struct ichwd_device *id; + struct ichwd_device *id_p; device_t ich = NULL; device_t dev; + uint32_t rcba; + int rc; - /* look for an ICH LPC interface bridge */ - for (id = ichwd_devices; id->desc != NULL; ++id) - if ((ich = pci_find_device(id->vendor, id->device)) != NULL) - break; + ich = ichwd_find_ich_lpc_bridge(&id_p); if (ich == NULL) return; - if (bootverbose) - printf("%s(): found ICH chipset: %s\n", __func__, id->desc); - - /* get for ACPI base address */ - pmbase = pci_read_config(ich, ICH_PMBASE, 2) & ICH_PMBASE_MASK; - if (pmbase == 0) { - if (bootverbose) - printf("%s(): ICH PMBASE register is empty\n", - __func__); - return; - } - - /* try to clear the NO_REBOOT bit */ - pci_write_config(ich, ICH_GEN_STA, 0x00, 1); - if (pci_read_config(ich, ICH_GEN_STA, 1) & ICH_GEN_STA_NO_REBOOT) { - if (bootverbose) - printf("%s(): ICH WDT present but disabled\n", - __func__); - return; - } - /* good, add child to bus */ if ((dev = device_find_child(parent, driver->name, 0)) == NULL) dev = BUS_ADD_CHILD(parent, 0, driver->name, 0); - if (dev != NULL) - device_set_desc_copy(dev, id->desc); + if (dev == NULL) + return; + + device_set_desc_copy(dev, id_p->desc); + + if (id_p->version >= 6) { + /* get RCBA (root complex base address) */ + rcba = pci_read_config(ich, ICH_RCBA, 4); + rc = bus_set_resource(ich, SYS_RES_MEMORY, 0, + (rcba & 0xffffc000) + ICH_GCS_OFFSET, ICH_GCS_SIZE); + if (rc) + ichwd_verbose_printf(dev, + "Can not set memory resource for RCBA\n"); + } } static int ichwd_probe(device_t dev) { + (void)dev; return (0); } @@ -257,18 +344,32 @@ static int ichwd_attach(device_t dev) { struct ichwd_softc *sc; + struct ichwd_device *id_p; + device_t ich; + unsigned int pmbase = 0; sc = device_get_softc(dev); sc->device = dev; + ich = ichwd_find_ich_lpc_bridge(&id_p); + if (ich == NULL) { + device_printf(sc->device, "Can not find ICH device.\n"); + goto fail; + } + sc->ich = ich; + sc->ich_version = id_p->version; + + /* get ACPI base address */ + pmbase = pci_read_config(ich, ICH_PMBASE, 2) & ICH_PMBASE_MASK; if (pmbase == 0) { - printf("Not found\n"); + device_printf(dev, "ICH PMBASE register is empty\n"); + goto fail; } /* allocate I/O register space */ sc->smi_rid = 0; sc->smi_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->smi_rid, - pmbase + SMI_BASE, ~0ul, SMI_LEN, + pmbase + SMI_BASE, pmbase + SMI_BASE + SMI_LEN - 1, SMI_LEN, RF_ACTIVE | RF_SHAREABLE); if (sc->smi_res == NULL) { device_printf(dev, "unable to reserve SMI registers\n"); @@ -279,7 +380,7 @@ ichwd_attach(device_t dev) sc->tco_rid = 1; sc->tco_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->tco_rid, - pmbase + TCO_BASE, ~0ul, TCO_LEN, + pmbase + TCO_BASE, pmbase + TCO_BASE + TCO_LEN - 1, TCO_LEN, RF_ACTIVE | RF_SHAREABLE); if (sc->tco_res == NULL) { device_printf(dev, "unable to reserve TCO registers\n"); @@ -287,8 +388,30 @@ ichwd_attach(device_t dev) } sc->tco_bst = rman_get_bustag(sc->tco_res); sc->tco_bsh = rman_get_bushandle(sc->tco_res); - /* reset the watchdog status registers */ + sc->gcs_rid = 0; + if (sc->ich_version >= 6) { + sc->gcs_res = bus_alloc_resource_any(ich, SYS_RES_MEMORY, + &sc->gcs_rid, RF_ACTIVE|RF_SHAREABLE); + if (sc->gcs_res == NULL) { + device_printf(dev, "unable to reserve GCS registers\n"); + goto fail; + } + sc->gcs_bst = rman_get_bustag(sc->gcs_res); + sc->gcs_bsh = rman_get_bushandle(sc->gcs_res); + } else { + sc->gcs_res = 0; + sc->gcs_bst = 0; + sc->gcs_bsh = 0; + } + + if (ichwd_clear_noreboot(sc) != 0) + goto fail; + + device_printf(dev, "%s (ICH%d or equivalent)\n", + device_get_desc(dev), sc->ich_version); + + /* reset the watchdog status registers */ ichwd_sts_reset(sc); /* make sure the WDT starts out inactive */ @@ -309,6 +432,10 @@ ichwd_attach(device_t dev) if (sc->smi_res != NULL) bus_release_resource(dev, SYS_RES_IOPORT, sc->smi_rid, sc->smi_res); + if (sc->gcs_res != NULL) + bus_release_resource(ich, SYS_RES_MEMORY, + sc->gcs_rid, sc->gcs_res); + return (ENXIO); } @@ -316,6 +443,7 @@ static int ichwd_detach(device_t dev) { struct ichwd_softc *sc; + device_t ich = NULL; sc = device_get_softc(dev); @@ -338,6 +466,11 @@ ichwd_detach(device_t dev) bus_release_resource(dev, SYS_RES_IOPORT, sc->tco_rid, sc->tco_res); bus_release_resource(dev, SYS_RES_IOPORT, sc->smi_rid, sc->smi_res); + /* deallocate memory resource */ + ich = ichwd_find_ich_lpc_bridge(NULL); + if (sc->gcs_res && ich) + bus_release_resource(ich, SYS_RES_MEMORY, sc->gcs_rid, sc->gcs_res); + return (0); } diff --git a/sys/dev/ichwd/ichwd.h b/sys/dev/ichwd/ichwd.h index ad66f9a..cf80c28 100644 --- a/sys/dev/ichwd/ichwd.h +++ b/sys/dev/ichwd/ichwd.h @@ -32,13 +32,15 @@ #define _ICHWD_H_ struct ichwd_device { - uint16_t vendor; uint16_t device; char *desc; + unsigned int version; }; struct ichwd_softc { device_t device; + device_t ich; + int ich_version; int active; unsigned int timeout; @@ -53,6 +55,11 @@ struct ichwd_softc { bus_space_tag_t tco_bst; bus_space_handle_t tco_bsh; + int gcs_rid; + struct resource *gcs_res; + bus_space_tag_t gcs_bst; + bus_space_handle_t gcs_bsh; + eventhandler_tag ev_tag; }; @@ -69,28 +76,45 @@ struct ichwd_softc { #define DEVICEID_82801EBR 0x24d0 #define DEVICEID_6300ESB 0x25a1 #define DEVICEID_82801FBR 0x2640 -#define DEVICEID_ICH5 0x27b8 - -/* ICH LPC Interface Bridge Registers */ +#define DEVICEID_ICH6M 0x2641 +#define DEVICEID_ICH6W 0x2642 +#define DEVICEID_ICH7 0x27b8 +#define DEVICEID_ICH7M 0x27b9 +#define DEVICEID_ICH7MDH 0x27bd +#define DEVICEID_ICH8 0x2810 +#define DEVICEID_ICH8DH 0x2812 +#define DEVICEID_ICH8DO 0x2814 + +/* ICH LPC Interface Bridge Registers (ICH5 and older) */ #define ICH_GEN_STA 0xd4 #define ICH_GEN_STA_NO_REBOOT 0x02 #define ICH_PMBASE 0x40 /* ACPI base address register */ #define ICH_PMBASE_MASK 0x7f80 /* bits 7-15 */ +/* ICH Chipset Configuration Registers (ICH6 and newer) */ +#define ICH_RCBA 0xf0 +#define ICH_GCS_OFFSET 0x3410 +#define ICH_GCS_SIZE 0x4 +#define ICH_GCS_NO_REBOOT 0x20 + /* register names and locations (relative to PMBASE) */ #define SMI_BASE 0x30 /* base address for SMI registers */ #define SMI_LEN 0x08 #define SMI_EN 0x00 /* SMI Control and Enable Register */ #define SMI_STS 0x04 /* SMI Status Register */ #define TCO_BASE 0x60 /* base address for TCO registers */ -#define TCO_LEN 0x0a +#define TCO_LEN 0x20 #define TCO_RLD 0x00 /* TCO Reload and Current Value */ -#define TCO_TMR 0x01 /* TCO Timer Initial Value */ +#define TCO_TMR1 0x01 /* TCO Timer Initial Value + (ICH5 and older, 8 bits) */ +#define TCO_TMR2 0x12 /* TCO Timer Initial Value + (ICH6 and newer, 16 bits) */ #define TCO_DAT_IN 0x02 /* TCO Data In (DO NOT USE) */ #define TCO_DAT_OUT 0x03 /* TCO Data Out (DO NOT USE) */ #define TCO1_STS 0x04 /* TCO Status 1 */ #define TCO2_STS 0x06 /* TCO Status 2 */ #define TCO1_CNT 0x08 /* TCO Control 1 */ +#define TCO2_CNT 0x08 /* TCO Control 2 */ /* bit definitions for SMI_EN and SMI_STS */ #define SMI_TCO_EN 0x2000 @@ -112,11 +136,7 @@ struct ichwd_softc { #define TCO_TMR_HALT 0x0800 /* clear to enable WDT */ #define TCO_CNT_PRESERVE 0x0200 /* preserve these bits */ -/* approximate length in nanoseconds of one WDT tick */ -#define ICHWD_TICK 1800000000 - -/* minimum / maximum timeout in WDT ticks */ -#define ICHWD_MIN_TIMEOUT 2 -#define ICHWD_MAX_TIMEOUT 63 +/* approximate length in nanoseconds of one WDT tick (about 0.6 sec) */ +#define ICHWD_TICK 600000000 #endif |