diff options
author | Martyn Welch <martyn.welch@gefanuc.com> | 2008-10-01 04:32:39 -0400 |
---|---|---|
committer | Kumar Gala <galak@kernel.crashing.org> | 2008-10-13 12:09:57 -0400 |
commit | 3a470247913e6537c453937720b61f4ecc3e39db (patch) | |
tree | 284e09ffda9f3513677464abd05020fb01f51196 /arch/powerpc/platforms | |
parent | 92ae954046b1434c8c11468893ed27c7c06f2c21 (diff) |
powerpc: GE Fanuc's FPGA based PIC controller on the SBC610
Support for the SBC610 VPX Single Board Computer from GE Fanuc (PowerPC MPC8641D).
A number of MPC8641D based route interrupts for on-board interrupts through
a FPGA based interrupt controller, which is chained with the
MPC8641D's mpic. This patch provides a basic driver to allow basic routing
of interrupts to the mpic.
Signed-off-by: Martyn Welch <martyn.welch@gefanuc.com>
Signed-off-by: Kumar Gala <galak@kernel.crashing.org>
Diffstat (limited to 'arch/powerpc/platforms')
-rw-r--r-- | arch/powerpc/platforms/86xx/Makefile | 2 | ||||
-rw-r--r-- | arch/powerpc/platforms/86xx/gef_pic.c | 258 | ||||
-rw-r--r-- | arch/powerpc/platforms/86xx/gef_pic.h | 11 | ||||
-rw-r--r-- | arch/powerpc/platforms/86xx/gef_sbc610.c | 25 |
4 files changed, 294 insertions, 2 deletions
diff --git a/arch/powerpc/platforms/86xx/Makefile b/arch/powerpc/platforms/86xx/Makefile index cb9fc8f4360b..4a56ff619afd 100644 --- a/arch/powerpc/platforms/86xx/Makefile +++ b/arch/powerpc/platforms/86xx/Makefile | |||
@@ -7,4 +7,4 @@ obj-$(CONFIG_SMP) += mpc86xx_smp.o | |||
7 | obj-$(CONFIG_MPC8641_HPCN) += mpc86xx_hpcn.o | 7 | obj-$(CONFIG_MPC8641_HPCN) += mpc86xx_hpcn.o |
8 | obj-$(CONFIG_SBC8641D) += sbc8641d.o | 8 | obj-$(CONFIG_SBC8641D) += sbc8641d.o |
9 | obj-$(CONFIG_MPC8610_HPCD) += mpc8610_hpcd.o | 9 | obj-$(CONFIG_MPC8610_HPCD) += mpc8610_hpcd.o |
10 | obj-$(CONFIG_GEF_SBC610) += gef_sbc610.o | 10 | obj-$(CONFIG_GEF_SBC610) += gef_sbc610.o gef_pic.o |
diff --git a/arch/powerpc/platforms/86xx/gef_pic.c b/arch/powerpc/platforms/86xx/gef_pic.c new file mode 100644 index 000000000000..50d0a2b63809 --- /dev/null +++ b/arch/powerpc/platforms/86xx/gef_pic.c | |||
@@ -0,0 +1,258 @@ | |||
1 | /* | ||
2 | * Interrupt handling for GE Fanuc's FPGA based PIC | ||
3 | * | ||
4 | * Author: Martyn Welch <martyn.welch@gefanuc.com> | ||
5 | * | ||
6 | * 2008 (c) GE Fanuc Intelligent Platforms Embedded Systems, Inc. | ||
7 | * | ||
8 | * This file is licensed under the terms of the GNU General Public License | ||
9 | * version 2. This program is licensed "as is" without any warranty of any | ||
10 | * kind, whether express or implied. | ||
11 | */ | ||
12 | |||
13 | #include <linux/stddef.h> | ||
14 | #include <linux/kernel.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/irq.h> | ||
17 | #include <linux/interrupt.h> | ||
18 | #include <linux/spinlock.h> | ||
19 | |||
20 | #include <asm/byteorder.h> | ||
21 | #include <asm/io.h> | ||
22 | #include <asm/prom.h> | ||
23 | #include <asm/irq.h> | ||
24 | |||
25 | #include "gef_pic.h" | ||
26 | |||
27 | #define DEBUG | ||
28 | #undef DEBUG | ||
29 | |||
30 | #ifdef DEBUG | ||
31 | #define DBG(fmt...) do { printk(KERN_DEBUG "gef_pic: " fmt); } while (0) | ||
32 | #else | ||
33 | #define DBG(fmt...) do { } while (0) | ||
34 | #endif | ||
35 | |||
36 | #define GEF_PIC_NUM_IRQS 32 | ||
37 | |||
38 | /* Interrupt Controller Interface Registers */ | ||
39 | #define GEF_PIC_INTR_STATUS 0x0000 | ||
40 | |||
41 | #define GEF_PIC_INTR_MASK(cpu) (0x0010 + (0x4 * cpu)) | ||
42 | #define GEF_PIC_CPU0_INTR_MASK GEF_PIC_INTR_MASK(0) | ||
43 | #define GEF_PIC_CPU1_INTR_MASK GEF_PIC_INTR_MASK(1) | ||
44 | |||
45 | #define GEF_PIC_MCP_MASK(cpu) (0x0018 + (0x4 * cpu)) | ||
46 | #define GEF_PIC_CPU0_MCP_MASK GEF_PIC_MCP_MASK(0) | ||
47 | #define GEF_PIC_CPU1_MCP_MASK GEF_PIC_MCP_MASK(1) | ||
48 | |||
49 | #define gef_irq_to_hw(virq) ((unsigned int)irq_map[virq].hwirq) | ||
50 | |||
51 | |||
52 | static DEFINE_SPINLOCK(gef_pic_lock); | ||
53 | |||
54 | static void __iomem *gef_pic_irq_reg_base; | ||
55 | static struct irq_host *gef_pic_irq_host; | ||
56 | static int gef_pic_cascade_irq; | ||
57 | |||
58 | /* | ||
59 | * Interrupt Controller Handling | ||
60 | * | ||
61 | * The interrupt controller handles interrupts for most on board interrupts, | ||
62 | * apart from PCI interrupts. For example on SBC610: | ||
63 | * | ||
64 | * 17:31 RO Reserved | ||
65 | * 16 RO PCI Express Doorbell 3 Status | ||
66 | * 15 RO PCI Express Doorbell 2 Status | ||
67 | * 14 RO PCI Express Doorbell 1 Status | ||
68 | * 13 RO PCI Express Doorbell 0 Status | ||
69 | * 12 RO Real Time Clock Interrupt Status | ||
70 | * 11 RO Temperature Interrupt Status | ||
71 | * 10 RO Temperature Critical Interrupt Status | ||
72 | * 9 RO Ethernet PHY1 Interrupt Status | ||
73 | * 8 RO Ethernet PHY3 Interrupt Status | ||
74 | * 7 RO PEX8548 Interrupt Status | ||
75 | * 6 RO Reserved | ||
76 | * 5 RO Watchdog 0 Interrupt Status | ||
77 | * 4 RO Watchdog 1 Interrupt Status | ||
78 | * 3 RO AXIS Message FIFO A Interrupt Status | ||
79 | * 2 RO AXIS Message FIFO B Interrupt Status | ||
80 | * 1 RO AXIS Message FIFO C Interrupt Status | ||
81 | * 0 RO AXIS Message FIFO D Interrupt Status | ||
82 | * | ||
83 | * Interrupts can be forwarded to one of two output lines. Nothing | ||
84 | * clever is done, so if the masks are incorrectly set, a single input | ||
85 | * interrupt could generate interrupts on both output lines! | ||
86 | * | ||
87 | * The dual lines are there to allow the chained interrupts to be easily | ||
88 | * passed into two different cores. We currently do not use this functionality | ||
89 | * in this driver. | ||
90 | * | ||
91 | * Controller can also be configured to generate Machine checks (MCP), again on | ||
92 | * two lines, to be attached to two different cores. It is suggested that these | ||
93 | * should be masked out. | ||
94 | */ | ||
95 | |||
96 | void gef_pic_cascade(unsigned int irq, struct irq_desc *desc) | ||
97 | { | ||
98 | unsigned int cascade_irq; | ||
99 | |||
100 | /* | ||
101 | * See if we actually have an interrupt, call generic handling code if | ||
102 | * we do. | ||
103 | */ | ||
104 | cascade_irq = gef_pic_get_irq(); | ||
105 | |||
106 | if (cascade_irq != NO_IRQ) | ||
107 | generic_handle_irq(cascade_irq); | ||
108 | |||
109 | desc->chip->eoi(irq); | ||
110 | |||
111 | } | ||
112 | |||
113 | static void gef_pic_mask(unsigned int virq) | ||
114 | { | ||
115 | unsigned long flags; | ||
116 | unsigned int hwirq; | ||
117 | u32 mask; | ||
118 | |||
119 | hwirq = gef_irq_to_hw(virq); | ||
120 | |||
121 | spin_lock_irqsave(&gef_pic_lock, flags); | ||
122 | mask = in_be32(gef_pic_irq_reg_base + GEF_PIC_INTR_MASK(0)); | ||
123 | mask &= ~(1 << hwirq); | ||
124 | out_be32(gef_pic_irq_reg_base + GEF_PIC_INTR_MASK(0), mask); | ||
125 | spin_unlock_irqrestore(&gef_pic_lock, flags); | ||
126 | } | ||
127 | |||
128 | static void gef_pic_mask_ack(unsigned int virq) | ||
129 | { | ||
130 | /* Don't think we actually have to do anything to ack an interrupt, | ||
131 | * we just need to clear down the devices interrupt and it will go away | ||
132 | */ | ||
133 | gef_pic_mask(virq); | ||
134 | } | ||
135 | |||
136 | static void gef_pic_unmask(unsigned int virq) | ||
137 | { | ||
138 | unsigned long flags; | ||
139 | unsigned int hwirq; | ||
140 | u32 mask; | ||
141 | |||
142 | hwirq = gef_irq_to_hw(virq); | ||
143 | |||
144 | spin_lock_irqsave(&gef_pic_lock, flags); | ||
145 | mask = in_be32(gef_pic_irq_reg_base + GEF_PIC_INTR_MASK(0)); | ||
146 | mask |= (1 << hwirq); | ||
147 | out_be32(gef_pic_irq_reg_base + GEF_PIC_INTR_MASK(0), mask); | ||
148 | spin_unlock_irqrestore(&gef_pic_lock, flags); | ||
149 | } | ||
150 | |||
151 | static struct irq_chip gef_pic_chip = { | ||
152 | .typename = "gefp", | ||
153 | .mask = gef_pic_mask, | ||
154 | .mask_ack = gef_pic_mask_ack, | ||
155 | .unmask = gef_pic_unmask, | ||
156 | }; | ||
157 | |||
158 | |||
159 | /* When an interrupt is being configured, this call allows some flexibilty | ||
160 | * in deciding which irq_chip structure is used | ||
161 | */ | ||
162 | static int gef_pic_host_map(struct irq_host *h, unsigned int virq, | ||
163 | irq_hw_number_t hwirq) | ||
164 | { | ||
165 | /* All interrupts are LEVEL sensitive */ | ||
166 | get_irq_desc(virq)->status |= IRQ_LEVEL; | ||
167 | set_irq_chip_and_handler(virq, &gef_pic_chip, handle_level_irq); | ||
168 | |||
169 | return 0; | ||
170 | } | ||
171 | |||
172 | static int gef_pic_host_xlate(struct irq_host *h, struct device_node *ct, | ||
173 | u32 *intspec, unsigned int intsize, | ||
174 | irq_hw_number_t *out_hwirq, unsigned int *out_flags) | ||
175 | { | ||
176 | |||
177 | *out_hwirq = intspec[0]; | ||
178 | if (intsize > 1) | ||
179 | *out_flags = intspec[1]; | ||
180 | else | ||
181 | *out_flags = IRQ_TYPE_LEVEL_HIGH; | ||
182 | |||
183 | return 0; | ||
184 | } | ||
185 | |||
186 | static struct irq_host_ops gef_pic_host_ops = { | ||
187 | .map = gef_pic_host_map, | ||
188 | .xlate = gef_pic_host_xlate, | ||
189 | }; | ||
190 | |||
191 | |||
192 | /* | ||
193 | * Initialisation of PIC, this should be called in BSP | ||
194 | */ | ||
195 | void __init gef_pic_init(struct device_node *np) | ||
196 | { | ||
197 | unsigned long flags; | ||
198 | |||
199 | /* Map the devices registers into memory */ | ||
200 | gef_pic_irq_reg_base = of_iomap(np, 0); | ||
201 | |||
202 | spin_lock_irqsave(&gef_pic_lock, flags); | ||
203 | |||
204 | /* Initialise everything as masked. */ | ||
205 | out_be32(gef_pic_irq_reg_base + GEF_PIC_CPU0_INTR_MASK, 0); | ||
206 | out_be32(gef_pic_irq_reg_base + GEF_PIC_CPU1_INTR_MASK, 0); | ||
207 | |||
208 | out_be32(gef_pic_irq_reg_base + GEF_PIC_CPU0_MCP_MASK, 0); | ||
209 | out_be32(gef_pic_irq_reg_base + GEF_PIC_CPU1_MCP_MASK, 0); | ||
210 | |||
211 | spin_unlock_irqrestore(&gef_pic_lock, flags); | ||
212 | |||
213 | /* Map controller */ | ||
214 | gef_pic_cascade_irq = irq_of_parse_and_map(np, 0); | ||
215 | if (gef_pic_cascade_irq == NO_IRQ) { | ||
216 | printk(KERN_ERR "SBC610: failed to map cascade interrupt"); | ||
217 | return; | ||
218 | } | ||
219 | |||
220 | /* Setup an irq_host structure */ | ||
221 | gef_pic_irq_host = irq_alloc_host(np, IRQ_HOST_MAP_LINEAR, | ||
222 | GEF_PIC_NUM_IRQS, | ||
223 | &gef_pic_host_ops, NO_IRQ); | ||
224 | if (gef_pic_irq_host == NULL) | ||
225 | return; | ||
226 | |||
227 | /* Chain with parent controller */ | ||
228 | set_irq_chained_handler(gef_pic_cascade_irq, gef_pic_cascade); | ||
229 | } | ||
230 | |||
231 | /* | ||
232 | * This is called when we receive an interrupt with apparently comes from this | ||
233 | * chip - check, returning the highest interrupt generated or return NO_IRQ | ||
234 | */ | ||
235 | unsigned int gef_pic_get_irq(void) | ||
236 | { | ||
237 | u32 cause, mask, active; | ||
238 | unsigned int virq = NO_IRQ; | ||
239 | int hwirq; | ||
240 | |||
241 | cause = in_be32(gef_pic_irq_reg_base + GEF_PIC_INTR_STATUS); | ||
242 | |||
243 | mask = in_be32(gef_pic_irq_reg_base + GEF_PIC_INTR_MASK(0)); | ||
244 | |||
245 | active = cause & mask; | ||
246 | |||
247 | if (active) { | ||
248 | for (hwirq = GEF_PIC_NUM_IRQS - 1; hwirq > -1; hwirq--) { | ||
249 | if (active & (0x1 << hwirq)) | ||
250 | break; | ||
251 | } | ||
252 | virq = irq_linear_revmap(gef_pic_irq_host, | ||
253 | (irq_hw_number_t)hwirq); | ||
254 | } | ||
255 | |||
256 | return virq; | ||
257 | } | ||
258 | |||
diff --git a/arch/powerpc/platforms/86xx/gef_pic.h b/arch/powerpc/platforms/86xx/gef_pic.h new file mode 100644 index 000000000000..6149916da3f4 --- /dev/null +++ b/arch/powerpc/platforms/86xx/gef_pic.h | |||
@@ -0,0 +1,11 @@ | |||
1 | #ifndef __GEF_PIC_H__ | ||
2 | #define __GEF_PIC_H__ | ||
3 | |||
4 | #include <linux/init.h> | ||
5 | |||
6 | void gef_pic_cascade(unsigned int, struct irq_desc *); | ||
7 | unsigned int gef_pic_get_irq(void); | ||
8 | void gef_pic_init(struct device_node *); | ||
9 | |||
10 | #endif /* __GEF_PIC_H__ */ | ||
11 | |||
diff --git a/arch/powerpc/platforms/86xx/gef_sbc610.c b/arch/powerpc/platforms/86xx/gef_sbc610.c index ee215002b1cf..3873c2018cc3 100644 --- a/arch/powerpc/platforms/86xx/gef_sbc610.c +++ b/arch/powerpc/platforms/86xx/gef_sbc610.c | |||
@@ -39,6 +39,7 @@ | |||
39 | #include <sysdev/fsl_soc.h> | 39 | #include <sysdev/fsl_soc.h> |
40 | 40 | ||
41 | #include "mpc86xx.h" | 41 | #include "mpc86xx.h" |
42 | #include "gef_pic.h" | ||
42 | 43 | ||
43 | #undef DEBUG | 44 | #undef DEBUG |
44 | 45 | ||
@@ -48,6 +49,28 @@ | |||
48 | #define DBG (fmt...) do { } while (0) | 49 | #define DBG (fmt...) do { } while (0) |
49 | #endif | 50 | #endif |
50 | 51 | ||
52 | void __iomem *sbc610_regs; | ||
53 | |||
54 | static void __init gef_sbc610_init_irq(void) | ||
55 | { | ||
56 | struct device_node *cascade_node = NULL; | ||
57 | |||
58 | mpc86xx_init_irq(); | ||
59 | |||
60 | /* | ||
61 | * There is a simple interrupt handler in the main FPGA, this needs | ||
62 | * to be cascaded into the MPIC | ||
63 | */ | ||
64 | cascade_node = of_find_compatible_node(NULL, NULL, "gef,fpga-pic"); | ||
65 | if (!cascade_node) { | ||
66 | printk(KERN_WARNING "SBC610: No FPGA PIC\n"); | ||
67 | return; | ||
68 | } | ||
69 | |||
70 | gef_pic_init(cascade_node); | ||
71 | of_node_put(cascade_node); | ||
72 | } | ||
73 | |||
51 | static void __init gef_sbc610_setup_arch(void) | 74 | static void __init gef_sbc610_setup_arch(void) |
52 | { | 75 | { |
53 | #ifdef CONFIG_PCI | 76 | #ifdef CONFIG_PCI |
@@ -145,7 +168,7 @@ define_machine(gef_sbc610) { | |||
145 | .name = "GE Fanuc SBC610", | 168 | .name = "GE Fanuc SBC610", |
146 | .probe = gef_sbc610_probe, | 169 | .probe = gef_sbc610_probe, |
147 | .setup_arch = gef_sbc610_setup_arch, | 170 | .setup_arch = gef_sbc610_setup_arch, |
148 | .init_IRQ = mpc86xx_init_irq, | 171 | .init_IRQ = gef_sbc610_init_irq, |
149 | .show_cpuinfo = gef_sbc610_show_cpuinfo, | 172 | .show_cpuinfo = gef_sbc610_show_cpuinfo, |
150 | .get_irq = mpic_get_irq, | 173 | .get_irq = mpic_get_irq, |
151 | .restart = fsl_rstcr_restart, | 174 | .restart = fsl_rstcr_restart, |