1 /* $NetBSD: hpet.c,v 1.17 2020/05/16 23:06:40 ad Exp $ */ 2 3 /* 4 * Copyright (c) 2006 Nicolas Joly 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. The name of the author may not be used to endorse or promote products 16 * derived from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS 19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 22 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /* 32 * High Precision Event Timer. 33 */ 34 35 #include <sys/cdefs.h> 36 __KERNEL_RCSID(0, "$NetBSD: hpet.c,v 1.17 2020/05/16 23:06:40 ad Exp $"); 37 38 #include <sys/systm.h> 39 #include <sys/device.h> 40 #include <sys/module.h> 41 42 #include <sys/time.h> 43 #include <sys/timetc.h> 44 45 #include <sys/bus.h> 46 #include <sys/lock.h> 47 48 #include <machine/cpu_counter.h> 49 50 #include <dev/ic/hpetreg.h> 51 #include <dev/ic/hpetvar.h> 52 53 static u_int hpet_get_timecount(struct timecounter *); 54 static bool hpet_resume(device_t, const pmf_qual_t *); 55 56 static struct hpet_softc *hpet0 __read_mostly; 57 static uint32_t hpet_attach_val; 58 static uint64_t hpet_attach_tsc; 59 60 int 61 hpet_detach(device_t dv, int flags) 62 { 63 #if 0 /* XXX DELAY() is based off this, detaching is not a good idea. */ 64 struct hpet_softc *sc = device_private(dv); 65 int rc; 66 67 if ((rc = tc_detach(&sc->sc_tc)) != 0) 68 return rc; 69 70 pmf_device_deregister(dv); 71 72 bus_space_write_4(sc->sc_memt, sc->sc_memh, HPET_CONFIG, sc->sc_config); 73 74 return 0; 75 #else 76 return EBUSY; 77 #endif 78 } 79 80 void 81 hpet_attach_subr(device_t dv) 82 { 83 struct hpet_softc *sc = device_private(dv); 84 struct timecounter *tc; 85 uint64_t tmp; 86 uint32_t val, sval, eval; 87 int i; 88 89 tc = &sc->sc_tc; 90 91 tc->tc_name = device_xname(dv); 92 tc->tc_get_timecount = hpet_get_timecount; 93 tc->tc_quality = 2000; 94 95 tc->tc_counter_mask = 0xffffffff; 96 97 /* Get frequency */ 98 sc->sc_period = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_PERIOD); 99 if (sc->sc_period == 0 || sc->sc_period > HPET_PERIOD_MAX) { 100 aprint_error_dev(dv, "invalid timer period\n"); 101 return; 102 } 103 104 /* 105 * The following loop is a workaround for AMD SB700 based systems. 106 * http://kerneltrap.org/mailarchive/git-commits-head/2008/8/17/2964724 107 * http://git.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=a6825f1c1fa83b1e92b6715ee5771a4d6524d3b9 108 */ 109 for (i = 0; bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_CONFIG) 110 == 0xffffffff; i++) { 111 if (i >= 1000) { 112 aprint_error_dev(dv, 113 "HPET_CONFIG value = 0xffffffff\n"); 114 return; 115 } 116 } 117 118 tmp = (1000000000000000ULL * 2) / sc->sc_period; 119 tc->tc_frequency = (tmp / 2) + (tmp & 1); 120 121 /* Enable timer */ 122 val = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_CONFIG); 123 sc->sc_config = val; 124 if ((val & HPET_CONFIG_ENABLE) == 0) { 125 val |= HPET_CONFIG_ENABLE; 126 bus_space_write_4(sc->sc_memt, sc->sc_memh, HPET_CONFIG, val); 127 } 128 129 tc->tc_priv = sc; 130 tc_init(tc); 131 132 if (!pmf_device_register(dv, NULL, hpet_resume)) 133 aprint_error_dev(dv, "couldn't establish power handler\n"); 134 135 if (device_unit(dv) == 0) 136 hpet0 = sc; 137 138 /* 139 * Determine approximately how long it takes to read the counter 140 * register once, and compute an ajustment for hpet_delay() based on 141 * that. 142 */ 143 (void)bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 144 sval = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 145 for (i = 0; i < 998; i++) 146 (void)bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 147 eval = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 148 val = eval - sval; 149 sc->sc_adj = (int64_t)val * sc->sc_period / 1000; 150 151 /* Store attach-time values for computing TSC frequency later. */ 152 if (cpu_hascounter() && sc == hpet0) { 153 (void)cpu_counter(); 154 val = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 155 hpet_attach_tsc = cpu_counter(); 156 hpet_attach_val = val; 157 } 158 } 159 160 static u_int 161 hpet_get_timecount(struct timecounter *tc) 162 { 163 struct hpet_softc *sc = tc->tc_priv; 164 165 return bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 166 } 167 168 static bool 169 hpet_resume(device_t dv, const pmf_qual_t *qual) 170 { 171 struct hpet_softc *sc = device_private(dv); 172 uint32_t val; 173 174 val = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_CONFIG); 175 val |= HPET_CONFIG_ENABLE; 176 bus_space_write_4(sc->sc_memt, sc->sc_memh, HPET_CONFIG, val); 177 178 return true; 179 } 180 181 bool 182 hpet_delay_p(void) 183 { 184 185 return hpet0 != NULL; 186 } 187 188 void 189 hpet_delay(unsigned int us) 190 { 191 struct hpet_softc *sc; 192 uint32_t ntick, otick; 193 int64_t delta; 194 195 /* 196 * Read timer before slow division. Convert microseconds to 197 * femtoseconds, subtract the cost of 1 counter register access, 198 * and convert to HPET units. 199 */ 200 sc = hpet0; 201 otick = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 202 delta = (((int64_t)us * 1000000000) - sc->sc_adj) / sc->sc_period; 203 204 while (delta > 0) { 205 SPINLOCK_BACKOFF_HOOK; 206 ntick = bus_space_read_4(sc->sc_memt, sc->sc_memh, 207 HPET_MCOUNT_LO); 208 delta -= (uint32_t)(ntick - otick); 209 otick = ntick; 210 } 211 } 212 213 uint64_t 214 hpet_tsc_freq(void) 215 { 216 struct hpet_softc *sc; 217 uint64_t td, val, freq; 218 uint32_t hd; 219 int s; 220 221 if (hpet0 == NULL || !cpu_hascounter()) 222 return 0; 223 224 /* Slow down if we got here from attach in under 0.1s. */ 225 sc = hpet0; 226 hd = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 227 hd -= hpet_attach_val; 228 if (hd < (uint64_t)100000 * 1000000000 / sc->sc_period) 229 hpet_delay(100000); 230 231 /* 232 * Determine TSC freq by comparing how far the TSC and HPET have 233 * advanced since attach time. Take the cost of reading HPET 234 * register into account and round result to the nearest 1000. 235 */ 236 s = splhigh(); 237 (void)cpu_counter(); 238 hd = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 239 td = cpu_counter(); 240 splx(s); 241 hd -= hpet_attach_val; 242 val = ((uint64_t)hd * sc->sc_period - sc->sc_adj) / 100000000; 243 freq = (td - hpet_attach_tsc) * 10000000 / val; 244 return rounddown(freq + 500, 1000); 245 } 246 247 MODULE(MODULE_CLASS_DRIVER, hpet, NULL); 248 249 #ifdef _MODULE 250 #include "ioconf.c" 251 #endif 252 253 static int 254 hpet_modcmd(modcmd_t cmd, void *aux) 255 { 256 int rv = 0; 257 258 switch (cmd) { 259 260 case MODULE_CMD_INIT: 261 262 #ifdef _MODULE 263 rv = config_init_component(cfdriver_ioconf_hpet, 264 cfattach_ioconf_hpet, cfdata_ioconf_hpet); 265 #endif 266 break; 267 268 case MODULE_CMD_FINI: 269 270 #ifdef _MODULE 271 rv = config_fini_component(cfdriver_ioconf_hpet, 272 cfattach_ioconf_hpet, cfdata_ioconf_hpet); 273 #endif 274 break; 275 276 default: 277 rv = ENOTTY; 278 } 279 280 return rv; 281 } 282