diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /arch/mips/au1000/common/time.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'arch/mips/au1000/common/time.c')
-rw-r--r-- | arch/mips/au1000/common/time.c | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/arch/mips/au1000/common/time.c b/arch/mips/au1000/common/time.c new file mode 100644 index 000000000000..fe418f1620c3 --- /dev/null +++ b/arch/mips/au1000/common/time.c | |||
@@ -0,0 +1,469 @@ | |||
1 | /* | ||
2 | * | ||
3 | * Copyright (C) 2001 MontaVista Software, ppopov@mvista.com | ||
4 | * Copied and modified Carsten Langgaard's time.c | ||
5 | * | ||
6 | * Carsten Langgaard, carstenl@mips.com | ||
7 | * Copyright (C) 1999,2000 MIPS Technologies, Inc. All rights reserved. | ||
8 | * | ||
9 | * ######################################################################## | ||
10 | * | ||
11 | * This program is free software; you can distribute it and/or modify it | ||
12 | * under the terms of the GNU General Public License (Version 2) as | ||
13 | * published by the Free Software Foundation. | ||
14 | * | ||
15 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
17 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
18 | * for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU General Public License along | ||
21 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
22 | * 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. | ||
23 | * | ||
24 | * ######################################################################## | ||
25 | * | ||
26 | * Setting up the clock on the MIPS boards. | ||
27 | * | ||
28 | * Update. Always configure the kernel with CONFIG_NEW_TIME_C. This | ||
29 | * will use the user interface gettimeofday() functions from the | ||
30 | * arch/mips/kernel/time.c, and we provide the clock interrupt processing | ||
31 | * and the timer offset compute functions. If CONFIG_PM is selected, | ||
32 | * we also ensure the 32KHz timer is available. -- Dan | ||
33 | */ | ||
34 | |||
35 | #include <linux/types.h> | ||
36 | #include <linux/config.h> | ||
37 | #include <linux/init.h> | ||
38 | #include <linux/kernel_stat.h> | ||
39 | #include <linux/sched.h> | ||
40 | #include <linux/spinlock.h> | ||
41 | #include <linux/hardirq.h> | ||
42 | |||
43 | #include <asm/compiler.h> | ||
44 | #include <asm/mipsregs.h> | ||
45 | #include <asm/ptrace.h> | ||
46 | #include <asm/time.h> | ||
47 | #include <asm/div64.h> | ||
48 | #include <asm/mach-au1x00/au1000.h> | ||
49 | |||
50 | #include <linux/mc146818rtc.h> | ||
51 | #include <linux/timex.h> | ||
52 | |||
53 | extern void startup_match20_interrupt(void); | ||
54 | extern void do_softirq(void); | ||
55 | extern volatile unsigned long wall_jiffies; | ||
56 | unsigned long missed_heart_beats = 0; | ||
57 | |||
58 | static unsigned long r4k_offset; /* Amount to increment compare reg each time */ | ||
59 | static unsigned long r4k_cur; /* What counter should be at next timer irq */ | ||
60 | int no_au1xxx_32khz; | ||
61 | void (*au1k_wait_ptr)(void); | ||
62 | |||
63 | /* Cycle counter value at the previous timer interrupt.. */ | ||
64 | static unsigned int timerhi = 0, timerlo = 0; | ||
65 | |||
66 | #ifdef CONFIG_PM | ||
67 | #define MATCH20_INC 328 | ||
68 | extern void startup_match20_interrupt(void); | ||
69 | static unsigned long last_pc0, last_match20; | ||
70 | #endif | ||
71 | |||
72 | static DEFINE_SPINLOCK(time_lock); | ||
73 | |||
74 | static inline void ack_r4ktimer(unsigned long newval) | ||
75 | { | ||
76 | write_c0_compare(newval); | ||
77 | } | ||
78 | |||
79 | /* | ||
80 | * There are a lot of conceptually broken versions of the MIPS timer interrupt | ||
81 | * handler floating around. This one is rather different, but the algorithm | ||
82 | * is provably more robust. | ||
83 | */ | ||
84 | unsigned long wtimer; | ||
85 | void mips_timer_interrupt(struct pt_regs *regs) | ||
86 | { | ||
87 | int irq = 63; | ||
88 | unsigned long count; | ||
89 | |||
90 | irq_enter(); | ||
91 | kstat_this_cpu.irqs[irq]++; | ||
92 | |||
93 | if (r4k_offset == 0) | ||
94 | goto null; | ||
95 | |||
96 | do { | ||
97 | count = read_c0_count(); | ||
98 | timerhi += (count < timerlo); /* Wrap around */ | ||
99 | timerlo = count; | ||
100 | |||
101 | kstat_this_cpu.irqs[irq]++; | ||
102 | do_timer(regs); | ||
103 | #ifndef CONFIG_SMP | ||
104 | update_process_times(user_mode(regs)); | ||
105 | #endif | ||
106 | r4k_cur += r4k_offset; | ||
107 | ack_r4ktimer(r4k_cur); | ||
108 | |||
109 | } while (((unsigned long)read_c0_count() | ||
110 | - r4k_cur) < 0x7fffffff); | ||
111 | |||
112 | irq_exit(); | ||
113 | return; | ||
114 | |||
115 | null: | ||
116 | ack_r4ktimer(0); | ||
117 | } | ||
118 | |||
119 | #ifdef CONFIG_PM | ||
120 | void counter0_irq(int irq, void *dev_id, struct pt_regs *regs) | ||
121 | { | ||
122 | unsigned long pc0; | ||
123 | int time_elapsed; | ||
124 | static int jiffie_drift = 0; | ||
125 | |||
126 | kstat.irqs[0][irq]++; | ||
127 | if (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_M20) { | ||
128 | /* should never happen! */ | ||
129 | printk(KERN_WARNING "counter 0 w status eror\n"); | ||
130 | return; | ||
131 | } | ||
132 | |||
133 | pc0 = au_readl(SYS_TOYREAD); | ||
134 | if (pc0 < last_match20) { | ||
135 | /* counter overflowed */ | ||
136 | time_elapsed = (0xffffffff - last_match20) + pc0; | ||
137 | } | ||
138 | else { | ||
139 | time_elapsed = pc0 - last_match20; | ||
140 | } | ||
141 | |||
142 | while (time_elapsed > 0) { | ||
143 | do_timer(regs); | ||
144 | #ifndef CONFIG_SMP | ||
145 | update_process_times(user_mode(regs)); | ||
146 | #endif | ||
147 | time_elapsed -= MATCH20_INC; | ||
148 | last_match20 += MATCH20_INC; | ||
149 | jiffie_drift++; | ||
150 | } | ||
151 | |||
152 | last_pc0 = pc0; | ||
153 | au_writel(last_match20 + MATCH20_INC, SYS_TOYMATCH2); | ||
154 | au_sync(); | ||
155 | |||
156 | /* our counter ticks at 10.009765625 ms/tick, we we're running | ||
157 | * almost 10uS too slow per tick. | ||
158 | */ | ||
159 | |||
160 | if (jiffie_drift >= 999) { | ||
161 | jiffie_drift -= 999; | ||
162 | do_timer(regs); /* increment jiffies by one */ | ||
163 | #ifndef CONFIG_SMP | ||
164 | update_process_times(user_mode(regs)); | ||
165 | #endif | ||
166 | } | ||
167 | } | ||
168 | |||
169 | /* When we wakeup from sleep, we have to "catch up" on all of the | ||
170 | * timer ticks we have missed. | ||
171 | */ | ||
172 | void | ||
173 | wakeup_counter0_adjust(void) | ||
174 | { | ||
175 | unsigned long pc0; | ||
176 | int time_elapsed; | ||
177 | |||
178 | pc0 = au_readl(SYS_TOYREAD); | ||
179 | if (pc0 < last_match20) { | ||
180 | /* counter overflowed */ | ||
181 | time_elapsed = (0xffffffff - last_match20) + pc0; | ||
182 | } | ||
183 | else { | ||
184 | time_elapsed = pc0 - last_match20; | ||
185 | } | ||
186 | |||
187 | while (time_elapsed > 0) { | ||
188 | time_elapsed -= MATCH20_INC; | ||
189 | last_match20 += MATCH20_INC; | ||
190 | } | ||
191 | |||
192 | last_pc0 = pc0; | ||
193 | au_writel(last_match20 + MATCH20_INC, SYS_TOYMATCH2); | ||
194 | au_sync(); | ||
195 | |||
196 | } | ||
197 | |||
198 | /* This is just for debugging to set the timer for a sleep delay. | ||
199 | */ | ||
200 | void | ||
201 | wakeup_counter0_set(int ticks) | ||
202 | { | ||
203 | unsigned long pc0; | ||
204 | |||
205 | pc0 = au_readl(SYS_TOYREAD); | ||
206 | last_pc0 = pc0; | ||
207 | au_writel(last_match20 + (MATCH20_INC * ticks), SYS_TOYMATCH2); | ||
208 | au_sync(); | ||
209 | } | ||
210 | #endif | ||
211 | |||
212 | /* I haven't found anyone that doesn't use a 12 MHz source clock, | ||
213 | * but just in case..... | ||
214 | */ | ||
215 | #ifdef CONFIG_AU1000_SRC_CLK | ||
216 | #define AU1000_SRC_CLK CONFIG_AU1000_SRC_CLK | ||
217 | #else | ||
218 | #define AU1000_SRC_CLK 12000000 | ||
219 | #endif | ||
220 | |||
221 | /* | ||
222 | * We read the real processor speed from the PLL. This is important | ||
223 | * because it is more accurate than computing it from the 32KHz | ||
224 | * counter, if it exists. If we don't have an accurate processor | ||
225 | * speed, all of the peripherals that derive their clocks based on | ||
226 | * this advertised speed will introduce error and sometimes not work | ||
227 | * properly. This function is futher convoluted to still allow configurations | ||
228 | * to do that in case they have really, really old silicon with a | ||
229 | * write-only PLL register, that we need the 32KHz when power management | ||
230 | * "wait" is enabled, and we need to detect if the 32KHz isn't present | ||
231 | * but requested......got it? :-) -- Dan | ||
232 | */ | ||
233 | unsigned long cal_r4koff(void) | ||
234 | { | ||
235 | unsigned long count; | ||
236 | unsigned long cpu_speed; | ||
237 | unsigned long flags; | ||
238 | unsigned long counter; | ||
239 | |||
240 | spin_lock_irqsave(&time_lock, flags); | ||
241 | |||
242 | /* Power management cares if we don't have a 32KHz counter. | ||
243 | */ | ||
244 | no_au1xxx_32khz = 0; | ||
245 | counter = au_readl(SYS_COUNTER_CNTRL); | ||
246 | if (counter & SYS_CNTRL_E0) { | ||
247 | int trim_divide = 16; | ||
248 | |||
249 | au_writel(counter | SYS_CNTRL_EN1, SYS_COUNTER_CNTRL); | ||
250 | |||
251 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_T1S); | ||
252 | /* RTC now ticks at 32.768/16 kHz */ | ||
253 | au_writel(trim_divide-1, SYS_RTCTRIM); | ||
254 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_T1S); | ||
255 | |||
256 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_C1S); | ||
257 | au_writel (0, SYS_TOYWRITE); | ||
258 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_C1S); | ||
259 | |||
260 | #if defined(CONFIG_AU1000_USE32K) | ||
261 | { | ||
262 | unsigned long start, end; | ||
263 | |||
264 | start = au_readl(SYS_RTCREAD); | ||
265 | start += 2; | ||
266 | /* wait for the beginning of a new tick | ||
267 | */ | ||
268 | while (au_readl(SYS_RTCREAD) < start); | ||
269 | |||
270 | /* Start r4k counter. | ||
271 | */ | ||
272 | write_c0_count(0); | ||
273 | |||
274 | /* Wait 0.5 seconds. | ||
275 | */ | ||
276 | end = start + (32768 / trim_divide)/2; | ||
277 | |||
278 | while (end > au_readl(SYS_RTCREAD)); | ||
279 | |||
280 | count = read_c0_count(); | ||
281 | cpu_speed = count * 2; | ||
282 | } | ||
283 | #else | ||
284 | cpu_speed = (au_readl(SYS_CPUPLL) & 0x0000003f) * | ||
285 | AU1000_SRC_CLK; | ||
286 | count = cpu_speed / 2; | ||
287 | #endif | ||
288 | } | ||
289 | else { | ||
290 | /* The 32KHz oscillator isn't running, so assume there | ||
291 | * isn't one and grab the processor speed from the PLL. | ||
292 | * NOTE: some old silicon doesn't allow reading the PLL. | ||
293 | */ | ||
294 | cpu_speed = (au_readl(SYS_CPUPLL) & 0x0000003f) * AU1000_SRC_CLK; | ||
295 | count = cpu_speed / 2; | ||
296 | no_au1xxx_32khz = 1; | ||
297 | } | ||
298 | mips_hpt_frequency = count; | ||
299 | // Equation: Baudrate = CPU / (SD * 2 * CLKDIV * 16) | ||
300 | set_au1x00_uart_baud_base(cpu_speed / (2 * ((int)(au_readl(SYS_POWERCTRL)&0x03) + 2) * 16)); | ||
301 | spin_unlock_irqrestore(&time_lock, flags); | ||
302 | return (cpu_speed / HZ); | ||
303 | } | ||
304 | |||
305 | /* This is for machines which generate the exact clock. */ | ||
306 | #define USECS_PER_JIFFY (1000000/HZ) | ||
307 | #define USECS_PER_JIFFY_FRAC (0x100000000LL*1000000/HZ&0xffffffff) | ||
308 | |||
309 | static unsigned long | ||
310 | div64_32(unsigned long v1, unsigned long v2, unsigned long v3) | ||
311 | { | ||
312 | unsigned long r0; | ||
313 | do_div64_32(r0, v1, v2, v3); | ||
314 | return r0; | ||
315 | } | ||
316 | |||
317 | static unsigned long do_fast_cp0_gettimeoffset(void) | ||
318 | { | ||
319 | u32 count; | ||
320 | unsigned long res, tmp; | ||
321 | unsigned long r0; | ||
322 | |||
323 | /* Last jiffy when do_fast_gettimeoffset() was called. */ | ||
324 | static unsigned long last_jiffies=0; | ||
325 | unsigned long quotient; | ||
326 | |||
327 | /* | ||
328 | * Cached "1/(clocks per usec)*2^32" value. | ||
329 | * It has to be recalculated once each jiffy. | ||
330 | */ | ||
331 | static unsigned long cached_quotient=0; | ||
332 | |||
333 | tmp = jiffies; | ||
334 | |||
335 | quotient = cached_quotient; | ||
336 | |||
337 | if (tmp && last_jiffies != tmp) { | ||
338 | last_jiffies = tmp; | ||
339 | if (last_jiffies != 0) { | ||
340 | r0 = div64_32(timerhi, timerlo, tmp); | ||
341 | quotient = div64_32(USECS_PER_JIFFY, USECS_PER_JIFFY_FRAC, r0); | ||
342 | cached_quotient = quotient; | ||
343 | } | ||
344 | } | ||
345 | |||
346 | /* Get last timer tick in absolute kernel time */ | ||
347 | count = read_c0_count(); | ||
348 | |||
349 | /* .. relative to previous jiffy (32 bits is enough) */ | ||
350 | count -= timerlo; | ||
351 | |||
352 | __asm__("multu\t%1,%2\n\t" | ||
353 | "mfhi\t%0" | ||
354 | : "=r" (res) | ||
355 | : "r" (count), "r" (quotient) | ||
356 | : "hi", "lo", GCC_REG_ACCUM); | ||
357 | |||
358 | /* | ||
359 | * Due to possible jiffies inconsistencies, we need to check | ||
360 | * the result so that we'll get a timer that is monotonic. | ||
361 | */ | ||
362 | if (res >= USECS_PER_JIFFY) | ||
363 | res = USECS_PER_JIFFY-1; | ||
364 | |||
365 | return res; | ||
366 | } | ||
367 | |||
368 | #ifdef CONFIG_PM | ||
369 | static unsigned long do_fast_pm_gettimeoffset(void) | ||
370 | { | ||
371 | unsigned long pc0; | ||
372 | unsigned long offset; | ||
373 | |||
374 | pc0 = au_readl(SYS_TOYREAD); | ||
375 | au_sync(); | ||
376 | offset = pc0 - last_pc0; | ||
377 | if (offset > 2*MATCH20_INC) { | ||
378 | printk("huge offset %x, last_pc0 %x last_match20 %x pc0 %x\n", | ||
379 | (unsigned)offset, (unsigned)last_pc0, | ||
380 | (unsigned)last_match20, (unsigned)pc0); | ||
381 | } | ||
382 | offset = (unsigned long)((offset * 305) / 10); | ||
383 | return offset; | ||
384 | } | ||
385 | #endif | ||
386 | |||
387 | void au1xxx_timer_setup(struct irqaction *irq) | ||
388 | { | ||
389 | unsigned int est_freq; | ||
390 | extern unsigned long (*do_gettimeoffset)(void); | ||
391 | extern void au1k_wait(void); | ||
392 | |||
393 | printk("calculating r4koff... "); | ||
394 | r4k_offset = cal_r4koff(); | ||
395 | printk("%08lx(%d)\n", r4k_offset, (int) r4k_offset); | ||
396 | |||
397 | //est_freq = 2*r4k_offset*HZ; | ||
398 | est_freq = r4k_offset*HZ; | ||
399 | est_freq += 5000; /* round */ | ||
400 | est_freq -= est_freq%10000; | ||
401 | printk("CPU frequency %d.%02d MHz\n", est_freq/1000000, | ||
402 | (est_freq%1000000)*100/1000000); | ||
403 | set_au1x00_speed(est_freq); | ||
404 | set_au1x00_lcd_clock(); // program the LCD clock | ||
405 | |||
406 | r4k_cur = (read_c0_count() + r4k_offset); | ||
407 | write_c0_compare(r4k_cur); | ||
408 | |||
409 | #ifdef CONFIG_PM | ||
410 | /* | ||
411 | * setup counter 0, since it keeps ticking after a | ||
412 | * 'wait' instruction has been executed. The CP0 timer and | ||
413 | * counter 1 do NOT continue running after 'wait' | ||
414 | * | ||
415 | * It's too early to call request_irq() here, so we handle | ||
416 | * counter 0 interrupt as a special irq and it doesn't show | ||
417 | * up under /proc/interrupts. | ||
418 | * | ||
419 | * Check to ensure we really have a 32KHz oscillator before | ||
420 | * we do this. | ||
421 | */ | ||
422 | if (no_au1xxx_32khz) { | ||
423 | unsigned int c0_status; | ||
424 | |||
425 | printk("WARNING: no 32KHz clock found.\n"); | ||
426 | do_gettimeoffset = do_fast_cp0_gettimeoffset; | ||
427 | |||
428 | /* Ensure we get CPO_COUNTER interrupts. | ||
429 | */ | ||
430 | c0_status = read_c0_status(); | ||
431 | c0_status |= IE_IRQ5; | ||
432 | write_c0_status(c0_status); | ||
433 | } | ||
434 | else { | ||
435 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_C0S); | ||
436 | au_writel(0, SYS_TOYWRITE); | ||
437 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_C0S); | ||
438 | |||
439 | au_writel(au_readl(SYS_WAKEMSK) | (1<<8), SYS_WAKEMSK); | ||
440 | au_writel(~0, SYS_WAKESRC); | ||
441 | au_sync(); | ||
442 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_M20); | ||
443 | |||
444 | /* setup match20 to interrupt once every 10ms */ | ||
445 | last_pc0 = last_match20 = au_readl(SYS_TOYREAD); | ||
446 | au_writel(last_match20 + MATCH20_INC, SYS_TOYMATCH2); | ||
447 | au_sync(); | ||
448 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_M20); | ||
449 | startup_match20_interrupt(); | ||
450 | |||
451 | do_gettimeoffset = do_fast_pm_gettimeoffset; | ||
452 | |||
453 | /* We can use the real 'wait' instruction. | ||
454 | */ | ||
455 | au1k_wait_ptr = au1k_wait; | ||
456 | } | ||
457 | |||
458 | #else | ||
459 | /* We have to do this here instead of in timer_init because | ||
460 | * the generic code in arch/mips/kernel/time.c will write | ||
461 | * over our function pointer. | ||
462 | */ | ||
463 | do_gettimeoffset = do_fast_cp0_gettimeoffset; | ||
464 | #endif | ||
465 | } | ||
466 | |||
467 | void __init au1xxx_time_init(void) | ||
468 | { | ||
469 | } | ||