1da177e4c3
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
207 lines
5.3 KiB
C
207 lines
5.3 KiB
C
/*
|
|
* This code largely moved from arch/i386/kernel/time.c.
|
|
* See comments there for proper credits.
|
|
*/
|
|
|
|
#include <linux/spinlock.h>
|
|
#include <linux/module.h>
|
|
#include <linux/device.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/sysdev.h>
|
|
#include <linux/timex.h>
|
|
#include <asm/delay.h>
|
|
#include <asm/mpspec.h>
|
|
#include <asm/timer.h>
|
|
#include <asm/smp.h>
|
|
#include <asm/io.h>
|
|
#include <asm/arch_hooks.h>
|
|
|
|
extern spinlock_t i8259A_lock;
|
|
extern spinlock_t i8253_lock;
|
|
#include "do_timer.h"
|
|
#include "io_ports.h"
|
|
|
|
static int count_p; /* counter in get_offset_pit() */
|
|
|
|
static int __init init_pit(char* override)
|
|
{
|
|
/* check clock override */
|
|
if (override[0] && strncmp(override,"pit",3))
|
|
printk(KERN_ERR "Warning: clock= override failed. Defaulting to PIT\n");
|
|
|
|
count_p = LATCH;
|
|
return 0;
|
|
}
|
|
|
|
static void mark_offset_pit(void)
|
|
{
|
|
/* nothing needed */
|
|
}
|
|
|
|
static unsigned long long monotonic_clock_pit(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void delay_pit(unsigned long loops)
|
|
{
|
|
int d0;
|
|
__asm__ __volatile__(
|
|
"\tjmp 1f\n"
|
|
".align 16\n"
|
|
"1:\tjmp 2f\n"
|
|
".align 16\n"
|
|
"2:\tdecl %0\n\tjns 2b"
|
|
:"=&a" (d0)
|
|
:"0" (loops));
|
|
}
|
|
|
|
|
|
/* This function must be called with xtime_lock held.
|
|
* It was inspired by Steve McCanne's microtime-i386 for BSD. -- jrs
|
|
*
|
|
* However, the pc-audio speaker driver changes the divisor so that
|
|
* it gets interrupted rather more often - it loads 64 into the
|
|
* counter rather than 11932! This has an adverse impact on
|
|
* do_gettimeoffset() -- it stops working! What is also not
|
|
* good is that the interval that our timer function gets called
|
|
* is no longer 10.0002 ms, but 9.9767 ms. To get around this
|
|
* would require using a different timing source. Maybe someone
|
|
* could use the RTC - I know that this can interrupt at frequencies
|
|
* ranging from 8192Hz to 2Hz. If I had the energy, I'd somehow fix
|
|
* it so that at startup, the timer code in sched.c would select
|
|
* using either the RTC or the 8253 timer. The decision would be
|
|
* based on whether there was any other device around that needed
|
|
* to trample on the 8253. I'd set up the RTC to interrupt at 1024 Hz,
|
|
* and then do some jiggery to have a version of do_timer that
|
|
* advanced the clock by 1/1024 s. Every time that reached over 1/100
|
|
* of a second, then do all the old code. If the time was kept correct
|
|
* then do_gettimeoffset could just return 0 - there is no low order
|
|
* divider that can be accessed.
|
|
*
|
|
* Ideally, you would be able to use the RTC for the speaker driver,
|
|
* but it appears that the speaker driver really needs interrupt more
|
|
* often than every 120 us or so.
|
|
*
|
|
* Anyway, this needs more thought.... pjsg (1993-08-28)
|
|
*
|
|
* If you are really that interested, you should be reading
|
|
* comp.protocols.time.ntp!
|
|
*/
|
|
|
|
static unsigned long get_offset_pit(void)
|
|
{
|
|
int count;
|
|
unsigned long flags;
|
|
static unsigned long jiffies_p = 0;
|
|
|
|
/*
|
|
* cache volatile jiffies temporarily; we have xtime_lock.
|
|
*/
|
|
unsigned long jiffies_t;
|
|
|
|
spin_lock_irqsave(&i8253_lock, flags);
|
|
/* timer count may underflow right here */
|
|
outb_p(0x00, PIT_MODE); /* latch the count ASAP */
|
|
|
|
count = inb_p(PIT_CH0); /* read the latched count */
|
|
|
|
/*
|
|
* We do this guaranteed double memory access instead of a _p
|
|
* postfix in the previous port access. Wheee, hackady hack
|
|
*/
|
|
jiffies_t = jiffies;
|
|
|
|
count |= inb_p(PIT_CH0) << 8;
|
|
|
|
/* VIA686a test code... reset the latch if count > max + 1 */
|
|
if (count > LATCH) {
|
|
outb_p(0x34, PIT_MODE);
|
|
outb_p(LATCH & 0xff, PIT_CH0);
|
|
outb(LATCH >> 8, PIT_CH0);
|
|
count = LATCH - 1;
|
|
}
|
|
|
|
/*
|
|
* avoiding timer inconsistencies (they are rare, but they happen)...
|
|
* there are two kinds of problems that must be avoided here:
|
|
* 1. the timer counter underflows
|
|
* 2. hardware problem with the timer, not giving us continuous time,
|
|
* the counter does small "jumps" upwards on some Pentium systems,
|
|
* (see c't 95/10 page 335 for Neptun bug.)
|
|
*/
|
|
|
|
if( jiffies_t == jiffies_p ) {
|
|
if( count > count_p ) {
|
|
/* the nutcase */
|
|
count = do_timer_overflow(count);
|
|
}
|
|
} else
|
|
jiffies_p = jiffies_t;
|
|
|
|
count_p = count;
|
|
|
|
spin_unlock_irqrestore(&i8253_lock, flags);
|
|
|
|
count = ((LATCH-1) - count) * TICK_SIZE;
|
|
count = (count + LATCH/2) / LATCH;
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
/* tsc timer_opts struct */
|
|
struct timer_opts timer_pit = {
|
|
.name = "pit",
|
|
.mark_offset = mark_offset_pit,
|
|
.get_offset = get_offset_pit,
|
|
.monotonic_clock = monotonic_clock_pit,
|
|
.delay = delay_pit,
|
|
};
|
|
|
|
struct init_timer_opts __initdata timer_pit_init = {
|
|
.init = init_pit,
|
|
.opts = &timer_pit,
|
|
};
|
|
|
|
void setup_pit_timer(void)
|
|
{
|
|
extern spinlock_t i8253_lock;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&i8253_lock, flags);
|
|
outb_p(0x34,PIT_MODE); /* binary, mode 2, LSB/MSB, ch 0 */
|
|
udelay(10);
|
|
outb_p(LATCH & 0xff , PIT_CH0); /* LSB */
|
|
udelay(10);
|
|
outb(LATCH >> 8 , PIT_CH0); /* MSB */
|
|
spin_unlock_irqrestore(&i8253_lock, flags);
|
|
}
|
|
|
|
static int timer_resume(struct sys_device *dev)
|
|
{
|
|
setup_pit_timer();
|
|
return 0;
|
|
}
|
|
|
|
static struct sysdev_class timer_sysclass = {
|
|
set_kset_name("timer_pit"),
|
|
.resume = timer_resume,
|
|
};
|
|
|
|
static struct sys_device device_timer = {
|
|
.id = 0,
|
|
.cls = &timer_sysclass,
|
|
};
|
|
|
|
static int __init init_timer_sysfs(void)
|
|
{
|
|
int error = sysdev_class_register(&timer_sysclass);
|
|
if (!error)
|
|
error = sysdev_register(&device_timer);
|
|
return error;
|
|
}
|
|
|
|
device_initcall(init_timer_sysfs);
|
|
|