aboutsummaryrefslogtreecommitdiffstats
path: root/arch/powerpc/sysdev
diff options
context:
space:
mode:
authorDavid Gibson <david@gibson.dropbear.id.au>2007-04-18 02:36:26 -0400
committerPaul Mackerras <paulus@samba.org>2007-04-24 07:32:01 -0400
commite58923ed14370e0facc5eb2c3923216adc3bf260 (patch)
tree9b6af9756600c9eccba1c6f300a3f2e965b6f815 /arch/powerpc/sysdev
parentf65573314e4e2d7deacd7ed101c004148e156296 (diff)
[POWERPC] Add arch/powerpc driver for UIC, PPC4xx interrupt controller
This patch adds a driver to arch/powerpc/sysdev for the UIC, the on-chip interrupt controller from IBM/AMCC 4xx chips. It uses the new irq host mapping infrastructure. Signed-off-by: David Gibson <dwg@au1.ibm.com> Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'arch/powerpc/sysdev')
-rw-r--r--arch/powerpc/sysdev/Makefile1
-rw-r--r--arch/powerpc/sysdev/uic.c342
2 files changed, 343 insertions, 0 deletions
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index 83fbbfc779a1..e96ca9618dbb 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_PM) += timer.o
21ifeq ($(CONFIG_PPC_MERGE),y) 21ifeq ($(CONFIG_PPC_MERGE),y)
22obj-$(CONFIG_PPC_I8259) += i8259.o 22obj-$(CONFIG_PPC_I8259) += i8259.o
23obj-$(CONFIG_PPC_83xx) += ipic.o 23obj-$(CONFIG_PPC_83xx) += ipic.o
24obj-$(CONFIG_4xx) += uic.o
24endif 25endif
25 26
26# Temporary hack until we have migrated to asm-powerpc 27# Temporary hack until we have migrated to asm-powerpc
diff --git a/arch/powerpc/sysdev/uic.c b/arch/powerpc/sysdev/uic.c
new file mode 100644
index 000000000000..cdbe68437afe
--- /dev/null
+++ b/arch/powerpc/sysdev/uic.c
@@ -0,0 +1,342 @@
1/*
2 * arch/powerpc/sysdev/uic.c
3 *
4 * IBM PowerPC 4xx Universal Interrupt Controller
5 *
6 * Copyright 2007 David Gibson <dwg@au1.ibm.com>, IBM Corporation.
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version.
12 */
13#include <linux/kernel.h>
14#include <linux/init.h>
15#include <linux/errno.h>
16#include <linux/reboot.h>
17#include <linux/slab.h>
18#include <linux/stddef.h>
19#include <linux/sched.h>
20#include <linux/signal.h>
21#include <linux/sysdev.h>
22#include <linux/device.h>
23#include <linux/bootmem.h>
24#include <linux/spinlock.h>
25#include <linux/irq.h>
26#include <linux/interrupt.h>
27#include <asm/irq.h>
28#include <asm/io.h>
29#include <asm/prom.h>
30#include <asm/dcr.h>
31
32#define NR_UIC_INTS 32
33
34#define UIC_SR 0x0
35#define UIC_ER 0x2
36#define UIC_CR 0x3
37#define UIC_PR 0x4
38#define UIC_TR 0x5
39#define UIC_MSR 0x6
40#define UIC_VR 0x7
41#define UIC_VCR 0x8
42
43#define uic_irq_to_hw(virq) (irq_map[virq].hwirq)
44
45struct uic *primary_uic;
46
47struct uic {
48 int index;
49 int dcrbase;
50
51 spinlock_t lock;
52
53 /* The remapper for this UIC */
54 struct irq_host *irqhost;
55
56 /* For secondary UICs, the cascade interrupt's irqaction */
57 struct irqaction cascade;
58
59 /* The device node of the interrupt controller */
60 struct device_node *of_node;
61};
62
63static void uic_unmask_irq(unsigned int virq)
64{
65 struct uic *uic = get_irq_chip_data(virq);
66 unsigned int src = uic_irq_to_hw(virq);
67 unsigned long flags;
68 u32 er;
69
70 spin_lock_irqsave(&uic->lock, flags);
71 er = mfdcr(uic->dcrbase + UIC_ER);
72 er |= 1 << (31 - src);
73 mtdcr(uic->dcrbase + UIC_ER, er);
74 spin_unlock_irqrestore(&uic->lock, flags);
75}
76
77static void uic_mask_irq(unsigned int virq)
78{
79 struct uic *uic = get_irq_chip_data(virq);
80 unsigned int src = uic_irq_to_hw(virq);
81 unsigned long flags;
82 u32 er;
83
84 spin_lock_irqsave(&uic->lock, flags);
85 er = mfdcr(uic->dcrbase + UIC_ER);
86 er &= ~(1 << (31 - src));
87 mtdcr(uic->dcrbase + UIC_ER, er);
88 spin_unlock_irqrestore(&uic->lock, flags);
89}
90
91static void uic_ack_irq(unsigned int virq)
92{
93 struct uic *uic = get_irq_chip_data(virq);
94 unsigned int src = uic_irq_to_hw(virq);
95 unsigned long flags;
96
97 spin_lock_irqsave(&uic->lock, flags);
98 mtdcr(uic->dcrbase + UIC_SR, 1 << (31-src));
99 spin_unlock_irqrestore(&uic->lock, flags);
100}
101
102static int uic_set_irq_type(unsigned int virq, unsigned int flow_type)
103{
104 struct uic *uic = get_irq_chip_data(virq);
105 unsigned int src = uic_irq_to_hw(virq);
106 struct irq_desc *desc = get_irq_desc(virq);
107 unsigned long flags;
108 int trigger, polarity;
109 u32 tr, pr, mask;
110
111 switch (flow_type & IRQ_TYPE_SENSE_MASK) {
112 case IRQ_TYPE_NONE:
113 uic_mask_irq(virq);
114 return 0;
115
116 case IRQ_TYPE_EDGE_RISING:
117 trigger = 1; polarity = 1;
118 break;
119 case IRQ_TYPE_EDGE_FALLING:
120 trigger = 1; polarity = 0;
121 break;
122 case IRQ_TYPE_LEVEL_HIGH:
123 trigger = 0; polarity = 1;
124 break;
125 case IRQ_TYPE_LEVEL_LOW:
126 trigger = 0; polarity = 0;
127 break;
128 default:
129 return -EINVAL;
130 }
131
132 mask = ~(1 << (31 - src));
133
134 spin_lock_irqsave(&uic->lock, flags);
135 tr = mfdcr(uic->dcrbase + UIC_TR);
136 pr = mfdcr(uic->dcrbase + UIC_PR);
137 tr = (tr & mask) | (trigger << (31-src));
138 pr = (pr & mask) | (polarity << (31-src));
139
140 mtdcr(uic->dcrbase + UIC_PR, pr);
141 mtdcr(uic->dcrbase + UIC_TR, tr);
142
143 desc->status &= ~(IRQ_TYPE_SENSE_MASK | IRQ_LEVEL);
144 desc->status |= flow_type & IRQ_TYPE_SENSE_MASK;
145 if (trigger)
146 desc->status |= IRQ_LEVEL;
147
148 spin_unlock_irqrestore(&uic->lock, flags);
149
150 return 0;
151}
152
153static struct irq_chip uic_irq_chip = {
154 .typename = " UIC ",
155 .unmask = uic_unmask_irq,
156 .mask = uic_mask_irq,
157/* .mask_ack = uic_mask_irq_and_ack, */
158 .ack = uic_ack_irq,
159 .set_type = uic_set_irq_type,
160};
161
162static int uic_host_match(struct irq_host *h, struct device_node *node)
163{
164 struct uic *uic = h->host_data;
165 return uic->of_node == node;
166}
167
168static int uic_host_map(struct irq_host *h, unsigned int virq,
169 irq_hw_number_t hw)
170{
171 struct uic *uic = h->host_data;
172
173 set_irq_chip_data(virq, uic);
174 /* Despite the name, handle_level_irq() works for both level
175 * and edge irqs on UIC. FIXME: check this is correct */
176 set_irq_chip_and_handler(virq, &uic_irq_chip, handle_level_irq);
177
178 /* Set default irq type */
179 set_irq_type(virq, IRQ_TYPE_NONE);
180
181 return 0;
182}
183
184static int uic_host_xlate(struct irq_host *h, struct device_node *ct,
185 u32 *intspec, unsigned int intsize,
186 irq_hw_number_t *out_hwirq, unsigned int *out_type)
187
188{
189 /* UIC intspecs must have 2 cells */
190 BUG_ON(intsize != 2);
191 *out_hwirq = intspec[0];
192 *out_type = intspec[1];
193 return 0;
194}
195
196static struct irq_host_ops uic_host_ops = {
197 .match = uic_host_match,
198 .map = uic_host_map,
199 .xlate = uic_host_xlate,
200};
201
202irqreturn_t uic_cascade(int virq, void *data)
203{
204 struct uic *uic = data;
205 u32 msr;
206 int src;
207 int subvirq;
208
209 msr = mfdcr(uic->dcrbase + UIC_MSR);
210 src = 32 - ffs(msr);
211
212 subvirq = irq_linear_revmap(uic->irqhost, src);
213 generic_handle_irq(subvirq);
214
215 return IRQ_HANDLED;
216}
217
218static struct uic * __init uic_init_one(struct device_node *node)
219{
220 struct uic *uic;
221 const u32 *indexp, *dcrreg;
222 int len;
223
224 BUG_ON(! device_is_compatible(node, "ibm,uic"));
225
226 uic = alloc_bootmem(sizeof(*uic));
227 if (! uic)
228 return NULL; /* FIXME: panic? */
229
230 memset(uic, 0, sizeof(*uic));
231 spin_lock_init(&uic->lock);
232 uic->of_node = of_node_get(node);
233 indexp = get_property(node, "cell-index", &len);
234 if (!indexp || (len != sizeof(u32))) {
235 printk(KERN_ERR "uic: Device node %s has missing or invalid "
236 "cell-index property\n", node->full_name);
237 return NULL;
238 }
239 uic->index = *indexp;
240
241 dcrreg = get_property(node, "dcr-reg", &len);
242 if (!dcrreg || (len != 2*sizeof(u32))) {
243 printk(KERN_ERR "uic: Device node %s has missing or invalid "
244 "dcr-reg property\n", node->full_name);
245 return NULL;
246 }
247 uic->dcrbase = *dcrreg;
248
249 uic->irqhost = irq_alloc_host(IRQ_HOST_MAP_LINEAR, NR_UIC_INTS,
250 &uic_host_ops, -1);
251 if (! uic->irqhost) {
252 of_node_put(node);
253 return NULL; /* FIXME: panic? */
254 }
255
256 uic->irqhost->host_data = uic;
257
258 /* Start with all interrupts disabled, level and non-critical */
259 mtdcr(uic->dcrbase + UIC_ER, 0);
260 mtdcr(uic->dcrbase + UIC_CR, 0);
261 mtdcr(uic->dcrbase + UIC_TR, 0);
262 /* Clear any pending interrupts, in case the firmware left some */
263 mtdcr(uic->dcrbase + UIC_SR, 0xffffffff);
264
265 printk ("UIC%d (%d IRQ sources) at DCR 0x%x\n", uic->index,
266 NR_UIC_INTS, uic->dcrbase);
267
268 return uic;
269}
270
271void __init uic_init_tree(void)
272{
273 struct device_node *np;
274 struct uic *uic;
275 const u32 *interrupts;
276
277 /* First locate and initialize the top-level UIC */
278
279 np = of_find_compatible_node(NULL, NULL, "ibm,uic");
280 while (np) {
281 interrupts = get_property(np, "interrupts", NULL);
282 if (! interrupts)
283 break;
284
285 np = of_find_compatible_node(np, NULL, "ibm,uic");
286 }
287
288 BUG_ON(!np); /* uic_init_tree() assumes there's a UIC as the
289 * top-level interrupt controller */
290 primary_uic = uic_init_one(np);
291 if (! primary_uic)
292 panic("Unable to initialize primary UIC %s\n", np->full_name);
293
294 irq_set_default_host(primary_uic->irqhost);
295 of_node_put(np);
296
297 /* The scan again for cascaded UICs */
298 np = of_find_compatible_node(NULL, NULL, "ibm,uic");
299 while (np) {
300 interrupts = get_property(np, "interrupts", NULL);
301 if (interrupts) {
302 /* Secondary UIC */
303 int cascade_virq;
304 int ret;
305
306 uic = uic_init_one(np);
307 if (! uic)
308 panic("Unable to initialize a secondary UIC %s\n",
309 np->full_name);
310
311 cascade_virq = irq_of_parse_and_map(np, 0);
312
313 uic->cascade.handler = uic_cascade;
314 uic->cascade.name = "UIC cascade";
315 uic->cascade.dev_id = uic;
316
317 ret = setup_irq(cascade_virq, &uic->cascade);
318 if (ret)
319 printk(KERN_ERR "Failed to setup_irq(%d) for "
320 "UIC%d cascade\n", cascade_virq,
321 uic->index);
322
323 /* FIXME: setup critical cascade?? */
324 }
325
326 np = of_find_compatible_node(np, NULL, "ibm,uic");
327 }
328}
329
330/* Return an interrupt vector or NO_IRQ if no interrupt is pending. */
331unsigned int uic_get_irq(void)
332{
333 u32 msr;
334 int src;
335
336 BUG_ON(! primary_uic);
337
338 msr = mfdcr(primary_uic->dcrbase + UIC_MSR);
339 src = 32 - ffs(msr);
340
341 return irq_linear_revmap(primary_uic->irqhost, src);
342}