Commit | Line | Data |
---|---|---|
393adcac WG |
1 | /* |
2 | * Copyright (C) 2008 Ilya Yanok, Emcraft Systems | |
3 | * | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | */ | |
10 | ||
11 | #include <linux/irq.h> | |
12 | #include <linux/of_platform.h> | |
13 | #include <linux/io.h> | |
14 | ||
15 | /* | |
16 | * The FPGA supports 9 interrupt sources, which can be routed to 3 | |
17 | * interrupt request lines of the MPIC. The line to be used can be | |
18 | * specified through the third cell of FDT property "interrupts". | |
19 | */ | |
20 | ||
21 | #define SOCRATES_FPGA_NUM_IRQS 9 | |
22 | ||
23 | #define FPGA_PIC_IRQCFG (0x0) | |
24 | #define FPGA_PIC_IRQMASK(n) (0x4 + 0x4 * (n)) | |
25 | ||
26 | #define SOCRATES_FPGA_IRQ_MASK ((1 << SOCRATES_FPGA_NUM_IRQS) - 1) | |
27 | ||
28 | struct socrates_fpga_irq_info { | |
29 | unsigned int irq_line; | |
30 | int type; | |
31 | }; | |
32 | ||
33 | /* | |
34 | * Interrupt routing and type table | |
35 | * | |
36 | * IRQ_TYPE_NONE means the interrupt type is configurable, | |
37 | * otherwise it's fixed to the specified value. | |
38 | */ | |
39 | static struct socrates_fpga_irq_info fpga_irqs[SOCRATES_FPGA_NUM_IRQS] = { | |
40 | [0] = {0, IRQ_TYPE_NONE}, | |
41 | [1] = {0, IRQ_TYPE_LEVEL_HIGH}, | |
42 | [2] = {0, IRQ_TYPE_LEVEL_LOW}, | |
43 | [3] = {0, IRQ_TYPE_NONE}, | |
44 | [4] = {0, IRQ_TYPE_NONE}, | |
45 | [5] = {0, IRQ_TYPE_NONE}, | |
46 | [6] = {0, IRQ_TYPE_NONE}, | |
47 | [7] = {0, IRQ_TYPE_NONE}, | |
48 | [8] = {0, IRQ_TYPE_LEVEL_HIGH}, | |
49 | }; | |
50 | ||
7e026f72 | 51 | static DEFINE_RAW_SPINLOCK(socrates_fpga_pic_lock); |
393adcac WG |
52 | |
53 | static void __iomem *socrates_fpga_pic_iobase; | |
bae1d8f1 | 54 | static struct irq_domain *socrates_fpga_pic_irq_host; |
393adcac WG |
55 | static unsigned int socrates_fpga_irqs[3]; |
56 | ||
57 | static inline uint32_t socrates_fpga_pic_read(int reg) | |
58 | { | |
59 | return in_be32(socrates_fpga_pic_iobase + reg); | |
60 | } | |
61 | ||
62 | static inline void socrates_fpga_pic_write(int reg, uint32_t val) | |
63 | { | |
64 | out_be32(socrates_fpga_pic_iobase + reg, val); | |
65 | } | |
66 | ||
67 | static inline unsigned int socrates_fpga_pic_get_irq(unsigned int irq) | |
68 | { | |
69 | uint32_t cause; | |
70 | unsigned long flags; | |
71 | int i; | |
72 | ||
73 | /* Check irq line routed to the MPIC */ | |
74 | for (i = 0; i < 3; i++) { | |
75 | if (irq == socrates_fpga_irqs[i]) | |
76 | break; | |
77 | } | |
78 | if (i == 3) | |
79 | return NO_IRQ; | |
80 | ||
7e026f72 | 81 | raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); |
393adcac | 82 | cause = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(i)); |
7e026f72 | 83 | raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); |
393adcac WG |
84 | for (i = SOCRATES_FPGA_NUM_IRQS - 1; i >= 0; i--) { |
85 | if (cause >> (i + 16)) | |
86 | break; | |
87 | } | |
88 | return irq_linear_revmap(socrates_fpga_pic_irq_host, | |
89 | (irq_hw_number_t)i); | |
90 | } | |
91 | ||
92 | void socrates_fpga_pic_cascade(unsigned int irq, struct irq_desc *desc) | |
93 | { | |
ec775d0e | 94 | struct irq_chip *chip = irq_desc_get_chip(desc); |
393adcac WG |
95 | unsigned int cascade_irq; |
96 | ||
97 | /* | |
98 | * See if we actually have an interrupt, call generic handling code if | |
99 | * we do. | |
100 | */ | |
101 | cascade_irq = socrates_fpga_pic_get_irq(irq); | |
102 | ||
103 | if (cascade_irq != NO_IRQ) | |
104 | generic_handle_irq(cascade_irq); | |
712d5d79 | 105 | chip->irq_eoi(&desc->irq_data); |
393adcac WG |
106 | } |
107 | ||
712d5d79 | 108 | static void socrates_fpga_pic_ack(struct irq_data *d) |
393adcac WG |
109 | { |
110 | unsigned long flags; | |
476eb491 | 111 | unsigned int irq_line, hwirq = irqd_to_hwirq(d); |
393adcac WG |
112 | uint32_t mask; |
113 | ||
393adcac | 114 | irq_line = fpga_irqs[hwirq].irq_line; |
7e026f72 | 115 | raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); |
393adcac WG |
116 | mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line)) |
117 | & SOCRATES_FPGA_IRQ_MASK; | |
118 | mask |= (1 << (hwirq + 16)); | |
119 | socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask); | |
7e026f72 | 120 | raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); |
393adcac WG |
121 | } |
122 | ||
712d5d79 | 123 | static void socrates_fpga_pic_mask(struct irq_data *d) |
393adcac WG |
124 | { |
125 | unsigned long flags; | |
476eb491 | 126 | unsigned int hwirq = irqd_to_hwirq(d); |
393adcac WG |
127 | int irq_line; |
128 | u32 mask; | |
129 | ||
393adcac | 130 | irq_line = fpga_irqs[hwirq].irq_line; |
7e026f72 | 131 | raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); |
393adcac WG |
132 | mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line)) |
133 | & SOCRATES_FPGA_IRQ_MASK; | |
134 | mask &= ~(1 << hwirq); | |
135 | socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask); | |
7e026f72 | 136 | raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); |
393adcac WG |
137 | } |
138 | ||
712d5d79 | 139 | static void socrates_fpga_pic_mask_ack(struct irq_data *d) |
393adcac WG |
140 | { |
141 | unsigned long flags; | |
476eb491 | 142 | unsigned int hwirq = irqd_to_hwirq(d); |
393adcac WG |
143 | int irq_line; |
144 | u32 mask; | |
145 | ||
393adcac | 146 | irq_line = fpga_irqs[hwirq].irq_line; |
7e026f72 | 147 | raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); |
393adcac WG |
148 | mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line)) |
149 | & SOCRATES_FPGA_IRQ_MASK; | |
150 | mask &= ~(1 << hwirq); | |
151 | mask |= (1 << (hwirq + 16)); | |
152 | socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask); | |
7e026f72 | 153 | raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); |
393adcac WG |
154 | } |
155 | ||
712d5d79 | 156 | static void socrates_fpga_pic_unmask(struct irq_data *d) |
393adcac WG |
157 | { |
158 | unsigned long flags; | |
476eb491 | 159 | unsigned int hwirq = irqd_to_hwirq(d); |
393adcac WG |
160 | int irq_line; |
161 | u32 mask; | |
162 | ||
393adcac | 163 | irq_line = fpga_irqs[hwirq].irq_line; |
7e026f72 | 164 | raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); |
393adcac WG |
165 | mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line)) |
166 | & SOCRATES_FPGA_IRQ_MASK; | |
167 | mask |= (1 << hwirq); | |
168 | socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask); | |
7e026f72 | 169 | raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); |
393adcac WG |
170 | } |
171 | ||
712d5d79 | 172 | static void socrates_fpga_pic_eoi(struct irq_data *d) |
393adcac WG |
173 | { |
174 | unsigned long flags; | |
476eb491 | 175 | unsigned int hwirq = irqd_to_hwirq(d); |
393adcac WG |
176 | int irq_line; |
177 | u32 mask; | |
178 | ||
393adcac | 179 | irq_line = fpga_irqs[hwirq].irq_line; |
7e026f72 | 180 | raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); |
393adcac WG |
181 | mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line)) |
182 | & SOCRATES_FPGA_IRQ_MASK; | |
183 | mask |= (1 << (hwirq + 16)); | |
184 | socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask); | |
7e026f72 | 185 | raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); |
393adcac WG |
186 | } |
187 | ||
712d5d79 | 188 | static int socrates_fpga_pic_set_type(struct irq_data *d, |
393adcac WG |
189 | unsigned int flow_type) |
190 | { | |
191 | unsigned long flags; | |
476eb491 | 192 | unsigned int hwirq = irqd_to_hwirq(d); |
393adcac WG |
193 | int polarity; |
194 | u32 mask; | |
195 | ||
393adcac WG |
196 | if (fpga_irqs[hwirq].type != IRQ_TYPE_NONE) |
197 | return -EINVAL; | |
198 | ||
199 | switch (flow_type & IRQ_TYPE_SENSE_MASK) { | |
200 | case IRQ_TYPE_LEVEL_HIGH: | |
201 | polarity = 1; | |
202 | break; | |
203 | case IRQ_TYPE_LEVEL_LOW: | |
204 | polarity = 0; | |
205 | break; | |
206 | default: | |
207 | return -EINVAL; | |
208 | } | |
7e026f72 | 209 | raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); |
393adcac WG |
210 | mask = socrates_fpga_pic_read(FPGA_PIC_IRQCFG); |
211 | if (polarity) | |
212 | mask |= (1 << hwirq); | |
213 | else | |
214 | mask &= ~(1 << hwirq); | |
215 | socrates_fpga_pic_write(FPGA_PIC_IRQCFG, mask); | |
7e026f72 | 216 | raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); |
393adcac WG |
217 | return 0; |
218 | } | |
219 | ||
220 | static struct irq_chip socrates_fpga_pic_chip = { | |
fc380c0c | 221 | .name = "FPGA-PIC", |
712d5d79 LB |
222 | .irq_ack = socrates_fpga_pic_ack, |
223 | .irq_mask = socrates_fpga_pic_mask, | |
224 | .irq_mask_ack = socrates_fpga_pic_mask_ack, | |
225 | .irq_unmask = socrates_fpga_pic_unmask, | |
226 | .irq_eoi = socrates_fpga_pic_eoi, | |
227 | .irq_set_type = socrates_fpga_pic_set_type, | |
393adcac WG |
228 | }; |
229 | ||
bae1d8f1 | 230 | static int socrates_fpga_pic_host_map(struct irq_domain *h, unsigned int virq, |
393adcac WG |
231 | irq_hw_number_t hwirq) |
232 | { | |
233 | /* All interrupts are LEVEL sensitive */ | |
98488db9 | 234 | irq_set_status_flags(virq, IRQ_LEVEL); |
ec775d0e TG |
235 | irq_set_chip_and_handler(virq, &socrates_fpga_pic_chip, |
236 | handle_fasteoi_irq); | |
393adcac WG |
237 | |
238 | return 0; | |
239 | } | |
240 | ||
bae1d8f1 | 241 | static int socrates_fpga_pic_host_xlate(struct irq_domain *h, |
40d50cf7 | 242 | struct device_node *ct, const u32 *intspec, unsigned int intsize, |
393adcac WG |
243 | irq_hw_number_t *out_hwirq, unsigned int *out_flags) |
244 | { | |
245 | struct socrates_fpga_irq_info *fpga_irq = &fpga_irqs[intspec[0]]; | |
246 | ||
247 | *out_hwirq = intspec[0]; | |
248 | if (fpga_irq->type == IRQ_TYPE_NONE) { | |
249 | /* type is configurable */ | |
250 | if (intspec[1] != IRQ_TYPE_LEVEL_LOW && | |
251 | intspec[1] != IRQ_TYPE_LEVEL_HIGH) { | |
252 | pr_warning("FPGA PIC: invalid irq type, " | |
253 | "setting default active low\n"); | |
254 | *out_flags = IRQ_TYPE_LEVEL_LOW; | |
255 | } else { | |
256 | *out_flags = intspec[1]; | |
257 | } | |
258 | } else { | |
259 | /* type is fixed */ | |
260 | *out_flags = fpga_irq->type; | |
261 | } | |
262 | ||
263 | /* Use specified interrupt routing */ | |
264 | if (intspec[2] <= 2) | |
265 | fpga_irq->irq_line = intspec[2]; | |
266 | else | |
267 | pr_warning("FPGA PIC: invalid irq routing\n"); | |
268 | ||
269 | return 0; | |
270 | } | |
271 | ||
bae1d8f1 | 272 | static struct irq_domain_ops socrates_fpga_pic_host_ops = { |
393adcac WG |
273 | .map = socrates_fpga_pic_host_map, |
274 | .xlate = socrates_fpga_pic_host_xlate, | |
275 | }; | |
276 | ||
277 | void socrates_fpga_pic_init(struct device_node *pic) | |
278 | { | |
279 | unsigned long flags; | |
280 | int i; | |
281 | ||
bae1d8f1 | 282 | /* Setup an irq_domain structure */ |
a8db8cf0 GL |
283 | socrates_fpga_pic_irq_host = irq_domain_add_linear(pic, |
284 | SOCRATES_FPGA_NUM_IRQS, &socrates_fpga_pic_host_ops, NULL); | |
393adcac WG |
285 | if (socrates_fpga_pic_irq_host == NULL) { |
286 | pr_err("FPGA PIC: Unable to allocate host\n"); | |
287 | return; | |
288 | } | |
289 | ||
290 | for (i = 0; i < 3; i++) { | |
291 | socrates_fpga_irqs[i] = irq_of_parse_and_map(pic, i); | |
292 | if (socrates_fpga_irqs[i] == NO_IRQ) { | |
293 | pr_warning("FPGA PIC: can't get irq%d.\n", i); | |
294 | continue; | |
295 | } | |
ec775d0e TG |
296 | irq_set_chained_handler(socrates_fpga_irqs[i], |
297 | socrates_fpga_pic_cascade); | |
393adcac WG |
298 | } |
299 | ||
300 | socrates_fpga_pic_iobase = of_iomap(pic, 0); | |
301 | ||
7e026f72 | 302 | raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); |
393adcac WG |
303 | socrates_fpga_pic_write(FPGA_PIC_IRQMASK(0), |
304 | SOCRATES_FPGA_IRQ_MASK << 16); | |
305 | socrates_fpga_pic_write(FPGA_PIC_IRQMASK(1), | |
306 | SOCRATES_FPGA_IRQ_MASK << 16); | |
307 | socrates_fpga_pic_write(FPGA_PIC_IRQMASK(2), | |
308 | SOCRATES_FPGA_IRQ_MASK << 16); | |
7e026f72 | 309 | raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); |
393adcac WG |
310 | |
311 | pr_info("FPGA PIC: Setting up Socrates FPGA PIC\n"); | |
312 | } |