308 lines
8.9 KiB
C
308 lines
8.9 KiB
C
/* $OpenBSD: acpihpet.c,v 1.31 2023/02/04 19:19:37 cheloha Exp $ */
|
|
/*
|
|
* Copyright (c) 2005 Thorsten Lockert <tholo@sigmasoft.com>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#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 { /* timers */
|
|
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
|
|
};
|
|
|
|
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);
|
|
|
|
/* stop, then save */
|
|
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:
|
|
/* stop, restore, then restart */
|
|
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 we do not have a table, it is not us; attach only once
|
|
*/
|
|
if (acpihpet_attached || aaa->aaa_table == NULL)
|
|
return (0);
|
|
|
|
/*
|
|
* If it is an HPET table, we can attach
|
|
*/
|
|
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; /* timer period in femtoseconds (10^-15) */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Revisions 0x30 through 0x3a of the AMD SB700, with spread
|
|
* spectrum enabled, have an SMM based HPET emulation that's
|
|
* subtly broken. The hardware is initialized upon first
|
|
* access of the configuration register. Initialization takes
|
|
* some time during which the configuration register returns
|
|
* 0xffffffff.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/* enable hpet */
|
|
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);
|
|
|
|
/* make sure hpet is working */
|
|
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));
|
|
|
|
/* Period must be > 0 and less than 100ns (10^8 fs) */
|
|
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));
|
|
}
|