Commit | Line | Data |
---|---|---|
bdf21b18 PP |
1 | /* |
2 | * | |
3 | * Copyright (C) 2005 Embedded Alley Solutions, Inc | |
4 | * Ported to 2.6. | |
5 | * | |
6 | * Per Hallsmark, per.hallsmark@mvista.com | |
7 | * Copyright (C) 2000, 2001 MIPS Technologies, Inc. | |
8 | * Copyright (C) 2001 Ralf Baechle | |
9 | * | |
10 | * Cleaned up and bug fixing: Pete Popov, ppopov@embeddedalley.com | |
11 | * | |
12 | * This program is free software; you can distribute it and/or modify it | |
13 | * under the terms of the GNU General Public License (Version 2) as | |
14 | * published by the Free Software Foundation. | |
15 | * | |
16 | * This program is distributed in the hope it will be useful, but WITHOUT | |
17 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
18 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
19 | * for more details. | |
20 | * | |
21 | * You should have received a copy of the GNU General Public License along | |
22 | * with this program; if not, write to the Free Software Foundation, Inc., | |
23 | * 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. | |
24 | * | |
25 | */ | |
bdf21b18 PP |
26 | #include <linux/init.h> |
27 | #include <linux/irq.h> | |
28 | #include <linux/sched.h> | |
29 | #include <linux/slab.h> | |
30 | #include <linux/interrupt.h> | |
31 | #include <linux/kernel_stat.h> | |
32 | #include <linux/random.h> | |
33 | #include <linux/module.h> | |
34 | ||
35 | #include <asm/io.h> | |
36 | #include <asm/gdb-stub.h> | |
37 | #include <int.h> | |
38 | #include <uart.h> | |
39 | ||
bdf21b18 PP |
40 | static DEFINE_SPINLOCK(irq_lock); |
41 | ||
42 | /* default prio for interrupts */ | |
43 | /* first one is a no-no so therefore always prio 0 (disabled) */ | |
44 | static char gic_prio[PNX8550_INT_GIC_TOTINT] = { | |
45 | 0, 1, 1, 1, 1, 15, 1, 1, 1, 1, // 0 - 9 | |
46 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 10 - 19 | |
47 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 20 - 29 | |
48 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 30 - 39 | |
49 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40 - 49 | |
50 | 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, // 50 - 59 | |
51 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60 - 69 | |
52 | 1 // 70 | |
53 | }; | |
54 | ||
e4ac58af | 55 | static void hw0_irqdispatch(int irq, struct pt_regs *regs) |
bdf21b18 PP |
56 | { |
57 | /* find out which interrupt */ | |
58 | irq = PNX8550_GIC_VECTOR_0 >> 3; | |
59 | ||
60 | if (irq == 0) { | |
61 | printk("hw0_irqdispatch: irq 0, spurious interrupt?\n"); | |
62 | return; | |
63 | } | |
64 | do_IRQ(PNX8550_INT_GIC_MIN + irq, regs); | |
65 | } | |
66 | ||
67 | ||
e4ac58af | 68 | static void timer_irqdispatch(int irq, struct pt_regs *regs) |
bdf21b18 PP |
69 | { |
70 | irq = (0x01c0 & read_c0_config7()) >> 6; | |
71 | ||
72 | if (irq == 0) { | |
73 | printk("timer_irqdispatch: irq 0, spurious interrupt?\n"); | |
74 | return; | |
75 | } | |
76 | ||
77 | if (irq & 0x1) { | |
78 | do_IRQ(PNX8550_INT_TIMER1, regs); | |
79 | } | |
80 | if (irq & 0x2) { | |
81 | do_IRQ(PNX8550_INT_TIMER2, regs); | |
82 | } | |
83 | if (irq & 0x4) { | |
84 | do_IRQ(PNX8550_INT_TIMER3, regs); | |
85 | } | |
86 | } | |
87 | ||
e4ac58af RB |
88 | asmlinkage void plat_irq_dispatch(struct pt_regs *regs) |
89 | { | |
90 | unsigned int pending = read_c0_status() & read_c0_cause(); | |
91 | ||
92 | if (pending & STATUSF_IP2) | |
93 | do_IRQ(2, regs); | |
94 | else if (pending & STATUSF_IP7) { | |
95 | if (read_c0_config7() & 0x01c0) | |
96 | timer_irqdispatch(7, regs); | |
97 | } | |
98 | ||
99 | spurious_interrupt(regs); | |
100 | } | |
101 | ||
bdf21b18 PP |
102 | static inline void modify_cp0_intmask(unsigned clr_mask, unsigned set_mask) |
103 | { | |
104 | unsigned long status = read_c0_status(); | |
105 | ||
106 | status &= ~((clr_mask & 0xFF) << 8); | |
107 | status |= (set_mask & 0xFF) << 8; | |
108 | ||
109 | write_c0_status(status); | |
110 | } | |
111 | ||
112 | static inline void mask_gic_int(unsigned int irq_nr) | |
113 | { | |
114 | /* interrupt disabled, bit 26(WE_ENABLE)=1 and bit 16(enable)=0 */ | |
115 | PNX8550_GIC_REQ(irq_nr) = 1<<28; /* set priority to 0 */ | |
116 | } | |
117 | ||
118 | static inline void unmask_gic_int(unsigned int irq_nr) | |
119 | { | |
120 | /* set prio mask to lower four bits and enable interrupt */ | |
121 | PNX8550_GIC_REQ(irq_nr) = (1<<26 | 1<<16) | (1<<28) | gic_prio[irq_nr]; | |
122 | } | |
123 | ||
124 | static inline void mask_irq(unsigned int irq_nr) | |
125 | { | |
126 | if ((PNX8550_INT_CP0_MIN <= irq_nr) && (irq_nr <= PNX8550_INT_CP0_MAX)) { | |
127 | modify_cp0_intmask(1 << irq_nr, 0); | |
128 | } else if ((PNX8550_INT_GIC_MIN <= irq_nr) && | |
129 | (irq_nr <= PNX8550_INT_GIC_MAX)) { | |
130 | mask_gic_int(irq_nr - PNX8550_INT_GIC_MIN); | |
131 | } else if ((PNX8550_INT_TIMER_MIN <= irq_nr) && | |
132 | (irq_nr <= PNX8550_INT_TIMER_MAX)) { | |
133 | modify_cp0_intmask(1 << 7, 0); | |
134 | } else { | |
135 | printk("mask_irq: irq %d doesn't exist!\n", irq_nr); | |
136 | } | |
137 | } | |
138 | ||
139 | static inline void unmask_irq(unsigned int irq_nr) | |
140 | { | |
141 | if ((PNX8550_INT_CP0_MIN <= irq_nr) && (irq_nr <= PNX8550_INT_CP0_MAX)) { | |
142 | modify_cp0_intmask(0, 1 << irq_nr); | |
143 | } else if ((PNX8550_INT_GIC_MIN <= irq_nr) && | |
144 | (irq_nr <= PNX8550_INT_GIC_MAX)) { | |
145 | unmask_gic_int(irq_nr - PNX8550_INT_GIC_MIN); | |
146 | } else if ((PNX8550_INT_TIMER_MIN <= irq_nr) && | |
147 | (irq_nr <= PNX8550_INT_TIMER_MAX)) { | |
148 | modify_cp0_intmask(0, 1 << 7); | |
149 | } else { | |
150 | printk("mask_irq: irq %d doesn't exist!\n", irq_nr); | |
151 | } | |
152 | } | |
153 | ||
154 | #define pnx8550_disable pnx8550_ack | |
155 | static void pnx8550_ack(unsigned int irq) | |
156 | { | |
157 | unsigned long flags; | |
158 | ||
159 | spin_lock_irqsave(&irq_lock, flags); | |
160 | mask_irq(irq); | |
161 | spin_unlock_irqrestore(&irq_lock, flags); | |
162 | } | |
163 | ||
164 | #define pnx8550_enable pnx8550_unmask | |
165 | static void pnx8550_unmask(unsigned int irq) | |
166 | { | |
167 | unsigned long flags; | |
168 | ||
169 | spin_lock_irqsave(&irq_lock, flags); | |
170 | unmask_irq(irq); | |
171 | spin_unlock_irqrestore(&irq_lock, flags); | |
172 | } | |
173 | ||
174 | static unsigned int startup_irq(unsigned int irq_nr) | |
175 | { | |
176 | pnx8550_unmask(irq_nr); | |
177 | return 0; | |
178 | } | |
179 | ||
180 | static void shutdown_irq(unsigned int irq_nr) | |
181 | { | |
182 | pnx8550_ack(irq_nr); | |
183 | return; | |
184 | } | |
185 | ||
186 | int pnx8550_set_gic_priority(int irq, int priority) | |
187 | { | |
188 | int gic_irq = irq-PNX8550_INT_GIC_MIN; | |
189 | int prev_priority = PNX8550_GIC_REQ(gic_irq) & 0xf; | |
190 | ||
191 | gic_prio[gic_irq] = priority; | |
192 | PNX8550_GIC_REQ(gic_irq) |= (0x10000000 | gic_prio[gic_irq]); | |
193 | ||
194 | return prev_priority; | |
195 | } | |
196 | ||
197 | static inline void mask_and_ack_level_irq(unsigned int irq) | |
198 | { | |
199 | pnx8550_disable(irq); | |
200 | return; | |
201 | } | |
202 | ||
203 | static void end_irq(unsigned int irq) | |
204 | { | |
205 | if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS))) { | |
206 | pnx8550_enable(irq); | |
207 | } | |
208 | } | |
209 | ||
210 | static struct hw_interrupt_type level_irq_type = { | |
211 | .typename = "PNX Level IRQ", | |
212 | .startup = startup_irq, | |
213 | .shutdown = shutdown_irq, | |
214 | .enable = pnx8550_enable, | |
215 | .disable = pnx8550_disable, | |
216 | .ack = mask_and_ack_level_irq, | |
217 | .end = end_irq, | |
218 | }; | |
219 | ||
220 | static struct irqaction gic_action = { | |
221 | .handler = no_action, | |
222 | .flags = SA_INTERRUPT, | |
223 | .name = "GIC", | |
224 | }; | |
225 | ||
226 | static struct irqaction timer_action = { | |
227 | .handler = no_action, | |
228 | .flags = SA_INTERRUPT, | |
229 | .name = "Timer", | |
230 | }; | |
231 | ||
232 | void __init arch_init_irq(void) | |
233 | { | |
234 | int i; | |
235 | int configPR; | |
236 | ||
bdf21b18 | 237 | for (i = 0; i < PNX8550_INT_CP0_TOTINT; i++) { |
d1bef4ed | 238 | irq_desc[i].chip = &level_irq_type; |
bdf21b18 PP |
239 | pnx8550_ack(i); /* mask the irq just in case */ |
240 | } | |
241 | ||
242 | /* init of GIC/IPC interrupts */ | |
243 | /* should be done before cp0 since cp0 init enables the GIC int */ | |
244 | for (i = PNX8550_INT_GIC_MIN; i <= PNX8550_INT_GIC_MAX; i++) { | |
245 | int gic_int_line = i - PNX8550_INT_GIC_MIN; | |
246 | if (gic_int_line == 0 ) | |
247 | continue; // don't fiddle with int 0 | |
248 | /* | |
249 | * enable change of TARGET, ENABLE and ACTIVE_LOW bits | |
250 | * set TARGET 0 to route through hw0 interrupt | |
251 | * set ACTIVE_LOW 0 active high (correct?) | |
252 | * | |
253 | * We really should setup an interrupt description table | |
254 | * to do this nicely. | |
255 | * Note, PCI INTA is active low on the bus, but inverted | |
256 | * in the GIC, so to us it's active high. | |
257 | */ | |
258 | #ifdef CONFIG_PNX8550_V2PCI | |
259 | if (gic_int_line == (PNX8550_INT_GPIO0 - PNX8550_INT_GIC_MIN)) { | |
260 | /* PCI INT through gpio 8, which is setup in | |
261 | * pnx8550_setup.c and routed to GPIO | |
a3dddd56 | 262 | * Interrupt Level 0 (GPIO Connection 58). |
bdf21b18 PP |
263 | * Set it active low. */ |
264 | ||
265 | PNX8550_GIC_REQ(gic_int_line) = 0x1E020000; | |
266 | } else | |
267 | #endif | |
268 | { | |
269 | PNX8550_GIC_REQ(i - PNX8550_INT_GIC_MIN) = 0x1E000000; | |
270 | } | |
271 | ||
272 | /* mask/priority is still 0 so we will not get any | |
273 | * interrupts until it is unmasked */ | |
274 | ||
d1bef4ed | 275 | irq_desc[i].chip = &level_irq_type; |
bdf21b18 PP |
276 | } |
277 | ||
278 | /* Priority level 0 */ | |
279 | PNX8550_GIC_PRIMASK_0 = PNX8550_GIC_PRIMASK_1 = 0; | |
280 | ||
281 | /* Set int vector table address */ | |
282 | PNX8550_GIC_VECTOR_0 = PNX8550_GIC_VECTOR_1 = 0; | |
283 | ||
d1bef4ed | 284 | irq_desc[MIPS_CPU_GIC_IRQ].chip = &level_irq_type; |
bdf21b18 PP |
285 | setup_irq(MIPS_CPU_GIC_IRQ, &gic_action); |
286 | ||
287 | /* init of Timer interrupts */ | |
288 | for (i = PNX8550_INT_TIMER_MIN; i <= PNX8550_INT_TIMER_MAX; i++) { | |
d1bef4ed | 289 | irq_desc[i].chip = &level_irq_type; |
bdf21b18 PP |
290 | } |
291 | ||
292 | /* Stop Timer 1-3 */ | |
293 | configPR = read_c0_config7(); | |
294 | configPR |= 0x00000038; | |
295 | write_c0_config7(configPR); | |
296 | ||
d1bef4ed | 297 | irq_desc[MIPS_CPU_TIMER_IRQ].chip = &level_irq_type; |
bdf21b18 PP |
298 | setup_irq(MIPS_CPU_TIMER_IRQ, &timer_action); |
299 | } | |
300 | ||
301 | EXPORT_SYMBOL(pnx8550_set_gic_priority); |