summaryrefslogtreecommitdiff
path: root/arch/mips/vr4181/common/time.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/mips/vr4181/common/time.c')
-rw-r--r--arch/mips/vr4181/common/time.c145
1 files changed, 145 insertions, 0 deletions
diff --git a/arch/mips/vr4181/common/time.c b/arch/mips/vr4181/common/time.c
new file mode 100644
index 000000000000..17814076b6f4
--- /dev/null
+++ b/arch/mips/vr4181/common/time.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2001 MontaVista Software Inc.
+ * Author: jsun@mvista.com or jsun@junsun.net
+ *
+ * rtc and time ops for vr4181. Part of code is drived from
+ * linux-vr, originally written by Bradley D. LaRonde & Michael Klar.
+ *
+ * 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
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/param.h> /* for HZ */
+#include <linux/time.h>
+#include <linux/interrupt.h>
+
+#include <asm/system.h>
+#include <asm/time.h>
+
+#include <asm/vr4181/vr4181.h>
+
+#define COUNTS_PER_JIFFY ((32768 + HZ/2) / HZ)
+
+/*
+ * RTC ops
+ */
+
+DEFINE_SPINLOCK(rtc_lock);
+
+/* per VR41xx docs, bad data can be read if between 2 counts */
+static inline unsigned short
+read_time_reg(volatile unsigned short *reg)
+{
+ unsigned short value;
+ do {
+ value = *reg;
+ barrier();
+ } while (value != *reg);
+ return value;
+}
+
+static unsigned long
+vr4181_rtc_get_time(void)
+{
+ unsigned short regh, regm, regl;
+
+ // why this crazy order, you ask? to guarantee that neither m
+ // nor l wrap before all 3 read
+ do {
+ regm = read_time_reg(VR4181_ETIMEMREG);
+ barrier();
+ regh = read_time_reg(VR4181_ETIMEHREG);
+ barrier();
+ regl = read_time_reg(VR4181_ETIMELREG);
+ } while (regm != read_time_reg(VR4181_ETIMEMREG));
+ return ((regh << 17) | (regm << 1) | (regl >> 15));
+}
+
+static int
+vr4181_rtc_set_time(unsigned long timeval)
+{
+ unsigned short intreg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rtc_lock, flags);
+ intreg = *VR4181_RTCINTREG & 0x05;
+ barrier();
+ *VR4181_ETIMELREG = timeval << 15;
+ *VR4181_ETIMEMREG = timeval >> 1;
+ *VR4181_ETIMEHREG = timeval >> 17;
+ barrier();
+ // assume that any ints that just triggered are invalid, since the
+ // time value is written non-atomically in 3 separate regs
+ *VR4181_RTCINTREG = 0x05 ^ intreg;
+ spin_unlock_irqrestore(&rtc_lock, flags);
+
+ return 0;
+}
+
+
+/*
+ * timer interrupt routine (wrapper)
+ *
+ * we need our own interrupt routine because we need to clear
+ * RTC1 interrupt.
+ */
+static void
+vr4181_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ /* Clear the interrupt. */
+ *VR4181_RTCINTREG = 0x2;
+
+ /* call the generic one */
+ timer_interrupt(irq, dev_id, regs);
+}
+
+
+/*
+ * vr4181_time_init:
+ *
+ * We pick the following choices:
+ * . we use elapsed timer as the RTC. We set some reasonable init data since
+ * it does not persist across reset
+ * . we use RTC1 as the system timer interrupt source.
+ * . we use CPU counter for fast_gettimeoffset and we calivrate the cpu
+ * frequency. In other words, we use calibrate_div64_gettimeoffset().
+ * . we use our own timer interrupt routine which clears the interrupt
+ * and then calls the generic high-level timer interrupt routine.
+ *
+ */
+
+extern int setup_irq(unsigned int irq, struct irqaction *irqaction);
+
+static void
+vr4181_timer_setup(struct irqaction *irq)
+{
+ /* over-write the handler to be our own one */
+ irq->handler = vr4181_timer_interrupt;
+
+ /* sets up the frequency */
+ *VR4181_RTCL1LREG = COUNTS_PER_JIFFY;
+ *VR4181_RTCL1HREG = 0;
+
+ /* and ack any pending ints */
+ *VR4181_RTCINTREG = 0x2;
+
+ /* setup irqaction */
+ setup_irq(VR4181_IRQ_INT1, irq);
+
+}
+
+void
+vr4181_init_time(void)
+{
+ /* setup hookup functions */
+ rtc_get_time = vr4181_rtc_get_time;
+ rtc_set_time = vr4181_rtc_set_time;
+
+ board_timer_setup = vr4181_timer_setup;
+}
+