| /* | 
 |  * 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; | 
 | } | 
 |  |