acpi_hpet.c revision 208436
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 208436 2010-05-23 07:53:22Z mav $");
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/include/acpi.h>
40#include <contrib/dev/acpica/include/accommon.h>
41
42#include <dev/acpica/acpivar.h>
43#include <dev/acpica/acpi_hpet.h>
44
45#define HPET_VENDID_AMD		0x4353
46#define HPET_VENDID_INTEL	0x8086
47
48ACPI_SERIAL_DECL(hpet, "ACPI HPET support");
49
50static devclass_t acpi_hpet_devclass;
51
52/* ACPI CA debugging */
53#define _COMPONENT	ACPI_TIMER
54ACPI_MODULE_NAME("HPET")
55
56struct acpi_hpet_softc {
57	device_t		dev;
58	struct resource		*mem_res;
59	ACPI_HANDLE		handle;
60};
61
62static u_int hpet_get_timecount(struct timecounter *tc);
63static void acpi_hpet_test(struct acpi_hpet_softc *sc);
64
65static char *hpet_ids[] = { "PNP0103", NULL };
66
67struct timecounter hpet_timecounter = {
68	.tc_get_timecount =	hpet_get_timecount,
69	.tc_counter_mask =	~0u,
70	.tc_name =		"HPET",
71	.tc_quality =		900,
72};
73
74static u_int
75hpet_get_timecount(struct timecounter *tc)
76{
77	struct acpi_hpet_softc *sc;
78
79	sc = tc->tc_priv;
80	return (bus_read_4(sc->mem_res, HPET_MAIN_COUNTER));
81}
82
83static void
84hpet_enable(struct acpi_hpet_softc *sc)
85{
86	uint32_t val;
87
88	val = bus_read_4(sc->mem_res, HPET_CONFIG);
89	val &= ~HPET_CNF_LEG_RT;
90	val |= HPET_CNF_ENABLE;
91	bus_write_4(sc->mem_res, HPET_CONFIG, val);
92}
93
94static void
95hpet_disable(struct acpi_hpet_softc *sc)
96{
97	uint32_t val;
98
99	val = bus_read_4(sc->mem_res, HPET_CONFIG);
100	val &= ~HPET_CNF_ENABLE;
101	bus_write_4(sc->mem_res, HPET_CONFIG, val);
102}
103
104static ACPI_STATUS
105acpi_hpet_find(ACPI_HANDLE handle, UINT32 level, void *context,
106    void **status)
107{
108	char 		**ids;
109	uint32_t	id = (uint32_t)(uintptr_t)context;
110	uint32_t	adr = 0;
111
112	for (ids = hpet_ids; *ids != NULL; ids++) {
113		if (acpi_MatchHid(handle, *ids))
114		        break;
115	}
116	if (*ids == NULL)
117		return (AE_OK);
118	if (ACPI_FAILURE(acpi_GetInteger(handle, "_ADR", &adr)))
119		adr = 0;
120	if (id == adr)
121		*((int *)status) = 1;
122	return (AE_OK);
123}
124
125/* Discover the HPET via the ACPI table of the same name. */
126static void
127acpi_hpet_identify(driver_t *driver, device_t parent)
128{
129	ACPI_TABLE_HPET *hpet;
130	ACPI_STATUS	status;
131	device_t	child;
132	int 		i, found;
133
134	/* Only one HPET device can be added. */
135	if (devclass_get_device(acpi_hpet_devclass, 0))
136		return;
137	for (i = 1; ; i++) {
138		/* Search for HPET table. */
139		status = AcpiGetTable(ACPI_SIG_HPET, i, (ACPI_TABLE_HEADER **)&hpet);
140		if (ACPI_FAILURE(status))
141			return;
142		/* Search for HPET device with same ID. */
143		found = 0;
144		AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
145		    100, acpi_hpet_find, NULL, (void *)(uintptr_t)hpet->Sequence, (void *)&found);
146		/* If found - let it be probed in normal way. */
147		if (found)
148			continue;
149		/* If not - create it from table info. */
150		child = BUS_ADD_CHILD(parent, ACPI_DEV_BASE_ORDER, "acpi_hpet", 0);
151		if (child == NULL) {
152			printf("%s: can't add child\n", __func__);
153			continue;
154		}
155		bus_set_resource(child, SYS_RES_MEMORY, 0, hpet->Address.Address,
156		    HPET_MEM_WIDTH);
157	}
158}
159
160static int
161acpi_hpet_probe(device_t dev)
162{
163	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
164
165	if (acpi_disabled("hpet"))
166		return (ENXIO);
167	if (acpi_get_handle(dev) != NULL &&
168	    ACPI_ID_PROBE(device_get_parent(dev), dev, hpet_ids) == NULL)
169		return (ENXIO);
170
171	device_set_desc(dev, "High Precision Event Timer");
172	return (0);
173}
174
175static int
176acpi_hpet_attach(device_t dev)
177{
178	struct acpi_hpet_softc *sc;
179	int rid, num_timers;
180	uint32_t val, val2;
181	uintmax_t freq;
182	uint16_t vendor;
183
184	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
185
186	sc = device_get_softc(dev);
187	sc->dev = dev;
188	sc->handle = acpi_get_handle(dev);
189
190	rid = 0;
191	sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
192	    RF_ACTIVE);
193	if (sc->mem_res == NULL)
194		return (ENOMEM);
195
196	/* Validate that we can access the whole region. */
197	if (rman_get_size(sc->mem_res) < HPET_MEM_WIDTH) {
198		device_printf(dev, "memory region width %ld too small\n",
199		    rman_get_size(sc->mem_res));
200		bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res);
201		return (ENXIO);
202	}
203
204	/* Be sure timer is enabled. */
205	hpet_enable(sc);
206
207	/* Read basic statistics about the timer. */
208	val = bus_read_4(sc->mem_res, HPET_PERIOD);
209	if (val == 0) {
210		device_printf(dev, "invalid period\n");
211		hpet_disable(sc);
212		bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res);
213		return (ENXIO);
214	}
215
216	freq = (1000000000000000LL + val / 2) / val;
217	if (bootverbose) {
218		val = bus_read_4(sc->mem_res, HPET_CAPABILITIES);
219
220		/*
221		 * ATI/AMD violates IA-PC HPET (High Precision Event Timers)
222		 * Specification and provides an off by one number
223		 * of timers/comparators.
224		 * Additionally, they use unregistered value in VENDOR_ID field.
225		 */
226		num_timers = 1 + ((val & HPET_CAP_NUM_TIM) >> 8);
227		vendor = val >> 16;
228		if (vendor == HPET_VENDID_AMD && num_timers > 0)
229			num_timers--;
230		device_printf(dev,
231		    "vend: 0x%x rev: 0x%x num: %d hz: %jd opts:%s%s\n",
232		    vendor, val & HPET_CAP_REV_ID,
233		    num_timers, freq,
234		    (val & HPET_CAP_LEG_RT) ? " legacy_route" : "",
235		    (val & HPET_CAP_COUNT_SIZE) ? " 64-bit" : "");
236	}
237
238	if (testenv("debug.acpi.hpet_test"))
239		acpi_hpet_test(sc);
240
241	/*
242	 * Don't attach if the timer never increments.  Since the spec
243	 * requires it to be at least 10 MHz, it has to change in 1 us.
244	 */
245	val = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
246	DELAY(1);
247	val2 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
248	if (val == val2) {
249		device_printf(dev, "HPET never increments, disabling\n");
250		hpet_disable(sc);
251		bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res);
252		return (ENXIO);
253	}
254	/* Announce first HPET as timecounter. */
255	if (device_get_unit(dev) == 0) {
256		hpet_timecounter.tc_frequency = freq;
257		hpet_timecounter.tc_priv = sc;
258		tc_init(&hpet_timecounter);
259	}
260	return (0);
261}
262
263static int
264acpi_hpet_detach(device_t dev)
265{
266	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
267
268	/* XXX Without a tc_remove() function, we can't detach. */
269	return (EBUSY);
270}
271
272static int
273acpi_hpet_suspend(device_t dev)
274{
275	struct acpi_hpet_softc *sc;
276
277	/*
278	 * Disable the timer during suspend.  The timer will not lose
279	 * its state in S1 or S2, but we are required to disable
280	 * it.
281	 */
282	sc = device_get_softc(dev);
283	hpet_disable(sc);
284
285	return (0);
286}
287
288static int
289acpi_hpet_resume(device_t dev)
290{
291	struct acpi_hpet_softc *sc;
292
293	/* Re-enable the timer after a resume to keep the clock advancing. */
294	sc = device_get_softc(dev);
295	hpet_enable(sc);
296
297	return (0);
298}
299
300/* Print some basic latency/rate information to assist in debugging. */
301static void
302acpi_hpet_test(struct acpi_hpet_softc *sc)
303{
304	int i;
305	uint32_t u1, u2;
306	struct bintime b0, b1, b2;
307	struct timespec ts;
308
309	binuptime(&b0);
310	binuptime(&b0);
311	binuptime(&b1);
312	u1 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
313	for (i = 1; i < 1000; i++)
314		u2 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
315	binuptime(&b2);
316	u2 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
317
318	bintime_sub(&b2, &b1);
319	bintime_sub(&b1, &b0);
320	bintime_sub(&b2, &b1);
321	bintime2timespec(&b2, &ts);
322
323	device_printf(sc->dev, "%ld.%09ld: %u ... %u = %u\n",
324	    (long)ts.tv_sec, ts.tv_nsec, u1, u2, u2 - u1);
325
326	device_printf(sc->dev, "time per call: %ld ns\n", ts.tv_nsec / 1000);
327}
328
329static device_method_t acpi_hpet_methods[] = {
330	/* Device interface */
331	DEVMETHOD(device_identify, acpi_hpet_identify),
332	DEVMETHOD(device_probe, acpi_hpet_probe),
333	DEVMETHOD(device_attach, acpi_hpet_attach),
334	DEVMETHOD(device_detach, acpi_hpet_detach),
335	DEVMETHOD(device_suspend, acpi_hpet_suspend),
336	DEVMETHOD(device_resume, acpi_hpet_resume),
337
338	{0, 0}
339};
340
341static driver_t	acpi_hpet_driver = {
342	"acpi_hpet",
343	acpi_hpet_methods,
344	sizeof(struct acpi_hpet_softc),
345};
346
347
348DRIVER_MODULE(acpi_hpet, acpi, acpi_hpet_driver, acpi_hpet_devclass, 0, 0);
349MODULE_DEPEND(acpi_hpet, acpi, 1, 1, 1);
350