xref: /xnu-8019.80.24/osfmk/i386/hpet.c (revision a325d9c4a84054e40bbe985afedcb50ab80993ea)
1 /*
2  * Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 #include <string.h>
30 #include <mach/vm_param.h>
31 #include <mach/vm_prot.h>
32 #include <mach/machine.h>
33 #include <mach/time_value.h>
34 #include <kern/spl.h>
35 #include <kern/assert.h>
36 #include <kern/debug.h>
37 #include <kern/misc_protos.h>
38 #include <kern/startup.h>
39 #include <kern/clock.h>
40 #include <kern/cpu_data.h>
41 #include <kern/processor.h>
42 #include <vm/vm_page.h>
43 #include <vm/pmap.h>
44 #include <vm/vm_kern.h>
45 #include <i386/cpuid.h>
46 #include <i386/machine_cpu.h>
47 #include <i386/mp.h>
48 #include <i386/machine_routines.h>
49 #include <i386/pmap.h>
50 #include <i386/misc_protos.h>
51 #include <i386/io_map_entries.h>
52 #include <architecture/i386/pio.h>
53 #include <i386/cpuid.h>
54 #include <i386/apic.h>
55 #include <i386/tsc.h>
56 #include <i386/hpet.h>
57 #include <i386/pmCPU.h>
58 #include <i386/cpu_topology.h>
59 #include <i386/cpu_threads.h>
60 #include <pexpert/device_tree.h>
61 
62 /* Decimal powers: */
63 #define kilo (1000ULL)
64 #define Mega (kilo * kilo)
65 #define Giga (kilo * Mega)
66 #define Tera (kilo * Giga)
67 #define Peta (kilo * Tera)
68 
69 vm_offset_t hpetArea = 0;
70 uint32_t hpetAreap = 0;
71 uint64_t hpetFemto = 0;
72 uint64_t hpetFreq = 0;
73 uint64_t hpetCvt = 0;                   /* (TAKE OUT LATER)  */
74 uint64_t hpetCvtt2n = 0;
75 uint64_t hpetCvtn2t = 0;
76 uint64_t tsc2hpet = 0;
77 uint64_t hpet2tsc = 0;
78 uint64_t bus2hpet = 0;
79 uint64_t hpet2bus = 0;
80 
81 vm_offset_t rcbaArea = 0;
82 uint32_t rcbaAreap = 0;
83 
84 static int (*hpet_req)(uint32_t apicid, void *arg, hpetRequest_t *hpet) = NULL;
85 static void *hpet_arg = NULL;
86 
87 #if DEBUG
88 #define DBG(x...)       kprintf("DBG: " x)
89 #else
90 #define DBG(x...)
91 #endif
92 
93 int
hpet_register_callback(int (* hpet_reqst)(uint32_t apicid,void * arg,hpetRequest_t * hpet),void * arg)94 hpet_register_callback(int (*hpet_reqst)(uint32_t apicid,
95     void *arg,
96     hpetRequest_t *hpet),
97     void *arg)
98 {
99 	hpet_req = hpet_reqst;
100 	hpet_arg = arg;
101 	return 0;
102 }
103 
104 /*
105  * This routine is called to obtain an HPET and have it assigned
106  * to a CPU.  It returns 0 if successful and non-zero if one could
107  * not be assigned.
108  */
109 int
hpet_request(uint32_t cpu)110 hpet_request(uint32_t cpu)
111 {
112 	hpetRequest_t       hpetReq;
113 	int                 rc;
114 	x86_lcpu_t          *lcpu;
115 	x86_core_t          *core;
116 	x86_pkg_t           *pkg;
117 	boolean_t           enabled;
118 
119 	if (hpet_req == NULL) {
120 		return -1;
121 	}
122 
123 	/*
124 	 * Deal with the case where the CPU # passed in is past the
125 	 * value specified in cpus=n in boot-args.
126 	 */
127 	if (cpu >= real_ncpus) {
128 		enabled = ml_set_interrupts_enabled(FALSE);
129 		lcpu = cpu_to_lcpu(cpu);
130 		if (lcpu != NULL) {
131 			core = lcpu->core;
132 			pkg  = core->package;
133 
134 			if (lcpu->primary) {
135 				pkg->flags |= X86PKG_FL_HAS_HPET;
136 			}
137 		}
138 
139 		ml_set_interrupts_enabled(enabled);
140 		return 0;
141 	}
142 
143 	rc = (*hpet_req)(ml_get_apicid(cpu), hpet_arg, &hpetReq);
144 	if (rc != 0) {
145 		return rc;
146 	}
147 
148 	enabled = ml_set_interrupts_enabled(FALSE);
149 	lcpu = cpu_to_lcpu(cpu);
150 	core = lcpu->core;
151 	pkg  = core->package;
152 
153 	/*
154 	 * Compute the address of the HPET.
155 	 */
156 	core->Hpet = (hpetTimer_t *)((uint8_t *)hpetArea + hpetReq.hpetOffset);
157 	core->HpetVec = hpetReq.hpetVector;
158 
159 	/*
160 	 * Enable interrupts
161 	 */
162 	core->Hpet->Config |= Tn_INT_ENB_CNF;
163 
164 	/*
165 	 * Save the configuration
166 	 */
167 	core->HpetCfg = core->Hpet->Config;
168 	core->HpetCmp = 0;
169 
170 	/*
171 	 * If the CPU is the "primary" for the package, then
172 	 * add the HPET to the package too.
173 	 */
174 	if (lcpu->primary) {
175 		pkg->Hpet = core->Hpet;
176 		pkg->HpetCfg = core->HpetCfg;
177 		pkg->HpetCmp = core->HpetCmp;
178 		pkg->flags |= X86PKG_FL_HAS_HPET;
179 	}
180 
181 	ml_set_interrupts_enabled(enabled);
182 
183 	return 0;
184 }
185 
186 /*
187  * Map the RCBA area.
188  */
189 static void
map_rcbaArea(void)190 map_rcbaArea(void)
191 {
192 	/*
193 	 * Get RCBA area physical address and map it
194 	 */
195 	outl(cfgAdr, lpcCfg | (0xF0 & 0xFC));
196 	rcbaAreap = inl(cfgDat | (0xF0 & 0x03));
197 	rcbaArea = io_map_spec(rcbaAreap & -4096, PAGE_SIZE * 4, VM_WIMG_IO);
198 	kprintf("RCBA: vaddr = %lX, paddr = %08X\n", (unsigned long)rcbaArea, rcbaAreap);
199 }
200 
201 /*
202  * Initialize the HPET
203  */
204 void
hpet_init(void)205 hpet_init(void)
206 {
207 	unsigned int    *xmod;
208 
209 	map_rcbaArea();
210 
211 	/*
212 	 * Is the HPET memory already enabled?
213 	 * If not, set address and enable.
214 	 */
215 	xmod = (uint32_t *)(rcbaArea + 0x3404); /* Point to the HPTC */
216 	uint32_t hptc = *xmod;                  /* Get HPET config */
217 	DBG("    current RCBA.HPTC:  %08X\n", *xmod);
218 	if (!(hptc & hptcAE)) {
219 		DBG("HPET memory is not enabled, "
220 		    "enabling and assigning to 0xFED00000 (hope that's ok)\n");
221 		*xmod = (hptc & ~3) | hptcAE;
222 	}
223 
224 	/*
225 	 * Get physical address of HPET and map it.
226 	 */
227 	hpetAreap = hpetAddr | ((hptc & 3) << 12);
228 	hpetArea = io_map_spec(hpetAreap & -4096, PAGE_SIZE * 4, VM_WIMG_IO);
229 	kprintf("HPET: vaddr = %lX, paddr = %08X\n", (unsigned long)hpetArea, hpetAreap);
230 
231 	/*
232 	 * Extract the HPET tick rate.
233 	 * The period of the HPET is reported in femtoseconds (10**-15s)
234 	 * and convert to frequency in hertz.
235 	 */
236 	hpetFemto = (uint32_t)(((hpetReg_t *)hpetArea)->GCAP_ID >> 32);
237 	hpetFreq = (1 * Peta) / hpetFemto;
238 
239 	/*
240 	 * The conversion factor is the number of nanoseconds per HPET tick
241 	 * with about 32 bits of fraction.  The value is converted to a
242 	 * base-2 fixed point number.  To convert from HPET to nanoseconds,
243 	 * multiply the value by the conversion factor using 96-bit arithmetic,
244 	 * then shift right 32 bits.  If the value is known to be small,
245 	 * 64-bit arithmetic will work.
246 	 */
247 
248 	/*
249 	 * Begin conversion of base 10 femtoseconds to base 2, calculate:
250 	 *  - HPET ticks to nanoseconds conversion in base 2 fraction (* 2**32)
251 	 *  - nanoseconds to HPET ticks conversion
252 	 */
253 	hpetCvtt2n = (uint64_t)hpetFemto << 32;
254 	hpetCvtt2n = hpetCvtt2n / 1000000ULL;
255 	hpetCvtn2t = 0xFFFFFFFFFFFFFFFFULL / hpetCvtt2n;
256 	kprintf("HPET: Frequency = %6d.%04dMHz, "
257 	    "cvtt2n = %08X.%08X, cvtn2t = %08X.%08X\n",
258 	    (uint32_t)(hpetFreq / Mega), (uint32_t)(hpetFreq % Mega),
259 	    (uint32_t)(hpetCvtt2n >> 32), (uint32_t)hpetCvtt2n,
260 	    (uint32_t)(hpetCvtn2t >> 32), (uint32_t)hpetCvtn2t);
261 
262 
263 	/* (TAKE OUT LATER)
264 	 * Begin conversion of base 10 femtoseconds to base 2
265 	 * HPET ticks to nanoseconds in base 2 fraction (times 1048576)
266 	 */
267 	hpetCvt = (uint64_t)hpetFemto << 20;
268 	hpetCvt = hpetCvt / 1000000ULL;
269 
270 	/* Calculate conversion from TSC to HPET */
271 	tsc2hpet = tmrCvt(tscFCvtt2n, hpetCvtn2t);
272 	DBG(" CVT: TSC to HPET = %08X.%08X\n",
273 	    (uint32_t)(tsc2hpet >> 32), (uint32_t)tsc2hpet);
274 
275 	/* Calculate conversion from HPET to TSC */
276 	hpet2tsc = tmrCvt(hpetCvtt2n, tscFCvtn2t);
277 	DBG(" CVT: HPET to TSC = %08X.%08X\n",
278 	    (uint32_t)(hpet2tsc >> 32), (uint32_t)hpet2tsc);
279 
280 	/* Calculate conversion from BUS to HPET */
281 	bus2hpet = tmrCvt(busFCvtt2n, hpetCvtn2t);
282 	DBG(" CVT: BUS to HPET = %08X.%08X\n",
283 	    (uint32_t)(bus2hpet >> 32), (uint32_t)bus2hpet);
284 
285 	/* Calculate conversion from HPET to BUS */
286 	hpet2bus = tmrCvt(hpetCvtt2n, busFCvtn2t);
287 	DBG(" CVT: HPET to BUS = %08X.%08X\n",
288 	    (uint32_t)(hpet2bus >> 32), (uint32_t)hpet2bus);
289 }
290 
291 /*
292  * This routine is used to get various information about the HPET
293  * without having to export gobs of globals.  It fills in a data
294  * structure with the info.
295  */
296 void
hpet_get_info(hpetInfo_t * info)297 hpet_get_info(hpetInfo_t *info)
298 {
299 	info->hpetCvtt2n = hpetCvtt2n;
300 	info->hpetCvtn2t = hpetCvtn2t;
301 	info->tsc2hpet   = tsc2hpet;
302 	info->hpet2tsc   = hpet2tsc;
303 	info->bus2hpet   = bus2hpet;
304 	info->hpet2bus   = hpet2bus;
305 	/*
306 	 * XXX
307 	 * We're repurposing the rcbaArea so we can use the HPET.
308 	 * Eventually we'll rename this correctly.
309 	 */
310 	info->rcbaArea   = hpetArea;
311 	info->rcbaAreap  = hpetAreap;
312 }
313 
314 
315 /*
316  * This routine is called by the HPET driver
317  * when it assigns an HPET timer to a processor.
318  *
319  * XXX with the new callback into the HPET driver,
320  * this routine will be deprecated.
321  */
322 void
ml_hpet_cfg(uint32_t cpu,uint32_t hpetVect)323 ml_hpet_cfg(uint32_t cpu, uint32_t hpetVect)
324 {
325 	uint64_t        *hpetVaddr;
326 	hpetTimer_t     *hpet;
327 	x86_lcpu_t      *lcpu;
328 	x86_core_t      *core;
329 	x86_pkg_t       *pkg;
330 	boolean_t       enabled;
331 
332 	if (cpu > 1) {
333 		panic("ml_hpet_cfg: invalid cpu = %d", cpu);
334 	}
335 
336 	lcpu = cpu_to_lcpu(cpu);
337 	core = lcpu->core;
338 	pkg  = core->package;
339 
340 	/*
341 	 * Only deal with the primary CPU for the package.
342 	 */
343 	if (!lcpu->primary) {
344 		return;
345 	}
346 
347 	enabled = ml_set_interrupts_enabled(FALSE);
348 
349 	/* Calculate address of the HPET for this processor */
350 	hpetVaddr = (uint64_t *)(((uintptr_t)&(((hpetReg_t *)hpetArea)->TIM1_CONF)) + (cpu << 5));
351 	hpet = (hpetTimer_t *)hpetVaddr;
352 
353 	DBG("ml_hpet_cfg: HPET for cpu %d at %p, vector = %d\n",
354 	    cpu, hpetVaddr, hpetVect);
355 
356 	/* Save the address and vector of the HPET for this processor */
357 	core->Hpet = hpet;
358 	core->HpetVec = hpetVect;
359 
360 	/*
361 	 * Enable interrupts
362 	 */
363 	core->Hpet->Config |= Tn_INT_ENB_CNF;
364 
365 	/* Save the configuration */
366 	core->HpetCfg = core->Hpet->Config;
367 	core->HpetCmp = 0;
368 
369 	/*
370 	 * We're only doing this for the primary CPU, so go
371 	 * ahead and add the HPET to the package too.
372 	 */
373 	pkg->Hpet = core->Hpet;
374 	pkg->HpetVec = core->HpetVec;
375 	pkg->HpetCfg = core->HpetCfg;
376 	pkg->HpetCmp = core->HpetCmp;
377 	pkg->flags |= X86PKG_FL_HAS_HPET;
378 
379 	ml_set_interrupts_enabled(enabled);
380 }
381 
382 /*
383  * This is the HPET interrupt handler.
384  *
385  * It just hands off to the power management code so that the
386  * appropriate things get done there.
387  */
388 int
HPETInterrupt(void)389 HPETInterrupt(void)
390 {
391 	/* All we do here is to bump the count */
392 	x86_package()->HpetInt++;
393 
394 	/*
395 	 * Let power management do it's thing.
396 	 */
397 	pmHPETInterrupt();
398 
399 	/* Return and show that the 'rupt has been handled... */
400 	return 1;
401 }
402 
403 
404 static hpetReg_t saved_hpet;
405 
406 void
hpet_save(void)407 hpet_save(void)
408 {
409 	hpetReg_t       *from = (hpetReg_t *) hpetArea;
410 	hpetReg_t       *to = &saved_hpet;
411 
412 	to->GEN_CONF  = from->GEN_CONF;
413 	to->TIM0_CONF = from->TIM0_CONF;
414 	to->TIM0_COMP = from->TIM0_COMP;
415 	to->TIM1_CONF = from->TIM1_CONF;
416 	to->TIM1_COMP = from->TIM1_COMP;
417 	to->TIM2_CONF = from->TIM2_CONF;
418 	to->TIM2_COMP = from->TIM2_COMP;
419 	to->MAIN_CNT  = from->MAIN_CNT;
420 }
421 
422 void
hpet_restore(void)423 hpet_restore(void)
424 {
425 	hpetReg_t       *from = &saved_hpet;
426 	hpetReg_t       *to = (hpetReg_t *) hpetArea;
427 
428 	/*
429 	 * Is the HPET memory already enabled?
430 	 * If not, set address and enable.
431 	 */
432 	uint32_t *hptcp = (uint32_t *)(rcbaArea + 0x3404);
433 	uint32_t hptc = *hptcp;
434 	if (!(hptc & hptcAE)) {
435 		DBG("HPET memory is not enabled, "
436 		    "enabling and assigning to 0xFED00000 (hope that's ok)\n");
437 		*hptcp = (hptc & ~3) | hptcAE;
438 	}
439 
440 	to->GEN_CONF  = from->GEN_CONF & ~1;
441 
442 	to->TIM0_CONF = from->TIM0_CONF;
443 	to->TIM0_COMP = from->TIM0_COMP;
444 	to->TIM1_CONF = from->TIM1_CONF;
445 	to->TIM1_COMP = from->TIM1_COMP;
446 	to->TIM2_CONF = from->TIM2_CONF;
447 	to->TIM2_COMP = from->TIM2_COMP;
448 	to->GINTR_STA = -1ULL;
449 	to->MAIN_CNT  = from->MAIN_CNT;
450 
451 	to->GEN_CONF = from->GEN_CONF;
452 }
453 
454 /*
455  *      Read the HPET timer
456  *
457  */
458 uint64_t
rdHPET(void)459 rdHPET(void)
460 {
461 	hpetReg_t               *hpetp = (hpetReg_t *) hpetArea;
462 	volatile uint32_t       *regp = (uint32_t *) &hpetp->MAIN_CNT;
463 	uint32_t                high;
464 	uint32_t                low;
465 
466 	do {
467 		high = *(regp + 1);
468 		low = *regp;
469 	} while (high != *(regp + 1));
470 
471 	return (((uint64_t) high) << 32) | low;
472 }
473