1/* $NetBSD: hpet.c,v 1.18 2022/08/20 06:47:28 mlelstv 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.18 2022/08/20 06:47:28 mlelstv 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 53static u_int hpet_get_timecount(struct timecounter *); 54static bool hpet_resume(device_t, const pmf_qual_t *); 55 56static struct hpet_softc *hpet0 __read_mostly; 57 58int 59hpet_detach(device_t dv, int flags) 60{ 61#if 0 /* XXX DELAY() is based off this, detaching is not a good idea. */ 62 struct hpet_softc *sc = device_private(dv); 63 int rc; 64 65 if ((rc = tc_detach(&sc->sc_tc)) != 0) 66 return rc; 67 68 pmf_device_deregister(dv); 69 70 bus_space_write_4(sc->sc_memt, sc->sc_memh, HPET_CONFIG, sc->sc_config); 71 72 return 0; 73#else 74 return EBUSY; 75#endif 76} 77 78void 79hpet_attach_subr(device_t dv) 80{ 81 struct hpet_softc *sc = device_private(dv); 82 struct timecounter *tc; 83 uint64_t tmp; 84 uint32_t val, sval, eval; 85 int i; 86 87 tc = &sc->sc_tc; 88 89 tc->tc_name = device_xname(dv); 90 tc->tc_get_timecount = hpet_get_timecount; 91 tc->tc_quality = 2000; 92 93 tc->tc_counter_mask = 0xffffffff; 94 95 /* Get frequency */ 96 sc->sc_period = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_PERIOD); 97 if (sc->sc_period == 0 || sc->sc_period > HPET_PERIOD_MAX) { 98 aprint_error_dev(dv, "invalid timer period\n"); 99 return; 100 } 101 102 /* 103 * The following loop is a workaround for AMD SB700 based systems. 104 * http://kerneltrap.org/mailarchive/git-commits-head/2008/8/17/2964724 105 * http://git.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=a6825f1c1fa83b1e92b6715ee5771a4d6524d3b9 106 */ 107 for (i = 0; bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_CONFIG) 108 == 0xffffffff; i++) { 109 if (i >= 1000) { 110 aprint_error_dev(dv, 111 "HPET_CONFIG value = 0xffffffff\n"); 112 return; 113 } 114 } 115 116 tmp = (1000000000000000ULL * 2) / sc->sc_period; 117 tc->tc_frequency = (tmp / 2) + (tmp & 1); 118 119 /* Enable timer */ 120 val = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_CONFIG); 121 sc->sc_config = val; 122 if ((val & HPET_CONFIG_ENABLE) == 0) { 123 val |= HPET_CONFIG_ENABLE; 124 bus_space_write_4(sc->sc_memt, sc->sc_memh, HPET_CONFIG, val); 125 } 126 127 tc->tc_priv = sc; 128 tc_init(tc); 129 130 if (!pmf_device_register(dv, NULL, hpet_resume)) 131 aprint_error_dev(dv, "couldn't establish power handler\n"); 132 133 if (device_unit(dv) == 0) 134 hpet0 = sc; 135 136 /* 137 * Determine approximately how long it takes to read the counter 138 * register once, and compute an ajustment for hpet_delay() based on 139 * that. 140 */ 141 (void)bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 142 sval = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 143 for (i = 0; i < 998; i++) 144 (void)bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 145 eval = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 146 val = eval - sval; 147 sc->sc_adj = (int64_t)val * sc->sc_period / 1000; 148} 149 150static u_int 151hpet_get_timecount(struct timecounter *tc) 152{ 153 struct hpet_softc *sc = tc->tc_priv; 154 155 return bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 156} 157 158static bool 159hpet_resume(device_t dv, const pmf_qual_t *qual) 160{ 161 struct hpet_softc *sc = device_private(dv); 162 uint32_t val; 163 164 val = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_CONFIG); 165 val |= HPET_CONFIG_ENABLE; 166 bus_space_write_4(sc->sc_memt, sc->sc_memh, HPET_CONFIG, val); 167 168 return true; 169} 170 171bool 172hpet_delay_p(void) 173{ 174 175 return hpet0 != NULL; 176} 177 178void 179hpet_delay(unsigned int us) 180{ 181 struct hpet_softc *sc; 182 uint32_t ntick, otick; 183 int64_t delta; 184 185 /* 186 * Read timer before slow division. Convert microseconds to 187 * femtoseconds, subtract the cost of 1 counter register access, 188 * and convert to HPET units. 189 */ 190 sc = hpet0; 191 otick = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 192 delta = (((int64_t)us * 1000000000) - sc->sc_adj) / sc->sc_period; 193 194 while (delta > 0) { 195 SPINLOCK_BACKOFF_HOOK; 196 ntick = bus_space_read_4(sc->sc_memt, sc->sc_memh, 197 HPET_MCOUNT_LO); 198 delta -= (uint32_t)(ntick - otick); 199 otick = ntick; 200 } 201} 202 203uint64_t 204hpet_tsc_freq(void) 205{ 206 struct hpet_softc *sc; 207 uint64_t td0, td, val, freq; 208 uint32_t hd0, hd; 209 int s; 210 211 if (hpet0 == NULL || !cpu_hascounter()) 212 return 0; 213 214 sc = hpet0; 215 216 s = splhigh(); 217 (void)cpu_counter(); 218 (void)bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 219 hd0 = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 220 td0 = cpu_counter(); 221 splx(s); 222 223 /* 224 * Wait 1000000 HPET ticks (typically 50..100ms). 225 * 226 * This interval can produce an error of 1ppm (a few kHz 227 * in estimated TSC frequency), however the HPET timer is 228 * allowed to drift +/- 500ppm in that interval. 229 * 230 */ 231 hpet_delay(sc->sc_period / 1000); 232 233 /* 234 * Determine TSC freq by comparing how far the TSC and HPET have 235 * advanced and round result to the nearest 1000. 236 */ 237 s = splhigh(); 238 (void)cpu_counter(); 239 (void)bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 240 hd = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO); 241 td = cpu_counter(); 242 splx(s); 243 244 val = (uint64_t)(hd - hd0) * sc->sc_period / 100000000; 245 freq = (td - td0) * 10000000 / val; 246 return rounddown(freq + 500, 1000); 247} 248 249MODULE(MODULE_CLASS_DRIVER, hpet, NULL); 250 251#ifdef _MODULE 252#include "ioconf.c" 253#endif 254 255static int 256hpet_modcmd(modcmd_t cmd, void *aux) 257{ 258 int rv = 0; 259 260 switch (cmd) { 261 262 case MODULE_CMD_INIT: 263 264#ifdef _MODULE 265 rv = config_init_component(cfdriver_ioconf_hpet, 266 cfattach_ioconf_hpet, cfdata_ioconf_hpet); 267#endif 268 break; 269 270 case MODULE_CMD_FINI: 271 272#ifdef _MODULE 273 rv = config_fini_component(cfdriver_ioconf_hpet, 274 cfattach_ioconf_hpet, cfdata_ioconf_hpet); 275#endif 276 break; 277 278 default: 279 rv = ENOTTY; 280 } 281 282 return rv; 283} 284