1 /* $OpenBSD: acpihpet.c,v 1.24 2020/07/06 13:33:08 pirofti Exp $ */ 2 /* 3 * Copyright (c) 2005 Thorsten Lockert <tholo@sigmasoft.com> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/param.h> 19 #include <sys/systm.h> 20 #include <sys/device.h> 21 #include <sys/timetc.h> 22 23 #include <machine/bus.h> 24 25 #include <dev/acpi/acpireg.h> 26 #include <dev/acpi/acpivar.h> 27 #include <dev/acpi/acpidev.h> 28 29 int acpihpet_attached; 30 31 int acpihpet_match(struct device *, void *, void *); 32 void acpihpet_attach(struct device *, struct device *, void *); 33 int acpihpet_activate(struct device *, int); 34 35 u_int acpihpet_gettime(struct timecounter *tc); 36 37 uint64_t acpihpet_r(bus_space_tag_t _iot, bus_space_handle_t _ioh, 38 bus_size_t _ioa); 39 void acpihpet_w(bus_space_tag_t _iot, bus_space_handle_t _ioh, 40 bus_size_t _ioa, uint64_t _val); 41 42 static struct timecounter hpet_timecounter = { 43 acpihpet_gettime, /* get_timecount */ 44 0, /* no poll_pps */ 45 0xffffffff, /* counter_mask (32 bits) */ 46 0, /* frequency */ 47 0, /* name */ 48 1000, /* quality */ 49 NULL, /* private bits */ 50 0, /* expose to user */ 51 }; 52 53 #define HPET_TIMERS 3 54 struct hpet_regs { 55 uint64_t configuration; 56 uint64_t interrupt_status; 57 uint64_t main_counter; 58 struct { /* timers */ 59 uint64_t config; 60 uint64_t compare; 61 uint64_t interrupt; 62 } timers[HPET_TIMERS]; 63 }; 64 65 struct acpihpet_softc { 66 struct device sc_dev; 67 68 bus_space_tag_t sc_iot; 69 bus_space_handle_t sc_ioh; 70 71 uint32_t sc_conf; 72 struct hpet_regs sc_save; 73 }; 74 75 struct cfattach acpihpet_ca = { 76 sizeof(struct acpihpet_softc), acpihpet_match, acpihpet_attach, 77 NULL, acpihpet_activate 78 }; 79 80 struct cfdriver acpihpet_cd = { 81 NULL, "acpihpet", DV_DULL 82 }; 83 84 uint64_t 85 acpihpet_r(bus_space_tag_t iot, bus_space_handle_t ioh, bus_size_t ioa) 86 { 87 uint64_t val; 88 89 val = bus_space_read_4(iot, ioh, ioa + 4); 90 val = val << 32; 91 val |= bus_space_read_4(iot, ioh, ioa); 92 return (val); 93 } 94 95 void 96 acpihpet_w(bus_space_tag_t iot, bus_space_handle_t ioh, bus_size_t ioa, 97 uint64_t val) 98 { 99 bus_space_write_4(iot, ioh, ioa + 4, val >> 32); 100 bus_space_write_4(iot, ioh, ioa, val & 0xffffffff); 101 } 102 103 int 104 acpihpet_activate(struct device *self, int act) 105 { 106 struct acpihpet_softc *sc = (struct acpihpet_softc *) self; 107 108 switch (act) { 109 case DVACT_SUSPEND: 110 /* stop, then save */ 111 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 112 HPET_CONFIGURATION, sc->sc_conf); 113 114 sc->sc_save.configuration = acpihpet_r(sc->sc_iot, 115 sc->sc_ioh, HPET_CONFIGURATION); 116 sc->sc_save.interrupt_status = acpihpet_r(sc->sc_iot, 117 sc->sc_ioh, HPET_INTERRUPT_STATUS); 118 sc->sc_save.main_counter = acpihpet_r(sc->sc_iot, 119 sc->sc_ioh, HPET_MAIN_COUNTER); 120 sc->sc_save.timers[0].config = acpihpet_r(sc->sc_iot, 121 sc->sc_ioh, HPET_TIMER0_CONFIG); 122 sc->sc_save.timers[0].interrupt = acpihpet_r(sc->sc_iot, 123 sc->sc_ioh, HPET_TIMER0_INTERRUPT); 124 sc->sc_save.timers[0].compare = acpihpet_r(sc->sc_iot, 125 sc->sc_ioh, HPET_TIMER0_COMPARE); 126 sc->sc_save.timers[1].config = acpihpet_r(sc->sc_iot, 127 sc->sc_ioh, HPET_TIMER1_CONFIG); 128 sc->sc_save.timers[1].interrupt = acpihpet_r(sc->sc_iot, 129 sc->sc_ioh, HPET_TIMER1_INTERRUPT); 130 sc->sc_save.timers[1].compare = acpihpet_r(sc->sc_iot, 131 sc->sc_ioh, HPET_TIMER1_COMPARE); 132 sc->sc_save.timers[2].config = acpihpet_r(sc->sc_iot, 133 sc->sc_ioh, HPET_TIMER2_CONFIG); 134 sc->sc_save.timers[2].interrupt = acpihpet_r(sc->sc_iot, 135 sc->sc_ioh, HPET_TIMER2_INTERRUPT); 136 sc->sc_save.timers[2].compare = acpihpet_r(sc->sc_iot, 137 sc->sc_ioh, HPET_TIMER2_COMPARE); 138 break; 139 case DVACT_RESUME: 140 /* stop, restore, then restart */ 141 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 142 HPET_CONFIGURATION, sc->sc_conf); 143 144 acpihpet_w(sc->sc_iot, sc->sc_ioh, 145 HPET_CONFIGURATION, sc->sc_save.configuration); 146 acpihpet_w(sc->sc_iot, sc->sc_ioh, 147 HPET_INTERRUPT_STATUS, sc->sc_save.interrupt_status); 148 acpihpet_w(sc->sc_iot, sc->sc_ioh, 149 HPET_MAIN_COUNTER, sc->sc_save.main_counter); 150 acpihpet_w(sc->sc_iot, sc->sc_ioh, 151 HPET_TIMER0_CONFIG, sc->sc_save.timers[0].config); 152 acpihpet_w(sc->sc_iot, sc->sc_ioh, 153 HPET_TIMER0_INTERRUPT, sc->sc_save.timers[0].interrupt); 154 acpihpet_w(sc->sc_iot, sc->sc_ioh, 155 HPET_TIMER0_COMPARE, sc->sc_save.timers[0].compare); 156 acpihpet_w(sc->sc_iot, sc->sc_ioh, 157 HPET_TIMER1_CONFIG, sc->sc_save.timers[1].config); 158 acpihpet_w(sc->sc_iot, sc->sc_ioh, 159 HPET_TIMER1_INTERRUPT, sc->sc_save.timers[1].interrupt); 160 acpihpet_w(sc->sc_iot, sc->sc_ioh, 161 HPET_TIMER1_COMPARE, sc->sc_save.timers[1].compare); 162 acpihpet_w(sc->sc_iot, sc->sc_ioh, 163 HPET_TIMER2_CONFIG, sc->sc_save.timers[2].config); 164 acpihpet_w(sc->sc_iot, sc->sc_ioh, 165 HPET_TIMER2_INTERRUPT, sc->sc_save.timers[2].interrupt); 166 acpihpet_w(sc->sc_iot, sc->sc_ioh, 167 HPET_TIMER2_COMPARE, sc->sc_save.timers[2].compare); 168 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 169 HPET_CONFIGURATION, sc->sc_conf | 1); 170 break; 171 } 172 173 return 0; 174 } 175 176 int 177 acpihpet_match(struct device *parent, void *match, void *aux) 178 { 179 struct acpi_attach_args *aaa = aux; 180 struct acpi_table_header *hdr; 181 182 /* 183 * If we do not have a table, it is not us; attach only once 184 */ 185 if (acpihpet_attached || aaa->aaa_table == NULL) 186 return (0); 187 188 /* 189 * If it is an HPET table, we can attach 190 */ 191 hdr = (struct acpi_table_header *)aaa->aaa_table; 192 if (memcmp(hdr->signature, HPET_SIG, sizeof(HPET_SIG) - 1) != 0) 193 return (0); 194 195 return (1); 196 } 197 198 void 199 acpihpet_attach(struct device *parent, struct device *self, void *aux) 200 { 201 struct acpihpet_softc *sc = (struct acpihpet_softc *) self; 202 struct acpi_softc *psc = (struct acpi_softc *)parent; 203 struct acpi_attach_args *aaa = aux; 204 struct acpi_hpet *hpet = (struct acpi_hpet *)aaa->aaa_table; 205 uint64_t period, freq; /* timer period in femtoseconds (10^-15) */ 206 uint32_t v1, v2; 207 int timeout; 208 209 if (acpi_map_address(psc, &hpet->base_address, 0, HPET_REG_SIZE, 210 &sc->sc_ioh, &sc->sc_iot)) { 211 printf(": can't map i/o space\n"); 212 return; 213 } 214 215 /* 216 * Revisions 0x30 through 0x3a of the AMD SB700, with spread 217 * spectrum enabled, have an SMM based HPET emulation that's 218 * subtly broken. The hardware is initialized upon first 219 * access of the configuration register. Initialization takes 220 * some time during which the configuration register returns 221 * 0xffffffff. 222 */ 223 timeout = 1000; 224 do { 225 if (bus_space_read_4(sc->sc_iot, sc->sc_ioh, 226 HPET_CONFIGURATION) != 0xffffffff) 227 break; 228 } while(--timeout > 0); 229 230 if (timeout == 0) { 231 printf(": disabled\n"); 232 return; 233 } 234 235 /* enable hpet */ 236 sc->sc_conf = bus_space_read_4(sc->sc_iot, sc->sc_ioh, 237 HPET_CONFIGURATION) & ~1; 238 bus_space_write_4(sc->sc_iot, sc->sc_ioh, HPET_CONFIGURATION, 239 sc->sc_conf | 1); 240 241 /* make sure hpet is working */ 242 v1 = bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER); 243 delay(1); 244 v2 = bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER); 245 if (v1 == v2) { 246 printf(": counter not incrementing\n"); 247 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 248 HPET_CONFIGURATION, sc->sc_conf); 249 return; 250 } 251 252 period = bus_space_read_4(sc->sc_iot, sc->sc_ioh, 253 HPET_CAPABILITIES + sizeof(uint32_t)); 254 255 /* Period must be > 0 and less than 100ns (10^8 fs) */ 256 if (period == 0 || period > HPET_MAX_PERIOD) { 257 printf(": invalid period\n"); 258 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 259 HPET_CONFIGURATION, sc->sc_conf); 260 return; 261 } 262 freq = 1000000000000000ull / period; 263 printf(": %lld Hz\n", freq); 264 265 hpet_timecounter.tc_frequency = (uint32_t)freq; 266 hpet_timecounter.tc_priv = sc; 267 hpet_timecounter.tc_name = sc->sc_dev.dv_xname; 268 tc_init(&hpet_timecounter); 269 #if defined(__amd64__) 270 extern void cpu_recalibrate_tsc(struct timecounter *); 271 cpu_recalibrate_tsc(&hpet_timecounter); 272 #endif 273 acpihpet_attached++; 274 } 275 276 u_int 277 acpihpet_gettime(struct timecounter *tc) 278 { 279 struct acpihpet_softc *sc = tc->tc_priv; 280 281 return (bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER)); 282 } 283