1*37ecb596Sderaadt /* $OpenBSD: tcpcib.c,v 1.7 2013/12/06 21:03:04 deraadt Exp $ */ 2f6ad04e6Sjsg 3f6ad04e6Sjsg /* 4f6ad04e6Sjsg * Copyright (c) 2012 Matt Dainty <matt@bodgit-n-scarper.com> 5f6ad04e6Sjsg * 6f6ad04e6Sjsg * Permission to use, copy, modify, and distribute this software for any 7f6ad04e6Sjsg * purpose with or without fee is hereby granted, provided that the above 8f6ad04e6Sjsg * copyright notice and this permission notice appear in all copies. 9f6ad04e6Sjsg * 10f6ad04e6Sjsg * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11f6ad04e6Sjsg * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12f6ad04e6Sjsg * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13f6ad04e6Sjsg * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14f6ad04e6Sjsg * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN 15f6ad04e6Sjsg * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 16f6ad04e6Sjsg * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17f6ad04e6Sjsg */ 18f6ad04e6Sjsg 19f6ad04e6Sjsg /* 2018078679Skettenis * Intel Atom E600 series LPC bridge also containing HPET and watchdog 21f6ad04e6Sjsg */ 22f6ad04e6Sjsg 23f6ad04e6Sjsg #include <sys/param.h> 24f6ad04e6Sjsg #include <sys/systm.h> 25f6ad04e6Sjsg #include <sys/device.h> 2618078679Skettenis #include <sys/timetc.h> 27f6ad04e6Sjsg 28f6ad04e6Sjsg #include <machine/bus.h> 29f6ad04e6Sjsg 30f6ad04e6Sjsg #include <dev/pci/pcireg.h> 31f6ad04e6Sjsg #include <dev/pci/pcivar.h> 32f6ad04e6Sjsg #include <dev/pci/pcidevs.h> 33f6ad04e6Sjsg 34f6ad04e6Sjsg #define E600_LPC_SMBA 0x40 /* SMBus Base Address */ 35f6ad04e6Sjsg #define E600_LPC_GBA 0x44 /* GPIO Base Address */ 36f6ad04e6Sjsg #define E600_LPC_WDTBA 0x84 /* WDT Base Address */ 37f6ad04e6Sjsg 38f6ad04e6Sjsg #define E600_WDT_SIZE 64 /* I/O region size */ 39f6ad04e6Sjsg #define E600_WDT_PV1 0x00 /* Preload Value 1 Register */ 40f6ad04e6Sjsg #define E600_WDT_PV2 0x04 /* Preload Value 2 Register */ 41f6ad04e6Sjsg #define E600_WDT_RR0 0x0c /* Reload Register 0 */ 42f6ad04e6Sjsg #define E600_WDT_RR1 0x0d /* Reload Register 1 */ 43f6ad04e6Sjsg #define E600_WDT_RR1_RELOAD (1 << 0) /* WDT Reload Flag */ 44f6ad04e6Sjsg #define E600_WDT_RR1_TIMEOUT (1 << 1) /* WDT Timeout Flag */ 45f6ad04e6Sjsg #define E600_WDT_WDTCR 0x10 /* WDT Configuration Register */ 46f6ad04e6Sjsg #define E600_WDT_WDTCR_PRE (1 << 2) /* WDT Prescalar Select */ 47f6ad04e6Sjsg #define E600_WDT_WDTCR_RESET (1 << 3) /* WDT Reset Select */ 48f6ad04e6Sjsg #define E600_WDT_WDTCR_ENABLE (1 << 4) /* WDT Reset Enable */ 49f6ad04e6Sjsg #define E600_WDT_WDTCR_TIMEOUT (1 << 5) /* WDT Timeout Output Enable */ 50f6ad04e6Sjsg #define E600_WDT_DCR 0x14 /* Down Counter Register */ 51f6ad04e6Sjsg #define E600_WDT_WDTLR 0x18 /* WDT Lock Register */ 52f6ad04e6Sjsg #define E600_WDT_WDTLR_LOCK (1 << 0) /* Watchdog Timer Lock */ 53f6ad04e6Sjsg #define E600_WDT_WDTLR_ENABLE (1 << 1) /* Watchdog Timer Enable */ 54f6ad04e6Sjsg #define E600_WDT_WDTLR_TIMEOUT (1 << 2) /* WDT Timeout Configuration */ 55f6ad04e6Sjsg 5618078679Skettenis #define E600_HPET_BASE 0xfed00000 /* HPET register base */ 5718078679Skettenis #define E600_HPET_SIZE 0x00000400 /* HPET register size */ 5818078679Skettenis 5918078679Skettenis #define E600_HPET_GCID 0x000 /* Capabilities and ID */ 6018078679Skettenis #define E600_HPET_GCID_WIDTH (1 << 13) /* Counter Size */ 6118078679Skettenis #define E600_HPET_PERIOD 0x004 /* Counter Tick Period */ 6218078679Skettenis #define E600_HPET_GC 0x010 /* General Configuration */ 6318078679Skettenis #define E600_HPET_GC_ENABLE (1 << 0) /* Overall Enable */ 6418078679Skettenis #define E600_HPET_GIS 0x020 /* General Interrupt Status */ 6518078679Skettenis #define E600_HPET_MCV 0x0f0 /* Main Counter Value */ 6618078679Skettenis #define E600_HPET_T0C 0x100 /* Timer 0 Config and Capabilities */ 6718078679Skettenis #define E600_HPET_T0CV 0x108 /* Timer 0 Comparator Value */ 6818078679Skettenis #define E600_HPET_T1C 0x120 /* Timer 1 Config and Capabilities */ 6918078679Skettenis #define E600_HPET_T1CV 0x128 /* Timer 1 Comparator Value */ 7018078679Skettenis #define E600_HPET_T2C 0x140 /* Timer 2 Config and Capabilities */ 7118078679Skettenis #define E600_HPET_T2CV 0x148 /* Timer 2 Comparator Value */ 7218078679Skettenis 73f6ad04e6Sjsg struct tcpcib_softc { 74f6ad04e6Sjsg struct device sc_dev; 75f6ad04e6Sjsg 76f6ad04e6Sjsg /* Keep track of which parts of the hardware are active */ 77f6ad04e6Sjsg int sc_active; 78f6ad04e6Sjsg #define E600_WDT_ACTIVE (1 << 0) 7918078679Skettenis #define E600_HPET_ACTIVE (1 << 1) 80f6ad04e6Sjsg 81f6ad04e6Sjsg /* Watchdog interface */ 82f6ad04e6Sjsg bus_space_tag_t sc_wdt_iot; 83f6ad04e6Sjsg bus_space_handle_t sc_wdt_ioh; 84f6ad04e6Sjsg 85f6ad04e6Sjsg int sc_wdt_period; 8618078679Skettenis 8718078679Skettenis /* High Precision Event Timer */ 8818078679Skettenis bus_space_tag_t sc_hpet_iot; 8918078679Skettenis bus_space_handle_t sc_hpet_ioh; 9018078679Skettenis 9118078679Skettenis struct timecounter sc_hpet_timecounter; 92f6ad04e6Sjsg }; 93f6ad04e6Sjsg 94f6ad04e6Sjsg struct cfdriver tcpcib_cd = { 95f6ad04e6Sjsg NULL, "tcpcib", DV_DULL 96f6ad04e6Sjsg }; 97f6ad04e6Sjsg 98f6ad04e6Sjsg int tcpcib_match(struct device *, void *, void *); 99f6ad04e6Sjsg void tcpcib_attach(struct device *, struct device *, void *); 100f6ad04e6Sjsg int tcpcib_activate(struct device *, int); 101f6ad04e6Sjsg 102f6ad04e6Sjsg int tcpcib_wdt_cb(void *, int); 103f6ad04e6Sjsg void tcpcib_wdt_init(struct tcpcib_softc *, int); 104f6ad04e6Sjsg void tcpcib_wdt_start(struct tcpcib_softc *); 105f6ad04e6Sjsg void tcpcib_wdt_stop(struct tcpcib_softc *); 106f6ad04e6Sjsg 10718078679Skettenis u_int tcpcib_hpet_get_timecount(struct timecounter *tc); 10818078679Skettenis 109f6ad04e6Sjsg struct cfattach tcpcib_ca = { 110f6ad04e6Sjsg sizeof(struct tcpcib_softc), tcpcib_match, tcpcib_attach, 111f6ad04e6Sjsg NULL, tcpcib_activate 112f6ad04e6Sjsg }; 113f6ad04e6Sjsg 114f6ad04e6Sjsg /* from arch/<*>/pci/pcib.c */ 115f6ad04e6Sjsg void pcibattach(struct device *parent, struct device *self, void *aux); 116f6ad04e6Sjsg 117f6ad04e6Sjsg const struct pci_matchid tcpcib_devices[] = { 118f6ad04e6Sjsg { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_E600_LPC } 119f6ad04e6Sjsg }; 120f6ad04e6Sjsg 121f6ad04e6Sjsg static __inline void 122f6ad04e6Sjsg tcpcib_wdt_unlock(struct tcpcib_softc *sc) 123f6ad04e6Sjsg { 124f6ad04e6Sjsg /* Register unlocking sequence */ 125f6ad04e6Sjsg bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR0, 0x80); 126f6ad04e6Sjsg bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR0, 0x86); 127f6ad04e6Sjsg } 128f6ad04e6Sjsg 129f6ad04e6Sjsg void 130f6ad04e6Sjsg tcpcib_wdt_init(struct tcpcib_softc *sc, int period) 131f6ad04e6Sjsg { 132f6ad04e6Sjsg u_int32_t preload; 133f6ad04e6Sjsg 134f6ad04e6Sjsg /* Set new timeout */ 135f6ad04e6Sjsg preload = (period * 33000000) >> 15; 136f6ad04e6Sjsg preload--; 137f6ad04e6Sjsg 138f6ad04e6Sjsg /* 139f6ad04e6Sjsg * Set watchdog to perform a cold reset toggling the GPIO pin and the 140f6ad04e6Sjsg * prescaler set to 1ms-10m resolution 141f6ad04e6Sjsg */ 142f6ad04e6Sjsg bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTCR, 143f6ad04e6Sjsg E600_WDT_WDTCR_ENABLE); 144f6ad04e6Sjsg tcpcib_wdt_unlock(sc); 145f6ad04e6Sjsg bus_space_write_4(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_PV1, 0); 146f6ad04e6Sjsg tcpcib_wdt_unlock(sc); 147f6ad04e6Sjsg bus_space_write_4(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_PV2, 148f6ad04e6Sjsg preload); 149f6ad04e6Sjsg tcpcib_wdt_unlock(sc); 150f6ad04e6Sjsg bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR1, 151f6ad04e6Sjsg E600_WDT_RR1_RELOAD); 152f6ad04e6Sjsg } 153f6ad04e6Sjsg 154f6ad04e6Sjsg void 155f6ad04e6Sjsg tcpcib_wdt_start(struct tcpcib_softc *sc) 156f6ad04e6Sjsg { 157f6ad04e6Sjsg /* Enable watchdog */ 158f6ad04e6Sjsg bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTLR, 159f6ad04e6Sjsg E600_WDT_WDTLR_ENABLE); 160f6ad04e6Sjsg } 161f6ad04e6Sjsg 162f6ad04e6Sjsg void 163f6ad04e6Sjsg tcpcib_wdt_stop(struct tcpcib_softc *sc) 164f6ad04e6Sjsg { 165f6ad04e6Sjsg /* Disable watchdog, with a reload before for safety */ 166f6ad04e6Sjsg tcpcib_wdt_unlock(sc); 167f6ad04e6Sjsg bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR1, 168f6ad04e6Sjsg E600_WDT_RR1_RELOAD); 169f6ad04e6Sjsg bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTLR, 0); 170f6ad04e6Sjsg } 171f6ad04e6Sjsg 172f6ad04e6Sjsg int 173f6ad04e6Sjsg tcpcib_match(struct device *parent, void *match, void *aux) 174f6ad04e6Sjsg { 175f6ad04e6Sjsg if (pci_matchbyid((struct pci_attach_args *)aux, tcpcib_devices, 176f6ad04e6Sjsg sizeof(tcpcib_devices) / sizeof(tcpcib_devices[0]))) 177f6ad04e6Sjsg return (2); 178f6ad04e6Sjsg 179f6ad04e6Sjsg return (0); 180f6ad04e6Sjsg } 181f6ad04e6Sjsg 182f6ad04e6Sjsg void 183f6ad04e6Sjsg tcpcib_attach(struct device *parent, struct device *self, void *aux) 184f6ad04e6Sjsg { 185f6ad04e6Sjsg struct tcpcib_softc *sc = (struct tcpcib_softc *)self; 186f6ad04e6Sjsg struct pci_attach_args *pa = aux; 18718078679Skettenis struct timecounter *tc = &sc->sc_hpet_timecounter; 188f6ad04e6Sjsg u_int32_t reg, wdtbase; 189f6ad04e6Sjsg 190f6ad04e6Sjsg sc->sc_active = 0; 191f6ad04e6Sjsg 19218078679Skettenis /* High Precision Event Timer */ 19318078679Skettenis sc->sc_hpet_iot = pa->pa_memt; 19418078679Skettenis if (bus_space_map(sc->sc_hpet_iot, E600_HPET_BASE, E600_HPET_SIZE, 0, 19518078679Skettenis &sc->sc_hpet_ioh) == 0) { 19618078679Skettenis tc->tc_get_timecount = tcpcib_hpet_get_timecount; 19718078679Skettenis /* XXX 64-bit counter is not supported! */ 19818078679Skettenis tc->tc_counter_mask = 0xffffffff; 19918078679Skettenis 20018078679Skettenis reg = bus_space_read_4(sc->sc_hpet_iot, sc->sc_hpet_ioh, 20118078679Skettenis E600_HPET_PERIOD); 20218078679Skettenis /* femtosecs -> Hz */ 20318078679Skettenis tc->tc_frequency = 1000000000000000ULL / reg; 20418078679Skettenis 20518078679Skettenis tc->tc_name = sc->sc_dev.dv_xname; 20618078679Skettenis tc->tc_quality = 2000; 20718078679Skettenis tc->tc_priv = sc; 20818078679Skettenis tc_init(tc); 20918078679Skettenis 21018078679Skettenis /* Enable counting */ 21118078679Skettenis bus_space_write_4(sc->sc_hpet_iot, sc->sc_hpet_ioh, 21218078679Skettenis E600_HPET_GC, E600_HPET_GC_ENABLE); 21318078679Skettenis 21418078679Skettenis sc->sc_active |= E600_HPET_ACTIVE; 21518078679Skettenis 21618078679Skettenis printf(": %llu Hz timer", tc->tc_frequency); 21718078679Skettenis } 21818078679Skettenis 219f6ad04e6Sjsg /* Map Watchdog I/O space */ 220f6ad04e6Sjsg reg = pci_conf_read(pa->pa_pc, pa->pa_tag, E600_LPC_WDTBA); 221f6ad04e6Sjsg wdtbase = reg & 0xffff; 222f6ad04e6Sjsg sc->sc_wdt_iot = pa->pa_iot; 22361e87b28Sderaadt if (reg & (1U << 31) && wdtbase) { 224f6ad04e6Sjsg if (PCI_MAPREG_IO_ADDR(wdtbase) == 0 || 225f6ad04e6Sjsg bus_space_map(sc->sc_wdt_iot, PCI_MAPREG_IO_ADDR(wdtbase), 226f6ad04e6Sjsg E600_WDT_SIZE, 0, &sc->sc_wdt_ioh)) { 22718078679Skettenis printf("%c can't map watchdog I/O space", 22818078679Skettenis sc->sc_active ? ',' : ':'); 229f6ad04e6Sjsg goto corepcib; 230f6ad04e6Sjsg } 23118078679Skettenis printf("%c watchdog", sc->sc_active ? ',' : ':'); 232f6ad04e6Sjsg 233f6ad04e6Sjsg /* Check for reboot on timeout */ 234f6ad04e6Sjsg reg = bus_space_read_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, 235f6ad04e6Sjsg E600_WDT_RR1); 236f6ad04e6Sjsg if (reg & E600_WDT_RR1_TIMEOUT) { 237f6ad04e6Sjsg printf(", reboot on timeout"); 238f6ad04e6Sjsg 239f6ad04e6Sjsg /* Clear timeout bit */ 240f6ad04e6Sjsg tcpcib_wdt_unlock(sc); 241f6ad04e6Sjsg bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, 242f6ad04e6Sjsg E600_WDT_RR1, E600_WDT_RR1_TIMEOUT); 243f6ad04e6Sjsg } 244f6ad04e6Sjsg 245f6ad04e6Sjsg /* Check it's not locked already */ 246f6ad04e6Sjsg reg = bus_space_read_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, 247f6ad04e6Sjsg E600_WDT_WDTLR); 248f6ad04e6Sjsg if (reg & E600_WDT_WDTLR_LOCK) { 249f6ad04e6Sjsg printf(", locked"); 250f6ad04e6Sjsg goto corepcib; 251f6ad04e6Sjsg } 252f6ad04e6Sjsg 253f6ad04e6Sjsg /* Disable watchdog */ 254f6ad04e6Sjsg tcpcib_wdt_stop(sc); 255f6ad04e6Sjsg sc->sc_wdt_period = 0; 256f6ad04e6Sjsg 257f6ad04e6Sjsg sc->sc_active |= E600_WDT_ACTIVE; 258f6ad04e6Sjsg 259f6ad04e6Sjsg /* Register new watchdog */ 2602bc62decSderaadt wdog_register(tcpcib_wdt_cb, sc); 261f6ad04e6Sjsg } 262f6ad04e6Sjsg 263f6ad04e6Sjsg corepcib: 264f6ad04e6Sjsg /* Provide core pcib(4) functionality */ 265f6ad04e6Sjsg pcibattach(parent, self, aux); 266f6ad04e6Sjsg } 267f6ad04e6Sjsg 268f6ad04e6Sjsg int 269f6ad04e6Sjsg tcpcib_activate(struct device *self, int act) 270f6ad04e6Sjsg { 271f6ad04e6Sjsg struct tcpcib_softc *sc = (struct tcpcib_softc *)self; 272*37ecb596Sderaadt int rv = 0; 273f6ad04e6Sjsg 274f6ad04e6Sjsg switch (act) { 275f6ad04e6Sjsg case DVACT_SUSPEND: 276*37ecb596Sderaadt rv = config_activate_children(self, act); 277f6ad04e6Sjsg /* Watchdog is running, disable it */ 278f6ad04e6Sjsg if (sc->sc_active & E600_WDT_ACTIVE && sc->sc_wdt_period != 0) 279f6ad04e6Sjsg tcpcib_wdt_stop(sc); 280f6ad04e6Sjsg break; 281f6ad04e6Sjsg case DVACT_RESUME: 282f6ad04e6Sjsg if (sc->sc_active & E600_WDT_ACTIVE) { 283f6ad04e6Sjsg /* 284f6ad04e6Sjsg * Watchdog was running prior to suspend so reenable 285f6ad04e6Sjsg * it, otherwise make sure it stays disabled 286f6ad04e6Sjsg */ 287f6ad04e6Sjsg if (sc->sc_wdt_period != 0) { 288f6ad04e6Sjsg tcpcib_wdt_init(sc, sc->sc_wdt_period); 289f6ad04e6Sjsg tcpcib_wdt_start(sc); 290f6ad04e6Sjsg } else 291f6ad04e6Sjsg tcpcib_wdt_stop(sc); 292f6ad04e6Sjsg } 29318078679Skettenis if (sc->sc_active & E600_HPET_ACTIVE) 29418078679Skettenis bus_space_write_4(sc->sc_hpet_iot, sc->sc_hpet_ioh, 29518078679Skettenis E600_HPET_GC, E600_HPET_GC_ENABLE); 296*37ecb596Sderaadt rv = config_activate_children(self, act); 297c06fda6dSderaadt break; 298c06fda6dSderaadt default: 299*37ecb596Sderaadt rv = config_activate_children(self, act); 300f6ad04e6Sjsg break; 301f6ad04e6Sjsg } 302*37ecb596Sderaadt return (rv); 303f6ad04e6Sjsg } 304f6ad04e6Sjsg 305f6ad04e6Sjsg int 306f6ad04e6Sjsg tcpcib_wdt_cb(void *arg, int period) 307f6ad04e6Sjsg { 308f6ad04e6Sjsg struct tcpcib_softc *sc = arg; 309f6ad04e6Sjsg 310f6ad04e6Sjsg if (period == 0) { 311f6ad04e6Sjsg if (sc->sc_wdt_period != 0) 312f6ad04e6Sjsg tcpcib_wdt_stop(sc); 313f6ad04e6Sjsg } else { 314f6ad04e6Sjsg /* 600 seconds is the maximum supported timeout value */ 315f6ad04e6Sjsg if (period > 600) 316f6ad04e6Sjsg period = 600; 317f6ad04e6Sjsg if (sc->sc_wdt_period != period) 318f6ad04e6Sjsg tcpcib_wdt_init(sc, period); 319f6ad04e6Sjsg if (sc->sc_wdt_period == 0) { 320f6ad04e6Sjsg tcpcib_wdt_start(sc); 321f6ad04e6Sjsg } else { 322f6ad04e6Sjsg /* Reset timer */ 323f6ad04e6Sjsg tcpcib_wdt_unlock(sc); 324f6ad04e6Sjsg bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, 325f6ad04e6Sjsg E600_WDT_RR1, E600_WDT_RR1_RELOAD); 326f6ad04e6Sjsg } 327f6ad04e6Sjsg } 328f6ad04e6Sjsg sc->sc_wdt_period = period; 329f6ad04e6Sjsg 330f6ad04e6Sjsg return (period); 331f6ad04e6Sjsg } 33218078679Skettenis 33318078679Skettenis u_int 33418078679Skettenis tcpcib_hpet_get_timecount(struct timecounter *tc) 33518078679Skettenis { 33618078679Skettenis struct tcpcib_softc *sc = tc->tc_priv; 33718078679Skettenis 33818078679Skettenis /* XXX 64-bit counter is not supported! */ 33918078679Skettenis return bus_space_read_4(sc->sc_hpet_iot, sc->sc_hpet_ioh, 34018078679Skettenis E600_HPET_MCV); 34118078679Skettenis } 342