blob: 17814076b6f407b932f1813beeb8f3d86d99919f [file] [log] [blame]
/*
* 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;
}