/* * This code largely moved from arch/i386/kernel/time.c. * See comments there for proper credits. */ #include #include #include #include #include #include #include #include #include #include "io_ports.h" #include "mach_timer.h" #include static unsigned long __read_mostly hpet_usec_quotient; /* convert hpet clks to usec */ static unsigned long tsc_hpet_quotient; /* convert tsc to hpet clks */ static unsigned long hpet_last; /* hpet counter value at last tick*/ static unsigned long last_tsc_low; /* lsb 32 bits of Time Stamp Counter */ static unsigned long last_tsc_high; /* msb 32 bits of Time Stamp Counter */ static unsigned long long monotonic_base; static seqlock_t monotonic_lock = SEQLOCK_UNLOCKED; /* 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_mhz * 10^6)) * ns = cycles * (10^3 / cpu_mhz) * * Then we use scaling math (suggested by george@mvista.com) to get: * ns = cycles * (10^3 * SC / cpu_mhz) / SC * ns = cycles * cyc2ns_scale / SC * * And since SC is a constant power of two, we can convert the div * into a shift. * -johnstul@us.ibm.com "math is hard, lets go shopping!" */ static unsigned long cyc2ns_scale; #define CYC2NS_SCALE_FACTOR 10 /* 2^10, carefully chosen */ static inline void set_cyc2ns_scale(unsigned long cpu_mhz) { cyc2ns_scale = (1000 << CYC2NS_SCALE_FACTOR)/cpu_mhz; } static inline unsigned long long cycles_2_ns(unsigned long long cyc) { return (cyc * cyc2ns_scale) >> CYC2NS_SCALE_FACTOR; } static unsigned long long monotonic_clock_hpet(void) { unsigned long long last_offset, this_offset, base; unsigned seq; /* atomically read monotonic base & last_offset */ do { seq = read_seqbegin(&monotonic_lock); last_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low; base = monotonic_base; } while (read_seqretry(&monotonic_lock, seq)); /* Read the Time Stamp Counter */ rdtscll(this_offset); /* return the value in ns */ return base + cycles_2_ns(this_offset - last_offset); } static unsigned long get_offset_hpet(void) { register unsigned long eax, edx; eax = hpet_readl(HPET_COUNTER); eax -= hpet_last; /* hpet delta */ eax = min(hpet_tick, eax); /* * Time offset = (hpet delta) * ( usecs per HPET clock ) * = (hpet delta) * ( usecs per tick / HPET clocks per tick) * = (hpet delta) * ( hpet_usec_quotient ) / (2^32) * * Where, * hpet_usec_quotient = (2^32 * usecs per tick)/HPET clocks per tick * * Using a mull instead of a divl saves some cycles in critical path. */ ASM_MUL64_REG(eax, edx, hpet_usec_quotient, eax); /* our adjusted time offset in microseconds */ return edx; } static void mark_offset_hpet(void) { unsigned long long this_offset, last_offset; unsigned long offset; write_seqlock(&monotonic_lock); last_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low; rdtsc(last_tsc_low, last_tsc_high); if (hpet_use_timer) offset = hpet_readl(HPET_T0_CMP) - hpet_tick; else offset = hpet_readl(HPET_COUNTER); if (unlikely(((offset - hpet_last) >= (2*hpet_tick)) && (hpet_last != 0))) { int lost_ticks = ((offset - hpet_last) / hpet_tick) - 1; jiffies_64 += lost_ticks; } hpet_last = offset; /* update the monotonic base value */ this_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low; monotonic_base += cycles_2_ns(this_offset - last_offset); write_sequnlock(&monotonic_lock); } static void delay_hpet(unsigned long loops) { unsigned long hpet_start, hpet_end; unsigned long eax; /* loops is the number of cpu cycles. Convert it to hpet clocks */ ASM_MUL64_REG(eax, loops, tsc_hpet_quotient, loops); hpet_start = hpet_readl(HPET_COUNTER); do { rep_nop(); hpet_end = hpet_readl(HPET_COUNTER); } while ((hpet_end - hpet_start) < (loops)); } static struct timer_opts timer_hpet; static int __init init_hpet(char* override) { unsigned long result, remain; /* check clock override */ if (override[0] && strncmp(override,"hpet",4)) return -ENODEV; if (!is_hpet_enabled()) return -ENODEV; printk("Using HPET for gettimeofday\n"); if (cpu_has_tsc) { unsigned long tsc_quotient = calibrate_tsc_hpet(&tsc_hpet_quotient); 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_DIV64_REG(cpu_khz, edx, tsc_quotient, eax, edx); printk("Detected %u.%03u MHz processor.\n", cpu_khz / 1000, cpu_khz % 1000); } set_cyc2ns_scale(cpu_khz/1000); } /* set this only when cpu_has_tsc */ timer_hpet.read_timer = read_timer_tsc; } /* * Math to calculate hpet to usec multiplier * Look for the comments at get_offset_hpet() */ ASM_DIV64_REG(result, remain, hpet_tick, 0, KERNEL_TICK_USEC); if (remain > (hpet_tick >> 1)) result++; /* rounding the result */ hpet_usec_quotient = result; return 0; } static int hpet_resume(void) { write_seqlock(&monotonic_lock); /* Assume this is the last mark offset time */ rdtsc(last_tsc_low, last_tsc_high); if (hpet_use_timer) hpet_last = hpet_readl(HPET_T0_CMP) - hpet_tick; else hpet_last = hpet_readl(HPET_COUNTER); write_sequnlock(&monotonic_lock); return 0; } /************************************************************/ /* tsc timer_opts struct */ static struct timer_opts timer_hpet __read_mostly = { .name = "hpet", .mark_offset = mark_offset_hpet, .get_offset = get_offset_hpet, .monotonic_clock = monotonic_clock_hpet, .delay = delay_hpet, .resume = hpet_resume, }; struct init_timer_opts __initdata timer_hpet_init = { .init = init_hpet, .opts = &timer_hpet, };