1 /* $OpenBSD: acpihpet.c,v 1.17 2014/03/05 23:47:00 deraadt 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/malloc.h> 22 #include <sys/timetc.h> 23 24 #include <machine/bus.h> 25 26 #include <dev/acpi/acpireg.h> 27 #include <dev/acpi/acpivar.h> 28 #include <dev/acpi/acpidev.h> 29 30 int acpihpet_attached; 31 32 int acpihpet_match(struct device *, void *, void *); 33 void acpihpet_attach(struct device *, struct device *, void *); 34 int acpihpet_activate(struct device *, int); 35 36 u_int acpihpet_gettime(struct timecounter *tc); 37 38 u_int64_t acpihpet_r(bus_space_tag_t _iot, bus_space_handle_t _ioh, 39 bus_size_t _ioa); 40 void acpihpet_w(bus_space_tag_t _iot, bus_space_handle_t _ioh, 41 bus_size_t _ioa, u_int64_t _val); 42 43 static struct timecounter hpet_timecounter = { 44 acpihpet_gettime, /* get_timecount */ 45 0, /* no poll_pps */ 46 0xffffffff, /* counter_mask (24 bits) */ 47 0, /* frequency */ 48 0, /* name */ 49 1000 /* quality */ 50 }; 51 52 #define HPET_TIMERS 3 53 struct hpet_regs { 54 u_int64_t capability; 55 u_int64_t configuration; 56 u_int64_t interrupt_status; 57 u_int64_t main_counter; 58 struct { /* timers */ 59 u_int64_t config; 60 u_int64_t compare; 61 u_int64_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 u_int32_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 u_int64_t 85 acpihpet_r(bus_space_tag_t iot, bus_space_handle_t ioh, bus_size_t ioa) 86 { 87 u_int64_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 u_int64_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.capability = acpihpet_r(sc->sc_iot, 115 sc->sc_ioh, HPET_CAPABILITIES); 116 sc->sc_save.configuration = acpihpet_r(sc->sc_iot, 117 sc->sc_ioh, HPET_CONFIGURATION); 118 sc->sc_save.interrupt_status = acpihpet_r(sc->sc_iot, 119 sc->sc_ioh, HPET_INTERRUPT_STATUS); 120 sc->sc_save.main_counter = acpihpet_r(sc->sc_iot, 121 sc->sc_ioh, HPET_MAIN_COUNTER); 122 sc->sc_save.timers[0].config = acpihpet_r(sc->sc_iot, 123 sc->sc_ioh, HPET_TIMER0_CONFIG); 124 sc->sc_save.timers[0].interrupt = acpihpet_r(sc->sc_iot, 125 sc->sc_ioh, HPET_TIMER0_INTERRUPT); 126 sc->sc_save.timers[0].compare = acpihpet_r(sc->sc_iot, 127 sc->sc_ioh, HPET_TIMER0_COMPARE); 128 sc->sc_save.timers[1].config = acpihpet_r(sc->sc_iot, 129 sc->sc_ioh, HPET_TIMER1_CONFIG); 130 sc->sc_save.timers[1].interrupt = acpihpet_r(sc->sc_iot, 131 sc->sc_ioh, HPET_TIMER1_INTERRUPT); 132 sc->sc_save.timers[1].compare = acpihpet_r(sc->sc_iot, 133 sc->sc_ioh, HPET_TIMER1_COMPARE); 134 sc->sc_save.timers[2].config = acpihpet_r(sc->sc_iot, 135 sc->sc_ioh, HPET_TIMER2_CONFIG); 136 sc->sc_save.timers[2].interrupt = acpihpet_r(sc->sc_iot, 137 sc->sc_ioh, HPET_TIMER2_INTERRUPT); 138 sc->sc_save.timers[2].compare = acpihpet_r(sc->sc_iot, 139 sc->sc_ioh, HPET_TIMER2_COMPARE); 140 break; 141 case DVACT_RESUME: 142 /* stop, restore, then restart */ 143 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 144 HPET_CONFIGURATION, sc->sc_conf); 145 146 acpihpet_w(sc->sc_iot, sc->sc_ioh, 147 HPET_CAPABILITIES, sc->sc_save.capability); 148 acpihpet_w(sc->sc_iot, sc->sc_ioh, 149 HPET_CONFIGURATION, sc->sc_save.configuration); 150 acpihpet_w(sc->sc_iot, sc->sc_ioh, 151 HPET_INTERRUPT_STATUS, sc->sc_save.interrupt_status); 152 acpihpet_w(sc->sc_iot, sc->sc_ioh, 153 HPET_MAIN_COUNTER, sc->sc_save.main_counter); 154 acpihpet_w(sc->sc_iot, sc->sc_ioh, 155 HPET_TIMER0_CONFIG, sc->sc_save.timers[0].config); 156 acpihpet_w(sc->sc_iot, sc->sc_ioh, 157 HPET_TIMER0_INTERRUPT, sc->sc_save.timers[0].interrupt); 158 acpihpet_w(sc->sc_iot, sc->sc_ioh, 159 HPET_TIMER0_COMPARE, sc->sc_save.timers[0].compare); 160 acpihpet_w(sc->sc_iot, sc->sc_ioh, 161 HPET_TIMER1_CONFIG, sc->sc_save.timers[1].config); 162 acpihpet_w(sc->sc_iot, sc->sc_ioh, 163 HPET_TIMER1_INTERRUPT, sc->sc_save.timers[1].interrupt); 164 acpihpet_w(sc->sc_iot, sc->sc_ioh, 165 HPET_TIMER1_COMPARE, sc->sc_save.timers[1].compare); 166 acpihpet_w(sc->sc_iot, sc->sc_ioh, 167 HPET_TIMER2_CONFIG, sc->sc_save.timers[2].config); 168 acpihpet_w(sc->sc_iot, sc->sc_ioh, 169 HPET_TIMER2_INTERRUPT, sc->sc_save.timers[2].interrupt); 170 acpihpet_w(sc->sc_iot, sc->sc_ioh, 171 HPET_TIMER2_COMPARE, sc->sc_save.timers[2].compare); 172 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 173 HPET_CONFIGURATION, sc->sc_conf | 1); 174 break; 175 } 176 177 return 0; 178 } 179 180 int 181 acpihpet_match(struct device *parent, void *match, void *aux) 182 { 183 struct acpi_attach_args *aaa = aux; 184 struct acpi_table_header *hdr; 185 186 /* 187 * If we do not have a table, it is not us; attach only once 188 */ 189 if (acpihpet_attached || aaa->aaa_table == NULL) 190 return (0); 191 192 /* 193 * If it is an HPET table, we can attach 194 */ 195 hdr = (struct acpi_table_header *)aaa->aaa_table; 196 if (memcmp(hdr->signature, HPET_SIG, sizeof(HPET_SIG) - 1) != 0) 197 return (0); 198 199 return (1); 200 } 201 202 void 203 acpihpet_attach(struct device *parent, struct device *self, void *aux) 204 { 205 struct acpihpet_softc *sc = (struct acpihpet_softc *) self; 206 struct acpi_softc *psc = (struct acpi_softc *)parent; 207 struct acpi_attach_args *aaa = aux; 208 struct acpi_hpet *hpet = (struct acpi_hpet *)aaa->aaa_table; 209 u_int64_t period, freq; /* timer period in femtoseconds (10^-15) */ 210 u_int32_t v1, v2; 211 int timeout; 212 213 if (acpi_map_address(psc, &hpet->base_address, 0, HPET_REG_SIZE, 214 &sc->sc_ioh, &sc->sc_iot)) { 215 printf(": can't map i/o space\n"); 216 return; 217 } 218 219 /* 220 * Revisions 0x30 through 0x3a of the AMD SB700, with spread 221 * spectrum enabled, have an SMM based HPET emulation that's 222 * subtly broken. The hardware is initialized upon first 223 * access of the configuration register. Initialization takes 224 * some time during which the configuration register returns 225 * 0xffffffff. 226 */ 227 timeout = 1000; 228 do { 229 if (bus_space_read_4(sc->sc_iot, sc->sc_ioh, 230 HPET_CONFIGURATION) != 0xffffffff) 231 break; 232 } while(--timeout > 0); 233 234 if (timeout == 0) { 235 printf(": disabled\n"); 236 return; 237 } 238 239 /* enable hpet */ 240 sc->sc_conf = bus_space_read_4(sc->sc_iot, sc->sc_ioh, 241 HPET_CONFIGURATION) & ~1; 242 bus_space_write_4(sc->sc_iot, sc->sc_ioh, HPET_CONFIGURATION, 243 sc->sc_conf | 1); 244 245 /* make sure hpet is working */ 246 v1 = bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER); 247 delay(1); 248 v2 = bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER); 249 if (v1 == v2) { 250 printf(": counter not incrementing\n"); 251 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 252 HPET_CONFIGURATION, sc->sc_conf); 253 return; 254 } 255 256 period = bus_space_read_4(sc->sc_iot, sc->sc_ioh, 257 HPET_CAPABILITIES + sizeof(u_int32_t)); 258 if (period == 0) { 259 printf(": invalid period\n"); 260 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 261 HPET_CONFIGURATION, sc->sc_conf); 262 return; 263 } 264 freq = 1000000000000000ull / period; 265 printf(": %lld Hz\n", freq); 266 267 hpet_timecounter.tc_frequency = (u_int32_t)freq; 268 hpet_timecounter.tc_priv = sc; 269 hpet_timecounter.tc_name = sc->sc_dev.dv_xname; 270 tc_init(&hpet_timecounter); 271 acpihpet_attached++; 272 } 273 274 u_int 275 acpihpet_gettime(struct timecounter *tc) 276 { 277 struct acpihpet_softc *sc = tc->tc_priv; 278 279 return (bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER)); 280 } 281