acpi_hpet.c revision 175385
1/*-
2 * Copyright (c) 2005 Poul-Henning Kamp
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sys/dev/acpica/acpi_hpet.c 175385 2008-01-16 18:47:07Z jhb $");
29
30#include "opt_acpi.h"
31#include <sys/param.h>
32#include <sys/bus.h>
33#include <sys/kernel.h>
34#include <sys/module.h>
35#include <sys/rman.h>
36#include <sys/time.h>
37#include <sys/timetc.h>
38
39#include <contrib/dev/acpica/acpi.h>
40#include <dev/acpica/acpivar.h>
41#include <dev/acpica/acpi_hpet.h>
42
43ACPI_SERIAL_DECL(hpet, "ACPI HPET support");
44
45static devclass_t acpi_hpet_devclass;
46
47/* ACPI CA debugging */
48#define _COMPONENT	ACPI_TIMER
49ACPI_MODULE_NAME("HPET")
50
51struct acpi_hpet_softc {
52	device_t		dev;
53	struct resource		*mem_res;
54	ACPI_HANDLE		handle;
55};
56
57static u_int hpet_get_timecount(struct timecounter *tc);
58static void acpi_hpet_test(struct acpi_hpet_softc *sc);
59
60static char *hpet_ids[] = { "PNP0103", NULL };
61
62#define DEV_HPET(x)	(acpi_get_magic(x) == (uintptr_t)&acpi_hpet_devclass)
63
64struct timecounter hpet_timecounter = {
65	.tc_get_timecount =	hpet_get_timecount,
66	.tc_counter_mask =	~0u,
67	.tc_name =		"HPET",
68	.tc_quality =		900,
69};
70
71static u_int
72hpet_get_timecount(struct timecounter *tc)
73{
74	struct acpi_hpet_softc *sc;
75
76	sc = tc->tc_priv;
77	return (bus_read_4(sc->mem_res, HPET_MAIN_COUNTER));
78}
79
80static void
81hpet_enable(struct acpi_hpet_softc *sc)
82{
83	uint32_t val;
84
85	val = bus_read_4(sc->mem_res, HPET_CONFIG);
86	bus_write_4(sc->mem_res, HPET_CONFIG, val | HPET_CNF_ENABLE);
87}
88
89static void
90hpet_disable(struct acpi_hpet_softc *sc)
91{
92	uint32_t val;
93
94	val = bus_read_4(sc->mem_res, HPET_CONFIG);
95	bus_write_4(sc->mem_res, HPET_CONFIG, val & ~HPET_CNF_ENABLE);
96}
97
98/* Discover the HPET via the ACPI table of the same name. */
99static void
100acpi_hpet_identify(driver_t *driver, device_t parent)
101{
102	ACPI_TABLE_HPET *hpet;
103	ACPI_TABLE_HEADER *hdr;
104	ACPI_STATUS	status;
105	device_t	child;
106
107	/* Only one HPET device can be added. */
108	if (devclass_get_device(acpi_hpet_devclass, 0))
109		return;
110
111	/* Currently, ID and minimum clock tick info is unused. */
112
113	status = AcpiGetTable(ACPI_SIG_HPET, 1, (ACPI_TABLE_HEADER **)&hdr);
114	if (ACPI_FAILURE(status))
115		return;
116
117	/*
118	 * The unit number could be derived from hdr->Sequence but we only
119	 * support one HPET device.
120	 */
121	hpet = (ACPI_TABLE_HPET *)hdr;
122	if (hpet->Sequence != 0)
123		printf("ACPI HPET table warning: Sequence is non-zero (%d)\n",
124		    hpet->Sequence);
125	child = BUS_ADD_CHILD(parent, ACPI_DEV_BASE_ORDER, "acpi_hpet", 0);
126	if (child == NULL) {
127		printf("%s: can't add child\n", __func__);
128		return;
129	}
130
131	/* Record a magic value so we can detect this device later. */
132	acpi_set_magic(child, (uintptr_t)&acpi_hpet_devclass);
133	bus_set_resource(child, SYS_RES_MEMORY, 0, hpet->Address.Address,
134	    HPET_MEM_WIDTH);
135}
136
137static int
138acpi_hpet_probe(device_t dev)
139{
140	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
141
142	if (acpi_disabled("hpet"))
143		return (ENXIO);
144	if (!DEV_HPET(dev) &&
145	    (ACPI_ID_PROBE(device_get_parent(dev), dev, hpet_ids) == NULL ||
146	    device_get_unit(dev) != 0))
147		return (ENXIO);
148
149	device_set_desc(dev, "High Precision Event Timer");
150	return (0);
151}
152
153static int
154acpi_hpet_attach(device_t dev)
155{
156	struct acpi_hpet_softc *sc;
157	int rid;
158	uint32_t val, val2;
159	uintmax_t freq;
160
161	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
162
163	sc = device_get_softc(dev);
164	sc->dev = dev;
165	sc->handle = acpi_get_handle(dev);
166
167	rid = 0;
168	sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
169	    RF_ACTIVE);
170	if (sc->mem_res == NULL)
171		return (ENOMEM);
172
173	/* Validate that we can access the whole region. */
174	if (rman_get_size(sc->mem_res) < HPET_MEM_WIDTH) {
175		device_printf(dev, "memory region width %ld too small\n",
176		    rman_get_size(sc->mem_res));
177		bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res);
178		return (ENXIO);
179	}
180
181	/* Be sure timer is enabled. */
182	hpet_enable(sc);
183
184	/* Read basic statistics about the timer. */
185	val = bus_read_4(sc->mem_res, HPET_PERIOD);
186	if (val == 0) {
187		device_printf(dev, "invalid period\n");
188		hpet_disable(sc);
189		bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res);
190		return (ENXIO);
191	}
192
193	freq = (1000000000000000LL + val / 2) / val;
194	if (bootverbose) {
195		val = bus_read_4(sc->mem_res, HPET_CAPABILITIES);
196		device_printf(dev,
197		    "vend: 0x%x rev: 0x%x num: %d hz: %jd opts:%s%s\n",
198		    val >> 16, val & HPET_CAP_REV_ID,
199		    (val & HPET_CAP_NUM_TIM) >> 8, freq,
200		    (val & HPET_CAP_LEG_RT) ? " legacy_route" : "",
201		    (val & HPET_CAP_COUNT_SIZE) ? " 64-bit" : "");
202	}
203
204	if (testenv("debug.acpi.hpet_test"))
205		acpi_hpet_test(sc);
206
207	/*
208	 * Don't attach if the timer never increments.  Since the spec
209	 * requires it to be at least 10 MHz, it has to change in 1 us.
210	 */
211	val = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
212	DELAY(1);
213	val2 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
214	if (val == val2) {
215		device_printf(dev, "HPET never increments, disabling\n");
216		hpet_disable(sc);
217		bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res);
218		return (ENXIO);
219	}
220
221	hpet_timecounter.tc_frequency = freq;
222	hpet_timecounter.tc_priv = sc;
223	tc_init(&hpet_timecounter);
224
225	return (0);
226}
227
228static int
229acpi_hpet_detach(device_t dev)
230{
231	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
232
233	/* XXX Without a tc_remove() function, we can't detach. */
234	return (EBUSY);
235}
236
237static int
238acpi_hpet_suspend(device_t dev)
239{
240	struct acpi_hpet_softc *sc;
241
242	/*
243	 * Disable the timer during suspend.  The timer will not lose
244	 * its state in S1 or S2, but we are required to disable
245	 * it.
246	 */
247	sc = device_get_softc(dev);
248	hpet_disable(sc);
249
250	return (0);
251}
252
253static int
254acpi_hpet_resume(device_t dev)
255{
256	struct acpi_hpet_softc *sc;
257
258	/* Re-enable the timer after a resume to keep the clock advancing. */
259	sc = device_get_softc(dev);
260	hpet_enable(sc);
261
262	return (0);
263}
264
265/* Print some basic latency/rate information to assist in debugging. */
266static void
267acpi_hpet_test(struct acpi_hpet_softc *sc)
268{
269	int i;
270	uint32_t u1, u2;
271	struct bintime b0, b1, b2;
272	struct timespec ts;
273
274	binuptime(&b0);
275	binuptime(&b0);
276	binuptime(&b1);
277	u1 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
278	for (i = 1; i < 1000; i++)
279		u2 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
280	binuptime(&b2);
281	u2 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
282
283	bintime_sub(&b2, &b1);
284	bintime_sub(&b1, &b0);
285	bintime_sub(&b2, &b1);
286	bintime2timespec(&b2, &ts);
287
288	device_printf(sc->dev, "%ld.%09ld: %u ... %u = %u\n",
289	    (long)ts.tv_sec, ts.tv_nsec, u1, u2, u2 - u1);
290
291	device_printf(sc->dev, "time per call: %ld ns\n", ts.tv_nsec / 1000);
292}
293
294static device_method_t acpi_hpet_methods[] = {
295	/* Device interface */
296	DEVMETHOD(device_identify, acpi_hpet_identify),
297	DEVMETHOD(device_probe, acpi_hpet_probe),
298	DEVMETHOD(device_attach, acpi_hpet_attach),
299	DEVMETHOD(device_detach, acpi_hpet_detach),
300	DEVMETHOD(device_suspend, acpi_hpet_suspend),
301	DEVMETHOD(device_resume, acpi_hpet_resume),
302
303	{0, 0}
304};
305
306static driver_t	acpi_hpet_driver = {
307	"acpi_hpet",
308	acpi_hpet_methods,
309	sizeof(struct acpi_hpet_softc),
310};
311
312
313DRIVER_MODULE(acpi_hpet, acpi, acpi_hpet_driver, acpi_hpet_devclass, 0, 0);
314MODULE_DEPEND(acpi_hpet, acpi, 1, 1, 1);
315