|
@@ -4,6 +4,7 @@
|
|
|
#include <linux/module.h>
|
|
|
#include <linux/timer.h>
|
|
|
#include <linux/acpi_pmtmr.h>
|
|
|
+#include <linux/cpufreq.h>
|
|
|
|
|
|
#include <asm/hpet.h>
|
|
|
|
|
@@ -215,3 +216,116 @@ int recalibrate_cpu_khz(void)
|
|
|
EXPORT_SYMBOL(recalibrate_cpu_khz);
|
|
|
|
|
|
#endif /* CONFIG_X86_32 */
|
|
|
+
|
|
|
+/* Accelerators for sched_clock()
|
|
|
+ * convert from cycles(64bits) => nanoseconds (64bits)
|
|
|
+ * basic equation:
|
|
|
+ * ns = cycles / (freq / ns_per_sec)
|
|
|
+ * ns = cycles * (ns_per_sec / freq)
|
|
|
+ * ns = cycles * (10^9 / (cpu_khz * 10^3))
|
|
|
+ * ns = cycles * (10^6 / cpu_khz)
|
|
|
+ *
|
|
|
+ * Then we use scaling math (suggested by george@mvista.com) to get:
|
|
|
+ * ns = cycles * (10^6 * SC / cpu_khz) / SC
|
|
|
+ * ns = cycles * cyc2ns_scale / SC
|
|
|
+ *
|
|
|
+ * And since SC is a constant power of two, we can convert the div
|
|
|
+ * into a shift.
|
|
|
+ *
|
|
|
+ * We can use khz divisor instead of mhz to keep a better precision, since
|
|
|
+ * cyc2ns_scale is limited to 10^6 * 2^10, which fits in 32 bits.
|
|
|
+ * (mathieu.desnoyers@polymtl.ca)
|
|
|
+ *
|
|
|
+ * -johnstul@us.ibm.com "math is hard, lets go shopping!"
|
|
|
+ */
|
|
|
+
|
|
|
+DEFINE_PER_CPU(unsigned long, cyc2ns);
|
|
|
+
|
|
|
+void set_cyc2ns_scale(unsigned long cpu_khz, int cpu)
|
|
|
+{
|
|
|
+ unsigned long long tsc_now, ns_now;
|
|
|
+ unsigned long flags, *scale;
|
|
|
+
|
|
|
+ local_irq_save(flags);
|
|
|
+ sched_clock_idle_sleep_event();
|
|
|
+
|
|
|
+ scale = &per_cpu(cyc2ns, cpu);
|
|
|
+
|
|
|
+ rdtscll(tsc_now);
|
|
|
+ ns_now = __cycles_2_ns(tsc_now);
|
|
|
+
|
|
|
+ if (cpu_khz)
|
|
|
+ *scale = (NSEC_PER_MSEC << CYC2NS_SCALE_FACTOR)/cpu_khz;
|
|
|
+
|
|
|
+ sched_clock_idle_wakeup_event(0);
|
|
|
+ local_irq_restore(flags);
|
|
|
+}
|
|
|
+
|
|
|
+#ifdef CONFIG_CPU_FREQ
|
|
|
+
|
|
|
+/* Frequency scaling support. Adjust the TSC based timer when the cpu frequency
|
|
|
+ * changes.
|
|
|
+ *
|
|
|
+ * RED-PEN: On SMP we assume all CPUs run with the same frequency. It's
|
|
|
+ * not that important because current Opteron setups do not support
|
|
|
+ * scaling on SMP anyroads.
|
|
|
+ *
|
|
|
+ * Should fix up last_tsc too. Currently gettimeofday in the
|
|
|
+ * first tick after the change will be slightly wrong.
|
|
|
+ */
|
|
|
+
|
|
|
+static unsigned int ref_freq;
|
|
|
+static unsigned long loops_per_jiffy_ref;
|
|
|
+static unsigned long tsc_khz_ref;
|
|
|
+
|
|
|
+static int time_cpufreq_notifier(struct notifier_block *nb, unsigned long val,
|
|
|
+ void *data)
|
|
|
+{
|
|
|
+ struct cpufreq_freqs *freq = data;
|
|
|
+ unsigned long *lpj, dummy;
|
|
|
+
|
|
|
+ if (cpu_has(&cpu_data(freq->cpu), X86_FEATURE_CONSTANT_TSC))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ lpj = &dummy;
|
|
|
+ if (!(freq->flags & CPUFREQ_CONST_LOOPS))
|
|
|
+#ifdef CONFIG_SMP
|
|
|
+ lpj = &cpu_data(freq->cpu).loops_per_jiffy;
|
|
|
+#else
|
|
|
+ lpj = &boot_cpu_data.loops_per_jiffy;
|
|
|
+#endif
|
|
|
+
|
|
|
+ if (!ref_freq) {
|
|
|
+ ref_freq = freq->old;
|
|
|
+ loops_per_jiffy_ref = *lpj;
|
|
|
+ tsc_khz_ref = tsc_khz;
|
|
|
+ }
|
|
|
+ if ((val == CPUFREQ_PRECHANGE && freq->old < freq->new) ||
|
|
|
+ (val == CPUFREQ_POSTCHANGE && freq->old > freq->new) ||
|
|
|
+ (val == CPUFREQ_RESUMECHANGE)) {
|
|
|
+ *lpj = cpufreq_scale(loops_per_jiffy_ref, ref_freq, freq->new);
|
|
|
+
|
|
|
+ tsc_khz = cpufreq_scale(tsc_khz_ref, ref_freq, freq->new);
|
|
|
+ if (!(freq->flags & CPUFREQ_CONST_LOOPS))
|
|
|
+ mark_tsc_unstable("cpufreq changes");
|
|
|
+ }
|
|
|
+
|
|
|
+ set_cyc2ns_scale(tsc_khz_ref, freq->cpu);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static struct notifier_block time_cpufreq_notifier_block = {
|
|
|
+ .notifier_call = time_cpufreq_notifier
|
|
|
+};
|
|
|
+
|
|
|
+static int __init cpufreq_tsc(void)
|
|
|
+{
|
|
|
+ cpufreq_register_notifier(&time_cpufreq_notifier_block,
|
|
|
+ CPUFREQ_TRANSITION_NOTIFIER);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+core_initcall(cpufreq_tsc);
|
|
|
+
|
|
|
+#endif /* CONFIG_CPU_FREQ */
|