diff options
Diffstat (limited to 'arch/blackfin/kernel/time.c')
-rw-r--r-- | arch/blackfin/kernel/time.c | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/arch/blackfin/kernel/time.c b/arch/blackfin/kernel/time.c new file mode 100644 index 000000000000..f578176b6d92 --- /dev/null +++ b/arch/blackfin/kernel/time.c | |||
@@ -0,0 +1,326 @@ | |||
1 | /* | ||
2 | * File: arch/blackfin/kernel/time.c | ||
3 | * Based on: none - original work | ||
4 | * Author: | ||
5 | * | ||
6 | * Created: | ||
7 | * Description: This file contains the bfin-specific time handling details. | ||
8 | * Most of the stuff is located in the machine specific files. | ||
9 | * | ||
10 | * Modified: | ||
11 | * Copyright 2004-2006 Analog Devices Inc. | ||
12 | * | ||
13 | * Bugs: Enter bugs at http://blackfin.uclinux.org/ | ||
14 | * | ||
15 | * This program is free software; you can redistribute it and/or modify | ||
16 | * it under the terms of the GNU General Public License as published by | ||
17 | * the Free Software Foundation; either version 2 of the License, or | ||
18 | * (at your option) any later version. | ||
19 | * | ||
20 | * This program is distributed in the hope that it will be useful, | ||
21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
23 | * GNU General Public License for more details. | ||
24 | * | ||
25 | * You should have received a copy of the GNU General Public License | ||
26 | * along with this program; if not, see the file COPYING, or write | ||
27 | * to the Free Software Foundation, Inc., | ||
28 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
29 | */ | ||
30 | |||
31 | #include <linux/module.h> | ||
32 | #include <linux/profile.h> | ||
33 | #include <linux/interrupt.h> | ||
34 | #include <linux/time.h> | ||
35 | #include <linux/irq.h> | ||
36 | |||
37 | #include <asm/blackfin.h> | ||
38 | |||
39 | /* This is an NTP setting */ | ||
40 | #define TICK_SIZE (tick_nsec / 1000) | ||
41 | |||
42 | static void time_sched_init(irqreturn_t(*timer_routine) | ||
43 | (int, void *)); | ||
44 | static unsigned long gettimeoffset(void); | ||
45 | static inline void do_leds(void); | ||
46 | |||
47 | #if (defined(CONFIG_BFIN_ALIVE_LED) || defined(CONFIG_BFIN_IDLE_LED)) | ||
48 | void __init init_leds(void) | ||
49 | { | ||
50 | unsigned int tmp = 0; | ||
51 | |||
52 | #if defined(CONFIG_BFIN_ALIVE_LED) | ||
53 | /* config pins as output. */ | ||
54 | tmp = bfin_read_CONFIG_BFIN_ALIVE_LED_DPORT(); | ||
55 | SSYNC(); | ||
56 | bfin_write_CONFIG_BFIN_ALIVE_LED_DPORT(tmp | CONFIG_BFIN_ALIVE_LED_PIN); | ||
57 | SSYNC(); | ||
58 | |||
59 | /* First set led be off */ | ||
60 | tmp = bfin_read_CONFIG_BFIN_ALIVE_LED_PORT(); | ||
61 | SSYNC(); | ||
62 | bfin_write_CONFIG_BFIN_ALIVE_LED_PORT(tmp | CONFIG_BFIN_ALIVE_LED_PIN); /* light off */ | ||
63 | SSYNC(); | ||
64 | #endif | ||
65 | |||
66 | #if defined(CONFIG_BFIN_IDLE_LED) | ||
67 | /* config pins as output. */ | ||
68 | tmp = bfin_read_CONFIG_BFIN_IDLE_LED_DPORT(); | ||
69 | SSYNC(); | ||
70 | bfin_write_CONFIG_BFIN_IDLE_LED_DPORT(tmp | CONFIG_BFIN_IDLE_LED_PIN); | ||
71 | SSYNC(); | ||
72 | |||
73 | /* First set led be off */ | ||
74 | tmp = bfin_read_CONFIG_BFIN_IDLE_LED_PORT(); | ||
75 | SSYNC(); | ||
76 | bfin_write_CONFIG_BFIN_IDLE_LED_PORT(tmp | CONFIG_BFIN_IDLE_LED_PIN); /* light off */ | ||
77 | SSYNC(); | ||
78 | #endif | ||
79 | } | ||
80 | #else | ||
81 | void __init init_leds(void) | ||
82 | { | ||
83 | } | ||
84 | #endif | ||
85 | |||
86 | #if defined(CONFIG_BFIN_ALIVE_LED) | ||
87 | static inline void do_leds(void) | ||
88 | { | ||
89 | static unsigned int count = 50; | ||
90 | static int flag = 0; | ||
91 | unsigned short tmp = 0; | ||
92 | |||
93 | if (--count == 0) { | ||
94 | count = 50; | ||
95 | flag = ~flag; | ||
96 | } | ||
97 | tmp = bfin_read_CONFIG_BFIN_ALIVE_LED_PORT(); | ||
98 | SSYNC(); | ||
99 | |||
100 | if (flag) | ||
101 | tmp &= ~CONFIG_BFIN_ALIVE_LED_PIN; /* light on */ | ||
102 | else | ||
103 | tmp |= CONFIG_BFIN_ALIVE_LED_PIN; /* light off */ | ||
104 | |||
105 | bfin_write_CONFIG_BFIN_ALIVE_LED_PORT(tmp); | ||
106 | SSYNC(); | ||
107 | |||
108 | } | ||
109 | #else | ||
110 | static inline void do_leds(void) | ||
111 | { | ||
112 | } | ||
113 | #endif | ||
114 | |||
115 | static struct irqaction bfin_timer_irq = { | ||
116 | .name = "BFIN Timer Tick", | ||
117 | .flags = IRQF_DISABLED | ||
118 | }; | ||
119 | |||
120 | /* | ||
121 | * The way that the Blackfin core timer works is: | ||
122 | * - CCLK is divided by a programmable 8-bit pre-scaler (TSCALE) | ||
123 | * - Every time TSCALE ticks, a 32bit is counted down (TCOUNT) | ||
124 | * | ||
125 | * If you take the fastest clock (1ns, or 1GHz to make the math work easier) | ||
126 | * 10ms is 10,000,000 clock ticks, which fits easy into a 32-bit counter | ||
127 | * (32 bit counter is 4,294,967,296ns or 4.2 seconds) so, we don't need | ||
128 | * to use TSCALE, and program it to zero (which is pass CCLK through). | ||
129 | * If you feel like using it, try to keep HZ * TIMESCALE to some | ||
130 | * value that divides easy (like power of 2). | ||
131 | */ | ||
132 | |||
133 | #define TIME_SCALE 1 | ||
134 | |||
135 | static void | ||
136 | time_sched_init(irqreturn_t(*timer_routine) (int, void *)) | ||
137 | { | ||
138 | u32 tcount; | ||
139 | |||
140 | /* power up the timer, but don't enable it just yet */ | ||
141 | bfin_write_TCNTL(1); | ||
142 | CSYNC(); | ||
143 | |||
144 | /* | ||
145 | * the TSCALE prescaler counter. | ||
146 | */ | ||
147 | bfin_write_TSCALE((TIME_SCALE - 1)); | ||
148 | |||
149 | tcount = ((get_cclk() / (HZ * TIME_SCALE)) - 1); | ||
150 | bfin_write_TPERIOD(tcount); | ||
151 | bfin_write_TCOUNT(tcount); | ||
152 | |||
153 | /* now enable the timer */ | ||
154 | CSYNC(); | ||
155 | |||
156 | bfin_write_TCNTL(7); | ||
157 | |||
158 | bfin_timer_irq.handler = (irq_handler_t)timer_routine; | ||
159 | /* call setup_irq instead of request_irq because request_irq calls | ||
160 | * kmalloc which has not been initialized yet | ||
161 | */ | ||
162 | setup_irq(IRQ_CORETMR, &bfin_timer_irq); | ||
163 | } | ||
164 | |||
165 | /* | ||
166 | * Should return useconds since last timer tick | ||
167 | */ | ||
168 | static unsigned long gettimeoffset(void) | ||
169 | { | ||
170 | unsigned long offset; | ||
171 | unsigned long clocks_per_jiffy; | ||
172 | |||
173 | clocks_per_jiffy = bfin_read_TPERIOD(); | ||
174 | offset = | ||
175 | (clocks_per_jiffy - | ||
176 | bfin_read_TCOUNT()) / (((clocks_per_jiffy + 1) * HZ) / | ||
177 | USEC_PER_SEC); | ||
178 | |||
179 | /* Check if we just wrapped the counters and maybe missed a tick */ | ||
180 | if ((bfin_read_ILAT() & (1 << IRQ_CORETMR)) | ||
181 | && (offset < (100000 / HZ / 2))) | ||
182 | offset += (USEC_PER_SEC / HZ); | ||
183 | |||
184 | return offset; | ||
185 | } | ||
186 | |||
187 | static inline int set_rtc_mmss(unsigned long nowtime) | ||
188 | { | ||
189 | return 0; | ||
190 | } | ||
191 | |||
192 | /* | ||
193 | * timer_interrupt() needs to keep up the real-time clock, | ||
194 | * as well as call the "do_timer()" routine every clocktick | ||
195 | */ | ||
196 | #ifdef CONFIG_CORE_TIMER_IRQ_L1 | ||
197 | irqreturn_t timer_interrupt(int irq, void *dummy)__attribute__((l1_text)); | ||
198 | #endif | ||
199 | |||
200 | irqreturn_t timer_interrupt(int irq, void *dummy) | ||
201 | { | ||
202 | /* last time the cmos clock got updated */ | ||
203 | static long last_rtc_update = 0; | ||
204 | |||
205 | write_seqlock(&xtime_lock); | ||
206 | |||
207 | do_timer(1); | ||
208 | do_leds(); | ||
209 | |||
210 | #ifndef CONFIG_SMP | ||
211 | update_process_times(user_mode(get_irq_regs())); | ||
212 | #endif | ||
213 | profile_tick(CPU_PROFILING); | ||
214 | |||
215 | /* | ||
216 | * If we have an externally synchronized Linux clock, then update | ||
217 | * CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be | ||
218 | * called as close as possible to 500 ms before the new second starts. | ||
219 | */ | ||
220 | |||
221 | if (ntp_synced() && | ||
222 | xtime.tv_sec > last_rtc_update + 660 && | ||
223 | (xtime.tv_nsec / NSEC_PER_USEC) >= | ||
224 | 500000 - ((unsigned)TICK_SIZE) / 2 | ||
225 | && (xtime.tv_nsec / NSEC_PER_USEC) <= | ||
226 | 500000 + ((unsigned)TICK_SIZE) / 2) { | ||
227 | if (set_rtc_mmss(xtime.tv_sec) == 0) | ||
228 | last_rtc_update = xtime.tv_sec; | ||
229 | else | ||
230 | /* Do it again in 60s. */ | ||
231 | last_rtc_update = xtime.tv_sec - 600; | ||
232 | } | ||
233 | write_sequnlock(&xtime_lock); | ||
234 | return IRQ_HANDLED; | ||
235 | } | ||
236 | |||
237 | void __init time_init(void) | ||
238 | { | ||
239 | time_t secs_since_1970 = (365 * 37 + 9) * 24 * 60 * 60; /* 1 Jan 2007 */ | ||
240 | |||
241 | #ifdef CONFIG_RTC_DRV_BFIN | ||
242 | /* [#2663] hack to filter junk RTC values that would cause | ||
243 | * userspace to have to deal with time values greater than | ||
244 | * 2^31 seconds (which uClibc cannot cope with yet) | ||
245 | */ | ||
246 | if ((bfin_read_RTC_STAT() & 0xC0000000) == 0xC0000000) { | ||
247 | printk(KERN_NOTICE "bfin-rtc: invalid date; resetting\n"); | ||
248 | bfin_write_RTC_STAT(0); | ||
249 | } | ||
250 | #endif | ||
251 | |||
252 | /* Initialize xtime. From now on, xtime is updated with timer interrupts */ | ||
253 | xtime.tv_sec = secs_since_1970; | ||
254 | xtime.tv_nsec = 0; | ||
255 | |||
256 | wall_to_monotonic.tv_sec = -xtime.tv_sec; | ||
257 | |||
258 | time_sched_init(timer_interrupt); | ||
259 | } | ||
260 | |||
261 | #ifndef CONFIG_GENERIC_TIME | ||
262 | void do_gettimeofday(struct timeval *tv) | ||
263 | { | ||
264 | unsigned long flags; | ||
265 | unsigned long seq; | ||
266 | unsigned long usec, sec; | ||
267 | |||
268 | do { | ||
269 | seq = read_seqbegin_irqsave(&xtime_lock, flags); | ||
270 | usec = gettimeoffset(); | ||
271 | sec = xtime.tv_sec; | ||
272 | usec += (xtime.tv_nsec / NSEC_PER_USEC); | ||
273 | } | ||
274 | while (read_seqretry_irqrestore(&xtime_lock, seq, flags)); | ||
275 | |||
276 | while (usec >= USEC_PER_SEC) { | ||
277 | usec -= USEC_PER_SEC; | ||
278 | sec++; | ||
279 | } | ||
280 | |||
281 | tv->tv_sec = sec; | ||
282 | tv->tv_usec = usec; | ||
283 | } | ||
284 | EXPORT_SYMBOL(do_gettimeofday); | ||
285 | |||
286 | int do_settimeofday(struct timespec *tv) | ||
287 | { | ||
288 | time_t wtm_sec, sec = tv->tv_sec; | ||
289 | long wtm_nsec, nsec = tv->tv_nsec; | ||
290 | |||
291 | if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC) | ||
292 | return -EINVAL; | ||
293 | |||
294 | write_seqlock_irq(&xtime_lock); | ||
295 | /* | ||
296 | * This is revolting. We need to set the xtime.tv_usec | ||
297 | * correctly. However, the value in this location is | ||
298 | * is value at the last tick. | ||
299 | * Discover what correction gettimeofday | ||
300 | * would have done, and then undo it! | ||
301 | */ | ||
302 | nsec -= (gettimeoffset() * NSEC_PER_USEC); | ||
303 | |||
304 | wtm_sec = wall_to_monotonic.tv_sec + (xtime.tv_sec - sec); | ||
305 | wtm_nsec = wall_to_monotonic.tv_nsec + (xtime.tv_nsec - nsec); | ||
306 | |||
307 | set_normalized_timespec(&xtime, sec, nsec); | ||
308 | set_normalized_timespec(&wall_to_monotonic, wtm_sec, wtm_nsec); | ||
309 | |||
310 | ntp_clear(); | ||
311 | |||
312 | write_sequnlock_irq(&xtime_lock); | ||
313 | clock_was_set(); | ||
314 | |||
315 | return 0; | ||
316 | } | ||
317 | EXPORT_SYMBOL(do_settimeofday); | ||
318 | #endif /* !CONFIG_GENERIC_TIME */ | ||
319 | |||
320 | /* | ||
321 | * Scheduler clock - returns current time in nanosec units. | ||
322 | */ | ||
323 | unsigned long long sched_clock(void) | ||
324 | { | ||
325 | return (unsigned long long)jiffies *(NSEC_PER_SEC / HZ); | ||
326 | } | ||