FreeBSD/Linux Kernel Cross Reference
sys/dev/ic/hpet.c
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 }
Cache object: 4988e8746e8961b9b7dbc3f2d55697b4
|