#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/stdint.h>
#include <sys/timetc.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpidev.h>
int acpihpet_attached;
int acpihpet_match(struct device *, void *, void *);
void acpihpet_attach(struct device *, struct device *, void *);
int acpihpet_activate(struct device *, int);
void acpihpet_delay(int);
u_int acpihpet_gettime(struct timecounter *tc);
uint64_t acpihpet_r(bus_space_tag_t _iot, bus_space_handle_t _ioh,
bus_size_t _ioa);
void acpihpet_w(bus_space_tag_t _iot, bus_space_handle_t _ioh,
bus_size_t _ioa, uint64_t _val);
static struct timecounter hpet_timecounter = {
.tc_get_timecount = acpihpet_gettime,
.tc_counter_mask = 0xffffffff,
.tc_frequency = 0,
.tc_name = 0,
.tc_quality = 1000,
.tc_priv = NULL,
.tc_user = 0,
};
#define HPET_TIMERS 3
struct hpet_regs {
uint64_t configuration;
uint64_t interrupt_status;
uint64_t main_counter;
struct {
uint64_t config;
uint64_t compare;
uint64_t interrupt;
} timers[HPET_TIMERS];
};
struct acpihpet_softc {
struct device sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
uint32_t sc_conf;
struct hpet_regs sc_save;
};
const struct cfattach acpihpet_ca = {
sizeof(struct acpihpet_softc), acpihpet_match, acpihpet_attach,
NULL, acpihpet_activate
};
struct cfdriver acpihpet_cd = {
NULL, "acpihpet", DV_DULL, CD_COCOVM
};
uint64_t
acpihpet_r(bus_space_tag_t iot, bus_space_handle_t ioh, bus_size_t ioa)
{
uint64_t val;
val = bus_space_read_4(iot, ioh, ioa + 4);
val = val << 32;
val |= bus_space_read_4(iot, ioh, ioa);
return (val);
}
void
acpihpet_w(bus_space_tag_t iot, bus_space_handle_t ioh, bus_size_t ioa,
uint64_t val)
{
bus_space_write_4(iot, ioh, ioa + 4, val >> 32);
bus_space_write_4(iot, ioh, ioa, val & 0xffffffff);
}
int
acpihpet_activate(struct device *self, int act)
{
struct acpihpet_softc *sc = (struct acpihpet_softc *) self;
switch (act) {
case DVACT_SUSPEND:
delay_fini(acpihpet_delay);
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
HPET_CONFIGURATION, sc->sc_conf);
sc->sc_save.configuration = acpihpet_r(sc->sc_iot,
sc->sc_ioh, HPET_CONFIGURATION);
sc->sc_save.interrupt_status = acpihpet_r(sc->sc_iot,
sc->sc_ioh, HPET_INTERRUPT_STATUS);
sc->sc_save.main_counter = acpihpet_r(sc->sc_iot,
sc->sc_ioh, HPET_MAIN_COUNTER);
sc->sc_save.timers[0].config = acpihpet_r(sc->sc_iot,
sc->sc_ioh, HPET_TIMER0_CONFIG);
sc->sc_save.timers[0].interrupt = acpihpet_r(sc->sc_iot,
sc->sc_ioh, HPET_TIMER0_INTERRUPT);
sc->sc_save.timers[0].compare = acpihpet_r(sc->sc_iot,
sc->sc_ioh, HPET_TIMER0_COMPARE);
sc->sc_save.timers[1].config = acpihpet_r(sc->sc_iot,
sc->sc_ioh, HPET_TIMER1_CONFIG);
sc->sc_save.timers[1].interrupt = acpihpet_r(sc->sc_iot,
sc->sc_ioh, HPET_TIMER1_INTERRUPT);
sc->sc_save.timers[1].compare = acpihpet_r(sc->sc_iot,
sc->sc_ioh, HPET_TIMER1_COMPARE);
sc->sc_save.timers[2].config = acpihpet_r(sc->sc_iot,
sc->sc_ioh, HPET_TIMER2_CONFIG);
sc->sc_save.timers[2].interrupt = acpihpet_r(sc->sc_iot,
sc->sc_ioh, HPET_TIMER2_INTERRUPT);
sc->sc_save.timers[2].compare = acpihpet_r(sc->sc_iot,
sc->sc_ioh, HPET_TIMER2_COMPARE);
break;
case DVACT_RESUME:
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
HPET_CONFIGURATION, sc->sc_conf);
acpihpet_w(sc->sc_iot, sc->sc_ioh,
HPET_CONFIGURATION, sc->sc_save.configuration);
acpihpet_w(sc->sc_iot, sc->sc_ioh,
HPET_INTERRUPT_STATUS, sc->sc_save.interrupt_status);
acpihpet_w(sc->sc_iot, sc->sc_ioh,
HPET_MAIN_COUNTER, sc->sc_save.main_counter);
acpihpet_w(sc->sc_iot, sc->sc_ioh,
HPET_TIMER0_CONFIG, sc->sc_save.timers[0].config);
acpihpet_w(sc->sc_iot, sc->sc_ioh,
HPET_TIMER0_INTERRUPT, sc->sc_save.timers[0].interrupt);
acpihpet_w(sc->sc_iot, sc->sc_ioh,
HPET_TIMER0_COMPARE, sc->sc_save.timers[0].compare);
acpihpet_w(sc->sc_iot, sc->sc_ioh,
HPET_TIMER1_CONFIG, sc->sc_save.timers[1].config);
acpihpet_w(sc->sc_iot, sc->sc_ioh,
HPET_TIMER1_INTERRUPT, sc->sc_save.timers[1].interrupt);
acpihpet_w(sc->sc_iot, sc->sc_ioh,
HPET_TIMER1_COMPARE, sc->sc_save.timers[1].compare);
acpihpet_w(sc->sc_iot, sc->sc_ioh,
HPET_TIMER2_CONFIG, sc->sc_save.timers[2].config);
acpihpet_w(sc->sc_iot, sc->sc_ioh,
HPET_TIMER2_INTERRUPT, sc->sc_save.timers[2].interrupt);
acpihpet_w(sc->sc_iot, sc->sc_ioh,
HPET_TIMER2_COMPARE, sc->sc_save.timers[2].compare);
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
HPET_CONFIGURATION, sc->sc_conf | 1);
delay_init(acpihpet_delay, 2000);
break;
}
return 0;
}
int
acpihpet_match(struct device *parent, void *match, void *aux)
{
struct acpi_attach_args *aaa = aux;
struct acpi_table_header *hdr;
if (acpihpet_attached || aaa->aaa_table == NULL)
return (0);
hdr = (struct acpi_table_header *)aaa->aaa_table;
if (memcmp(hdr->signature, HPET_SIG, sizeof(HPET_SIG) - 1) != 0)
return (0);
return (1);
}
void
acpihpet_attach(struct device *parent, struct device *self, void *aux)
{
struct acpihpet_softc *sc = (struct acpihpet_softc *) self;
struct acpi_softc *psc = (struct acpi_softc *)parent;
struct acpi_attach_args *aaa = aux;
struct acpi_hpet *hpet = (struct acpi_hpet *)aaa->aaa_table;
uint64_t period, freq;
uint32_t v1, v2;
int timeout;
if (acpi_map_address(psc, &hpet->base_address, 0, HPET_REG_SIZE,
&sc->sc_ioh, &sc->sc_iot)) {
printf(": can't map i/o space\n");
return;
}
timeout = 1000;
do {
if (bus_space_read_4(sc->sc_iot, sc->sc_ioh,
HPET_CONFIGURATION) != 0xffffffff)
break;
} while(--timeout > 0);
if (timeout == 0) {
printf(": disabled\n");
return;
}
sc->sc_conf = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
HPET_CONFIGURATION) & ~1;
bus_space_write_4(sc->sc_iot, sc->sc_ioh, HPET_CONFIGURATION,
sc->sc_conf | 1);
v1 = bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER);
delay(1);
v2 = bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER);
if (v1 == v2) {
printf(": counter not incrementing\n");
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
HPET_CONFIGURATION, sc->sc_conf);
return;
}
period = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
HPET_CAPABILITIES + sizeof(uint32_t));
if (period == 0 || period > HPET_MAX_PERIOD) {
printf(": invalid period\n");
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
HPET_CONFIGURATION, sc->sc_conf);
return;
}
freq = 1000000000000000ull / period;
printf(": %lld Hz\n", freq);
hpet_timecounter.tc_frequency = freq;
hpet_timecounter.tc_priv = sc;
hpet_timecounter.tc_name = sc->sc_dev.dv_xname;
tc_init(&hpet_timecounter);
delay_init(acpihpet_delay, 2000);
#if defined(__amd64__)
extern void cpu_recalibrate_tsc(struct timecounter *);
cpu_recalibrate_tsc(&hpet_timecounter);
#endif
acpihpet_attached++;
}
void
acpihpet_delay(int usecs)
{
uint64_t count = 0, cycles;
struct acpihpet_softc *sc = hpet_timecounter.tc_priv;
uint32_t val1, val2;
val2 = bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER);
cycles = usecs * hpet_timecounter.tc_frequency / 1000000;
while (count < cycles) {
CPU_BUSY_CYCLE();
val1 = val2;
val2 = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
HPET_MAIN_COUNTER);
count += val2 - val1;
}
}
u_int
acpihpet_gettime(struct timecounter *tc)
{
struct acpihpet_softc *sc = tc->tc_priv;
return (bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER));
}