]>
Commit | Line | Data |
---|---|---|
3a470247 | 1 | /* |
948e78c3 | 2 | * Interrupt handling for GE FPGA based PIC |
3a470247 | 3 | * |
948e78c3 | 4 | * Author: Martyn Welch <martyn.welch@ge.com> |
3a470247 | 5 | * |
948e78c3 | 6 | * 2008 (c) GE Intelligent Platforms Embedded Systems, Inc. |
3a470247 MW |
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 | ||
44b24b74 | 25 | #include "ge_pic.h" |
3a470247 MW |
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 | ||
3a470247 | 49 | |
6f3d395a | 50 | static DEFINE_RAW_SPINLOCK(gef_pic_lock); |
3a470247 MW |
51 | |
52 | static void __iomem *gef_pic_irq_reg_base; | |
bae1d8f1 | 53 | static struct irq_domain *gef_pic_irq_host; |
3a470247 MW |
54 | static int gef_pic_cascade_irq; |
55 | ||
56 | /* | |
57 | * Interrupt Controller Handling | |
58 | * | |
59 | * The interrupt controller handles interrupts for most on board interrupts, | |
60 | * apart from PCI interrupts. For example on SBC610: | |
61 | * | |
62 | * 17:31 RO Reserved | |
63 | * 16 RO PCI Express Doorbell 3 Status | |
64 | * 15 RO PCI Express Doorbell 2 Status | |
65 | * 14 RO PCI Express Doorbell 1 Status | |
66 | * 13 RO PCI Express Doorbell 0 Status | |
67 | * 12 RO Real Time Clock Interrupt Status | |
68 | * 11 RO Temperature Interrupt Status | |
69 | * 10 RO Temperature Critical Interrupt Status | |
70 | * 9 RO Ethernet PHY1 Interrupt Status | |
71 | * 8 RO Ethernet PHY3 Interrupt Status | |
72 | * 7 RO PEX8548 Interrupt Status | |
73 | * 6 RO Reserved | |
74 | * 5 RO Watchdog 0 Interrupt Status | |
75 | * 4 RO Watchdog 1 Interrupt Status | |
76 | * 3 RO AXIS Message FIFO A Interrupt Status | |
77 | * 2 RO AXIS Message FIFO B Interrupt Status | |
78 | * 1 RO AXIS Message FIFO C Interrupt Status | |
79 | * 0 RO AXIS Message FIFO D Interrupt Status | |
80 | * | |
81 | * Interrupts can be forwarded to one of two output lines. Nothing | |
82 | * clever is done, so if the masks are incorrectly set, a single input | |
83 | * interrupt could generate interrupts on both output lines! | |
84 | * | |
85 | * The dual lines are there to allow the chained interrupts to be easily | |
86 | * passed into two different cores. We currently do not use this functionality | |
87 | * in this driver. | |
88 | * | |
89 | * Controller can also be configured to generate Machine checks (MCP), again on | |
90 | * two lines, to be attached to two different cores. It is suggested that these | |
91 | * should be masked out. | |
92 | */ | |
93 | ||
bd0b9ac4 | 94 | static void gef_pic_cascade(struct irq_desc *desc) |
3a470247 | 95 | { |
ec775d0e | 96 | struct irq_chip *chip = irq_desc_get_chip(desc); |
3a470247 MW |
97 | unsigned int cascade_irq; |
98 | ||
99 | /* | |
100 | * See if we actually have an interrupt, call generic handling code if | |
101 | * we do. | |
102 | */ | |
103 | cascade_irq = gef_pic_get_irq(); | |
104 | ||
105 | if (cascade_irq != NO_IRQ) | |
106 | generic_handle_irq(cascade_irq); | |
107 | ||
5b250889 | 108 | chip->irq_eoi(&desc->irq_data); |
3a470247 MW |
109 | } |
110 | ||
5b250889 | 111 | static void gef_pic_mask(struct irq_data *d) |
3a470247 MW |
112 | { |
113 | unsigned long flags; | |
476eb491 | 114 | unsigned int hwirq = irqd_to_hwirq(d); |
3a470247 MW |
115 | u32 mask; |
116 | ||
6f3d395a | 117 | raw_spin_lock_irqsave(&gef_pic_lock, flags); |
3a470247 MW |
118 | mask = in_be32(gef_pic_irq_reg_base + GEF_PIC_INTR_MASK(0)); |
119 | mask &= ~(1 << hwirq); | |
120 | out_be32(gef_pic_irq_reg_base + GEF_PIC_INTR_MASK(0), mask); | |
6f3d395a | 121 | raw_spin_unlock_irqrestore(&gef_pic_lock, flags); |
3a470247 MW |
122 | } |
123 | ||
5b250889 | 124 | static void gef_pic_mask_ack(struct irq_data *d) |
3a470247 MW |
125 | { |
126 | /* Don't think we actually have to do anything to ack an interrupt, | |
127 | * we just need to clear down the devices interrupt and it will go away | |
128 | */ | |
5b250889 | 129 | gef_pic_mask(d); |
3a470247 MW |
130 | } |
131 | ||
5b250889 | 132 | static void gef_pic_unmask(struct irq_data *d) |
3a470247 MW |
133 | { |
134 | unsigned long flags; | |
476eb491 | 135 | unsigned int hwirq = irqd_to_hwirq(d); |
3a470247 MW |
136 | u32 mask; |
137 | ||
6f3d395a | 138 | raw_spin_lock_irqsave(&gef_pic_lock, flags); |
3a470247 MW |
139 | mask = in_be32(gef_pic_irq_reg_base + GEF_PIC_INTR_MASK(0)); |
140 | mask |= (1 << hwirq); | |
141 | out_be32(gef_pic_irq_reg_base + GEF_PIC_INTR_MASK(0), mask); | |
6f3d395a | 142 | raw_spin_unlock_irqrestore(&gef_pic_lock, flags); |
3a470247 MW |
143 | } |
144 | ||
145 | static struct irq_chip gef_pic_chip = { | |
b27df672 | 146 | .name = "gefp", |
5b250889 LB |
147 | .irq_mask = gef_pic_mask, |
148 | .irq_mask_ack = gef_pic_mask_ack, | |
149 | .irq_unmask = gef_pic_unmask, | |
3a470247 MW |
150 | }; |
151 | ||
152 | ||
153 | /* When an interrupt is being configured, this call allows some flexibilty | |
154 | * in deciding which irq_chip structure is used | |
155 | */ | |
bae1d8f1 | 156 | static int gef_pic_host_map(struct irq_domain *h, unsigned int virq, |
3a470247 MW |
157 | irq_hw_number_t hwirq) |
158 | { | |
159 | /* All interrupts are LEVEL sensitive */ | |
98488db9 | 160 | irq_set_status_flags(virq, IRQ_LEVEL); |
ec775d0e | 161 | irq_set_chip_and_handler(virq, &gef_pic_chip, handle_level_irq); |
3a470247 MW |
162 | |
163 | return 0; | |
164 | } | |
165 | ||
bae1d8f1 | 166 | static int gef_pic_host_xlate(struct irq_domain *h, struct device_node *ct, |
40d50cf7 | 167 | const u32 *intspec, unsigned int intsize, |
3a470247 MW |
168 | irq_hw_number_t *out_hwirq, unsigned int *out_flags) |
169 | { | |
170 | ||
171 | *out_hwirq = intspec[0]; | |
172 | if (intsize > 1) | |
173 | *out_flags = intspec[1]; | |
174 | else | |
175 | *out_flags = IRQ_TYPE_LEVEL_HIGH; | |
176 | ||
177 | return 0; | |
178 | } | |
179 | ||
9f70b8eb | 180 | static const struct irq_domain_ops gef_pic_host_ops = { |
3a470247 MW |
181 | .map = gef_pic_host_map, |
182 | .xlate = gef_pic_host_xlate, | |
183 | }; | |
184 | ||
185 | ||
186 | /* | |
187 | * Initialisation of PIC, this should be called in BSP | |
188 | */ | |
189 | void __init gef_pic_init(struct device_node *np) | |
190 | { | |
191 | unsigned long flags; | |
192 | ||
193 | /* Map the devices registers into memory */ | |
194 | gef_pic_irq_reg_base = of_iomap(np, 0); | |
195 | ||
6f3d395a | 196 | raw_spin_lock_irqsave(&gef_pic_lock, flags); |
3a470247 MW |
197 | |
198 | /* Initialise everything as masked. */ | |
199 | out_be32(gef_pic_irq_reg_base + GEF_PIC_CPU0_INTR_MASK, 0); | |
200 | out_be32(gef_pic_irq_reg_base + GEF_PIC_CPU1_INTR_MASK, 0); | |
201 | ||
202 | out_be32(gef_pic_irq_reg_base + GEF_PIC_CPU0_MCP_MASK, 0); | |
203 | out_be32(gef_pic_irq_reg_base + GEF_PIC_CPU1_MCP_MASK, 0); | |
204 | ||
6f3d395a | 205 | raw_spin_unlock_irqrestore(&gef_pic_lock, flags); |
3a470247 MW |
206 | |
207 | /* Map controller */ | |
208 | gef_pic_cascade_irq = irq_of_parse_and_map(np, 0); | |
209 | if (gef_pic_cascade_irq == NO_IRQ) { | |
210 | printk(KERN_ERR "SBC610: failed to map cascade interrupt"); | |
211 | return; | |
212 | } | |
213 | ||
bae1d8f1 | 214 | /* Setup an irq_domain structure */ |
a8db8cf0 GL |
215 | gef_pic_irq_host = irq_domain_add_linear(np, GEF_PIC_NUM_IRQS, |
216 | &gef_pic_host_ops, NULL); | |
3a470247 MW |
217 | if (gef_pic_irq_host == NULL) |
218 | return; | |
219 | ||
220 | /* Chain with parent controller */ | |
ec775d0e | 221 | irq_set_chained_handler(gef_pic_cascade_irq, gef_pic_cascade); |
3a470247 MW |
222 | } |
223 | ||
224 | /* | |
225 | * This is called when we receive an interrupt with apparently comes from this | |
226 | * chip - check, returning the highest interrupt generated or return NO_IRQ | |
227 | */ | |
228 | unsigned int gef_pic_get_irq(void) | |
229 | { | |
230 | u32 cause, mask, active; | |
231 | unsigned int virq = NO_IRQ; | |
232 | int hwirq; | |
233 | ||
234 | cause = in_be32(gef_pic_irq_reg_base + GEF_PIC_INTR_STATUS); | |
235 | ||
236 | mask = in_be32(gef_pic_irq_reg_base + GEF_PIC_INTR_MASK(0)); | |
237 | ||
238 | active = cause & mask; | |
239 | ||
240 | if (active) { | |
241 | for (hwirq = GEF_PIC_NUM_IRQS - 1; hwirq > -1; hwirq--) { | |
242 | if (active & (0x1 << hwirq)) | |
243 | break; | |
244 | } | |
245 | virq = irq_linear_revmap(gef_pic_irq_host, | |
246 | (irq_hw_number_t)hwirq); | |
247 | } | |
248 | ||
249 | return virq; | |
250 | } | |
251 |