patch-2.4.22 linux-2.4.22/arch/mips/kernel/time.c
Next file: linux-2.4.22/arch/mips/kernel/traps.c
Previous file: linux-2.4.22/arch/mips/kernel/sysmips.c
Back to the patch index
Back to the overall index
- Lines: 713
- Date:
2003-08-25 04:44:40.000000000 -0700
- Orig file:
linux-2.4.21/arch/mips/kernel/time.c
- Orig date:
2002-11-28 15:53:10.000000000 -0800
diff -urN linux-2.4.21/arch/mips/kernel/time.c linux-2.4.22/arch/mips/kernel/time.c
@@ -1,9 +1,10 @@
/*
* Copyright 2001 MontaVista Software Inc.
* Author: Jun Sun, jsun@mvista.com or jsun@junsun.net
+ * Copyright (c) 2003 Maciej W. Rozycki
*
* Common time service routines for MIPS machines. See
- * Documents/MIPS/README.txt.
+ * Documentation/mips/time.README.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -17,10 +18,12 @@
#include <linux/sched.h>
#include <linux/param.h>
#include <linux/time.h>
+#include <linux/timex.h>
#include <linux/smp.h>
#include <linux/kernel_stat.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
+#include <linux/module.h>
#include <asm/bootinfo.h>
#include <asm/cpu.h>
@@ -28,9 +31,14 @@
#include <asm/hardirq.h>
#include <asm/div64.h>
-/* This is for machines which generate the exact clock. */
-#define USECS_PER_JIFFY (1000000/HZ)
-#define USECS_PER_JIFFY_FRAC ((u32)((1000000ULL << 32) / HZ))
+/*
+ * The integer part of the number of usecs per jiffy is taken from tick,
+ * but the fractional part is not recorded, so we calculate it using the
+ * initial value of HZ. This aids systems where tick isn't really an
+ * integer (e.g. for HZ = 128).
+ */
+#define USECS_PER_JIFFY tick
+#define USECS_PER_JIFFY_FRAC ((unsigned long)(u32)((1000000ULL << 32) / HZ))
/*
* forward reference
@@ -38,11 +46,14 @@
extern rwlock_t xtime_lock;
extern volatile unsigned long wall_jiffies;
+spinlock_t rtc_lock = SPIN_LOCK_UNLOCKED;
+
/*
* whether we emulate local_timer_interrupts for SMP machines.
*/
int emulate_local_timer_interrupt;
+
/*
* By default we provide the null RTC ops
*/
@@ -58,6 +69,85 @@
unsigned long (*rtc_get_time)(void) = null_rtc_get_time;
int (*rtc_set_time)(unsigned long) = null_rtc_set_time;
+int (*rtc_set_mmss)(unsigned long);
+
+
+/* usecs per counter cycle, shifted to left by 32 bits */
+static unsigned int sll32_usecs_per_cycle;
+
+/* how many counter cycles in a jiffy */
+static unsigned long cycles_per_jiffy;
+
+/* Cycle counter value at the previous timer interrupt.. */
+static unsigned int timerhi, timerlo;
+
+/* expirelo is the count value for next CPU timer interrupt */
+static unsigned int expirelo;
+
+
+/*
+ * Null timer ack for systems not needing one (e.g. i8254).
+ */
+static void null_timer_ack(void) { /* nothing */ }
+
+/*
+ * Null high precision timer functions for systems lacking one.
+ */
+static unsigned int null_hpt_read(void)
+{
+ return 0;
+}
+
+static void null_hpt_init(unsigned int count) { /* nothing */ }
+
+
+/*
+ * Timer ack for a R4k-compatible timer of a known frequency.
+ */
+static void c0_fixed_timer_ack(void)
+{
+ unsigned int count;
+
+ /* Ack this timer interrupt and set the next one. */
+ expirelo += cycles_per_jiffy;
+ write_c0_compare(expirelo);
+
+ /* Check to see if we have missed any timer interrupts. */
+ count = read_c0_count();
+ if ((count - expirelo) < 0x7fffffff) {
+ /* missed_timer_count++; */
+ expirelo = count + cycles_per_jiffy;
+ write_c0_compare(expirelo);
+ }
+}
+
+/*
+ * High precision timer functions for a R4k-compatible timer.
+ */
+static unsigned int c0_hpt_read(void)
+{
+ return read_c0_count();
+}
+
+/* For unknown frequency. */
+static void c0_hpt_init(unsigned int count)
+{
+ write_c0_count(read_c0_count() - count);
+}
+
+/* For a known frequency. Used as an interrupt source. */
+static void c0_fixed_hpt_init(unsigned int count)
+{
+ expirelo = cycles_per_jiffy;
+ count = read_c0_count() - count;
+ write_c0_count(0);
+ write_c0_compare(cycles_per_jiffy);
+ write_c0_count(count);
+}
+
+void (*mips_timer_ack)(void);
+unsigned int (*mips_hpt_read)(void);
+void (*mips_hpt_init)(unsigned int);
/*
@@ -65,22 +155,23 @@
*/
void do_gettimeofday(struct timeval *tv)
{
- unsigned long flags;
+ unsigned long flags, lost;
+
+ read_lock_irqsave(&xtime_lock, flags);
- read_lock_irqsave (&xtime_lock, flags);
*tv = xtime;
tv->tv_usec += do_gettimeoffset();
-
/*
- * xtime is atomically updated in timer_bh. jiffies - wall_jiffies
- * is nonzero if the timer bottom half hasnt executed yet.
+ * xtime is atomically updated in timer_bh. jiffies - wall_jiffies
+ * is nonzero if the timer bottom half hasn't executed yet.
*/
- if (jiffies - wall_jiffies)
- tv->tv_usec += USECS_PER_JIFFY;
+ lost = jiffies - wall_jiffies;
+ if (lost)
+ tv->tv_usec += lost * USECS_PER_JIFFY;
- read_unlock_irqrestore (&xtime_lock, flags);
+ read_unlock_irqrestore(&xtime_lock, flags);
- if (tv->tv_usec >= 1000000) {
+ while (tv->tv_usec >= 1000000) {
tv->tv_usec -= 1000000;
tv->tv_sec++;
}
@@ -88,27 +179,29 @@
void do_settimeofday(struct timeval *tv)
{
- write_lock_irq (&xtime_lock);
+ 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.
- * Discover what correction gettimeofday
- * would have done, and then undo it!
+ /*
+ * This is revolting. We need to set "xtime" correctly. However,
+ * the value in this location is the value at the most recent update
+ * of wall time. Discover what correction gettimeofday() would have
+ * made, and then undo it!
*/
tv->tv_usec -= do_gettimeoffset();
+ tv->tv_usec -= (jiffies - wall_jiffies) * USECS_PER_JIFFY;
- if (tv->tv_usec < 0) {
+ while (tv->tv_usec < 0) {
tv->tv_usec += 1000000;
tv->tv_sec--;
}
+
xtime = *tv;
time_adjust = 0; /* stop active adjtime() */
time_status |= STA_UNSYNC;
time_maxerror = NTP_PHASE_LIMIT;
time_esterror = NTP_PHASE_LIMIT;
- write_unlock_irq (&xtime_lock);
+ write_unlock_irq(&xtime_lock);
}
@@ -119,76 +212,61 @@
* If the exact CPU counter frequency is known, use fixed_rate_gettimeoffset.
* Otherwise use calibrate_gettimeoffset()
*
- * If the CPU does not have counter register all, you can either supply
- * your own gettimeoffset() routine, or use null_gettimeoffset() routines,
- * which gives the same resolution as HZ.
+ * If the CPU does not have the counter register, you can either supply
+ * your own gettimeoffset() routine, or use null_gettimeoffset(), which
+ * gives the same resolution as HZ.
*/
+static unsigned long null_gettimeoffset(void)
+{
+ return 0;
+}
-/* This is for machines which generate the exact clock. */
-#define USECS_PER_JIFFY (1000000/HZ)
-
-/* usecs per counter cycle, shifted to left by 32 bits */
-static unsigned int sll32_usecs_per_cycle=0;
-
-/* how many counter cycles in a jiffy */
-static unsigned long cycles_per_jiffy=0;
-
-/* Cycle counter value at the previous timer interrupt.. */
-static unsigned int timerhi, timerlo;
-
-/* last time when xtime and rtc are sync'ed up */
-static long last_rtc_update;
-/* the function pointer to one of the gettimeoffset funcs*/
+/* The function pointer to one of the gettimeoffset funcs. */
unsigned long (*do_gettimeoffset)(void) = null_gettimeoffset;
-unsigned long null_gettimeoffset(void)
-{
- return 0;
-}
-unsigned long fixed_rate_gettimeoffset(void)
+static unsigned long fixed_rate_gettimeoffset(void)
{
u32 count;
unsigned long res;
/* Get last timer tick in absolute kernel time */
- count = read_32bit_cp0_register(CP0_COUNT);
+ count = mips_hpt_read();
/* .. relative to previous jiffy (32 bits is enough) */
count -= timerlo;
- __asm__("multu\t%1,%2\n\t"
- "mfhi\t%0"
- :"=r" (res)
- :"r" (count),
- "r" (sll32_usecs_per_cycle));
+ __asm__("multu %1,%2"
+ : "=h" (res)
+ : "r" (count), "r" (sll32_usecs_per_cycle)
+ : "lo", "accum");
/*
* Due to possible jiffies inconsistencies, we need to check
* the result so that we'll get a timer that is monotonic.
*/
if (res >= USECS_PER_JIFFY)
- res = USECS_PER_JIFFY-1;
+ res = USECS_PER_JIFFY - 1;
return res;
}
+
/*
- * Cached "1/(clocks per usec)*2^32" value.
+ * Cached "1/(clocks per usec) * 2^32" value.
* It has to be recalculated once each jiffy.
*/
static unsigned long cached_quotient;
/* Last jiffy when calibrate_divXX_gettimeoffset() was called. */
-static unsigned long last_jiffies = 0;
-
+static unsigned long last_jiffies;
/*
- * This is copied from dec/time.c:do_ioasic_gettimeoffset() by Mercij.
+ * This is moved from dec/time.c:do_ioasic_gettimeoffset() by Maciej.
*/
-unsigned long calibrate_div32_gettimeoffset(void)
+static unsigned long calibrate_div32_gettimeoffset(void)
{
u32 count;
unsigned long res, tmp;
@@ -204,20 +282,21 @@
unsigned long r0;
do_div64_32(r0, timerhi, timerlo, tmp);
do_div64_32(quotient, USECS_PER_JIFFY,
- USECS_PER_JIFFY_FRAC, r0);
+ USECS_PER_JIFFY_FRAC, r0);
cached_quotient = quotient;
}
}
/* Get last timer tick in absolute kernel time */
- count = read_32bit_cp0_register(CP0_COUNT);
+ count = mips_hpt_read();
/* .. relative to previous jiffy (32 bits is enough) */
count -= timerlo;
- __asm__("multu %2,%3"
- : "=l" (tmp), "=h" (res)
- : "r" (count), "r" (quotient));
+ __asm__("multu %1,%2"
+ : "=h" (res)
+ : "r" (count), "r" (quotient)
+ : "lo", "accum");
/*
* Due to possible jiffies inconsistencies, we need to check
@@ -229,7 +308,7 @@
return res;
}
-unsigned long calibrate_div64_gettimeoffset(void)
+static unsigned long calibrate_div64_gettimeoffset(void)
{
u32 count;
unsigned long res, tmp;
@@ -239,54 +318,56 @@
quotient = cached_quotient;
- if (tmp && last_jiffies != tmp) {
+ if (last_jiffies != tmp) {
last_jiffies = tmp;
- __asm__(".set\tnoreorder\n\t"
- ".set\tnoat\n\t"
- ".set\tmips3\n\t"
- "lwu\t%0,%2\n\t"
- "dsll32\t$1,%1,0\n\t"
- "or\t$1,$1,%0\n\t"
- "ddivu\t$0,$1,%3\n\t"
- "mflo\t$1\n\t"
- "dsll32\t%0,%4,0\n\t"
- "nop\n\t"
- "ddivu\t$0,%0,$1\n\t"
- "mflo\t%0\n\t"
- ".set\tmips0\n\t"
- ".set\tat\n\t"
- ".set\treorder"
- :"=&r" (quotient)
- :"r" (timerhi),
- "m" (timerlo),
- "r" (tmp),
- "r" (USECS_PER_JIFFY));
- cached_quotient = quotient;
+ if (last_jiffies) {
+ unsigned long r0;
+ __asm__(".set push\n\t"
+ ".set mips3\n\t"
+ "lwu %0,%3\n\t"
+ "dsll32 %1,%2,0\n\t"
+ "or %1,%1,%0\n\t"
+ "ddivu $0,%1,%4\n\t"
+ "dsll32 %0,%5,0\n\t"
+ "or %0,%0,%6\n\t"
+ "mflo %1\n\t"
+ "ddivu $0,%0,%1\n\t"
+ "mflo %0\n\t"
+ ".set pop"
+ : "=&r" (quotient), "=&r" (r0)
+ : "r" (timerhi), "m" (timerlo),
+ "r" (tmp), "r" (USECS_PER_JIFFY),
+ "r" (USECS_PER_JIFFY_FRAC)
+ : "hi", "lo", "accum");
+ cached_quotient = quotient;
+ }
}
/* Get last timer tick in absolute kernel time */
- count = read_32bit_cp0_register(CP0_COUNT);
+ count = mips_hpt_read();
/* .. relative to previous jiffy (32 bits is enough) */
count -= timerlo;
- __asm__("multu\t%1,%2\n\t"
- "mfhi\t%0"
- :"=r" (res)
- :"r" (count),
- "r" (quotient));
+ __asm__("multu %1,%2"
+ : "=h" (res)
+ : "r" (count), "r" (quotient)
+ : "lo", "accum");
/*
* Due to possible jiffies inconsistencies, we need to check
* the result so that we'll get a timer that is monotonic.
*/
if (res >= USECS_PER_JIFFY)
- res = USECS_PER_JIFFY-1;
+ res = USECS_PER_JIFFY - 1;
return res;
}
+/* last time when xtime and rtc are sync'ed up */
+static long last_rtc_update;
+
/*
* local_timer_interrupt() does profiling and process accounting
* on a per-CPU basis.
@@ -311,8 +392,8 @@
* put them into the last histogram slot, so if
* present, they will show up as a sharp peak.
*/
- if (pc > prof_len-1)
- pc = prof_len-1;
+ if (pc > prof_len - 1)
+ pc = prof_len - 1;
atomic_inc((atomic_t *)&prof_buffer[pc]);
}
}
@@ -324,31 +405,19 @@
}
/*
- * high-level timer interrupt service routines. This function
+ * High-level timer interrupt service routines. This function
* is set as irqaction->handler and is invoked through do_IRQ.
*/
void timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
- if (mips_cpu.options & MIPS_CPU_COUNTER) {
- unsigned int count;
+ unsigned int count;
- /*
- * The cycle counter is only 32 bit which is good for about
- * a minute at current count rates of upto 150MHz or so.
- */
- count = read_32bit_cp0_register(CP0_COUNT);
- timerhi += (count < timerlo); /* Wrap around */
- timerlo = count;
+ count = mips_hpt_read();
+ mips_timer_ack();
- /*
- * set up for next timer interrupt - no harm if the machine
- * is using another timer interrupt source.
- * Note that writing to COMPARE register clears the interrupt
- */
- write_32bit_cp0_register (CP0_COMPARE,
- count + cycles_per_jiffy);
-
- }
+ /* Update timerhi/timerlo for intra-jiffy calibration. */
+ timerhi += count < timerlo; /* Wrap around */
+ timerlo = count;
/*
* call the generic timer interrupt handling
@@ -360,27 +429,28 @@
* CMOS clock accordingly every ~11 minutes. rtc_set_time() has to be
* called as close as possible to 500 ms before the new second starts.
*/
- read_lock (&xtime_lock);
+ 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) {
- if (rtc_set_time(xtime.tv_sec) == 0) {
+ if (rtc_set_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 */
+ last_rtc_update = xtime.tv_sec - 600;
}
}
- read_unlock (&xtime_lock);
+ read_unlock(&xtime_lock);
/*
- * If jiffies has overflowed in this timer_interrupt we must
+ * If jiffies has overflown in this timer_interrupt, we must
* update the timer[hi]/[lo] to make fast gettimeoffset funcs
* quotient calc still valid. -arca
*/
if (!jiffies) {
timerhi = timerlo = 0;
+ mips_hpt_init(count);
}
#if !defined(CONFIG_SMP)
@@ -391,7 +461,7 @@
* In SMP mode, local_timer_interrupt() is invoked by appropriate
* low-level local timer interrupt handler.
*/
- local_timer_interrupt(0, NULL, regs);
+ local_timer_interrupt(irq, dev_id, regs);
#else /* CONFIG_SMP */
@@ -458,18 +528,15 @@
* c) enable the timer interrupt
*/
-void (*board_time_init)(void) = NULL;
-void (*board_timer_setup)(struct irqaction *irq) = NULL;
+void (*board_time_init)(void);
+void (*board_timer_setup)(struct irqaction *irq);
-unsigned int mips_counter_frequency = 0;
+unsigned int mips_counter_frequency;
static struct irqaction timer_irqaction = {
- timer_interrupt,
- SA_INTERRUPT,
- 0,
- "timer",
- NULL,
- NULL
+ .handler = timer_interrupt,
+ .flags = SA_INTERRUPT,
+ .name = "timer",
};
void __init time_init(void)
@@ -477,55 +544,76 @@
if (board_time_init)
board_time_init();
+ if (!rtc_set_mmss)
+ rtc_set_mmss = rtc_set_time;
+
xtime.tv_sec = rtc_get_time();
xtime.tv_usec = 0;
- /* choose appropriate gettimeoffset routine */
- if (!(mips_cpu.options & MIPS_CPU_COUNTER)) {
- /* no cpu counter - sorry */
- do_gettimeoffset = null_gettimeoffset;
- } else if (mips_counter_frequency != 0) {
- /* we have cpu counter and know counter frequency! */
- do_gettimeoffset = fixed_rate_gettimeoffset;
- } else if ((mips_cpu.isa_level == MIPS_CPU_ISA_M32) ||
- (mips_cpu.isa_level == MIPS_CPU_ISA_I) ||
- (mips_cpu.isa_level == MIPS_CPU_ISA_II) ) {
- /* we need to calibrate the counter but we don't have
- * 64-bit division. */
- do_gettimeoffset = calibrate_div32_gettimeoffset;
+ /* Choose appropriate high precision timer routines. */
+ if (!cpu_has_counter && !mips_hpt_read) {
+ /* No high precision timer -- sorry. */
+ mips_hpt_read = null_hpt_read;
+ mips_hpt_init = null_hpt_init;
+ } else if (!mips_counter_frequency) {
+ /* A high precision timer of unknown frequency. */
+ if (!mips_hpt_read) {
+ /* No external high precision timer -- use R4k. */
+ mips_hpt_read = c0_hpt_read;
+ mips_hpt_init = c0_hpt_init;
+ }
+
+ if ((current_cpu_data.isa_level == MIPS_CPU_ISA_M32) ||
+ (current_cpu_data.isa_level == MIPS_CPU_ISA_I) ||
+ (current_cpu_data.isa_level == MIPS_CPU_ISA_II))
+ /*
+ * We need to calibrate the counter but we don't have
+ * 64-bit division.
+ */
+ do_gettimeoffset = calibrate_div32_gettimeoffset;
+ else
+ /*
+ * We need to calibrate the counter but we *do* have
+ * 64-bit division.
+ */
+ do_gettimeoffset = calibrate_div64_gettimeoffset;
} else {
- /* we need to calibrate the counter but we *do* have
- * 64-bit division. */
- do_gettimeoffset = calibrate_div64_gettimeoffset;
- }
+ /* We know counter frequency! */
+ if (!mips_hpt_read) {
+ /* No external high precision timer -- use R4k. */
+ mips_hpt_read = c0_hpt_read;
+ mips_hpt_init = c0_fixed_hpt_init;
+
+ if (!mips_timer_ack)
+ /* R4k timer interrupt ack. */
+ mips_timer_ack = c0_fixed_timer_ack;
+ }
- /* caclulate cache parameters */
- if (mips_counter_frequency) {
- u32 count;
+ do_gettimeoffset = fixed_rate_gettimeoffset;
+ /* Calculate cache parameters. */
cycles_per_jiffy = mips_counter_frequency / HZ;
- /* sll32_usecs_per_cycle = 10^6 * 2^32 / mips_counter_freq */
- /* any better way to do this? */
+ /* sll32_usecs_per_cycle = 10^6 * 2^32 / mips_counter_freq */
+ /* Any better way to do this? */
sll32_usecs_per_cycle = mips_counter_frequency / 100000;
sll32_usecs_per_cycle = 0xffffffff / sll32_usecs_per_cycle;
sll32_usecs_per_cycle *= 10;
-
- /*
- * For those using cpu counter as timer, this sets up the
- * first interrupt
- */
- count = read_32bit_cp0_register(CP0_COUNT);
- write_32bit_cp0_register (CP0_COMPARE,
- count + cycles_per_jiffy);
}
+ if (!mips_timer_ack)
+ /* No timer interrupt ack (e.g. i8254). */
+ mips_timer_ack = null_timer_ack;
+
+ /* This sets up the high precision timer for the first interrupt. */
+ mips_hpt_init(mips_hpt_read());
+
/*
* Call board specific timer interrupt setup.
*
* this pointer must be setup in machine setup routine.
*
- * Even if the machine choose to use low-level timer interrupt,
+ * Even if a machine chooses to use a low-level timer interrupt,
* it still needs to setup the timer_irqaction.
* In that case, it might be better to set timer_irqaction.handler
* to be NULL function so that we are sure the high-level code
@@ -538,15 +626,15 @@
#define STARTOFTIME 1970
#define SECDAY 86400L
#define SECYR (SECDAY * 365)
-#define leapyear(year) ((year) % 4 == 0)
-#define days_in_year(a) (leapyear(a) ? 366 : 365)
-#define days_in_month(a) (month_days[(a) - 1])
+#define leapyear(y) ((!((y) % 4) && ((y) % 100)) || !((y) % 400))
+#define days_in_year(y) (leapyear(y) ? 366 : 365)
+#define days_in_month(m) (month_days[(m) - 1])
static int month_days[12] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
-void to_tm(unsigned long tim, struct rtc_time * tm)
+void to_tm(unsigned long tim, struct rtc_time *tm)
{
long hms, day, gday;
int i;
@@ -561,16 +649,16 @@
/* Number of years in days */
for (i = STARTOFTIME; day >= days_in_year(i); i++)
- day -= days_in_year(i);
+ day -= days_in_year(i);
tm->tm_year = i;
/* Number of months in days left */
if (leapyear(tm->tm_year))
- days_in_month(FEBRUARY) = 29;
+ days_in_month(FEBRUARY) = 29;
for (i = 1; day >= days_in_month(i); i++)
- day -= days_in_month(i);
+ day -= days_in_month(i);
days_in_month(FEBRUARY) = 28;
- tm->tm_mon = i-1; /* tm_mon starts from 0 to 11 */
+ tm->tm_mon = i - 1; /* tm_mon starts from 0 to 11 */
/* Days are what is left over (+1) from all that. */
tm->tm_mday = day + 1;
@@ -578,5 +666,10 @@
/*
* Determine the day of week
*/
- tm->tm_wday = (gday + 4) % 7; /* 1970/1/1 was Thursday */
+ tm->tm_wday = (gday + 4) % 7; /* 1970/1/1 was Thursday */
}
+
+EXPORT_SYMBOL(rtc_lock);
+EXPORT_SYMBOL(to_tm);
+EXPORT_SYMBOL(rtc_set_time);
+EXPORT_SYMBOL(rtc_get_time);
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)