/* * 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 #include #include #include /* for HZ */ #include #include #include #include #include #define COUNTS_PER_JIFFY ((32768 + HZ/2) / HZ) /* * RTC ops */ spinlock_t rtc_lock = SPIN_LOCK_UNLOCKED; /* 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; }