summaryrefslogtreecommitdiffstats
path: root/sys
diff options
context:
space:
mode:
authordes <des@FreeBSD.org>2007-08-13 18:52:37 +0000
committerdes <des@FreeBSD.org>2007-08-13 18:52:37 +0000
commit4720b5aa389174b60e267cefb9ab1851a4420a66 (patch)
tree24b5d8b90229b34ff66d0f0587cf95ea4181c9ad /sys
parent47d488244c8d1ac0d8f9233b24cf663131829cc0 (diff)
downloadFreeBSD-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.c261
-rw-r--r--sys/dev/ichwd/ichwd.h44
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
OpenPOWER on IntegriCloud