Index: head/sys/dev/ichwd/ichwd.h =================================================================== --- head/sys/dev/ichwd/ichwd.h +++ head/sys/dev/ichwd/ichwd.h @@ -272,6 +272,7 @@ #define DEVICEID_WCPT_LP6 0x9cc6 #define DEVICEID_WCPT_LP7 0x9cc7 #define DEVICEID_WCPT_LP9 0x9cc9 +#define DEVICEID_LEWISBURG_SMB 0xa1a3 /* ICH LPC Interface Bridge Registers (ICH5 and older) */ #define ICH_GEN_STA 0xd4 @@ -290,6 +291,14 @@ #define ICH_PMC_OFFSET 0x08 #define ICH_PMC_SIZE 0x4 #define ICH_PMC_NO_REBOOT 0x10 + +/* Lewisburg configration registers in SMBus controller. */ +#define ICH_TCOBASE 0x50 /* TCO Base Addr */ +#define ICH_TCOBASE_ADDRMASK 0xffe0 +#define ICH_TCOBASE_SIZE 32 +#define ICH_TCOCTL 0x54 /* TCO Control */ +#define ICH_TCOCTL_TCO_BASE_EN 0x0100 /* TCO Base decoding enabled */ +#define ICH_TCOCTL_TCO_BASE_LOCK 0x0001 /* TCOBASE is locked */ /* register names and locations (relative to PMBASE) */ #define SMI_BASE 0x30 /* base address for SMI registers */ Index: head/sys/dev/ichwd/ichwd.c =================================================================== --- head/sys/dev/ichwd/ichwd.c +++ head/sys/dev/ichwd/ichwd.c @@ -289,6 +289,11 @@ { 0, NULL, 0, 0 }, }; +static struct ichwd_device ichwd_smb_devices[] = { + { DEVICEID_LEWISBURG_SMB, "Lewisburg watchdog timer", 10, 4 }, + { 0, NULL, 0, 0 }, +}; + static devclass_t ichwd_devclass; #define ichwd_read_tco_1(sc, off) \ @@ -374,7 +379,8 @@ * be done in two separate operations. */ ichwd_write_tco_2(sc, TCO2_STS, TCO_SECOND_TO_STS); - ichwd_write_tco_2(sc, TCO2_STS, TCO_BOOT_STS); + if (sc->tco_version < 4) + ichwd_write_tco_2(sc, TCO2_STS, TCO_BOOT_STS); } /* @@ -488,6 +494,11 @@ if (status & ICH_PMC_NO_REBOOT) rc = EIO; break; + case 4: + /* + * TODO. This needs access to a hidden PCI device at 31:1. + */ + break; default: ichwd_verbose_printf(sc->device, "Unknown TCO Version: %d, can't set NO_REBOOT.\n", @@ -560,6 +571,36 @@ return (NULL); } +static device_t +ichwd_find_smb_dev(device_t isa, struct ichwd_device **id_p) +{ + struct ichwd_device *id; + device_t isab, smb; + uint16_t devid; + + /* + * Check if SMBus controller provides TCO configuration. + * The controller's device and function are fixed and we expect + * it to be on the same bus as ISA bridge. + */ + isab = device_get_parent(isa); + smb = pci_find_dbsf(pci_get_domain(isab), pci_get_bus(isab), 31, 4); + if (smb == NULL) + return (NULL); + if (pci_get_vendor(smb) != VENDORID_INTEL) + return (NULL); + devid = pci_get_device(smb); + for (id = ichwd_smb_devices; id->desc != NULL; ++id) { + if (devid == id->device) { + if (id_p != NULL) + *id_p = id; + return (smb); + } + } + + return (NULL); +} + /* * Look for an ICH LPC interface bridge. If one is found, register an * ichwd device. There can be only one. @@ -568,14 +609,18 @@ ichwd_identify(driver_t *driver, device_t parent) { struct ichwd_device *id_p; - device_t ich = NULL; + device_t ich, smb; device_t dev; uint32_t base_address; + uint32_t ctl; int rc; ich = ichwd_find_ich_lpc_bridge(parent, &id_p); - if (ich == NULL) - return; + if (ich == NULL) { + smb = ichwd_find_smb_dev(parent, &id_p); + if (smb == NULL) + return; + } /* good, add child to bus */ if ((dev = device_find_child(parent, driver->name, 0)) == NULL) @@ -609,6 +654,24 @@ "Can not set TCO v%d memory resource for PBASE\n", id_p->tco_version); break; + case 4: + /* Get TCO base address. */ + ctl = pci_read_config(smb, ICH_TCOCTL, 4); + if ((ctl & ICH_TCOCTL_TCO_BASE_EN) == 0) { + ichwd_verbose_printf(dev, + "TCO v%d decoding is not enabled\n", + id_p->tco_version); + break; + } + base_address = pci_read_config(smb, ICH_TCOBASE, 4); + rc = bus_set_resource(dev, SYS_RES_IOPORT, 0, + base_address & ICH_TCOBASE_ADDRMASK, ICH_TCOBASE_SIZE); + if (rc != 0) { + ichwd_verbose_printf(dev, + "Can not set TCO v%d I/O resource (err = %d)\n", + id_p->tco_version, rc); + } + break; default: ichwd_verbose_printf(dev, "Can not set unknown TCO v%d memory resource for unknown base address\n", @@ -626,7 +689,8 @@ if (isa_get_logicalid(dev) != 0) return (ENXIO); - if (ichwd_find_ich_lpc_bridge(device_get_parent(dev), &id_p) == NULL) + if (ichwd_find_ich_lpc_bridge(device_get_parent(dev), &id_p) == NULL && + ichwd_find_smb_dev(device_get_parent(dev), &id_p) == NULL) return (ENXIO); device_set_desc_copy(dev, id_p->desc); @@ -634,21 +698,71 @@ } static int -ichwd_attach(device_t dev) +ichwd_smb_attach(device_t dev) { struct ichwd_softc *sc; struct ichwd_device *id_p; + device_t isab, pmdev; + device_t smb; + uint32_t acpi_base; + + sc = device_get_softc(dev); + smb = ichwd_find_smb_dev(device_get_parent(dev), &id_p); + if (smb == NULL) + return (ENXIO); + + sc->ich_version = id_p->ich_version; + sc->tco_version = id_p->tco_version; + + /* Allocate TCO control I/O register space. */ + sc->tco_rid = 0; + sc->tco_res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->tco_rid, + RF_ACTIVE | RF_SHAREABLE); + if (sc->tco_res == NULL) { + device_printf(dev, "unable to reserve TCO registers\n"); + return (ENXIO); + } + + /* Get ACPI base address. */ + isab = device_get_parent(device_get_parent(dev)); + pmdev = pci_find_dbsf(pci_get_domain(isab), pci_get_bus(isab), 31, 2); + if (pmdev == NULL) { + device_printf(dev, "unable to find Power Management device\n"); + return (ENXIO); + } + acpi_base = pci_read_config(pmdev, ICH_PMBASE, 4) & 0xffffff00; + if (acpi_base == 0) { + device_printf(dev, "ACPI base address is not set\n"); + return (ENXIO); + } + + /* Allocate SMI control I/O register space. */ + sc->smi_rid = 1; + sc->smi_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->smi_rid, + acpi_base + SMI_BASE, acpi_base + 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"); + return (ENXIO); + } + + return (0); +} + +static int +ichwd_lpc_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(device_get_parent(dev), &id_p); - if (ich == NULL) { - device_printf(sc->device, "Can not find ICH device.\n"); - goto fail; - } + if (ich == NULL) + return (ENXIO); + sc->ich = ich; sc->ich_version = id_p->ich_version; sc->tco_version = id_p->tco_version; @@ -657,7 +771,7 @@ pmbase = pci_read_config(ich, ICH_PMBASE, 2) & ICH_PMBASE_MASK; if (pmbase == 0) { device_printf(dev, "ICH PMBASE register is empty\n"); - goto fail; + return (ENXIO); } /* allocate I/O register space */ @@ -667,7 +781,7 @@ RF_ACTIVE | RF_SHAREABLE); if (sc->smi_res == NULL) { device_printf(dev, "unable to reserve SMI registers\n"); - goto fail; + return (ENXIO); } sc->tco_rid = 1; @@ -676,7 +790,7 @@ RF_ACTIVE | RF_SHAREABLE); if (sc->tco_res == NULL) { device_printf(dev, "unable to reserve TCO registers\n"); - goto fail; + return (ENXIO); } sc->gcs_rid = 0; @@ -685,10 +799,24 @@ &sc->gcs_rid, RF_ACTIVE|RF_SHAREABLE); if (sc->gcs_res == NULL) { device_printf(dev, "unable to reserve GCS registers\n"); - goto fail; + return (ENXIO); } } + return (0); +} + +static int +ichwd_attach(device_t dev) +{ + struct ichwd_softc *sc; + + sc = device_get_softc(dev); + sc->device = dev; + + if (ichwd_lpc_attach(dev) != 0 && ichwd_smb_attach(dev) != 0) + goto fail; + if (ichwd_clear_noreboot(sc) != 0) goto fail; @@ -724,7 +852,7 @@ 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, + bus_release_resource(sc->ich, SYS_RES_MEMORY, sc->gcs_rid, sc->gcs_res); return (ENXIO);