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