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