]>
Commit | Line | Data |
---|---|---|
0de1ffc8 WD |
1 | /* |
2 | * (C) Copyright 2000-2002 | |
3 | * Wolfgang Denk, DENX Software Engineering, [email protected]. | |
4 | * | |
5 | * See file CREDITS for list of people who contributed to this | |
6 | * project. | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License as | |
10 | * published by the Free Software Foundation; either version 2 of | |
11 | * the License, or (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, | |
21 | * MA 02111-1307 USA | |
22 | */ | |
23 | ||
24 | #include <common.h> | |
25 | #include <watchdog.h> | |
26 | #include <mpc8xx.h> | |
27 | #include <mpc8xx_irq.h> | |
28 | #include <asm/processor.h> | |
29 | #include <commproc.h> | |
30 | ||
7c7a23bd | 31 | /************************************************************************/ |
0de1ffc8 | 32 | |
7c7a23bd | 33 | unsigned decrementer_count; /* count value for 1e6/HZ microseconds */ |
0de1ffc8 | 34 | |
7c7a23bd | 35 | /************************************************************************/ |
0de1ffc8 WD |
36 | |
37 | /* | |
38 | * CPM interrupt vector functions. | |
39 | */ | |
7c7a23bd WD |
40 | struct interrupt_action { |
41 | interrupt_handler_t *handler; | |
42 | void *arg; | |
0de1ffc8 WD |
43 | }; |
44 | ||
7c7a23bd WD |
45 | static struct interrupt_action cpm_vecs[CPMVEC_NR]; |
46 | static struct interrupt_action irq_vecs[NR_IRQS]; | |
0de1ffc8 WD |
47 | |
48 | static void cpm_interrupt_init (void); | |
7c7a23bd | 49 | static void cpm_interrupt (void *regs); |
0de1ffc8 | 50 | |
7c7a23bd | 51 | /************************************************************************/ |
0de1ffc8 | 52 | |
7c7a23bd | 53 | static __inline__ unsigned long get_msr (void) |
0de1ffc8 | 54 | { |
7c7a23bd | 55 | unsigned long msr; |
0de1ffc8 | 56 | |
7c7a23bd WD |
57 | asm volatile ("mfmsr %0":"=r" (msr):); |
58 | ||
59 | return msr; | |
0de1ffc8 WD |
60 | } |
61 | ||
7c7a23bd | 62 | static __inline__ void set_msr (unsigned long msr) |
0de1ffc8 | 63 | { |
7c7a23bd | 64 | asm volatile ("mtmsr %0"::"r" (msr)); |
0de1ffc8 WD |
65 | } |
66 | ||
7c7a23bd | 67 | static __inline__ unsigned long get_dec (void) |
0de1ffc8 | 68 | { |
7c7a23bd WD |
69 | unsigned long val; |
70 | ||
71 | asm volatile ("mfdec %0":"=r" (val):); | |
0de1ffc8 | 72 | |
7c7a23bd | 73 | return val; |
0de1ffc8 WD |
74 | } |
75 | ||
76 | ||
7c7a23bd | 77 | static __inline__ void set_dec (unsigned long val) |
0de1ffc8 | 78 | { |
7c7a23bd | 79 | asm volatile ("mtdec %0"::"r" (val)); |
0de1ffc8 WD |
80 | } |
81 | ||
82 | ||
83 | void enable_interrupts (void) | |
84 | { | |
7c7a23bd | 85 | set_msr (get_msr () | MSR_EE); |
0de1ffc8 WD |
86 | } |
87 | ||
88 | /* returns flag if MSR_EE was set before */ | |
89 | int disable_interrupts (void) | |
90 | { | |
7c7a23bd WD |
91 | ulong msr = get_msr (); |
92 | ||
0de1ffc8 WD |
93 | set_msr (msr & ~MSR_EE); |
94 | return ((msr & MSR_EE) != 0); | |
95 | } | |
96 | ||
7c7a23bd | 97 | /************************************************************************/ |
0de1ffc8 | 98 | |
7c7a23bd | 99 | int interrupt_init (void) |
0de1ffc8 | 100 | { |
7c7a23bd | 101 | volatile immap_t *immr = (immap_t *) CFG_IMMR; |
0de1ffc8 | 102 | |
7c7a23bd | 103 | decrementer_count = get_tbclk () / CFG_HZ; |
0de1ffc8 | 104 | |
7c7a23bd WD |
105 | /* disable all interrupts */ |
106 | immr->im_siu_conf.sc_simask = 0; | |
0de1ffc8 | 107 | |
7c7a23bd WD |
108 | /* Configure CPM interrupts */ |
109 | cpm_interrupt_init (); | |
0de1ffc8 WD |
110 | |
111 | set_dec (decrementer_count); | |
112 | ||
7c7a23bd | 113 | set_msr (get_msr () | MSR_EE); |
0de1ffc8 WD |
114 | |
115 | return (0); | |
116 | } | |
117 | ||
7c7a23bd | 118 | /************************************************************************/ |
0de1ffc8 WD |
119 | |
120 | /* | |
121 | * Handle external interrupts | |
122 | */ | |
7c7a23bd | 123 | void external_interrupt (struct pt_regs *regs) |
0de1ffc8 | 124 | { |
7c7a23bd WD |
125 | volatile immap_t *immr = (immap_t *) CFG_IMMR; |
126 | int irq; | |
127 | ulong simask, newmask; | |
128 | ulong vec, v_bit; | |
0de1ffc8 WD |
129 | |
130 | /* | |
131 | * read the SIVEC register and shift the bits down | |
132 | * to get the irq number | |
133 | */ | |
134 | vec = immr->im_siu_conf.sc_sivec; | |
135 | irq = vec >> 26; | |
136 | v_bit = 0x80000000UL >> irq; | |
137 | ||
138 | /* | |
139 | * Read Interrupt Mask Register and Mask Interrupts | |
140 | */ | |
141 | simask = immr->im_siu_conf.sc_simask; | |
142 | newmask = simask & (~(0xFFFF0000 >> irq)); | |
143 | immr->im_siu_conf.sc_simask = newmask; | |
144 | ||
7c7a23bd | 145 | if (!(irq & 0x1)) { /* External Interrupt ? */ |
0de1ffc8 | 146 | ulong siel; |
7c7a23bd | 147 | |
0de1ffc8 WD |
148 | /* |
149 | * Read Interrupt Edge/Level Register | |
150 | */ | |
151 | siel = immr->im_siu_conf.sc_siel; | |
152 | ||
7c7a23bd | 153 | if (siel & v_bit) { /* edge triggered interrupt ? */ |
0de1ffc8 WD |
154 | /* |
155 | * Rewrite SIPEND Register to clear interrupt | |
156 | */ | |
157 | immr->im_siu_conf.sc_sipend = v_bit; | |
158 | } | |
159 | } | |
160 | ||
7c7a23bd WD |
161 | if (irq_vecs[irq].handler != NULL) { |
162 | irq_vecs[irq].handler (irq_vecs[irq].arg); | |
163 | } else { | |
0de1ffc8 | 164 | printf ("\nBogus External Interrupt IRQ %d Vector %ld\n", |
7c7a23bd | 165 | irq, vec); |
0de1ffc8 WD |
166 | /* turn off the bogus interrupt to avoid it from now */ |
167 | simask &= ~v_bit; | |
0de1ffc8 | 168 | } |
0de1ffc8 WD |
169 | /* |
170 | * Re-Enable old Interrupt Mask | |
171 | */ | |
172 | immr->im_siu_conf.sc_simask = simask; | |
173 | } | |
174 | ||
7c7a23bd | 175 | /************************************************************************/ |
0de1ffc8 WD |
176 | |
177 | /* | |
178 | * CPM interrupt handler | |
179 | */ | |
7c7a23bd | 180 | static void cpm_interrupt (void *regs) |
0de1ffc8 | 181 | { |
7c7a23bd WD |
182 | volatile immap_t *immr = (immap_t *) CFG_IMMR; |
183 | uint vec; | |
0de1ffc8 WD |
184 | |
185 | /* | |
186 | * Get the vector by setting the ACK bit | |
187 | * and then reading the register. | |
188 | */ | |
189 | immr->im_cpic.cpic_civr = 1; | |
190 | vec = immr->im_cpic.cpic_civr; | |
191 | vec >>= 11; | |
192 | ||
193 | if (cpm_vecs[vec].handler != NULL) { | |
7c7a23bd | 194 | (*cpm_vecs[vec].handler) (cpm_vecs[vec].arg); |
0de1ffc8 WD |
195 | } else { |
196 | immr->im_cpic.cpic_cimr &= ~(1 << vec); | |
197 | printf ("Masking bogus CPM interrupt vector 0x%x\n", vec); | |
198 | } | |
199 | /* | |
7c7a23bd WD |
200 | * After servicing the interrupt, |
201 | * we have to remove the status indicator. | |
0de1ffc8 WD |
202 | */ |
203 | immr->im_cpic.cpic_cisr |= (1 << vec); | |
204 | } | |
205 | ||
206 | /* | |
207 | * The CPM can generate the error interrupt when there is a race | |
208 | * condition between generating and masking interrupts. All we have | |
209 | * to do is ACK it and return. This is a no-op function so we don't | |
210 | * need any special tests in the interrupt handler. | |
211 | */ | |
7c7a23bd | 212 | static void cpm_error_interrupt (void *dummy) |
0de1ffc8 WD |
213 | { |
214 | } | |
215 | ||
7c7a23bd | 216 | /************************************************************************/ |
0de1ffc8 | 217 | /* |
7c7a23bd | 218 | * Install and free an interrupt handler |
0de1ffc8 | 219 | */ |
7c7a23bd WD |
220 | void irq_install_handler (int vec, interrupt_handler_t * handler, |
221 | void *arg) | |
0de1ffc8 | 222 | { |
7c7a23bd WD |
223 | volatile immap_t *immr = (immap_t *) CFG_IMMR; |
224 | ||
225 | if ((vec & CPMVEC_OFFSET) != 0) { | |
226 | /* CPM interrupt */ | |
227 | vec &= 0xffff; | |
228 | if (cpm_vecs[vec].handler != NULL) { | |
229 | printf ("CPM interrupt 0x%x replacing 0x%x\n", | |
230 | (uint) handler, | |
231 | (uint) cpm_vecs[vec].handler); | |
232 | } | |
233 | cpm_vecs[vec].handler = handler; | |
234 | cpm_vecs[vec].arg = arg; | |
235 | immr->im_cpic.cpic_cimr |= (1 << vec); | |
236 | #if 0 | |
237 | printf ("Install CPM interrupt for vector %d ==> %p\n", | |
238 | vec, handler); | |
239 | #endif | |
240 | } else { | |
241 | /* SIU interrupt */ | |
242 | if (irq_vecs[vec].handler != NULL) { | |
243 | printf ("SIU interrupt %d 0x%x replacing 0x%x\n", | |
244 | vec, | |
245 | (uint) handler, | |
246 | (uint) cpm_vecs[vec].handler); | |
247 | } | |
248 | irq_vecs[vec].handler = handler; | |
249 | irq_vecs[vec].arg = arg; | |
250 | immr->im_siu_conf.sc_simask |= 1 << (31 - vec); | |
0de1ffc8 | 251 | #if 0 |
7c7a23bd WD |
252 | printf ("Install SIU interrupt for vector %d ==> %p\n", |
253 | vec, handler); | |
0de1ffc8 | 254 | #endif |
7c7a23bd | 255 | } |
0de1ffc8 WD |
256 | } |
257 | ||
7c7a23bd | 258 | void irq_free_handler (int vec) |
0de1ffc8 | 259 | { |
7c7a23bd WD |
260 | volatile immap_t *immr = (immap_t *) CFG_IMMR; |
261 | ||
262 | if ((vec & CPMVEC_OFFSET) != 0) { | |
263 | /* CPM interrupt */ | |
264 | vec &= 0xffff; | |
265 | #if 0 | |
266 | printf ("Free CPM interrupt for vector %d ==> %p\n", | |
267 | vec, cpm_vecs[vec].handler); | |
268 | #endif | |
269 | immr->im_cpic.cpic_cimr &= ~(1 << vec); | |
270 | cpm_vecs[vec].handler = NULL; | |
271 | cpm_vecs[vec].arg = NULL; | |
272 | } else { | |
273 | /* SIU interrupt */ | |
0de1ffc8 | 274 | #if 0 |
7c7a23bd WD |
275 | printf ("Free CPM interrupt for vector %d ==> %p\n", |
276 | vec, cpm_vecs[vec].handler); | |
0de1ffc8 | 277 | #endif |
7c7a23bd WD |
278 | immr->im_siu_conf.sc_simask &= ~(1 << (31 - vec)); |
279 | irq_vecs[vec].handler = NULL; | |
280 | irq_vecs[vec].arg = NULL; | |
281 | } | |
0de1ffc8 WD |
282 | } |
283 | ||
7c7a23bd | 284 | /************************************************************************/ |
0de1ffc8 | 285 | |
7c7a23bd | 286 | static void cpm_interrupt_init (void) |
0de1ffc8 | 287 | { |
7c7a23bd | 288 | volatile immap_t *immr = (immap_t *) CFG_IMMR; |
0de1ffc8 WD |
289 | |
290 | /* | |
291 | * Initialize the CPM interrupt controller. | |
292 | */ | |
293 | ||
294 | immr->im_cpic.cpic_cicr = | |
7c7a23bd WD |
295 | (CICR_SCD_SCC4 | |
296 | CICR_SCC_SCC3 | | |
297 | CICR_SCB_SCC2 | | |
298 | CICR_SCA_SCC1) | ((CPM_INTERRUPT / 2) << 13) | CICR_HP_MASK; | |
0de1ffc8 WD |
299 | |
300 | immr->im_cpic.cpic_cimr = 0; | |
301 | ||
302 | /* | |
303 | * Install the error handler. | |
304 | */ | |
7c7a23bd | 305 | irq_install_handler (CPMVEC_ERROR, cpm_error_interrupt, NULL); |
0de1ffc8 WD |
306 | |
307 | immr->im_cpic.cpic_cicr |= CICR_IEN; | |
7c7a23bd WD |
308 | |
309 | /* | |
310 | * Install the cpm interrupt handler | |
311 | */ | |
312 | irq_install_handler (CPM_INTERRUPT, cpm_interrupt, NULL); | |
0de1ffc8 WD |
313 | } |
314 | ||
7c7a23bd | 315 | /************************************************************************/ |
0de1ffc8 WD |
316 | |
317 | volatile ulong timestamp = 0; | |
318 | ||
319 | /* | |
320 | * timer_interrupt - gets called when the decrementer overflows, | |
321 | * with interrupts disabled. | |
322 | * Trivial implementation - no need to be really accurate. | |
323 | */ | |
7c7a23bd | 324 | void timer_interrupt (struct pt_regs *regs) |
0de1ffc8 | 325 | { |
7c7a23bd WD |
326 | volatile immap_t *immr = (immap_t *) CFG_IMMR; |
327 | ||
0de1ffc8 | 328 | #ifdef CONFIG_STATUS_LED |
7c7a23bd | 329 | extern void status_led_tick (ulong); |
0de1ffc8 WD |
330 | #endif |
331 | #if 0 | |
332 | printf ("*** Timer Interrupt *** "); | |
333 | #endif | |
334 | /* Reset Timer Expired and Timers Interrupt Status */ | |
335 | immr->im_clkrstk.cark_plprcrk = KAPWR_KEY; | |
7c7a23bd | 336 | __asm__ ("nop"); |
2535d602 WD |
337 | #ifdef CONFIG_MPC866_et_al |
338 | immr->im_clkrst.car_plprcr |= PLPRCR_TEXPS; | |
339 | #else | |
0de1ffc8 | 340 | immr->im_clkrst.car_plprcr |= PLPRCR_TEXPS | PLPRCR_TMIST; |
2535d602 | 341 | #endif |
0de1ffc8 WD |
342 | /* Restore Decrementer Count */ |
343 | set_dec (decrementer_count); | |
344 | ||
345 | timestamp++; | |
346 | ||
347 | #ifdef CONFIG_STATUS_LED | |
348 | status_led_tick (timestamp); | |
7c7a23bd | 349 | #endif /* CONFIG_STATUS_LED */ |
0de1ffc8 WD |
350 | |
351 | #if defined(CONFIG_WATCHDOG) || defined(CFG_CMA_LCD_HEARTBEAT) | |
352 | ||
0de1ffc8 WD |
353 | /* |
354 | * The shortest watchdog period of all boards (except LWMON) | |
355 | * is approx. 1 sec, thus re-trigger watchdog at least | |
356 | * every 500 ms = CFG_HZ / 2 | |
357 | */ | |
358 | #ifndef CONFIG_LWMON | |
359 | if ((timestamp % (CFG_HZ / 2)) == 0) { | |
360 | #else | |
361 | if ((timestamp % (CFG_HZ / 20)) == 0) { | |
362 | #endif | |
363 | ||
364 | #if defined(CFG_CMA_LCD_HEARTBEAT) | |
7c7a23bd WD |
365 | extern void lcd_heartbeat (void); |
366 | ||
367 | lcd_heartbeat (); | |
0de1ffc8 WD |
368 | #endif /* CFG_CMA_LCD_HEARTBEAT */ |
369 | ||
370 | #if defined(CONFIG_WATCHDOG) | |
7c7a23bd | 371 | reset_8xx_watchdog (immr); |
0de1ffc8 WD |
372 | #endif /* CONFIG_WATCHDOG */ |
373 | ||
374 | } | |
0de1ffc8 WD |
375 | #endif /* CONFIG_WATCHDOG || CFG_CMA_LCD_HEARTBEAT */ |
376 | } | |
377 | ||
7c7a23bd | 378 | /************************************************************************/ |
0de1ffc8 WD |
379 | |
380 | void reset_timer (void) | |
381 | { | |
382 | timestamp = 0; | |
383 | } | |
384 | ||
385 | ulong get_timer (ulong base) | |
386 | { | |
387 | return (timestamp - base); | |
388 | } | |
389 | ||
390 | void set_timer (ulong t) | |
391 | { | |
392 | timestamp = t; | |
393 | } | |
394 | ||
7c7a23bd | 395 | /************************************************************************/ |