#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/timetc.h>
#include <machine/bus.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>
#define E600_LPC_SMBA 0x40
#define E600_LPC_GBA 0x44
#define E600_LPC_WDTBA 0x84
#define E600_WDT_SIZE 64
#define E600_WDT_PV1 0x00
#define E600_WDT_PV2 0x04
#define E600_WDT_RR0 0x0c
#define E600_WDT_RR1 0x0d
#define E600_WDT_RR1_RELOAD (1 << 0)
#define E600_WDT_RR1_TIMEOUT (1 << 1)
#define E600_WDT_WDTCR 0x10
#define E600_WDT_WDTCR_PRE (1 << 2)
#define E600_WDT_WDTCR_RESET (1 << 3)
#define E600_WDT_WDTCR_ENABLE (1 << 4)
#define E600_WDT_WDTCR_TIMEOUT (1 << 5)
#define E600_WDT_DCR 0x14
#define E600_WDT_WDTLR 0x18
#define E600_WDT_WDTLR_LOCK (1 << 0)
#define E600_WDT_WDTLR_ENABLE (1 << 1)
#define E600_WDT_WDTLR_TIMEOUT (1 << 2)
#define E600_HPET_BASE 0xfed00000
#define E600_HPET_SIZE 0x00000400
#define E600_HPET_GCID 0x000
#define E600_HPET_GCID_WIDTH (1 << 13)
#define E600_HPET_PERIOD 0x004
#define E600_HPET_GC 0x010
#define E600_HPET_GC_ENABLE (1 << 0)
#define E600_HPET_GIS 0x020
#define E600_HPET_MCV 0x0f0
#define E600_HPET_T0C 0x100
#define E600_HPET_T0CV 0x108
#define E600_HPET_T1C 0x120
#define E600_HPET_T1CV 0x128
#define E600_HPET_T2C 0x140
#define E600_HPET_T2CV 0x148
struct tcpcib_softc {
struct device sc_dev;
int sc_active;
#define E600_WDT_ACTIVE (1 << 0)
#define E600_HPET_ACTIVE (1 << 1)
bus_space_tag_t sc_wdt_iot;
bus_space_handle_t sc_wdt_ioh;
int sc_wdt_period;
bus_space_tag_t sc_hpet_iot;
bus_space_handle_t sc_hpet_ioh;
struct timecounter sc_hpet_timecounter;
};
struct cfdriver tcpcib_cd = {
NULL, "tcpcib", DV_DULL
};
int tcpcib_match(struct device *, void *, void *);
void tcpcib_attach(struct device *, struct device *, void *);
int tcpcib_activate(struct device *, int);
int tcpcib_wdt_cb(void *, int);
void tcpcib_wdt_init(struct tcpcib_softc *, int);
void tcpcib_wdt_start(struct tcpcib_softc *);
void tcpcib_wdt_stop(struct tcpcib_softc *);
u_int tcpcib_hpet_get_timecount(struct timecounter *tc);
const struct cfattach tcpcib_ca = {
sizeof(struct tcpcib_softc), tcpcib_match, tcpcib_attach,
NULL, tcpcib_activate
};
void pcibattach(struct device *parent, struct device *self, void *aux);
const struct pci_matchid tcpcib_devices[] = {
{ PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_E600_LPC }
};
static __inline void
tcpcib_wdt_unlock(struct tcpcib_softc *sc)
{
bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR0, 0x80);
bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR0, 0x86);
}
void
tcpcib_wdt_init(struct tcpcib_softc *sc, int period)
{
u_int32_t preload;
preload = (period * 33000000) >> 15;
preload--;
bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTCR,
E600_WDT_WDTCR_ENABLE);
tcpcib_wdt_unlock(sc);
bus_space_write_4(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_PV1, 0);
tcpcib_wdt_unlock(sc);
bus_space_write_4(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_PV2,
preload);
tcpcib_wdt_unlock(sc);
bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR1,
E600_WDT_RR1_RELOAD);
}
void
tcpcib_wdt_start(struct tcpcib_softc *sc)
{
bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTLR,
E600_WDT_WDTLR_ENABLE);
}
void
tcpcib_wdt_stop(struct tcpcib_softc *sc)
{
tcpcib_wdt_unlock(sc);
bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR1,
E600_WDT_RR1_RELOAD);
bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTLR, 0);
}
int
tcpcib_match(struct device *parent, void *match, void *aux)
{
if (pci_matchbyid((struct pci_attach_args *)aux, tcpcib_devices,
sizeof(tcpcib_devices) / sizeof(tcpcib_devices[0])))
return (2);
return (0);
}
void
tcpcib_attach(struct device *parent, struct device *self, void *aux)
{
struct tcpcib_softc *sc = (struct tcpcib_softc *)self;
struct pci_attach_args *pa = aux;
struct timecounter *tc = &sc->sc_hpet_timecounter;
u_int32_t reg, wdtbase;
sc->sc_active = 0;
sc->sc_hpet_iot = pa->pa_memt;
if (bus_space_map(sc->sc_hpet_iot, E600_HPET_BASE, E600_HPET_SIZE, 0,
&sc->sc_hpet_ioh) == 0) {
tc->tc_get_timecount = tcpcib_hpet_get_timecount;
tc->tc_counter_mask = 0xffffffff;
reg = bus_space_read_4(sc->sc_hpet_iot, sc->sc_hpet_ioh,
E600_HPET_PERIOD);
tc->tc_frequency = 1000000000000000ULL / reg;
tc->tc_name = sc->sc_dev.dv_xname;
tc->tc_quality = 2000;
tc->tc_priv = sc;
tc_init(tc);
bus_space_write_4(sc->sc_hpet_iot, sc->sc_hpet_ioh,
E600_HPET_GC, E600_HPET_GC_ENABLE);
sc->sc_active |= E600_HPET_ACTIVE;
printf(": %llu Hz timer", tc->tc_frequency);
}
reg = pci_conf_read(pa->pa_pc, pa->pa_tag, E600_LPC_WDTBA);
wdtbase = reg & 0xffff;
sc->sc_wdt_iot = pa->pa_iot;
if (reg & (1U << 31) && wdtbase) {
if (PCI_MAPREG_IO_ADDR(wdtbase) == 0 ||
bus_space_map(sc->sc_wdt_iot, PCI_MAPREG_IO_ADDR(wdtbase),
E600_WDT_SIZE, 0, &sc->sc_wdt_ioh)) {
printf("%c can't map watchdog I/O space",
sc->sc_active ? ',' : ':');
goto corepcib;
}
printf("%c watchdog", sc->sc_active ? ',' : ':');
reg = bus_space_read_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
E600_WDT_RR1);
if (reg & E600_WDT_RR1_TIMEOUT) {
printf(", reboot on timeout");
tcpcib_wdt_unlock(sc);
bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
E600_WDT_RR1, E600_WDT_RR1_TIMEOUT);
}
reg = bus_space_read_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
E600_WDT_WDTLR);
if (reg & E600_WDT_WDTLR_LOCK) {
printf(", locked");
goto corepcib;
}
tcpcib_wdt_stop(sc);
sc->sc_wdt_period = 0;
sc->sc_active |= E600_WDT_ACTIVE;
wdog_register(tcpcib_wdt_cb, sc);
}
corepcib:
pcibattach(parent, self, aux);
}
int
tcpcib_activate(struct device *self, int act)
{
struct tcpcib_softc *sc = (struct tcpcib_softc *)self;
int rv = 0;
switch (act) {
case DVACT_SUSPEND:
rv = config_activate_children(self, act);
if (sc->sc_active & E600_WDT_ACTIVE && sc->sc_wdt_period != 0)
tcpcib_wdt_stop(sc);
break;
case DVACT_RESUME:
if (sc->sc_active & E600_WDT_ACTIVE) {
if (sc->sc_wdt_period != 0) {
tcpcib_wdt_init(sc, sc->sc_wdt_period);
tcpcib_wdt_start(sc);
} else
tcpcib_wdt_stop(sc);
}
if (sc->sc_active & E600_HPET_ACTIVE)
bus_space_write_4(sc->sc_hpet_iot, sc->sc_hpet_ioh,
E600_HPET_GC, E600_HPET_GC_ENABLE);
rv = config_activate_children(self, act);
break;
case DVACT_POWERDOWN:
if (sc->sc_active & E600_WDT_ACTIVE)
wdog_shutdown(self);
rv = config_activate_children(self, act);
break;
default:
rv = config_activate_children(self, act);
break;
}
return (rv);
}
int
tcpcib_wdt_cb(void *arg, int period)
{
struct tcpcib_softc *sc = arg;
if (period == 0) {
if (sc->sc_wdt_period != 0)
tcpcib_wdt_stop(sc);
} else {
if (period > 600)
period = 600;
if (sc->sc_wdt_period != period)
tcpcib_wdt_init(sc, period);
if (sc->sc_wdt_period == 0) {
tcpcib_wdt_start(sc);
} else {
tcpcib_wdt_unlock(sc);
bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
E600_WDT_RR1, E600_WDT_RR1_RELOAD);
}
}
sc->sc_wdt_period = period;
return (period);
}
u_int
tcpcib_hpet_get_timecount(struct timecounter *tc)
{
struct tcpcib_softc *sc = tc->tc_priv;
return bus_space_read_4(sc->sc_hpet_iot, sc->sc_hpet_ioh,
E600_HPET_MCV);
}