1*c7fb772bSthorpej /* $NetBSD: tcpcib.c,v 1.4 2021/08/07 16:19:08 thorpej Exp $ */
20a1df220Schristos /* $OpenBSD: tcpcib.c,v 1.4 2012/10/17 22:32:01 deraadt Exp $ */
30a1df220Schristos
40a1df220Schristos /*
50a1df220Schristos * Copyright (c) 2012 Matt Dainty <matt@bodgit-n-scarper.com>
60a1df220Schristos *
70a1df220Schristos * Permission to use, copy, modify, and distribute this software for any
80a1df220Schristos * purpose with or without fee is hereby granted, provided that the above
90a1df220Schristos * copyright notice and this permission notice appear in all copies.
100a1df220Schristos *
110a1df220Schristos * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
120a1df220Schristos * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
130a1df220Schristos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
140a1df220Schristos * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
150a1df220Schristos * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
160a1df220Schristos * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
170a1df220Schristos * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
180a1df220Schristos */
190a1df220Schristos
200a1df220Schristos /*
210a1df220Schristos * Intel Atom E600 series LPC bridge also containing HPET and watchdog
220a1df220Schristos */
230a1df220Schristos
240a1df220Schristos #include <sys/cdefs.h>
25*c7fb772bSthorpej __KERNEL_RCSID(0, "$NetBSD: tcpcib.c,v 1.4 2021/08/07 16:19:08 thorpej Exp $");
260a1df220Schristos
270a1df220Schristos #include <sys/param.h>
280a1df220Schristos #include <sys/systm.h>
290a1df220Schristos #include <sys/device.h>
300a1df220Schristos #include <sys/timetc.h>
310a1df220Schristos #include <sys/bus.h>
320a1df220Schristos
330a1df220Schristos #include <dev/pci/pcireg.h>
340a1df220Schristos #include <dev/pci/pcivar.h>
350a1df220Schristos #include <dev/pci/pcidevs.h>
360a1df220Schristos
370a1df220Schristos #include <dev/ic/i82801lpcvar.h>
380a1df220Schristos
390a1df220Schristos #include <dev/sysmon/sysmonvar.h>
400a1df220Schristos
410a1df220Schristos #include "pcibvar.h"
420a1df220Schristos
430a1df220Schristos #define E600_LPC_SMBA 0x40 /* SMBus Base Address */
440a1df220Schristos #define E600_LPC_GBA 0x44 /* GPIO Base Address */
450a1df220Schristos #define E600_LPC_WDTBA 0x84 /* WDT Base Address */
460a1df220Schristos
470a1df220Schristos #define E600_WDT_SIZE 64 /* I/O region size */
480a1df220Schristos #define E600_WDT_PV1 0x00 /* Preload Value 1 Register */
490a1df220Schristos #define E600_WDT_PV2 0x04 /* Preload Value 2 Register */
500a1df220Schristos #define E600_WDT_RR0 0x0c /* Reload Register 0 */
510a1df220Schristos #define E600_WDT_RR1 0x0d /* Reload Register 1 */
520a1df220Schristos #define E600_WDT_RR1_RELOAD (1 << 0) /* WDT Reload Flag */
530a1df220Schristos #define E600_WDT_RR1_TIMEOUT (1 << 1) /* WDT Timeout Flag */
540a1df220Schristos #define E600_WDT_WDTCR 0x10 /* WDT Configuration Register */
550a1df220Schristos #define E600_WDT_WDTCR_PRE (1 << 2) /* WDT Prescalar Select */
560a1df220Schristos #define E600_WDT_WDTCR_RESET (1 << 3) /* WDT Reset Select */
570a1df220Schristos #define E600_WDT_WDTCR_ENABLE (1 << 4) /* WDT Reset Enable */
580a1df220Schristos #define E600_WDT_WDTCR_TIMEOUT (1 << 5) /* WDT Timeout Output Enable */
590a1df220Schristos #define E600_WDT_DCR 0x14 /* Down Counter Register */
600a1df220Schristos #define E600_WDT_WDTLR 0x18 /* WDT Lock Register */
610a1df220Schristos #define E600_WDT_WDTLR_LOCK (1 << 0) /* Watchdog Timer Lock */
620a1df220Schristos #define E600_WDT_WDTLR_ENABLE (1 << 1) /* Watchdog Timer Enable */
630a1df220Schristos #define E600_WDT_WDTLR_TIMEOUT (1 << 2) /* WDT Timeout Configuration */
640a1df220Schristos
650a1df220Schristos #define E600_HPET_BASE 0xfed00000 /* HPET register base */
660a1df220Schristos #define E600_HPET_SIZE 0x00000400 /* HPET register size */
670a1df220Schristos
680a1df220Schristos #define E600_HPET_GCID 0x000 /* Capabilities and ID */
690a1df220Schristos #define E600_HPET_GCID_WIDTH (1 << 13) /* Counter Size */
700a1df220Schristos #define E600_HPET_PERIOD 0x004 /* Counter Tick Period */
710a1df220Schristos #define E600_HPET_GC 0x010 /* General Configuration */
720a1df220Schristos #define E600_HPET_GC_ENABLE (1 << 0) /* Overall Enable */
730a1df220Schristos #define E600_HPET_GIS 0x020 /* General Interrupt Status */
740a1df220Schristos #define E600_HPET_MCV 0x0f0 /* Main Counter Value */
750a1df220Schristos #define E600_HPET_T0C 0x100 /* Timer 0 Config and Capabilities */
760a1df220Schristos #define E600_HPET_T0CV 0x108 /* Timer 0 Comparator Value */
770a1df220Schristos #define E600_HPET_T1C 0x120 /* Timer 1 Config and Capabilities */
780a1df220Schristos #define E600_HPET_T1CV 0x128 /* Timer 1 Comparator Value */
790a1df220Schristos #define E600_HPET_T2C 0x140 /* Timer 2 Config and Capabilities */
800a1df220Schristos #define E600_HPET_T2CV 0x148 /* Timer 2 Comparator Value */
810a1df220Schristos
820a1df220Schristos struct tcpcib_softc {
830a1df220Schristos /* we call pcibattach() which assumes this starts like this: */
840a1df220Schristos struct pcib_softc sc_pcib;
850a1df220Schristos
860a1df220Schristos /* Watchdog interface */
870a1df220Schristos bool sc_wdt_valid;
880a1df220Schristos struct sysmon_wdog sc_wdt_smw;
890a1df220Schristos bus_space_tag_t sc_wdt_iot;
900a1df220Schristos bus_space_handle_t sc_wdt_ioh;
910a1df220Schristos
920a1df220Schristos /* High Precision Event Timer */
930a1df220Schristos device_t sc_hpetbus;
940a1df220Schristos bus_space_tag_t sc_hpet_memt;
950a1df220Schristos };
960a1df220Schristos
970a1df220Schristos static int tcpcib_match(device_t, cfdata_t, void *);
980a1df220Schristos static void tcpcib_attach(device_t, device_t, void *);
990a1df220Schristos static int tcpcib_detach(device_t, int);
1000a1df220Schristos static int tcpcib_rescan(device_t, const char *, const int *);
1010a1df220Schristos static void tcpcib_childdet(device_t, device_t);
1020a1df220Schristos static bool tcpcib_suspend(device_t, const pmf_qual_t *);
1030a1df220Schristos static bool tcpcib_resume(device_t, const pmf_qual_t *);
1040a1df220Schristos
1050a1df220Schristos static int tcpcib_wdt_setmode(struct sysmon_wdog *);
1060a1df220Schristos static int tcpcib_wdt_tickle(struct sysmon_wdog *);
1070a1df220Schristos static void tcpcib_wdt_init(struct tcpcib_softc *, int);
1080a1df220Schristos static void tcpcib_wdt_start(struct tcpcib_softc *);
1090a1df220Schristos static void tcpcib_wdt_stop(struct tcpcib_softc *);
1100a1df220Schristos
1110a1df220Schristos CFATTACH_DECL2_NEW(tcpcib, sizeof(struct tcpcib_softc),
1120a1df220Schristos tcpcib_match, tcpcib_attach, tcpcib_detach, NULL,
1130a1df220Schristos tcpcib_rescan, tcpcib_childdet);
1140a1df220Schristos
1150a1df220Schristos static struct tcpcib_device {
1160a1df220Schristos pcireg_t vendor, product;
1170a1df220Schristos } tcpcib_devices[] = {
1180a1df220Schristos { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_E600_LPC }
1190a1df220Schristos };
1200a1df220Schristos
1210a1df220Schristos static void
tcpcib_wdt_unlock(struct tcpcib_softc * sc)1220a1df220Schristos tcpcib_wdt_unlock(struct tcpcib_softc *sc)
1230a1df220Schristos {
1240a1df220Schristos /* Register unlocking sequence */
1250a1df220Schristos bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR0, 0x80);
1260a1df220Schristos bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR0, 0x86);
1270a1df220Schristos }
1280a1df220Schristos
1290a1df220Schristos static void
tcpcib_wdt_init(struct tcpcib_softc * sc,int period)1300a1df220Schristos tcpcib_wdt_init(struct tcpcib_softc *sc, int period)
1310a1df220Schristos {
1320a1df220Schristos uint32_t preload;
1330a1df220Schristos
1340a1df220Schristos /* Set new timeout */
1350a1df220Schristos preload = (period * 33000000) >> 15;
1360a1df220Schristos preload--;
1370a1df220Schristos
1380a1df220Schristos /*
1390a1df220Schristos * Set watchdog to perform a cold reset toggling the GPIO pin and the
1400a1df220Schristos * prescaler set to 1ms-10m resolution
1410a1df220Schristos */
1420a1df220Schristos bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTCR,
1430a1df220Schristos E600_WDT_WDTCR_ENABLE);
1440a1df220Schristos tcpcib_wdt_unlock(sc);
1450a1df220Schristos bus_space_write_4(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_PV1, 0);
1460a1df220Schristos tcpcib_wdt_unlock(sc);
1470a1df220Schristos bus_space_write_4(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_PV2,
1480a1df220Schristos preload);
1490a1df220Schristos tcpcib_wdt_unlock(sc);
1500a1df220Schristos bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR1,
1510a1df220Schristos E600_WDT_RR1_RELOAD);
1520a1df220Schristos }
1530a1df220Schristos
1540a1df220Schristos static void
tcpcib_wdt_start(struct tcpcib_softc * sc)1550a1df220Schristos tcpcib_wdt_start(struct tcpcib_softc *sc)
1560a1df220Schristos {
1570a1df220Schristos /* Enable watchdog */
1580a1df220Schristos bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTLR,
1590a1df220Schristos E600_WDT_WDTLR_ENABLE);
1600a1df220Schristos }
1610a1df220Schristos
1620a1df220Schristos static void
tcpcib_wdt_stop(struct tcpcib_softc * sc)1630a1df220Schristos tcpcib_wdt_stop(struct tcpcib_softc *sc)
1640a1df220Schristos {
1650a1df220Schristos /* Disable watchdog, with a reload before for safety */
1660a1df220Schristos tcpcib_wdt_unlock(sc);
1670a1df220Schristos bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR1,
1680a1df220Schristos E600_WDT_RR1_RELOAD);
1690a1df220Schristos bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTLR, 0);
1700a1df220Schristos }
1710a1df220Schristos
1720a1df220Schristos static int
tcpcib_match(device_t parent,cfdata_t match,void * aux)1730a1df220Schristos tcpcib_match(device_t parent, cfdata_t match, void *aux)
1740a1df220Schristos {
1750a1df220Schristos struct pci_attach_args *pa = aux;
1760a1df220Schristos unsigned int n;
1770a1df220Schristos
1780a1df220Schristos if (PCI_CLASS(pa->pa_class) != PCI_CLASS_BRIDGE ||
1790a1df220Schristos PCI_SUBCLASS(pa->pa_class) != PCI_SUBCLASS_BRIDGE_ISA)
1800a1df220Schristos return 0;
1810a1df220Schristos
1820a1df220Schristos for (n = 0; n < __arraycount(tcpcib_devices); n++) {
1830a1df220Schristos if (PCI_VENDOR(pa->pa_id) == tcpcib_devices[n].vendor &&
1840a1df220Schristos PCI_PRODUCT(pa->pa_id) == tcpcib_devices[n].product)
1850a1df220Schristos return 10; /* beat pcib(4) */
1860a1df220Schristos }
1870a1df220Schristos
1880a1df220Schristos return 0;
1890a1df220Schristos }
1900a1df220Schristos
1910a1df220Schristos static void
tcpcib_attach(device_t parent,device_t self,void * aux)1920a1df220Schristos tcpcib_attach(device_t parent, device_t self, void *aux)
1930a1df220Schristos {
1940a1df220Schristos struct tcpcib_softc *sc = device_private(self);
1950a1df220Schristos struct pci_attach_args *pa = aux;
1960a1df220Schristos uint32_t reg, wdtbase;
1970a1df220Schristos
1980a1df220Schristos pmf_device_register(self, tcpcib_suspend, tcpcib_resume);
1990a1df220Schristos
2000a1df220Schristos /* Provide core pcib(4) functionality */
2010a1df220Schristos pcibattach(parent, self, aux);
2020a1df220Schristos
2030a1df220Schristos /* High Precision Event Timer */
2040a1df220Schristos sc->sc_hpet_memt = pa->pa_memt;
2050a1df220Schristos tcpcib_rescan(self, "hpetichbus", NULL);
2060a1df220Schristos
2070a1df220Schristos /* Map Watchdog I/O space */
2080a1df220Schristos reg = pci_conf_read(pa->pa_pc, pa->pa_tag, E600_LPC_WDTBA);
2090a1df220Schristos wdtbase = reg & 0xffff;
2100a1df220Schristos sc->sc_wdt_iot = pa->pa_iot;
2110a1df220Schristos if (reg & (1 << 31) && wdtbase) {
2120a1df220Schristos if (PCI_MAPREG_IO_ADDR(wdtbase) == 0 ||
2130a1df220Schristos bus_space_map(sc->sc_wdt_iot, PCI_MAPREG_IO_ADDR(wdtbase),
2140a1df220Schristos E600_WDT_SIZE, 0, &sc->sc_wdt_ioh)) {
21571fbb921Smsaitoh aprint_error_dev(self,
21671fbb921Smsaitoh "can't map watchdog I/O space\n");
2170a1df220Schristos return;
2180a1df220Schristos }
2190a1df220Schristos aprint_normal_dev(self, "watchdog");
2200a1df220Schristos
2210a1df220Schristos /* Check for reboot on timeout */
2220a1df220Schristos reg = bus_space_read_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
2230a1df220Schristos E600_WDT_RR1);
2240a1df220Schristos if (reg & E600_WDT_RR1_TIMEOUT) {
2250a1df220Schristos aprint_normal(", reboot on timeout");
2260a1df220Schristos
2270a1df220Schristos /* Clear timeout bit */
2280a1df220Schristos tcpcib_wdt_unlock(sc);
2290a1df220Schristos bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
2300a1df220Schristos E600_WDT_RR1, E600_WDT_RR1_TIMEOUT);
2310a1df220Schristos }
2320a1df220Schristos
2330a1df220Schristos /* Check it's not locked already */
2340a1df220Schristos reg = bus_space_read_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
2350a1df220Schristos E600_WDT_WDTLR);
2360a1df220Schristos if (reg & E600_WDT_WDTLR_LOCK) {
2370a1df220Schristos aprint_error(", locked\n");
2380a1df220Schristos return;
2390a1df220Schristos }
2400a1df220Schristos
2410a1df220Schristos /* Disable watchdog */
2420a1df220Schristos tcpcib_wdt_stop(sc);
2430a1df220Schristos
2440a1df220Schristos /* Register new watchdog */
2450a1df220Schristos sc->sc_wdt_smw.smw_name = device_xname(self);
2460a1df220Schristos sc->sc_wdt_smw.smw_cookie = sc;
2470a1df220Schristos sc->sc_wdt_smw.smw_setmode = tcpcib_wdt_setmode;
2480a1df220Schristos sc->sc_wdt_smw.smw_tickle = tcpcib_wdt_tickle;
2490a1df220Schristos sc->sc_wdt_smw.smw_period = 600; /* seconds */
2500a1df220Schristos if (sysmon_wdog_register(&sc->sc_wdt_smw)) {
2510a1df220Schristos aprint_error(", unable to register wdog timer\n");
2520a1df220Schristos return;
2530a1df220Schristos }
2540a1df220Schristos
2550a1df220Schristos sc->sc_wdt_valid = true;
2560a1df220Schristos aprint_normal("\n");
2570a1df220Schristos }
2580a1df220Schristos
2590a1df220Schristos }
2600a1df220Schristos
2610a1df220Schristos static int
tcpcib_detach(device_t self,int flags)2620a1df220Schristos tcpcib_detach(device_t self, int flags)
2630a1df220Schristos {
2640a1df220Schristos return pcibdetach(self, flags);
2650a1df220Schristos }
2660a1df220Schristos
2670a1df220Schristos static int
tcpcib_rescan(device_t self,const char * ifattr,const int * locators)2680a1df220Schristos tcpcib_rescan(device_t self, const char *ifattr, const int *locators)
2690a1df220Schristos {
2700a1df220Schristos struct tcpcib_softc *sc = device_private(self);
2710a1df220Schristos
2720a1df220Schristos if (ifattr_match(ifattr, "hpetichbus") && sc->sc_hpetbus == NULL) {
2730a1df220Schristos struct lpcib_hpet_attach_args hpet_arg;
2740a1df220Schristos hpet_arg.hpet_mem_t = sc->sc_hpet_memt;
2750a1df220Schristos hpet_arg.hpet_reg = E600_HPET_BASE;
2762685996bSthorpej sc->sc_hpetbus =
2772685996bSthorpej config_found(self, &hpet_arg, NULL,
278*c7fb772bSthorpej CFARGS(.iattr = "hpetichbus"));
2790a1df220Schristos }
2800a1df220Schristos
2810a1df220Schristos return pcibrescan(self, ifattr, locators);
2820a1df220Schristos }
2830a1df220Schristos
2840a1df220Schristos static void
tcpcib_childdet(device_t self,device_t child)2850a1df220Schristos tcpcib_childdet(device_t self, device_t child)
2860a1df220Schristos {
2870a1df220Schristos struct tcpcib_softc *sc = device_private(self);
2880a1df220Schristos
2890a1df220Schristos if (sc->sc_hpetbus == child) {
2900a1df220Schristos sc->sc_hpetbus = NULL;
2910a1df220Schristos return;
2920a1df220Schristos }
2930a1df220Schristos
2940a1df220Schristos pcibchilddet(self, child);
2950a1df220Schristos }
2960a1df220Schristos
2970a1df220Schristos static bool
tcpcib_suspend(device_t self,const pmf_qual_t * qual)2980a1df220Schristos tcpcib_suspend(device_t self, const pmf_qual_t *qual)
2990a1df220Schristos {
3000a1df220Schristos struct tcpcib_softc *sc = device_private(self);
3010a1df220Schristos
3020a1df220Schristos if (sc->sc_wdt_valid)
3030a1df220Schristos tcpcib_wdt_stop(sc);
3040a1df220Schristos
3050a1df220Schristos return true;
3060a1df220Schristos }
3070a1df220Schristos
3080a1df220Schristos static bool
tcpcib_resume(device_t self,const pmf_qual_t * qual)3090a1df220Schristos tcpcib_resume(device_t self, const pmf_qual_t *qual)
3100a1df220Schristos {
3110a1df220Schristos struct tcpcib_softc *sc = device_private(self);
3120a1df220Schristos struct sysmon_wdog *smw = &sc->sc_wdt_smw;
3130a1df220Schristos
3140a1df220Schristos if (sc->sc_wdt_valid) {
3150a1df220Schristos if ((smw->smw_mode & WDOG_MODE_MASK) != WDOG_MODE_DISARMED &&
3160a1df220Schristos smw->smw_period > 0) {
3170a1df220Schristos tcpcib_wdt_init(sc, smw->smw_period);
3180a1df220Schristos tcpcib_wdt_start(sc);
3190a1df220Schristos } else {
3200a1df220Schristos tcpcib_wdt_stop(sc);
3210a1df220Schristos }
3220a1df220Schristos }
3230a1df220Schristos
3240a1df220Schristos return true;
3250a1df220Schristos }
3260a1df220Schristos
3270a1df220Schristos static int
tcpcib_wdt_setmode(struct sysmon_wdog * smw)3280a1df220Schristos tcpcib_wdt_setmode(struct sysmon_wdog *smw)
3290a1df220Schristos {
3300a1df220Schristos struct tcpcib_softc *sc = smw->smw_cookie;
3310a1df220Schristos unsigned int period;
3320a1df220Schristos
3330a1df220Schristos period = smw->smw_period;
3340a1df220Schristos if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
3350a1df220Schristos tcpcib_wdt_stop(sc);
3360a1df220Schristos } else {
3370a1df220Schristos /* 600 seconds is the maximum supported timeout value */
3380a1df220Schristos if (period > 600)
3390a1df220Schristos return EINVAL;
3400a1df220Schristos
3410a1df220Schristos tcpcib_wdt_stop(sc);
3420a1df220Schristos tcpcib_wdt_init(sc, period);
3430a1df220Schristos tcpcib_wdt_start(sc);
3440a1df220Schristos tcpcib_wdt_tickle(smw);
3450a1df220Schristos }
3460a1df220Schristos
3470a1df220Schristos return 0;
3480a1df220Schristos }
3490a1df220Schristos
3500a1df220Schristos static int
tcpcib_wdt_tickle(struct sysmon_wdog * smw)3510a1df220Schristos tcpcib_wdt_tickle(struct sysmon_wdog *smw)
3520a1df220Schristos {
3530a1df220Schristos struct tcpcib_softc *sc = smw->smw_cookie;
3540a1df220Schristos
3550a1df220Schristos /* Reset timer */
3560a1df220Schristos tcpcib_wdt_unlock(sc);
3570a1df220Schristos bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
3580a1df220Schristos E600_WDT_RR1, E600_WDT_RR1_RELOAD);
3590a1df220Schristos
3600a1df220Schristos return 0;
3610a1df220Schristos }
362