diff options
Diffstat (limited to 'arch/x86/kernel/tsc.c')
-rw-r--r-- | arch/x86/kernel/tsc.c | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/arch/x86/kernel/tsc.c b/arch/x86/kernel/tsc.c index e6ee14533c75..595f78a22212 100644 --- a/arch/x86/kernel/tsc.c +++ b/arch/x86/kernel/tsc.c | |||
@@ -4,6 +4,7 @@ | |||
4 | #include <linux/module.h> | 4 | #include <linux/module.h> |
5 | #include <linux/timer.h> | 5 | #include <linux/timer.h> |
6 | #include <linux/acpi_pmtmr.h> | 6 | #include <linux/acpi_pmtmr.h> |
7 | #include <linux/cpufreq.h> | ||
7 | 8 | ||
8 | #include <asm/hpet.h> | 9 | #include <asm/hpet.h> |
9 | 10 | ||
@@ -215,3 +216,116 @@ int recalibrate_cpu_khz(void) | |||
215 | EXPORT_SYMBOL(recalibrate_cpu_khz); | 216 | EXPORT_SYMBOL(recalibrate_cpu_khz); |
216 | 217 | ||
217 | #endif /* CONFIG_X86_32 */ | 218 | #endif /* CONFIG_X86_32 */ |
219 | |||
220 | /* Accelerators for sched_clock() | ||
221 | * convert from cycles(64bits) => nanoseconds (64bits) | ||
222 | * basic equation: | ||
223 | * ns = cycles / (freq / ns_per_sec) | ||
224 | * ns = cycles * (ns_per_sec / freq) | ||
225 | * ns = cycles * (10^9 / (cpu_khz * 10^3)) | ||
226 | * ns = cycles * (10^6 / cpu_khz) | ||
227 | * | ||
228 | * Then we use scaling math (suggested by george@mvista.com) to get: | ||
229 | * ns = cycles * (10^6 * SC / cpu_khz) / SC | ||
230 | * ns = cycles * cyc2ns_scale / SC | ||
231 | * | ||
232 | * And since SC is a constant power of two, we can convert the div | ||
233 | * into a shift. | ||
234 | * | ||
235 | * We can use khz divisor instead of mhz to keep a better precision, since | ||
236 | * cyc2ns_scale is limited to 10^6 * 2^10, which fits in 32 bits. | ||
237 | * (mathieu.desnoyers@polymtl.ca) | ||
238 | * | ||
239 | * -johnstul@us.ibm.com "math is hard, lets go shopping!" | ||
240 | */ | ||
241 | |||
242 | DEFINE_PER_CPU(unsigned long, cyc2ns); | ||
243 | |||
244 | void set_cyc2ns_scale(unsigned long cpu_khz, int cpu) | ||
245 | { | ||
246 | unsigned long long tsc_now, ns_now; | ||
247 | unsigned long flags, *scale; | ||
248 | |||
249 | local_irq_save(flags); | ||
250 | sched_clock_idle_sleep_event(); | ||
251 | |||
252 | scale = &per_cpu(cyc2ns, cpu); | ||
253 | |||
254 | rdtscll(tsc_now); | ||
255 | ns_now = __cycles_2_ns(tsc_now); | ||
256 | |||
257 | if (cpu_khz) | ||
258 | *scale = (NSEC_PER_MSEC << CYC2NS_SCALE_FACTOR)/cpu_khz; | ||
259 | |||
260 | sched_clock_idle_wakeup_event(0); | ||
261 | local_irq_restore(flags); | ||
262 | } | ||
263 | |||
264 | #ifdef CONFIG_CPU_FREQ | ||
265 | |||
266 | /* Frequency scaling support. Adjust the TSC based timer when the cpu frequency | ||
267 | * changes. | ||
268 | * | ||
269 | * RED-PEN: On SMP we assume all CPUs run with the same frequency. It's | ||
270 | * not that important because current Opteron setups do not support | ||
271 | * scaling on SMP anyroads. | ||
272 | * | ||
273 | * Should fix up last_tsc too. Currently gettimeofday in the | ||
274 | * first tick after the change will be slightly wrong. | ||
275 | */ | ||
276 | |||
277 | static unsigned int ref_freq; | ||
278 | static unsigned long loops_per_jiffy_ref; | ||
279 | static unsigned long tsc_khz_ref; | ||
280 | |||
281 | static int time_cpufreq_notifier(struct notifier_block *nb, unsigned long val, | ||
282 | void *data) | ||
283 | { | ||
284 | struct cpufreq_freqs *freq = data; | ||
285 | unsigned long *lpj, dummy; | ||
286 | |||
287 | if (cpu_has(&cpu_data(freq->cpu), X86_FEATURE_CONSTANT_TSC)) | ||
288 | return 0; | ||
289 | |||
290 | lpj = &dummy; | ||
291 | if (!(freq->flags & CPUFREQ_CONST_LOOPS)) | ||
292 | #ifdef CONFIG_SMP | ||
293 | lpj = &cpu_data(freq->cpu).loops_per_jiffy; | ||
294 | #else | ||
295 | lpj = &boot_cpu_data.loops_per_jiffy; | ||
296 | #endif | ||
297 | |||
298 | if (!ref_freq) { | ||
299 | ref_freq = freq->old; | ||
300 | loops_per_jiffy_ref = *lpj; | ||
301 | tsc_khz_ref = tsc_khz; | ||
302 | } | ||
303 | if ((val == CPUFREQ_PRECHANGE && freq->old < freq->new) || | ||
304 | (val == CPUFREQ_POSTCHANGE && freq->old > freq->new) || | ||
305 | (val == CPUFREQ_RESUMECHANGE)) { | ||
306 | *lpj = cpufreq_scale(loops_per_jiffy_ref, ref_freq, freq->new); | ||
307 | |||
308 | tsc_khz = cpufreq_scale(tsc_khz_ref, ref_freq, freq->new); | ||
309 | if (!(freq->flags & CPUFREQ_CONST_LOOPS)) | ||
310 | mark_tsc_unstable("cpufreq changes"); | ||
311 | } | ||
312 | |||
313 | set_cyc2ns_scale(tsc_khz_ref, freq->cpu); | ||
314 | |||
315 | return 0; | ||
316 | } | ||
317 | |||
318 | static struct notifier_block time_cpufreq_notifier_block = { | ||
319 | .notifier_call = time_cpufreq_notifier | ||
320 | }; | ||
321 | |||
322 | static int __init cpufreq_tsc(void) | ||
323 | { | ||
324 | cpufreq_register_notifier(&time_cpufreq_notifier_block, | ||
325 | CPUFREQ_TRANSITION_NOTIFIER); | ||
326 | return 0; | ||
327 | } | ||
328 | |||
329 | core_initcall(cpufreq_tsc); | ||
330 | |||
331 | #endif /* CONFIG_CPU_FREQ */ | ||