1/*
2 * Copyright 2009-2010, Stefano Ceccherini (stefano.ceccherini@gmail.com)
3 * Copyright 2008, Dustin Howett, dustin.howett@gmail.com. All rights reserved.
4 * Distributed under the terms of the MIT License.
5 */
6
7#include <debug.h>
8#include <int.h>
9#include <smp.h>
10#include <timer.h>
11
12#include <arch/int.h>
13#include <arch/cpu.h>
14#include <arch/x86/timer.h>
15#include <arch/x86/arch_hpet.h>
16
17#include <boot/kernel_args.h>
18#include <vm/vm.h>
19
20
21//#define TRACE_HPET
22#ifdef TRACE_HPET
23#	define TRACE(x...) dprintf("hpet: " x)
24#else
25#	define TRACE(x...) ;
26#endif
27
28#define TEST_HPET
29
30
31static struct hpet_regs *sHPETRegs;
32static volatile struct hpet_timer *sTimer;
33static uint64 sHPETPeriod;
34
35static int hpet_get_priority();
36static status_t hpet_set_hardware_timer(bigtime_t relativeTimeout);
37static status_t hpet_clear_hardware_timer();
38static status_t hpet_init(struct kernel_args *args);
39
40
41struct timer_info gHPETTimer = {
42	"HPET",
43	&hpet_get_priority,
44	&hpet_set_hardware_timer,
45	&hpet_clear_hardware_timer,
46	&hpet_init
47};
48
49
50static int
51hpet_get_priority()
52{
53	// TODO: Fix HPET in SMP mode.
54	if (smp_get_num_cpus() > 1)
55		return 0;
56
57	// HPET timers, being off-chip, are more expensive to setup
58	// than the LAPIC.
59	return 0;
60}
61
62
63static int32
64hpet_timer_interrupt(void *arg)
65{
66	//dprintf_no_syslog("HPET timer_interrupt!!!!\n");
67	return timer_interrupt();
68}
69
70
71static inline bigtime_t
72hpet_convert_timeout(const bigtime_t &relativeTimeout)
73{
74	return ((relativeTimeout * 1000000000ULL)
75		/ sHPETPeriod) + sHPETRegs->u0.counter64;
76}
77
78
79#define MIN_TIMEOUT 3000
80
81static status_t
82hpet_set_hardware_timer(bigtime_t relativeTimeout)
83{
84	cpu_status state = disable_interrupts();
85
86	// enable timer interrupt
87	sTimer->config |= HPET_CONF_TIMER_INT_ENABLE;
88
89	// TODO:
90	if (relativeTimeout < MIN_TIMEOUT)
91		relativeTimeout = MIN_TIMEOUT;
92
93	bigtime_t timerValue = hpet_convert_timeout(relativeTimeout);
94
95	sTimer->u0.comparator64 = timerValue;
96
97	restore_interrupts(state);
98
99	return B_OK;
100}
101
102
103static status_t
104hpet_clear_hardware_timer()
105{
106	// Disable timer interrupt
107	sTimer->config &= ~HPET_CONF_TIMER_INT_ENABLE;
108	return B_OK;
109}
110
111
112static status_t
113hpet_set_enabled(bool enabled)
114{
115	if (enabled)
116		sHPETRegs->config |= HPET_CONF_MASK_ENABLED;
117	else
118		sHPETRegs->config &= ~HPET_CONF_MASK_ENABLED;
119	return B_OK;
120}
121
122
123static status_t
124hpet_set_legacy(bool enabled)
125{
126	if (!HPET_IS_LEGACY_CAPABLE(sHPETRegs)) {
127		dprintf("hpet_init: HPET doesn't support legacy mode. Skipping.\n");
128		return B_NOT_SUPPORTED;
129	}
130
131	if (enabled)
132		sHPETRegs->config |= HPET_CONF_MASK_LEGACY;
133	else
134		sHPETRegs->config &= ~HPET_CONF_MASK_LEGACY;
135
136	return B_OK;
137}
138
139
140#ifdef TRACE_HPET
141static void
142hpet_dump_timer(volatile struct hpet_timer *timer)
143{
144	dprintf("HPET Timer %ld:\n", (timer - sHPETRegs->timer));
145
146	dprintf("\troutable IRQs: ");
147	uint32 interrupts = (uint32)HPET_GET_CAP_TIMER_ROUTE(timer);
148	for (int i = 0; i < 32; i++) {
149		if (interrupts & (1 << i))
150			dprintf("%d ", i);
151	}
152	dprintf("\n");
153	dprintf("\tconfiguration: 0x%" B_PRIx64 "\n", timer->config);
154	dprintf("\tFSB Enabled: %s\n",
155		timer->config & HPET_CONF_TIMER_FSB_ENABLE ? "Yes" : "No");
156	dprintf("\tInterrupt Enabled: %s\n",
157		timer->config & HPET_CONF_TIMER_INT_ENABLE ? "Yes" : "No");
158	dprintf("\tTimer type: %s\n",
159		timer->config & HPET_CONF_TIMER_TYPE ? "Periodic" : "OneShot");
160	dprintf("\tInterrupt Type: %s\n",
161		timer->config & HPET_CONF_TIMER_INT_TYPE ? "Level" : "Edge");
162
163	dprintf("\tconfigured IRQ: %" B_PRId64 "\n",
164		HPET_GET_CONF_TIMER_INT_ROUTE(timer));
165
166	if (timer->config & HPET_CONF_TIMER_FSB_ENABLE) {
167		dprintf("\tfsb_route[0]: 0x%" B_PRIx64 "\n", timer->fsb_route[0]);
168		dprintf("\tfsb_route[1]: 0x%" B_PRIx64 "\n", timer->fsb_route[1]);
169	}
170}
171#endif
172
173
174static void
175hpet_init_timer(volatile struct hpet_timer *timer)
176{
177	sTimer = timer;
178
179	uint32 interrupt = 0;
180
181	sTimer->config |= (interrupt << HPET_CONF_TIMER_INT_ROUTE_SHIFT)
182		& HPET_CONF_TIMER_INT_ROUTE_MASK;
183
184	// Non-periodic mode, edge triggered
185	sTimer->config &= ~(HPET_CONF_TIMER_TYPE | HPET_CONF_TIMER_INT_TYPE);
186
187	sTimer->config &= ~HPET_CONF_TIMER_FSB_ENABLE;
188	sTimer->config &= ~HPET_CONF_TIMER_32MODE;
189
190	// Enable timer
191	sTimer->config |= HPET_CONF_TIMER_INT_ENABLE;
192
193#ifdef TRACE_HPET
194	hpet_dump_timer(sTimer);
195#endif
196}
197
198
199static status_t
200hpet_test()
201{
202	uint64 initialValue = sHPETRegs->u0.counter64;
203	spin(10);
204	uint64 finalValue = sHPETRegs->u0.counter64;
205
206	if (initialValue == finalValue) {
207		dprintf("hpet_test: counter does not increment\n");
208		return B_ERROR;
209	}
210
211	return B_OK;
212}
213
214
215static status_t
216hpet_init(struct kernel_args *args)
217{
218	/* hpet_acpi_probe() through a similar "scan spots" table
219	   to that of smp.cpp.
220	   Seems to be the most elegant solution right now. */
221	if (args->arch_args.hpet == NULL)
222		return B_ERROR;
223
224	if (sHPETRegs == NULL) {
225		sHPETRegs = (struct hpet_regs *)args->arch_args.hpet.Pointer();
226		if (vm_map_physical_memory(B_SYSTEM_TEAM, "hpet",
227			(void **)&sHPETRegs, B_EXACT_ADDRESS, B_PAGE_SIZE,
228			B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
229			(phys_addr_t)args->arch_args.hpet_phys, true) < B_OK) {
230			// Would it be better to panic here?
231			dprintf("hpet_init: Failed to map memory for the HPET registers.");
232			return B_ERROR;
233		}
234	}
235
236	sHPETPeriod = HPET_GET_PERIOD(sHPETRegs);
237
238	TRACE("hpet_init: HPET is at %p.\n\tVendor ID: %llx, rev: %llx, period: %" B_PRId64 "\n",
239		sHPETRegs, HPET_GET_VENDOR_ID(sHPETRegs), HPET_GET_REVID(sHPETRegs),
240		sHPETPeriod);
241
242	status_t status = hpet_set_enabled(false);
243	if (status != B_OK)
244		return status;
245
246	status = hpet_set_legacy(true);
247	if (status != B_OK)
248		return status;
249
250	uint32 numTimers = HPET_GET_NUM_TIMERS(sHPETRegs) + 1;
251
252	TRACE("hpet_init: HPET supports %" B_PRIu32 " timers, and is %s bits wide.\n",
253		numTimers, HPET_IS_64BIT(sHPETRegs) ? "64" : "32");
254
255	TRACE("hpet_init: configuration: 0x%" B_PRIx64 ", timer_interrupts: 0x%" B_PRIx64 "\n",
256		sHPETRegs->config, sHPETRegs->interrupt_status);
257
258	if (numTimers < 3) {
259		dprintf("hpet_init: HPET does not have at least 3 timers. Skipping.\n");
260		return B_ERROR;
261	}
262
263#ifdef TRACE_HPET
264	for (uint32 c = 0; c < numTimers; c++)
265		hpet_dump_timer(&sHPETRegs->timer[c]);
266#endif
267
268	hpet_init_timer(&sHPETRegs->timer[0]);
269
270	status = hpet_set_enabled(true);
271	if (status != B_OK)
272		return status;
273
274#ifdef TEST_HPET
275	status = hpet_test();
276	if (status != B_OK)
277		return status;
278#endif
279
280	int32 configuredIRQ = HPET_GET_CONF_TIMER_INT_ROUTE(sTimer);
281
282	install_io_interrupt_handler(configuredIRQ, &hpet_timer_interrupt,
283		NULL, B_NO_LOCK_VECTOR);
284
285	return status;
286}
287
288