1 /* $OpenBSD: acpihpet.c,v 1.21 2015/10/06 20:49:32 matthew 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 u_int64_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, u_int64_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 }; 50 51 #define HPET_TIMERS 3 52 struct hpet_regs { 53 u_int64_t configuration; 54 u_int64_t interrupt_status; 55 u_int64_t main_counter; 56 struct { /* timers */ 57 u_int64_t config; 58 u_int64_t compare; 59 u_int64_t interrupt; 60 } timers[HPET_TIMERS]; 61 }; 62 63 struct acpihpet_softc { 64 struct device sc_dev; 65 66 bus_space_tag_t sc_iot; 67 bus_space_handle_t sc_ioh; 68 69 u_int32_t sc_conf; 70 struct hpet_regs sc_save; 71 }; 72 73 struct cfattach acpihpet_ca = { 74 sizeof(struct acpihpet_softc), acpihpet_match, acpihpet_attach, 75 NULL, acpihpet_activate 76 }; 77 78 struct cfdriver acpihpet_cd = { 79 NULL, "acpihpet", DV_DULL 80 }; 81 82 u_int64_t 83 acpihpet_r(bus_space_tag_t iot, bus_space_handle_t ioh, bus_size_t ioa) 84 { 85 u_int64_t val; 86 87 val = bus_space_read_4(iot, ioh, ioa + 4); 88 val = val << 32; 89 val |= bus_space_read_4(iot, ioh, ioa); 90 return (val); 91 } 92 93 void 94 acpihpet_w(bus_space_tag_t iot, bus_space_handle_t ioh, bus_size_t ioa, 95 u_int64_t val) 96 { 97 bus_space_write_4(iot, ioh, ioa + 4, val >> 32); 98 bus_space_write_4(iot, ioh, ioa, val & 0xffffffff); 99 } 100 101 int 102 acpihpet_activate(struct device *self, int act) 103 { 104 struct acpihpet_softc *sc = (struct acpihpet_softc *) self; 105 106 switch (act) { 107 case DVACT_SUSPEND: 108 /* stop, then save */ 109 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 110 HPET_CONFIGURATION, sc->sc_conf); 111 112 sc->sc_save.configuration = acpihpet_r(sc->sc_iot, 113 sc->sc_ioh, HPET_CONFIGURATION); 114 sc->sc_save.interrupt_status = acpihpet_r(sc->sc_iot, 115 sc->sc_ioh, HPET_INTERRUPT_STATUS); 116 sc->sc_save.main_counter = acpihpet_r(sc->sc_iot, 117 sc->sc_ioh, HPET_MAIN_COUNTER); 118 sc->sc_save.timers[0].config = acpihpet_r(sc->sc_iot, 119 sc->sc_ioh, HPET_TIMER0_CONFIG); 120 sc->sc_save.timers[0].interrupt = acpihpet_r(sc->sc_iot, 121 sc->sc_ioh, HPET_TIMER0_INTERRUPT); 122 sc->sc_save.timers[0].compare = acpihpet_r(sc->sc_iot, 123 sc->sc_ioh, HPET_TIMER0_COMPARE); 124 sc->sc_save.timers[1].config = acpihpet_r(sc->sc_iot, 125 sc->sc_ioh, HPET_TIMER1_CONFIG); 126 sc->sc_save.timers[1].interrupt = acpihpet_r(sc->sc_iot, 127 sc->sc_ioh, HPET_TIMER1_INTERRUPT); 128 sc->sc_save.timers[1].compare = acpihpet_r(sc->sc_iot, 129 sc->sc_ioh, HPET_TIMER1_COMPARE); 130 sc->sc_save.timers[2].config = acpihpet_r(sc->sc_iot, 131 sc->sc_ioh, HPET_TIMER2_CONFIG); 132 sc->sc_save.timers[2].interrupt = acpihpet_r(sc->sc_iot, 133 sc->sc_ioh, HPET_TIMER2_INTERRUPT); 134 sc->sc_save.timers[2].compare = acpihpet_r(sc->sc_iot, 135 sc->sc_ioh, HPET_TIMER2_COMPARE); 136 break; 137 case DVACT_RESUME: 138 /* stop, restore, then restart */ 139 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 140 HPET_CONFIGURATION, sc->sc_conf); 141 142 acpihpet_w(sc->sc_iot, sc->sc_ioh, 143 HPET_CONFIGURATION, sc->sc_save.configuration); 144 acpihpet_w(sc->sc_iot, sc->sc_ioh, 145 HPET_INTERRUPT_STATUS, sc->sc_save.interrupt_status); 146 acpihpet_w(sc->sc_iot, sc->sc_ioh, 147 HPET_MAIN_COUNTER, sc->sc_save.main_counter); 148 acpihpet_w(sc->sc_iot, sc->sc_ioh, 149 HPET_TIMER0_CONFIG, sc->sc_save.timers[0].config); 150 acpihpet_w(sc->sc_iot, sc->sc_ioh, 151 HPET_TIMER0_INTERRUPT, sc->sc_save.timers[0].interrupt); 152 acpihpet_w(sc->sc_iot, sc->sc_ioh, 153 HPET_TIMER0_COMPARE, sc->sc_save.timers[0].compare); 154 acpihpet_w(sc->sc_iot, sc->sc_ioh, 155 HPET_TIMER1_CONFIG, sc->sc_save.timers[1].config); 156 acpihpet_w(sc->sc_iot, sc->sc_ioh, 157 HPET_TIMER1_INTERRUPT, sc->sc_save.timers[1].interrupt); 158 acpihpet_w(sc->sc_iot, sc->sc_ioh, 159 HPET_TIMER1_COMPARE, sc->sc_save.timers[1].compare); 160 acpihpet_w(sc->sc_iot, sc->sc_ioh, 161 HPET_TIMER2_CONFIG, sc->sc_save.timers[2].config); 162 acpihpet_w(sc->sc_iot, sc->sc_ioh, 163 HPET_TIMER2_INTERRUPT, sc->sc_save.timers[2].interrupt); 164 acpihpet_w(sc->sc_iot, sc->sc_ioh, 165 HPET_TIMER2_COMPARE, sc->sc_save.timers[2].compare); 166 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 167 HPET_CONFIGURATION, sc->sc_conf | 1); 168 break; 169 } 170 171 return 0; 172 } 173 174 int 175 acpihpet_match(struct device *parent, void *match, void *aux) 176 { 177 struct acpi_attach_args *aaa = aux; 178 struct acpi_table_header *hdr; 179 180 /* 181 * If we do not have a table, it is not us; attach only once 182 */ 183 if (acpihpet_attached || aaa->aaa_table == NULL) 184 return (0); 185 186 /* 187 * If it is an HPET table, we can attach 188 */ 189 hdr = (struct acpi_table_header *)aaa->aaa_table; 190 if (memcmp(hdr->signature, HPET_SIG, sizeof(HPET_SIG) - 1) != 0) 191 return (0); 192 193 return (1); 194 } 195 196 void 197 acpihpet_attach(struct device *parent, struct device *self, void *aux) 198 { 199 struct acpihpet_softc *sc = (struct acpihpet_softc *) self; 200 struct acpi_softc *psc = (struct acpi_softc *)parent; 201 struct acpi_attach_args *aaa = aux; 202 struct acpi_hpet *hpet = (struct acpi_hpet *)aaa->aaa_table; 203 u_int64_t period, freq; /* timer period in femtoseconds (10^-15) */ 204 u_int32_t v1, v2; 205 int timeout; 206 207 if (acpi_map_address(psc, &hpet->base_address, 0, HPET_REG_SIZE, 208 &sc->sc_ioh, &sc->sc_iot)) { 209 printf(": can't map i/o space\n"); 210 return; 211 } 212 213 /* 214 * Revisions 0x30 through 0x3a of the AMD SB700, with spread 215 * spectrum enabled, have an SMM based HPET emulation that's 216 * subtly broken. The hardware is initialized upon first 217 * access of the configuration register. Initialization takes 218 * some time during which the configuration register returns 219 * 0xffffffff. 220 */ 221 timeout = 1000; 222 do { 223 if (bus_space_read_4(sc->sc_iot, sc->sc_ioh, 224 HPET_CONFIGURATION) != 0xffffffff) 225 break; 226 } while(--timeout > 0); 227 228 if (timeout == 0) { 229 printf(": disabled\n"); 230 return; 231 } 232 233 /* enable hpet */ 234 sc->sc_conf = bus_space_read_4(sc->sc_iot, sc->sc_ioh, 235 HPET_CONFIGURATION) & ~1; 236 bus_space_write_4(sc->sc_iot, sc->sc_ioh, HPET_CONFIGURATION, 237 sc->sc_conf | 1); 238 239 /* make sure hpet is working */ 240 v1 = bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER); 241 delay(1); 242 v2 = bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER); 243 if (v1 == v2) { 244 printf(": counter not incrementing\n"); 245 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 246 HPET_CONFIGURATION, sc->sc_conf); 247 return; 248 } 249 250 period = bus_space_read_4(sc->sc_iot, sc->sc_ioh, 251 HPET_CAPABILITIES + sizeof(u_int32_t)); 252 253 /* Period must be > 0 and less than 100ns (10^8 fs) */ 254 if (period == 0 || period > HPET_MAX_PERIOD) { 255 printf(": invalid period\n"); 256 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 257 HPET_CONFIGURATION, sc->sc_conf); 258 return; 259 } 260 freq = 1000000000000000ull / period; 261 printf(": %lld Hz\n", freq); 262 263 hpet_timecounter.tc_frequency = (u_int32_t)freq; 264 hpet_timecounter.tc_priv = sc; 265 hpet_timecounter.tc_name = sc->sc_dev.dv_xname; 266 tc_init(&hpet_timecounter); 267 acpihpet_attached++; 268 } 269 270 u_int 271 acpihpet_gettime(struct timecounter *tc) 272 { 273 struct acpihpet_softc *sc = tc->tc_priv; 274 275 return (bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER)); 276 } 277