aboutsummaryrefslogtreecommitdiffstats
path: root/arch/i386/kernel/timers/common.c
blob: 8e201219f5253f53a84d1254b922ab7a7ff1a519 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/*
 *	Common functions used across the timers go here
 */

#include <linux/init.h>
#include <linux/timex.h>
#include <linux/errno.h>
#include <linux/jiffies.h>
#include <linux/module.h>

#include <asm/io.h>
#include <asm/timer.h>
#include <asm/hpet.h>

#include "mach_timer.h"

/* ------ Calibrate the TSC -------
 * Return 2^32 * (1 / (TSC clocks per usec)) for do_fast_gettimeoffset().
 * Too much 64-bit arithmetic here to do this cleanly in C, and for
 * accuracy's sake we want to keep the overhead on the CTC speaker (channel 2)
 * output busy loop as low as possible. We avoid reading the CTC registers
 * directly because of the awkward 8-bit access mechanism of the 82C54
 * device.
 */

#define CALIBRATE_TIME	(5 * 1000020/HZ)

unsigned long calibrate_tsc(void)
{
	mach_prepare_counter();

	{
		unsigned long startlow, starthigh;
		unsigned long endlow, endhigh;
		unsigned long count;

		rdtsc(startlow,starthigh);
		mach_countup(&count);
		rdtsc(endlow,endhigh);


		/* Error: ECTCNEVERSET */
		if (count <= 1)
			goto bad_ctc;

		/* 64-bit subtract - gcc just messes up with long longs */
		__asm__("subl %2,%0\n\t"
			"sbbl %3,%1"
			:"=a" (endlow), "=d" (endhigh)
			:"g" (startlow), "g" (starthigh),
			 "0" (endlow), "1" (endhigh));

		/* Error: ECPUTOOFAST */
		if (endhigh)
			goto bad_ctc;

		/* Error: ECPUTOOSLOW */
		if (endlow <= CALIBRATE_TIME)
			goto bad_ctc;

		__asm__("divl %2"
			:"=a" (endlow), "=d" (endhigh)
			:"r" (endlow), "0" (0), "1" (CALIBRATE_TIME));

		return endlow;
	}

	/*
	 * The CTC wasn't reliable: we got a hit on the very first read,
	 * or the CPU was so fast/slow that the quotient wouldn't fit in
	 * 32 bits..
	 */
bad_ctc:
	return 0;
}

#ifdef CONFIG_HPET_TIMER
/* ------ Calibrate the TSC using HPET -------
 * Return 2^32 * (1 / (TSC clocks per usec)) for getting the CPU freq.
 * Second output is parameter 1 (when non NULL)
 * Set 2^32 * (1 / (tsc per HPET clk)) for delay_hpet().
 * calibrate_tsc() calibrates the processor TSC by comparing
 * it to the HPET timer of known frequency.
 * Too much 64-bit arithmetic here to do this cleanly in C
 */
#define CALIBRATE_CNT_HPET 	(5 * hpet_tick)
#define CALIBRATE_TIME_HPET 	(5 * KERNEL_TICK_USEC)

unsigned long __init calibrate_tsc_hpet(unsigned long *tsc_hpet_quotient_ptr)
{
	unsigned long tsc_startlow, tsc_starthigh;
	unsigned long tsc_endlow, tsc_endhigh;
	unsigned long hpet_start, hpet_end;
	unsigned long result, remain;

	hpet_start = hpet_readl(HPET_COUNTER);
	rdtsc(tsc_startlow, tsc_starthigh);
	do {
		hpet_end = hpet_readl(HPET_COUNTER);
	} while ((hpet_end - hpet_start) < CALIBRATE_CNT_HPET);
	rdtsc(tsc_endlow, tsc_endhigh);

	/* 64-bit subtract - gcc just messes up with long longs */
	__asm__("subl %2,%0\n\t"
		"sbbl %3,%1"
		:"=a" (tsc_endlow), "=d" (tsc_endhigh)
		:"g" (tsc_startlow), "g" (tsc_starthigh),
		 "0" (tsc_endlow), "1" (tsc_endhigh));

	/* Error: ECPUTOOFAST */
	if (tsc_endhigh)
		goto bad_calibration;

	/* Error: ECPUTOOSLOW */
	if (tsc_endlow <= CALIBRATE_TIME_HPET)
		goto bad_calibration;

	ASM_DIV64_REG(result, remain, tsc_endlow, 0, CALIBRATE_TIME_HPET);
	if (remain > (tsc_endlow >> 1))
		result++; /* rounding the result */

	if (tsc_hpet_quotient_ptr) {
		unsigned long tsc_hpet_quotient;

		ASM_DIV64_REG(tsc_hpet_quotient, remain, tsc_endlow, 0,
			CALIBRATE_CNT_HPET);
		if (remain > (tsc_endlow >> 1))
			tsc_hpet_quotient++; /* rounding the result */
		*tsc_hpet_quotient_ptr = tsc_hpet_quotient;
	}

	return result;
bad_calibration:
	/*
	 * the CPU was so fast/slow that the quotient wouldn't fit in
	 * 32 bits..
	 */
	return 0;
}
#endif

/* calculate cpu_khz */
void init_cpu_khz(void)
{
	if (cpu_has_tsc) {
		unsigned long tsc_quotient = calibrate_tsc();
		if (tsc_quotient) {
			/* report CPU clock rate in Hz.
			 * The formula is (10^6 * 2^32) / (2^32 * 1 / (clocks/us)) =
			 * clock/second. Our precision is about 100 ppm.
			 */
			{	unsigned long eax=0, edx=1000;
				__asm__("divl %2"
		       		:"=a" (cpu_khz), "=d" (edx)
        	       		:"r" (tsc_quotient),
	                	"0" (eax), "1" (edx));
				printk("Detected %lu.%03lu MHz processor.\n", cpu_khz / 1000, cpu_khz % 1000);
			}
		}
	}
}