]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
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 |