Home | History | Annotate | Line # | Download | only in ic
      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 
     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 
     58 int
     59 hpet_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 
     78 void
     79 hpet_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 
    150 static u_int
    151 hpet_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 
    158 static bool
    159 hpet_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 
    171 bool
    172 hpet_delay_p(void)
    173 {
    174 
    175 	return hpet0 != NULL;
    176 }
    177 
    178 void
    179 hpet_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 
    203 uint64_t
    204 hpet_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 
    249 MODULE(MODULE_CLASS_DRIVER, hpet, NULL);
    250 
    251 #ifdef _MODULE
    252 #include "ioconf.c"
    253 #endif
    254 
    255 static int
    256 hpet_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