1
linux/arch/i386/kernel/timers/timer_hpet.c
john stultz 35492df5ae [PATCH] i386: fix hpet for systems that don't support legacy replacement
Currently the i386 HPET code assumes the entire HPET implementation from
the spec is present.  This breaks on boxes that do not implement the
optional legacy timer replacement functionality portion of the spec.

This patch, which is very similar to my x86-64 patch for the same issue,
fixes the problem allowing i386 systems that cannot use the HPET for the
timer interrupt and RTC to still use the HPET as a time source.  I've
tested this patch on a system systems without HPET, with HPET but without
legacy timer replacement, as well as HPET with legacy timer replacement.

Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-05-01 08:58:50 -07:00

195 lines
5.4 KiB
C

/*
* This code largely moved from arch/i386/kernel/time.c.
* See comments there for proper credits.
*/
#include <linux/spinlock.h>
#include <linux/init.h>
#include <linux/timex.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/jiffies.h>
#include <asm/timer.h>
#include <asm/io.h>
#include <asm/processor.h>
#include "io_ports.h"
#include "mach_timer.h"
#include <asm/hpet.h>
static unsigned long 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 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 %lu.%03lu MHz processor.\n",
cpu_khz / 1000, cpu_khz % 1000);
}
set_cyc2ns_scale(cpu_khz/1000);
}
}
/*
* 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;
}
/************************************************************/
/* tsc timer_opts struct */
static struct timer_opts timer_hpet = {
.name = "hpet",
.mark_offset = mark_offset_hpet,
.get_offset = get_offset_hpet,
.monotonic_clock = monotonic_clock_hpet,
.delay = delay_hpet,
};
struct init_timer_opts __initdata timer_hpet_init = {
.init = init_hpet,
.opts = &timer_hpet,
};