diff options
Diffstat (limited to 'arch/mips/vr41xx/common/giu.c')
-rw-r--r-- | arch/mips/vr41xx/common/giu.c | 455 |
1 files changed, 455 insertions, 0 deletions
diff --git a/arch/mips/vr41xx/common/giu.c b/arch/mips/vr41xx/common/giu.c new file mode 100644 index 000000000000..9c6b21a79e8f --- /dev/null +++ b/arch/mips/vr41xx/common/giu.c | |||
@@ -0,0 +1,455 @@ | |||
1 | /* | ||
2 | * giu.c, General-purpose I/O Unit Interrupt routines for NEC VR4100 series. | ||
3 | * | ||
4 | * Copyright (C) 2002 MontaVista Software Inc. | ||
5 | * Author: Yoichi Yuasa <yyuasa@mvista.com or source@mvista.com> | ||
6 | * Copyright (C) 2003-2004 Yoichi Yuasa <yuasa@hh.iij4u.or.jp> | ||
7 | * Copyright (C) 2005 Ralf Baechle (ralf@linux-mips.org) | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation; either version 2 of the License, or | ||
12 | * (at your option) any later version. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with this program; if not, write to the Free Software | ||
21 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
22 | */ | ||
23 | /* | ||
24 | * Changes: | ||
25 | * MontaVista Software Inc. <yyuasa@mvista.com> or <source@mvista.com> | ||
26 | * - New creation, NEC VR4111, VR4121, VR4122 and VR4131 are supported. | ||
27 | * | ||
28 | * Yoichi Yuasa <yuasa@hh.iij4u.or.jp> | ||
29 | * - Added support for NEC VR4133. | ||
30 | * - Removed board_irq_init. | ||
31 | */ | ||
32 | #include <linux/errno.h> | ||
33 | #include <linux/init.h> | ||
34 | #include <linux/irq.h> | ||
35 | #include <linux/kernel.h> | ||
36 | #include <linux/module.h> | ||
37 | #include <linux/smp.h> | ||
38 | #include <linux/types.h> | ||
39 | |||
40 | #include <asm/cpu.h> | ||
41 | #include <asm/io.h> | ||
42 | #include <asm/vr41xx/vr41xx.h> | ||
43 | |||
44 | #define GIUIOSELL_TYPE1 KSEG1ADDR(0x0b000100) | ||
45 | #define GIUIOSELL_TYPE2 KSEG1ADDR(0x0f000140) | ||
46 | |||
47 | #define GIUIOSELL 0x00 | ||
48 | #define GIUIOSELH 0x02 | ||
49 | #define GIUINTSTATL 0x08 | ||
50 | #define GIUINTSTATH 0x0a | ||
51 | #define GIUINTENL 0x0c | ||
52 | #define GIUINTENH 0x0e | ||
53 | #define GIUINTTYPL 0x10 | ||
54 | #define GIUINTTYPH 0x12 | ||
55 | #define GIUINTALSELL 0x14 | ||
56 | #define GIUINTALSELH 0x16 | ||
57 | #define GIUINTHTSELL 0x18 | ||
58 | #define GIUINTHTSELH 0x1a | ||
59 | #define GIUFEDGEINHL 0x20 | ||
60 | #define GIUFEDGEINHH 0x22 | ||
61 | #define GIUREDGEINHL 0x24 | ||
62 | #define GIUREDGEINHH 0x26 | ||
63 | |||
64 | static uint32_t giu_base; | ||
65 | |||
66 | static struct irqaction giu_cascade = { | ||
67 | .handler = no_action, | ||
68 | .mask = CPU_MASK_NONE, | ||
69 | .name = "cascade", | ||
70 | }; | ||
71 | |||
72 | #define read_giuint(offset) readw(giu_base + (offset)) | ||
73 | #define write_giuint(val, offset) writew((val), giu_base + (offset)) | ||
74 | |||
75 | #define GIUINT_HIGH_OFFSET 16 | ||
76 | |||
77 | static inline uint16_t set_giuint(uint8_t offset, uint16_t set) | ||
78 | { | ||
79 | uint16_t res; | ||
80 | |||
81 | res = read_giuint(offset); | ||
82 | res |= set; | ||
83 | write_giuint(res, offset); | ||
84 | |||
85 | return res; | ||
86 | } | ||
87 | |||
88 | static inline uint16_t clear_giuint(uint8_t offset, uint16_t clear) | ||
89 | { | ||
90 | uint16_t res; | ||
91 | |||
92 | res = read_giuint(offset); | ||
93 | res &= ~clear; | ||
94 | write_giuint(res, offset); | ||
95 | |||
96 | return res; | ||
97 | } | ||
98 | |||
99 | static unsigned int startup_giuint_low_irq(unsigned int irq) | ||
100 | { | ||
101 | unsigned int pin; | ||
102 | |||
103 | pin = GIU_IRQ_TO_PIN(irq); | ||
104 | write_giuint((uint16_t)1 << pin, GIUINTSTATL); | ||
105 | set_giuint(GIUINTENL, (uint16_t)1 << pin); | ||
106 | |||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | static void shutdown_giuint_low_irq(unsigned int irq) | ||
111 | { | ||
112 | clear_giuint(GIUINTENL, (uint16_t)1 << GIU_IRQ_TO_PIN(irq)); | ||
113 | } | ||
114 | |||
115 | static void enable_giuint_low_irq(unsigned int irq) | ||
116 | { | ||
117 | set_giuint(GIUINTENL, (uint16_t)1 << GIU_IRQ_TO_PIN(irq)); | ||
118 | } | ||
119 | |||
120 | #define disable_giuint_low_irq shutdown_giuint_low_irq | ||
121 | |||
122 | static void ack_giuint_low_irq(unsigned int irq) | ||
123 | { | ||
124 | unsigned int pin; | ||
125 | |||
126 | pin = GIU_IRQ_TO_PIN(irq); | ||
127 | clear_giuint(GIUINTENL, (uint16_t)1 << pin); | ||
128 | write_giuint((uint16_t)1 << pin, GIUINTSTATL); | ||
129 | } | ||
130 | |||
131 | static void end_giuint_low_irq(unsigned int irq) | ||
132 | { | ||
133 | if (!(irq_desc[irq].status & (IRQ_DISABLED | IRQ_INPROGRESS))) | ||
134 | set_giuint(GIUINTENL, (uint16_t)1 << GIU_IRQ_TO_PIN(irq)); | ||
135 | } | ||
136 | |||
137 | static struct hw_interrupt_type giuint_low_irq_type = { | ||
138 | .typename = "GIUINTL", | ||
139 | .startup = startup_giuint_low_irq, | ||
140 | .shutdown = shutdown_giuint_low_irq, | ||
141 | .enable = enable_giuint_low_irq, | ||
142 | .disable = disable_giuint_low_irq, | ||
143 | .ack = ack_giuint_low_irq, | ||
144 | .end = end_giuint_low_irq, | ||
145 | }; | ||
146 | |||
147 | static unsigned int startup_giuint_high_irq(unsigned int irq) | ||
148 | { | ||
149 | unsigned int pin; | ||
150 | |||
151 | pin = GIU_IRQ_TO_PIN(irq - GIUINT_HIGH_OFFSET); | ||
152 | write_giuint((uint16_t)1 << pin, GIUINTSTATH); | ||
153 | set_giuint(GIUINTENH, (uint16_t)1 << pin); | ||
154 | |||
155 | return 0; | ||
156 | } | ||
157 | |||
158 | static void shutdown_giuint_high_irq(unsigned int irq) | ||
159 | { | ||
160 | clear_giuint(GIUINTENH, (uint16_t)1 << GIU_IRQ_TO_PIN(irq - GIUINT_HIGH_OFFSET)); | ||
161 | } | ||
162 | |||
163 | static void enable_giuint_high_irq(unsigned int irq) | ||
164 | { | ||
165 | set_giuint(GIUINTENH, (uint16_t)1 << GIU_IRQ_TO_PIN(irq - GIUINT_HIGH_OFFSET)); | ||
166 | } | ||
167 | |||
168 | #define disable_giuint_high_irq shutdown_giuint_high_irq | ||
169 | |||
170 | static void ack_giuint_high_irq(unsigned int irq) | ||
171 | { | ||
172 | unsigned int pin; | ||
173 | |||
174 | pin = GIU_IRQ_TO_PIN(irq - GIUINT_HIGH_OFFSET); | ||
175 | clear_giuint(GIUINTENH, (uint16_t)1 << pin); | ||
176 | write_giuint((uint16_t)1 << pin, GIUINTSTATH); | ||
177 | } | ||
178 | |||
179 | static void end_giuint_high_irq(unsigned int irq) | ||
180 | { | ||
181 | if (!(irq_desc[irq].status & (IRQ_DISABLED | IRQ_INPROGRESS))) | ||
182 | set_giuint(GIUINTENH, (uint16_t)1 << GIU_IRQ_TO_PIN(irq - GIUINT_HIGH_OFFSET)); | ||
183 | } | ||
184 | |||
185 | static struct hw_interrupt_type giuint_high_irq_type = { | ||
186 | .typename = "GIUINTH", | ||
187 | .startup = startup_giuint_high_irq, | ||
188 | .shutdown = shutdown_giuint_high_irq, | ||
189 | .enable = enable_giuint_high_irq, | ||
190 | .disable = disable_giuint_high_irq, | ||
191 | .ack = ack_giuint_high_irq, | ||
192 | .end = end_giuint_high_irq, | ||
193 | }; | ||
194 | |||
195 | void __init init_vr41xx_giuint_irq(void) | ||
196 | { | ||
197 | int i; | ||
198 | |||
199 | for (i = GIU_IRQ_BASE; i <= GIU_IRQ_LAST; i++) { | ||
200 | if (i < (GIU_IRQ_BASE + GIUINT_HIGH_OFFSET)) | ||
201 | irq_desc[i].handler = &giuint_low_irq_type; | ||
202 | else | ||
203 | irq_desc[i].handler = &giuint_high_irq_type; | ||
204 | } | ||
205 | |||
206 | setup_irq(GIUINT_CASCADE_IRQ, &giu_cascade); | ||
207 | } | ||
208 | |||
209 | void vr41xx_set_irq_trigger(int pin, int trigger, int hold) | ||
210 | { | ||
211 | uint16_t mask; | ||
212 | |||
213 | if (pin < GIUINT_HIGH_OFFSET) { | ||
214 | mask = (uint16_t)1 << pin; | ||
215 | if (trigger != TRIGGER_LEVEL) { | ||
216 | set_giuint(GIUINTTYPL, mask); | ||
217 | if (hold == SIGNAL_HOLD) | ||
218 | set_giuint(GIUINTHTSELL, mask); | ||
219 | else | ||
220 | clear_giuint(GIUINTHTSELL, mask); | ||
221 | if (current_cpu_data.cputype == CPU_VR4133) { | ||
222 | switch (trigger) { | ||
223 | case TRIGGER_EDGE_FALLING: | ||
224 | set_giuint(GIUFEDGEINHL, mask); | ||
225 | clear_giuint(GIUREDGEINHL, mask); | ||
226 | break; | ||
227 | case TRIGGER_EDGE_RISING: | ||
228 | clear_giuint(GIUFEDGEINHL, mask); | ||
229 | set_giuint(GIUREDGEINHL, mask); | ||
230 | break; | ||
231 | default: | ||
232 | set_giuint(GIUFEDGEINHL, mask); | ||
233 | set_giuint(GIUREDGEINHL, mask); | ||
234 | break; | ||
235 | } | ||
236 | } | ||
237 | } else { | ||
238 | clear_giuint(GIUINTTYPL, mask); | ||
239 | clear_giuint(GIUINTHTSELL, mask); | ||
240 | } | ||
241 | write_giuint(mask, GIUINTSTATL); | ||
242 | } else { | ||
243 | mask = (uint16_t)1 << (pin - GIUINT_HIGH_OFFSET); | ||
244 | if (trigger != TRIGGER_LEVEL) { | ||
245 | set_giuint(GIUINTTYPH, mask); | ||
246 | if (hold == SIGNAL_HOLD) | ||
247 | set_giuint(GIUINTHTSELH, mask); | ||
248 | else | ||
249 | clear_giuint(GIUINTHTSELH, mask); | ||
250 | if (current_cpu_data.cputype == CPU_VR4133) { | ||
251 | switch (trigger) { | ||
252 | case TRIGGER_EDGE_FALLING: | ||
253 | set_giuint(GIUFEDGEINHH, mask); | ||
254 | clear_giuint(GIUREDGEINHH, mask); | ||
255 | break; | ||
256 | case TRIGGER_EDGE_RISING: | ||
257 | clear_giuint(GIUFEDGEINHH, mask); | ||
258 | set_giuint(GIUREDGEINHH, mask); | ||
259 | break; | ||
260 | default: | ||
261 | set_giuint(GIUFEDGEINHH, mask); | ||
262 | set_giuint(GIUREDGEINHH, mask); | ||
263 | break; | ||
264 | } | ||
265 | } | ||
266 | } else { | ||
267 | clear_giuint(GIUINTTYPH, mask); | ||
268 | clear_giuint(GIUINTHTSELH, mask); | ||
269 | } | ||
270 | write_giuint(mask, GIUINTSTATH); | ||
271 | } | ||
272 | } | ||
273 | |||
274 | EXPORT_SYMBOL(vr41xx_set_irq_trigger); | ||
275 | |||
276 | void vr41xx_set_irq_level(int pin, int level) | ||
277 | { | ||
278 | uint16_t mask; | ||
279 | |||
280 | if (pin < GIUINT_HIGH_OFFSET) { | ||
281 | mask = (uint16_t)1 << pin; | ||
282 | if (level == LEVEL_HIGH) | ||
283 | set_giuint(GIUINTALSELL, mask); | ||
284 | else | ||
285 | clear_giuint(GIUINTALSELL, mask); | ||
286 | write_giuint(mask, GIUINTSTATL); | ||
287 | } else { | ||
288 | mask = (uint16_t)1 << (pin - GIUINT_HIGH_OFFSET); | ||
289 | if (level == LEVEL_HIGH) | ||
290 | set_giuint(GIUINTALSELH, mask); | ||
291 | else | ||
292 | clear_giuint(GIUINTALSELH, mask); | ||
293 | write_giuint(mask, GIUINTSTATH); | ||
294 | } | ||
295 | } | ||
296 | |||
297 | EXPORT_SYMBOL(vr41xx_set_irq_level); | ||
298 | |||
299 | #define GIUINT_NR_IRQS 32 | ||
300 | |||
301 | enum { | ||
302 | GIUINT_NO_CASCADE, | ||
303 | GIUINT_CASCADE | ||
304 | }; | ||
305 | |||
306 | struct vr41xx_giuint_cascade { | ||
307 | unsigned int flag; | ||
308 | int (*get_irq_number)(int irq); | ||
309 | }; | ||
310 | |||
311 | static struct vr41xx_giuint_cascade giuint_cascade[GIUINT_NR_IRQS]; | ||
312 | |||
313 | static int no_irq_number(int irq) | ||
314 | { | ||
315 | return -EINVAL; | ||
316 | } | ||
317 | |||
318 | int vr41xx_cascade_irq(unsigned int irq, int (*get_irq_number)(int irq)) | ||
319 | { | ||
320 | unsigned int pin; | ||
321 | int retval; | ||
322 | |||
323 | if (irq < GIU_IRQ(0) || irq > GIU_IRQ(31)) | ||
324 | return -EINVAL; | ||
325 | |||
326 | if(!get_irq_number) | ||
327 | return -EINVAL; | ||
328 | |||
329 | pin = GIU_IRQ_TO_PIN(irq); | ||
330 | giuint_cascade[pin].flag = GIUINT_CASCADE; | ||
331 | giuint_cascade[pin].get_irq_number = get_irq_number; | ||
332 | |||
333 | retval = setup_irq(irq, &giu_cascade); | ||
334 | if (retval != 0) { | ||
335 | giuint_cascade[pin].flag = GIUINT_NO_CASCADE; | ||
336 | giuint_cascade[pin].get_irq_number = no_irq_number; | ||
337 | } | ||
338 | |||
339 | return retval; | ||
340 | } | ||
341 | |||
342 | EXPORT_SYMBOL(vr41xx_cascade_irq); | ||
343 | |||
344 | static inline int get_irq_pin_number(void) | ||
345 | { | ||
346 | uint16_t pendl, pendh, maskl, maskh; | ||
347 | int i; | ||
348 | |||
349 | pendl = read_giuint(GIUINTSTATL); | ||
350 | pendh = read_giuint(GIUINTSTATH); | ||
351 | maskl = read_giuint(GIUINTENL); | ||
352 | maskh = read_giuint(GIUINTENH); | ||
353 | |||
354 | maskl &= pendl; | ||
355 | maskh &= pendh; | ||
356 | |||
357 | if (maskl) { | ||
358 | for (i = 0; i < 16; i++) { | ||
359 | if (maskl & ((uint16_t)1 << i)) | ||
360 | return i; | ||
361 | } | ||
362 | } else if (maskh) { | ||
363 | for (i = 0; i < 16; i++) { | ||
364 | if (maskh & ((uint16_t)1 << i)) | ||
365 | return i + GIUINT_HIGH_OFFSET; | ||
366 | } | ||
367 | } | ||
368 | |||
369 | printk(KERN_ERR "spurious GIU interrupt: %04x(%04x),%04x(%04x)\n", | ||
370 | maskl, pendl, maskh, pendh); | ||
371 | |||
372 | atomic_inc(&irq_err_count); | ||
373 | |||
374 | return -1; | ||
375 | } | ||
376 | |||
377 | static inline void ack_giuint_irq(int pin) | ||
378 | { | ||
379 | if (pin < GIUINT_HIGH_OFFSET) { | ||
380 | clear_giuint(GIUINTENL, (uint16_t)1 << pin); | ||
381 | write_giuint((uint16_t)1 << pin, GIUINTSTATL); | ||
382 | } else { | ||
383 | pin -= GIUINT_HIGH_OFFSET; | ||
384 | clear_giuint(GIUINTENH, (uint16_t)1 << pin); | ||
385 | write_giuint((uint16_t)1 << pin, GIUINTSTATH); | ||
386 | } | ||
387 | } | ||
388 | |||
389 | static inline void end_giuint_irq(int pin) | ||
390 | { | ||
391 | if (pin < GIUINT_HIGH_OFFSET) | ||
392 | set_giuint(GIUINTENL, (uint16_t)1 << pin); | ||
393 | else | ||
394 | set_giuint(GIUINTENH, (uint16_t)1 << (pin - GIUINT_HIGH_OFFSET)); | ||
395 | } | ||
396 | |||
397 | void giuint_irq_dispatch(struct pt_regs *regs) | ||
398 | { | ||
399 | struct vr41xx_giuint_cascade *cascade; | ||
400 | unsigned int giuint_irq; | ||
401 | int pin; | ||
402 | |||
403 | pin = get_irq_pin_number(); | ||
404 | if (pin < 0) | ||
405 | return; | ||
406 | |||
407 | disable_irq(GIUINT_CASCADE_IRQ); | ||
408 | |||
409 | cascade = &giuint_cascade[pin]; | ||
410 | giuint_irq = GIU_IRQ(pin); | ||
411 | if (cascade->flag == GIUINT_CASCADE) { | ||
412 | int irq = cascade->get_irq_number(giuint_irq); | ||
413 | ack_giuint_irq(pin); | ||
414 | if (irq >= 0) | ||
415 | do_IRQ(irq, regs); | ||
416 | end_giuint_irq(pin); | ||
417 | } else { | ||
418 | do_IRQ(giuint_irq, regs); | ||
419 | } | ||
420 | |||
421 | enable_irq(GIUINT_CASCADE_IRQ); | ||
422 | } | ||
423 | |||
424 | static int __init vr41xx_giu_init(void) | ||
425 | { | ||
426 | int i; | ||
427 | |||
428 | switch (current_cpu_data.cputype) { | ||
429 | case CPU_VR4111: | ||
430 | case CPU_VR4121: | ||
431 | giu_base = GIUIOSELL_TYPE1; | ||
432 | break; | ||
433 | case CPU_VR4122: | ||
434 | case CPU_VR4131: | ||
435 | case CPU_VR4133: | ||
436 | giu_base = GIUIOSELL_TYPE2; | ||
437 | break; | ||
438 | default: | ||
439 | printk(KERN_ERR "GIU: Unexpected CPU of NEC VR4100 series\n"); | ||
440 | return -EINVAL; | ||
441 | } | ||
442 | |||
443 | for (i = 0; i < GIUINT_NR_IRQS; i++) { | ||
444 | if (i < GIUINT_HIGH_OFFSET) | ||
445 | clear_giuint(GIUINTENL, (uint16_t)1 << i); | ||
446 | else | ||
447 | clear_giuint(GIUINTENH, (uint16_t)1 << (i - GIUINT_HIGH_OFFSET)); | ||
448 | giuint_cascade[i].flag = GIUINT_NO_CASCADE; | ||
449 | giuint_cascade[i].get_irq_number = no_irq_number; | ||
450 | } | ||
451 | |||
452 | return 0; | ||
453 | } | ||
454 | |||
455 | early_initcall(vr41xx_giu_init); | ||