summaryrefslogtreecommitdiffstats
path: root/arch
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>2000-07-09 21:44:37 +0000
committerRalf Baechle <ralf@linux-mips.org>2000-07-09 21:44:37 +0000
commitd9d8062e7b49943b2a2fb034f817a9fc217fd40f (patch)
tree39dec0bbe8bd707aa09199e84999e06b69e76593 /arch
parentd69a8cf8c1b7a1aced66732997ff60cbf543de6d (diff)
Microsecond timers for Indy. From Keith Wesolowsky.
Diffstat (limited to 'arch')
-rw-r--r--arch/mips/kernel/Makefile9
-rw-r--r--arch/mips/kernel/time.c41
-rw-r--r--arch/mips/sgi/kernel/Makefile4
-rw-r--r--arch/mips/sgi/kernel/indyIRQ.S4
-rw-r--r--arch/mips/sgi/kernel/setup.c98
-rw-r--r--arch/mips/sgi/kernel/time.c24
6 files changed, 153 insertions, 27 deletions
diff --git a/arch/mips/kernel/Makefile b/arch/mips/kernel/Makefile
index 9ef5ecbb2..e213c0d38 100644
--- a/arch/mips/kernel/Makefile
+++ b/arch/mips/kernel/Makefile
@@ -1,4 +1,3 @@
-# $Id: Makefile,v 1.14 1999/10/09 00:00:58 ralf Exp $
#
# Makefile for the Linux/MIPS kernel.
#
@@ -36,10 +35,10 @@ endif
#
# SGIs have very different interrupt/timer hardware.
#
-ifndef CONFIG_SGI_IP22
- ifndef CONFIG_DECSTATION
- ifndef CONFIG_BAGET_MIPS
- O_OBJS += time.o
+ifndef CONFIG_DECSTATION
+ ifndef CONFIG_BAGET_MIPS
+ O_OBJS += time.o
+ ifndef CONFIG_SGI_IP22
OX_OBJS += irq.o
endif
endif
diff --git a/arch/mips/kernel/time.c b/arch/mips/kernel/time.c
index 6ef9a2f7b..8de389a50 100644
--- a/arch/mips/kernel/time.c
+++ b/arch/mips/kernel/time.c
@@ -14,6 +14,7 @@
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
+#include <linux/kernel_stat.h>
#include <asm/bootinfo.h>
#include <asm/mipsregs.h>
@@ -25,6 +26,8 @@
#include <linux/timex.h>
extern volatile unsigned long lost_ticks;
+unsigned long r4k_interval = 0;
+extern rwlock_t xtime_lock;
/*
* Change this if you have some constant time drift
@@ -218,7 +221,7 @@ void do_gettimeofday(struct timeval *tv)
{
unsigned long flags;
- save_and_cli(flags);
+ read_lock_irqsave (&xtime_lock, flags);
*tv = xtime;
tv->tv_usec += do_gettimeoffset();
@@ -229,7 +232,7 @@ void do_gettimeofday(struct timeval *tv)
if (lost_ticks)
tv->tv_usec += USECS_PER_JIFFY;
- restore_flags(flags);
+ read_unlock_irqrestore (&xtime_lock, flags);
if (tv->tv_usec >= 1000000) {
tv->tv_usec -= 1000000;
@@ -239,7 +242,7 @@ void do_gettimeofday(struct timeval *tv)
void do_settimeofday(struct timeval *tv)
{
- cli();
+ write_lock_irq (&xtime_lock);
/* This is revolting. We need to set the xtime.tv_usec
* correctly. However, the value in this location is
* is value at the last tick.
@@ -258,7 +261,7 @@ void do_settimeofday(struct timeval *tv)
time_status |= STA_UNSYNC;
time_maxerror = NTP_PHASE_LIMIT;
time_esterror = NTP_PHASE_LIMIT;
- sti();
+ write_unlock_irq (&xtime_lock);
}
/*
@@ -378,22 +381,26 @@ timer_interrupt(int irq, void *dev_id, struct pt_regs * regs)
* CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be
* called as close as possible to 500 ms before the new second starts.
*/
+ read_lock (&xtime_lock);
if ((time_status & STA_UNSYNC) == 0 &&
xtime.tv_sec > last_rtc_update + 660 &&
xtime.tv_usec >= 500000 - ((unsigned) tick) / 2 &&
- xtime.tv_usec <= 500000 + ((unsigned) tick) / 2)
+ xtime.tv_usec <= 500000 + ((unsigned) tick) / 2) {
if (set_rtc_mmss(xtime.tv_sec) == 0)
last_rtc_update = xtime.tv_sec;
else
last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */
+ }
/* As we return to user mode fire off the other CPU schedulers.. this is
basically because we don't yet share IRQ's around. This message is
rigged to be safe on the 386 - basically it's a hack, so don't look
closely for now.. */
/*smp_message_pass(MSG_ALL_BUT_SELF, MSG_RESCHEDULE, 0L, 0); */
+ read_unlock (&xtime_lock);
}
-static void r4k_timer_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+static inline void
+r4k_timer_interrupt(int irq, void *dev_id, struct pt_regs * regs)
{
unsigned int count;
@@ -405,6 +412,18 @@ static void r4k_timer_interrupt(int irq, void *dev_id, struct pt_regs * regs)
timerhi += (count < timerlo); /* Wrap around */
timerlo = count;
+#ifdef CONFIG_SGI_IP22
+ /* Since we don't get anything but r4k timer interrupts, we need to
+ * set this up so that we'll get one next time. Fortunately since we
+ * have timerhi/timerlo, we don't care so much if we miss one. So
+ * we need only ask for the next in r4k_interval counts. On other
+ * archs we have a real timer, so we don't want this.
+ */
+ write_32bit_cp0_register (CP0_COMPARE,
+ (unsigned long) (count + r4k_interval));
+ kstat.irqs[0][irq]++;
+#endif
+
timer_interrupt(irq, dev_id, regs);
if (!jiffies)
@@ -418,6 +437,12 @@ static void r4k_timer_interrupt(int irq, void *dev_id, struct pt_regs * regs)
}
}
+void indy_r4k_timer_interrupt (struct pt_regs *regs)
+{
+ static const int INDY_R4K_TIMER_IRQ = 7;
+ r4k_timer_interrupt (INDY_R4K_TIMER_IRQ, NULL, regs);
+}
+
/* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
* Assumes input in normal date format, i.e. 1980-12-31 23:59:59
* => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
@@ -497,7 +522,7 @@ void (*board_time_init)(struct irqaction *irq);
void __init time_init(void)
{
- unsigned int epoch, year, mon, day, hour, min, sec;
+ unsigned int epoch = 0, year, mon, day, hour, min, sec;
int i;
/* The Linux interpretation of the CMOS clock register contents:
@@ -540,8 +565,10 @@ void __init time_init(void)
}
year += epoch;
+ write_lock_irq (&xtime_lock);
xtime.tv_sec = mktime(year, mon, day, hour, min, sec);
xtime.tv_usec = 0;
+ write_unlock_irq (&xtime_lock);
init_cycle_counter();
diff --git a/arch/mips/sgi/kernel/Makefile b/arch/mips/sgi/kernel/Makefile
index 760e9fe51..6eb3f1775 100644
--- a/arch/mips/sgi/kernel/Makefile
+++ b/arch/mips/sgi/kernel/Makefile
@@ -1,4 +1,4 @@
-# $Id: Makefile,v 1.7 1999/05/07 18:00:16 ulfc Exp $
+#
# Makefile for the SGI specific kernel interface routines
# under Linux.
#
@@ -14,7 +14,7 @@
$(CC) $(CFLAGS) -c $< -o $*.o
OBJS = indy_mc.o indy_sc.o indy_hpc.o indy_int.o indy_rtc.o \
- system.o indy_timer.o indyIRQ.o reset.o setup.o time.o
+ system.o indyIRQ.o reset.o setup.o time.o
ifdef CONFIG_SGI_PROM_CONSOLE
OBJS += promcon.o
endif
diff --git a/arch/mips/sgi/kernel/indyIRQ.S b/arch/mips/sgi/kernel/indyIRQ.S
index fd8e2a1dd..14d20f044 100644
--- a/arch/mips/sgi/kernel/indyIRQ.S
+++ b/arch/mips/sgi/kernel/indyIRQ.S
@@ -1,4 +1,4 @@
-/* $Id: indyIRQ.S,v 1.3 1998/03/22 23:27:17 ralf Exp $
+/*
* indyIRQ.S: Interrupt exception dispatch code for FullHouse and
* Guiness.
*
@@ -67,7 +67,7 @@
/* Wheee, a timer interrupt. */
move a0, sp
- jal indy_timer_interrupt
+ jal indy_r4k_timer_interrupt
nop # delay slot
j ret_from_irq
diff --git a/arch/mips/sgi/kernel/setup.c b/arch/mips/sgi/kernel/setup.c
index feefa10b2..afd52c343 100644
--- a/arch/mips/sgi/kernel/setup.c
+++ b/arch/mips/sgi/kernel/setup.c
@@ -1,5 +1,4 @@
-/* $Id: setup.c,v 1.29 2000/01/27 01:05:23 ralf Exp $
- *
+/*
* setup.c: SGI specific setup, including init of the feature struct.
*
* Copyright (C) 1996 David S. Miller (dm@engr.sgi.com)
@@ -37,6 +36,8 @@ static int remote_debug = 0;
extern void console_setup(char *);
#endif
+extern unsigned long r4k_interval; /* Cycle counter ticks per 1/HZ seconds */
+
extern struct rtc_ops indy_rtc_ops;
void indy_reboot_setup(void);
void sgi_volume_set(unsigned char);
@@ -136,6 +137,98 @@ int __init page_is_ram(unsigned long pagenr)
return 0;
}
+void (*board_time_init)(struct irqaction *irq);
+
+static unsigned long dosample(volatile unsigned char *tcwp,
+ volatile unsigned char *tc2p)
+{
+ unsigned long ct0, ct1;
+ unsigned char msb, lsb;
+
+ /* Start the counter. */
+ *tcwp = (SGINT_TCWORD_CNT2 | SGINT_TCWORD_CALL | SGINT_TCWORD_MRGEN);
+ *tc2p = (SGINT_TCSAMP_COUNTER & 0xff);
+ *tc2p = (SGINT_TCSAMP_COUNTER >> 8);
+
+ /* Get initial counter invariant */
+ ct0 = read_32bit_cp0_register(CP0_COUNT);
+
+ /* Latch and spin until top byte of counter2 is zero */
+ do {
+ *tcwp = (SGINT_TCWORD_CNT2 | SGINT_TCWORD_CLAT);
+ lsb = *tc2p;
+ msb = *tc2p;
+ ct1 = read_32bit_cp0_register(CP0_COUNT);
+ } while(msb);
+
+ /* Stop the counter. */
+ *tcwp = (SGINT_TCWORD_CNT2 | SGINT_TCWORD_CALL | SGINT_TCWORD_MSWST);
+
+ /* Return the difference, this is how far the r4k counter increments
+ * for every 1/HZ seconds. We round off the the nearest 1 MHz of
+ * master clock (= 1000000 / 100 / 2 = 5000 count).
+ */
+ return ((ct1 - ct0) / 5000) * 5000;
+}
+
+#define ALLINTS (IE_IRQ0 | IE_IRQ1 | IE_IRQ2 | IE_IRQ3 | IE_IRQ4 | IE_IRQ5)
+
+void sgi_time_init (struct irqaction *irq) {
+ /* Here we need to calibrate the cycle counter to at least be close.
+ * We don't need to actually register the irq handler because that's
+ * all done in indyIRQ.S.
+ */
+ struct sgi_ioc_timers *p;
+ volatile unsigned char *tcwp, *tc2p;
+ unsigned long r4k_ticks[3];
+ unsigned long r4k_next;
+
+ /* Figure out the r4k offset, the algorithm is very simple
+ * and works in _all_ cases as long as the 8254 counter
+ * register itself works ok (as an interrupt driving timer
+ * it does not because of bug, this is why we are using
+ * the onchip r4k counter/compare register to serve this
+ * purpose, but for r4k_offset calculation it will work
+ * ok for us). There are other very complicated ways
+ * of performing this calculation but this one works just
+ * fine so I am not going to futz around. ;-)
+ */
+ p = ioc_timers;
+ tcwp = &p->tcword;
+ tc2p = &p->tcnt2;
+
+ printk("Calibrating system timer... ");
+ dosample(tcwp, tc2p); /* First sample. */
+ dosample(tcwp, tc2p); /* Eat one. */
+ r4k_ticks[0] = dosample(tcwp, tc2p); /* Second sample. */
+ dosample(tcwp, tc2p); /* Eat another. */
+ r4k_ticks[1] += dosample (tcwp, tc2p); /* Third sample. */
+
+ if (r4k_ticks[0] != r4k_ticks[1]) {
+ printk ("warning: timer counts differ, retrying...");
+ dosample (tcwp, tc2p);
+ r4k_ticks[2] = dosample (tcwp, tc2p);
+ if (r4k_ticks[2] == r4k_ticks[0]
+ || r4k_ticks[2] == r4k_ticks[1])
+ r4k_interval = r4k_ticks[2];
+ else {
+ printk ("disagreement, using average...");
+ r4k_interval = (r4k_ticks[0] + r4k_ticks[1]
+ + r4k_ticks[2]) / 3;
+ }
+ } else
+ r4k_interval = r4k_ticks[0];
+
+ printk("%d [%d.%02d MHz CPU]\n", (int) r4k_interval,
+ (int) (r4k_interval / 5000), (int) (r4k_interval % 5000) / 50);
+
+ /* Set ourselves up for future interrupts */
+ r4k_next = (read_32bit_cp0_register(CP0_COUNT) + r4k_interval);
+ write_32bit_cp0_register(CP0_COMPARE, r4k_next);
+ set_cp0_status(ST0_IM, ALLINTS);
+ sti ();
+}
+
void __init sgi_setup(void)
{
#ifdef CONFIG_SERIAL_CONSOLE
@@ -147,6 +240,7 @@ void __init sgi_setup(void)
irq_setup = sgi_irq_setup;
+ board_time_init = sgi_time_init;
/* Init the INDY HPC I/O controller. Need to call this before
* fucking with the memory controller because it needs to know the
diff --git a/arch/mips/sgi/kernel/time.c b/arch/mips/sgi/kernel/time.c
index 355f1c471..ea85b75db 100644
--- a/arch/mips/sgi/kernel/time.c
+++ b/arch/mips/sgi/kernel/time.c
@@ -1,15 +1,21 @@
-/* $Id: time.c,v 1.2 1998/04/05 11:24:00 ralf Exp $
- * time.c: Generic SGI time_init() code, this will dispatch to the
- * appropriate per-architecture time/counter init code.
+/*
+ * time.c: Generic SGI handler for (spurious) 8254 interrupts
*
* Copyright (C) 1996 David S. Miller (dm@engr.sgi.com)
*/
-#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel_stat.h>
+#include <asm/sgialib.h>
-extern void indy_timer_init(void);
-
-void __init time_init(void)
+void indy_8254timer_irq(void)
{
- /* XXX assume INDY for now XXX */
- indy_timer_init();
+ int cpu = smp_processor_id();
+ int irq = 4;
+
+ irq_enter(cpu);
+ kstat.irqs[0][irq]++;
+ printk("indy_8254timer_irq: Whoops, should not have gotten this IRQ\n");
+ prom_getchar();
+ prom_imode();
+ irq_exit(cpu);
}