acpi_hpet.c revision 203062
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 203062 2010-01-27 10:17:28Z avg $");
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
104/* Discover the HPET via the ACPI table of the same name. */
105static void
106acpi_hpet_identify(driver_t *driver, device_t parent)
107{
108	ACPI_TABLE_HPET *hpet;
109	ACPI_TABLE_HEADER *hdr;
110	ACPI_STATUS	status;
111	device_t	child;
112
113	/* Only one HPET device can be added. */
114	if (devclass_get_device(acpi_hpet_devclass, 0))
115		return;
116
117	/* Currently, ID and minimum clock tick info is unused. */
118
119	status = AcpiGetTable(ACPI_SIG_HPET, 1, (ACPI_TABLE_HEADER **)&hdr);
120	if (ACPI_FAILURE(status))
121		return;
122
123	/*
124	 * The unit number could be derived from hdr->Sequence but we only
125	 * support one HPET device.
126	 */
127	hpet = (ACPI_TABLE_HPET *)hdr;
128	if (hpet->Sequence != 0)
129		printf("ACPI HPET table warning: Sequence is non-zero (%d)\n",
130		    hpet->Sequence);
131	child = BUS_ADD_CHILD(parent, ACPI_DEV_BASE_ORDER, "acpi_hpet", 0);
132	if (child == NULL) {
133		printf("%s: can't add child\n", __func__);
134		return;
135	}
136
137	bus_set_resource(child, SYS_RES_MEMORY, 0, hpet->Address.Address,
138	    HPET_MEM_WIDTH);
139}
140
141static int
142acpi_hpet_probe(device_t dev)
143{
144	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
145
146	if (acpi_disabled("hpet"))
147		return (ENXIO);
148	if (acpi_get_handle(dev) != NULL &&
149	    (ACPI_ID_PROBE(device_get_parent(dev), dev, hpet_ids) == NULL ||
150	    device_get_unit(dev) != 0))
151		return (ENXIO);
152
153	device_set_desc(dev, "High Precision Event Timer");
154	return (0);
155}
156
157static int
158acpi_hpet_attach(device_t dev)
159{
160	struct acpi_hpet_softc *sc;
161	int rid, num_timers;
162	uint32_t val, val2;
163	uintmax_t freq;
164	uint16_t vendor;
165
166	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
167
168	sc = device_get_softc(dev);
169	sc->dev = dev;
170	sc->handle = acpi_get_handle(dev);
171
172	rid = 0;
173	sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
174	    RF_ACTIVE);
175	if (sc->mem_res == NULL)
176		return (ENOMEM);
177
178	/* Validate that we can access the whole region. */
179	if (rman_get_size(sc->mem_res) < HPET_MEM_WIDTH) {
180		device_printf(dev, "memory region width %ld too small\n",
181		    rman_get_size(sc->mem_res));
182		bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res);
183		return (ENXIO);
184	}
185
186	/* Be sure timer is enabled. */
187	hpet_enable(sc);
188
189	/* Read basic statistics about the timer. */
190	val = bus_read_4(sc->mem_res, HPET_PERIOD);
191	if (val == 0) {
192		device_printf(dev, "invalid period\n");
193		hpet_disable(sc);
194		bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res);
195		return (ENXIO);
196	}
197
198	freq = (1000000000000000LL + val / 2) / val;
199	if (bootverbose) {
200		val = bus_read_4(sc->mem_res, HPET_CAPABILITIES);
201
202		/*
203		 * ATI/AMD violates IA-PC HPET (High Precision Event Timers)
204		 * Specification and provides an off by one number
205		 * of timers/comparators.
206		 * Additionally, they use unregistered value in VENDOR_ID field.
207		 */
208		num_timers = 1 + ((val & HPET_CAP_NUM_TIM) >> 8);
209		vendor = val >> 16;
210		if (vendor == HPET_VENDID_AMD && num_timers > 0)
211			num_timers--;
212		device_printf(dev,
213		    "vend: 0x%x rev: 0x%x num: %d hz: %jd opts:%s%s\n",
214		    vendor, val & HPET_CAP_REV_ID,
215		    num_timers, freq,
216		    (val & HPET_CAP_LEG_RT) ? " legacy_route" : "",
217		    (val & HPET_CAP_COUNT_SIZE) ? " 64-bit" : "");
218	}
219
220	if (testenv("debug.acpi.hpet_test"))
221		acpi_hpet_test(sc);
222
223	/*
224	 * Don't attach if the timer never increments.  Since the spec
225	 * requires it to be at least 10 MHz, it has to change in 1 us.
226	 */
227	val = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
228	DELAY(1);
229	val2 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
230	if (val == val2) {
231		device_printf(dev, "HPET never increments, disabling\n");
232		hpet_disable(sc);
233		bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res);
234		return (ENXIO);
235	}
236
237	hpet_timecounter.tc_frequency = freq;
238	hpet_timecounter.tc_priv = sc;
239	tc_init(&hpet_timecounter);
240
241	return (0);
242}
243
244static int
245acpi_hpet_detach(device_t dev)
246{
247	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
248
249	/* XXX Without a tc_remove() function, we can't detach. */
250	return (EBUSY);
251}
252
253static int
254acpi_hpet_suspend(device_t dev)
255{
256	struct acpi_hpet_softc *sc;
257
258	/*
259	 * Disable the timer during suspend.  The timer will not lose
260	 * its state in S1 or S2, but we are required to disable
261	 * it.
262	 */
263	sc = device_get_softc(dev);
264	hpet_disable(sc);
265
266	return (0);
267}
268
269static int
270acpi_hpet_resume(device_t dev)
271{
272	struct acpi_hpet_softc *sc;
273
274	/* Re-enable the timer after a resume to keep the clock advancing. */
275	sc = device_get_softc(dev);
276	hpet_enable(sc);
277
278	return (0);
279}
280
281/* Print some basic latency/rate information to assist in debugging. */
282static void
283acpi_hpet_test(struct acpi_hpet_softc *sc)
284{
285	int i;
286	uint32_t u1, u2;
287	struct bintime b0, b1, b2;
288	struct timespec ts;
289
290	binuptime(&b0);
291	binuptime(&b0);
292	binuptime(&b1);
293	u1 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
294	for (i = 1; i < 1000; i++)
295		u2 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
296	binuptime(&b2);
297	u2 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
298
299	bintime_sub(&b2, &b1);
300	bintime_sub(&b1, &b0);
301	bintime_sub(&b2, &b1);
302	bintime2timespec(&b2, &ts);
303
304	device_printf(sc->dev, "%ld.%09ld: %u ... %u = %u\n",
305	    (long)ts.tv_sec, ts.tv_nsec, u1, u2, u2 - u1);
306
307	device_printf(sc->dev, "time per call: %ld ns\n", ts.tv_nsec / 1000);
308}
309
310static device_method_t acpi_hpet_methods[] = {
311	/* Device interface */
312	DEVMETHOD(device_identify, acpi_hpet_identify),
313	DEVMETHOD(device_probe, acpi_hpet_probe),
314	DEVMETHOD(device_attach, acpi_hpet_attach),
315	DEVMETHOD(device_detach, acpi_hpet_detach),
316	DEVMETHOD(device_suspend, acpi_hpet_suspend),
317	DEVMETHOD(device_resume, acpi_hpet_resume),
318
319	{0, 0}
320};
321
322static driver_t	acpi_hpet_driver = {
323	"acpi_hpet",
324	acpi_hpet_methods,
325	sizeof(struct acpi_hpet_softc),
326};
327
328
329DRIVER_MODULE(acpi_hpet, acpi, acpi_hpet_driver, acpi_hpet_devclass, 0, 0);
330MODULE_DEPEND(acpi_hpet, acpi, 1, 1, 1);
331