Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * This code largely moved from arch/i386/kernel/time.c. | |
3 | * See comments there for proper credits. | |
4 | */ | |
5 | ||
6 | #include <linux/spinlock.h> | |
7 | #include <linux/init.h> | |
8 | #include <linux/timex.h> | |
9 | #include <linux/errno.h> | |
10 | #include <linux/string.h> | |
11 | #include <linux/jiffies.h> | |
12 | ||
13 | #include <asm/timer.h> | |
14 | #include <asm/io.h> | |
15 | #include <asm/processor.h> | |
16 | ||
17 | #include "io_ports.h" | |
18 | #include "mach_timer.h" | |
19 | #include <asm/hpet.h> | |
20 | ||
c3d8c141 | 21 | static unsigned long hpet_usec_quotient __read_mostly; /* convert hpet clks to usec */ |
6c231b7b | 22 | static unsigned long tsc_hpet_quotient __read_mostly; /* convert tsc to hpet clks */ |
1da177e4 LT |
23 | static unsigned long hpet_last; /* hpet counter value at last tick*/ |
24 | static unsigned long last_tsc_low; /* lsb 32 bits of Time Stamp Counter */ | |
25 | static unsigned long last_tsc_high; /* msb 32 bits of Time Stamp Counter */ | |
26 | static unsigned long long monotonic_base; | |
27 | static seqlock_t monotonic_lock = SEQLOCK_UNLOCKED; | |
28 | ||
29 | /* convert from cycles(64bits) => nanoseconds (64bits) | |
30 | * basic equation: | |
31 | * ns = cycles / (freq / ns_per_sec) | |
32 | * ns = cycles * (ns_per_sec / freq) | |
33 | * ns = cycles * (10^9 / (cpu_mhz * 10^6)) | |
34 | * ns = cycles * (10^3 / cpu_mhz) | |
35 | * | |
36 | * Then we use scaling math (suggested by george@mvista.com) to get: | |
37 | * ns = cycles * (10^3 * SC / cpu_mhz) / SC | |
38 | * ns = cycles * cyc2ns_scale / SC | |
39 | * | |
40 | * And since SC is a constant power of two, we can convert the div | |
41 | * into a shift. | |
42 | * -johnstul@us.ibm.com "math is hard, lets go shopping!" | |
43 | */ | |
44 | static unsigned long cyc2ns_scale; | |
45 | #define CYC2NS_SCALE_FACTOR 10 /* 2^10, carefully chosen */ | |
46 | ||
47 | static inline void set_cyc2ns_scale(unsigned long cpu_mhz) | |
48 | { | |
49 | cyc2ns_scale = (1000 << CYC2NS_SCALE_FACTOR)/cpu_mhz; | |
50 | } | |
51 | ||
52 | static inline unsigned long long cycles_2_ns(unsigned long long cyc) | |
53 | { | |
54 | return (cyc * cyc2ns_scale) >> CYC2NS_SCALE_FACTOR; | |
55 | } | |
56 | ||
57 | static unsigned long long monotonic_clock_hpet(void) | |
58 | { | |
59 | unsigned long long last_offset, this_offset, base; | |
60 | unsigned seq; | |
61 | ||
62 | /* atomically read monotonic base & last_offset */ | |
63 | do { | |
64 | seq = read_seqbegin(&monotonic_lock); | |
65 | last_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low; | |
66 | base = monotonic_base; | |
67 | } while (read_seqretry(&monotonic_lock, seq)); | |
68 | ||
69 | /* Read the Time Stamp Counter */ | |
70 | rdtscll(this_offset); | |
71 | ||
72 | /* return the value in ns */ | |
73 | return base + cycles_2_ns(this_offset - last_offset); | |
74 | } | |
75 | ||
76 | static unsigned long get_offset_hpet(void) | |
77 | { | |
78 | register unsigned long eax, edx; | |
79 | ||
80 | eax = hpet_readl(HPET_COUNTER); | |
81 | eax -= hpet_last; /* hpet delta */ | |
35492df5 | 82 | eax = min(hpet_tick, eax); |
1da177e4 LT |
83 | /* |
84 | * Time offset = (hpet delta) * ( usecs per HPET clock ) | |
85 | * = (hpet delta) * ( usecs per tick / HPET clocks per tick) | |
86 | * = (hpet delta) * ( hpet_usec_quotient ) / (2^32) | |
87 | * | |
88 | * Where, | |
89 | * hpet_usec_quotient = (2^32 * usecs per tick)/HPET clocks per tick | |
90 | * | |
91 | * Using a mull instead of a divl saves some cycles in critical path. | |
92 | */ | |
93 | ASM_MUL64_REG(eax, edx, hpet_usec_quotient, eax); | |
94 | ||
95 | /* our adjusted time offset in microseconds */ | |
96 | return edx; | |
97 | } | |
98 | ||
99 | static void mark_offset_hpet(void) | |
100 | { | |
101 | unsigned long long this_offset, last_offset; | |
102 | unsigned long offset; | |
103 | ||
104 | write_seqlock(&monotonic_lock); | |
105 | last_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low; | |
106 | rdtsc(last_tsc_low, last_tsc_high); | |
107 | ||
35492df5 | 108 | if (hpet_use_timer) |
109 | offset = hpet_readl(HPET_T0_CMP) - hpet_tick; | |
110 | else | |
111 | offset = hpet_readl(HPET_COUNTER); | |
112 | if (unlikely(((offset - hpet_last) >= (2*hpet_tick)) && (hpet_last != 0))) { | |
113 | int lost_ticks = ((offset - hpet_last) / hpet_tick) - 1; | |
1da177e4 LT |
114 | jiffies_64 += lost_ticks; |
115 | } | |
116 | hpet_last = offset; | |
117 | ||
118 | /* update the monotonic base value */ | |
119 | this_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low; | |
120 | monotonic_base += cycles_2_ns(this_offset - last_offset); | |
121 | write_sequnlock(&monotonic_lock); | |
122 | } | |
123 | ||
124 | static void delay_hpet(unsigned long loops) | |
125 | { | |
126 | unsigned long hpet_start, hpet_end; | |
127 | unsigned long eax; | |
128 | ||
129 | /* loops is the number of cpu cycles. Convert it to hpet clocks */ | |
130 | ASM_MUL64_REG(eax, loops, tsc_hpet_quotient, loops); | |
131 | ||
132 | hpet_start = hpet_readl(HPET_COUNTER); | |
133 | do { | |
134 | rep_nop(); | |
135 | hpet_end = hpet_readl(HPET_COUNTER); | |
136 | } while ((hpet_end - hpet_start) < (loops)); | |
137 | } | |
138 | ||
4116c527 VP |
139 | static struct timer_opts timer_hpet; |
140 | ||
1da177e4 LT |
141 | static int __init init_hpet(char* override) |
142 | { | |
143 | unsigned long result, remain; | |
144 | ||
145 | /* check clock override */ | |
146 | if (override[0] && strncmp(override,"hpet",4)) | |
147 | return -ENODEV; | |
148 | ||
149 | if (!is_hpet_enabled()) | |
150 | return -ENODEV; | |
151 | ||
152 | printk("Using HPET for gettimeofday\n"); | |
153 | if (cpu_has_tsc) { | |
154 | unsigned long tsc_quotient = calibrate_tsc_hpet(&tsc_hpet_quotient); | |
155 | if (tsc_quotient) { | |
156 | /* report CPU clock rate in Hz. | |
157 | * The formula is (10^6 * 2^32) / (2^32 * 1 / (clocks/us)) = | |
158 | * clock/second. Our precision is about 100 ppm. | |
159 | */ | |
160 | { unsigned long eax=0, edx=1000; | |
161 | ASM_DIV64_REG(cpu_khz, edx, tsc_quotient, | |
162 | eax, edx); | |
a3a255e7 | 163 | printk("Detected %u.%03u MHz processor.\n", |
1da177e4 LT |
164 | cpu_khz / 1000, cpu_khz % 1000); |
165 | } | |
166 | set_cyc2ns_scale(cpu_khz/1000); | |
167 | } | |
4116c527 VP |
168 | /* set this only when cpu_has_tsc */ |
169 | timer_hpet.read_timer = read_timer_tsc; | |
1da177e4 LT |
170 | } |
171 | ||
172 | /* | |
173 | * Math to calculate hpet to usec multiplier | |
174 | * Look for the comments at get_offset_hpet() | |
175 | */ | |
176 | ASM_DIV64_REG(result, remain, hpet_tick, 0, KERNEL_TICK_USEC); | |
177 | if (remain > (hpet_tick >> 1)) | |
178 | result++; /* rounding the result */ | |
179 | hpet_usec_quotient = result; | |
180 | ||
181 | return 0; | |
182 | } | |
183 | ||
c3c433e4 SL |
184 | static int hpet_resume(void) |
185 | { | |
186 | write_seqlock(&monotonic_lock); | |
187 | /* Assume this is the last mark offset time */ | |
188 | rdtsc(last_tsc_low, last_tsc_high); | |
189 | ||
190 | if (hpet_use_timer) | |
191 | hpet_last = hpet_readl(HPET_T0_CMP) - hpet_tick; | |
192 | else | |
193 | hpet_last = hpet_readl(HPET_COUNTER); | |
194 | write_sequnlock(&monotonic_lock); | |
195 | return 0; | |
196 | } | |
1da177e4 LT |
197 | /************************************************************/ |
198 | ||
199 | /* tsc timer_opts struct */ | |
6c036527 | 200 | static struct timer_opts timer_hpet __read_mostly = { |
1da177e4 LT |
201 | .name = "hpet", |
202 | .mark_offset = mark_offset_hpet, | |
203 | .get_offset = get_offset_hpet, | |
204 | .monotonic_clock = monotonic_clock_hpet, | |
205 | .delay = delay_hpet, | |
c3c433e4 | 206 | .resume = hpet_resume, |
1da177e4 LT |
207 | }; |
208 | ||
209 | struct init_timer_opts __initdata timer_hpet_init = { | |
210 | .init = init_hpet, | |
211 | .opts = &timer_hpet, | |
212 | }; |