| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  * Copyright 2001 MontaVista Software Inc. | 
 | 3 |  * Author: jsun@mvista.com or jsun@junsun.net | 
 | 4 |  * | 
 | 5 |  * rtc and time ops for vr4181.	 Part of code is drived from | 
 | 6 |  * linux-vr, originally written	 by Bradley D. LaRonde & Michael Klar. | 
 | 7 |  * | 
 | 8 |  * This program is free software; you can redistribute	it and/or modify it | 
 | 9 |  * under  the terms of	the GNU General	 Public License as published by the | 
 | 10 |  * Free Software Foundation;  either version 2 of the  License, or (at your | 
 | 11 |  * option) any later version. | 
 | 12 |  * | 
 | 13 |  */ | 
 | 14 |  | 
 | 15 | #include <linux/kernel.h> | 
 | 16 | #include <linux/spinlock.h> | 
 | 17 | #include <linux/param.h>			/* for HZ */ | 
 | 18 | #include <linux/time.h> | 
 | 19 | #include <linux/interrupt.h> | 
 | 20 |  | 
 | 21 | #include <asm/system.h> | 
 | 22 | #include <asm/time.h> | 
 | 23 |  | 
 | 24 | #include <asm/vr4181/vr4181.h> | 
 | 25 |  | 
 | 26 | #define COUNTS_PER_JIFFY ((32768 + HZ/2) / HZ) | 
 | 27 |  | 
 | 28 | /* | 
 | 29 |  * RTC ops | 
 | 30 |  */ | 
 | 31 |  | 
 | 32 | DEFINE_SPINLOCK(rtc_lock); | 
 | 33 |  | 
 | 34 | /* per VR41xx docs, bad data can be read if between 2 counts */ | 
 | 35 | static inline unsigned short | 
 | 36 | read_time_reg(volatile unsigned short *reg) | 
 | 37 | { | 
 | 38 | 	unsigned short value; | 
 | 39 | 	do { | 
 | 40 | 		value = *reg; | 
 | 41 | 		barrier(); | 
 | 42 | 	} while (value != *reg); | 
 | 43 | 	return value; | 
 | 44 | } | 
 | 45 |  | 
 | 46 | static unsigned long | 
 | 47 | vr4181_rtc_get_time(void) | 
 | 48 | { | 
 | 49 | 	unsigned short regh, regm, regl; | 
 | 50 |  | 
 | 51 | 	// why this crazy order, you ask?  to guarantee that neither m | 
 | 52 | 	// nor l wrap before all 3 read | 
 | 53 | 	do { | 
 | 54 | 		regm = read_time_reg(VR4181_ETIMEMREG); | 
 | 55 | 		barrier(); | 
 | 56 | 		regh = read_time_reg(VR4181_ETIMEHREG); | 
 | 57 | 		barrier(); | 
 | 58 | 		regl = read_time_reg(VR4181_ETIMELREG); | 
 | 59 | 	} while (regm != read_time_reg(VR4181_ETIMEMREG)); | 
 | 60 | 	return ((regh << 17) | (regm << 1) | (regl >> 15)); | 
 | 61 | } | 
 | 62 |  | 
 | 63 | static int | 
 | 64 | vr4181_rtc_set_time(unsigned long timeval) | 
 | 65 | { | 
 | 66 | 	unsigned short intreg; | 
 | 67 | 	unsigned long flags; | 
 | 68 |  | 
 | 69 | 	spin_lock_irqsave(&rtc_lock, flags); | 
 | 70 | 	intreg = *VR4181_RTCINTREG & 0x05; | 
 | 71 | 	barrier(); | 
 | 72 | 	*VR4181_ETIMELREG = timeval << 15; | 
 | 73 | 	*VR4181_ETIMEMREG = timeval >> 1; | 
 | 74 | 	*VR4181_ETIMEHREG = timeval >> 17; | 
 | 75 | 	barrier(); | 
 | 76 | 	// assume that any ints that just triggered are invalid, since the | 
 | 77 | 	// time value is written non-atomically in 3 separate regs | 
 | 78 | 	*VR4181_RTCINTREG = 0x05 ^ intreg; | 
 | 79 | 	spin_unlock_irqrestore(&rtc_lock, flags); | 
 | 80 |  | 
 | 81 | 	return 0; | 
 | 82 | } | 
 | 83 |  | 
 | 84 |  | 
 | 85 | /* | 
 | 86 |  * timer interrupt routine (wrapper) | 
 | 87 |  * | 
 | 88 |  * we need our own interrupt routine because we need to clear | 
 | 89 |  * RTC1 interrupt. | 
 | 90 |  */ | 
 | 91 | static void | 
 | 92 | vr4181_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) | 
 | 93 | { | 
 | 94 | 	/* Clear the interrupt. */ | 
 | 95 | 	*VR4181_RTCINTREG = 0x2; | 
 | 96 |  | 
 | 97 | 	/* call the generic one */ | 
 | 98 | 	timer_interrupt(irq, dev_id, regs); | 
 | 99 | } | 
 | 100 |  | 
 | 101 |  | 
 | 102 | /* | 
 | 103 |  * vr4181_time_init: | 
 | 104 |  * | 
 | 105 |  * We pick the following choices: | 
 | 106 |  *   . we use elapsed timer as the RTC.	 We set some reasonable init data since | 
 | 107 |  *     it does not persist across reset | 
 | 108 |  *   . we use RTC1 as the system timer interrupt source. | 
 | 109 |  *   . we use CPU counter for fast_gettimeoffset and we calivrate the cpu | 
 | 110 |  *     frequency.  In other words, we use calibrate_div64_gettimeoffset(). | 
 | 111 |  *   . we use our own timer interrupt routine which clears the interrupt | 
 | 112 |  *     and then calls the generic high-level timer interrupt routine. | 
 | 113 |  * | 
 | 114 |  */ | 
 | 115 |  | 
 | 116 | extern int setup_irq(unsigned int irq, struct irqaction *irqaction); | 
 | 117 |  | 
 | 118 | static void | 
 | 119 | vr4181_timer_setup(struct irqaction *irq) | 
 | 120 | { | 
 | 121 | 	/* over-write the handler to be our own one */ | 
 | 122 | 	irq->handler = vr4181_timer_interrupt; | 
 | 123 |  | 
 | 124 | 	/* sets up the frequency */ | 
 | 125 | 	*VR4181_RTCL1LREG = COUNTS_PER_JIFFY; | 
 | 126 | 	*VR4181_RTCL1HREG = 0; | 
 | 127 |  | 
 | 128 | 	/* and ack any pending ints */ | 
 | 129 | 	*VR4181_RTCINTREG = 0x2; | 
 | 130 |  | 
 | 131 | 	/* setup irqaction */ | 
 | 132 | 	setup_irq(VR4181_IRQ_INT1, irq); | 
 | 133 |  | 
 | 134 | } | 
 | 135 |  | 
 | 136 | void | 
 | 137 | vr4181_init_time(void) | 
 | 138 | { | 
 | 139 | 	/* setup hookup functions */ | 
 | 140 | 	rtc_get_time = vr4181_rtc_get_time; | 
 | 141 | 	rtc_set_time = vr4181_rtc_set_time; | 
 | 142 |  | 
 | 143 | 	board_timer_setup = vr4181_timer_setup; | 
 | 144 | } | 
 | 145 |  |