Commit | Line | Data |
---|---|---|
04e2d1e0 GJ |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * PRU-ICSS INTC IRQChip driver for various TI SoCs | |
4 | * | |
5 | * Copyright (C) 2016-2020 Texas Instruments Incorporated - http://www.ti.com/ | |
6 | * | |
7 | * Author(s): | |
8 | * Andrew F. Davis <afd@ti.com> | |
9 | * Suman Anna <s-anna@ti.com> | |
10 | * Grzegorz Jaszczyk <grzegorz.jaszczyk@linaro.org> for Texas Instruments | |
11 | * | |
12 | * Copyright (C) 2019 David Lechner <david@lechnology.com> | |
13 | */ | |
14 | ||
b1026e8a | 15 | #include <linux/interrupt.h> |
04e2d1e0 GJ |
16 | #include <linux/irq.h> |
17 | #include <linux/irqchip/chained_irq.h> | |
18 | #include <linux/irqdomain.h> | |
19 | #include <linux/module.h> | |
20 | #include <linux/of_device.h> | |
21 | #include <linux/platform_device.h> | |
22 | ||
23 | /* | |
24 | * Number of host interrupts reaching the main MPU sub-system. Note that this | |
25 | * is not the same as the total number of host interrupts supported by the PRUSS | |
26 | * INTC instance | |
27 | */ | |
28 | #define MAX_NUM_HOST_IRQS 8 | |
29 | ||
30 | /* minimum starting host interrupt number for MPU */ | |
31 | #define FIRST_PRU_HOST_INT 2 | |
32 | ||
33 | /* PRU_ICSS_INTC registers */ | |
34 | #define PRU_INTC_REVID 0x0000 | |
35 | #define PRU_INTC_CR 0x0004 | |
36 | #define PRU_INTC_GER 0x0010 | |
37 | #define PRU_INTC_GNLR 0x001c | |
38 | #define PRU_INTC_SISR 0x0020 | |
39 | #define PRU_INTC_SICR 0x0024 | |
40 | #define PRU_INTC_EISR 0x0028 | |
41 | #define PRU_INTC_EICR 0x002c | |
42 | #define PRU_INTC_HIEISR 0x0034 | |
43 | #define PRU_INTC_HIDISR 0x0038 | |
44 | #define PRU_INTC_GPIR 0x0080 | |
45 | #define PRU_INTC_SRSR(x) (0x0200 + (x) * 4) | |
46 | #define PRU_INTC_SECR(x) (0x0280 + (x) * 4) | |
47 | #define PRU_INTC_ESR(x) (0x0300 + (x) * 4) | |
48 | #define PRU_INTC_ECR(x) (0x0380 + (x) * 4) | |
49 | #define PRU_INTC_CMR(x) (0x0400 + (x) * 4) | |
50 | #define PRU_INTC_HMR(x) (0x0800 + (x) * 4) | |
51 | #define PRU_INTC_HIPIR(x) (0x0900 + (x) * 4) | |
52 | #define PRU_INTC_SIPR(x) (0x0d00 + (x) * 4) | |
53 | #define PRU_INTC_SITR(x) (0x0d80 + (x) * 4) | |
54 | #define PRU_INTC_HINLR(x) (0x1100 + (x) * 4) | |
55 | #define PRU_INTC_HIER 0x1500 | |
56 | ||
57 | /* CMR register bit-field macros */ | |
58 | #define CMR_EVT_MAP_MASK 0xf | |
59 | #define CMR_EVT_MAP_BITS 8 | |
60 | #define CMR_EVT_PER_REG 4 | |
61 | ||
62 | /* HMR register bit-field macros */ | |
63 | #define HMR_CH_MAP_MASK 0xf | |
64 | #define HMR_CH_MAP_BITS 8 | |
65 | #define HMR_CH_PER_REG 4 | |
66 | ||
67 | /* HIPIR register bit-fields */ | |
68 | #define INTC_HIPIR_NONE_HINT 0x80000000 | |
69 | ||
70 | #define MAX_PRU_SYS_EVENTS 160 | |
71 | #define MAX_PRU_CHANNELS 20 | |
72 | ||
73 | /** | |
74 | * struct pruss_intc_map_record - keeps track of actual mapping state | |
75 | * @value: The currently mapped value (channel or host) | |
76 | * @ref_count: Keeps track of number of current users of this resource | |
77 | */ | |
78 | struct pruss_intc_map_record { | |
79 | u8 value; | |
80 | u8 ref_count; | |
81 | }; | |
82 | ||
83 | /** | |
84 | * struct pruss_intc_match_data - match data to handle SoC variations | |
85 | * @num_system_events: number of input system events handled by the PRUSS INTC | |
86 | * @num_host_events: number of host events (which is equal to number of | |
87 | * channels) supported by the PRUSS INTC | |
88 | */ | |
89 | struct pruss_intc_match_data { | |
90 | u8 num_system_events; | |
91 | u8 num_host_events; | |
92 | }; | |
93 | ||
94 | /** | |
95 | * struct pruss_intc - PRUSS interrupt controller structure | |
96 | * @event_channel: current state of system event to channel mappings | |
97 | * @channel_host: current state of channel to host mappings | |
98 | * @irqs: kernel irq numbers corresponding to PRUSS host interrupts | |
99 | * @base: base virtual address of INTC register space | |
100 | * @domain: irq domain for this interrupt controller | |
101 | * @soc_config: cached PRUSS INTC IP configuration data | |
102 | * @dev: PRUSS INTC device pointer | |
103 | * @lock: mutex to serialize interrupts mapping | |
104 | */ | |
105 | struct pruss_intc { | |
106 | struct pruss_intc_map_record event_channel[MAX_PRU_SYS_EVENTS]; | |
107 | struct pruss_intc_map_record channel_host[MAX_PRU_CHANNELS]; | |
108 | unsigned int irqs[MAX_NUM_HOST_IRQS]; | |
109 | void __iomem *base; | |
110 | struct irq_domain *domain; | |
111 | const struct pruss_intc_match_data *soc_config; | |
112 | struct device *dev; | |
113 | struct mutex lock; /* PRUSS INTC lock */ | |
114 | }; | |
115 | ||
116 | /** | |
117 | * struct pruss_host_irq_data - PRUSS host irq data structure | |
118 | * @intc: PRUSS interrupt controller pointer | |
119 | * @host_irq: host irq number | |
120 | */ | |
121 | struct pruss_host_irq_data { | |
122 | struct pruss_intc *intc; | |
123 | u8 host_irq; | |
124 | }; | |
125 | ||
126 | static inline u32 pruss_intc_read_reg(struct pruss_intc *intc, unsigned int reg) | |
127 | { | |
128 | return readl_relaxed(intc->base + reg); | |
129 | } | |
130 | ||
131 | static inline void pruss_intc_write_reg(struct pruss_intc *intc, | |
132 | unsigned int reg, u32 val) | |
133 | { | |
134 | writel_relaxed(val, intc->base + reg); | |
135 | } | |
136 | ||
137 | static void pruss_intc_update_cmr(struct pruss_intc *intc, unsigned int evt, | |
138 | u8 ch) | |
139 | { | |
140 | u32 idx, offset, val; | |
141 | ||
142 | idx = evt / CMR_EVT_PER_REG; | |
143 | offset = (evt % CMR_EVT_PER_REG) * CMR_EVT_MAP_BITS; | |
144 | ||
145 | val = pruss_intc_read_reg(intc, PRU_INTC_CMR(idx)); | |
146 | val &= ~(CMR_EVT_MAP_MASK << offset); | |
147 | val |= ch << offset; | |
148 | pruss_intc_write_reg(intc, PRU_INTC_CMR(idx), val); | |
149 | ||
150 | dev_dbg(intc->dev, "SYSEV%u -> CH%d (CMR%d 0x%08x)\n", evt, ch, | |
151 | idx, pruss_intc_read_reg(intc, PRU_INTC_CMR(idx))); | |
152 | } | |
153 | ||
154 | static void pruss_intc_update_hmr(struct pruss_intc *intc, u8 ch, u8 host) | |
155 | { | |
156 | u32 idx, offset, val; | |
157 | ||
158 | idx = ch / HMR_CH_PER_REG; | |
159 | offset = (ch % HMR_CH_PER_REG) * HMR_CH_MAP_BITS; | |
160 | ||
161 | val = pruss_intc_read_reg(intc, PRU_INTC_HMR(idx)); | |
162 | val &= ~(HMR_CH_MAP_MASK << offset); | |
163 | val |= host << offset; | |
164 | pruss_intc_write_reg(intc, PRU_INTC_HMR(idx), val); | |
165 | ||
166 | dev_dbg(intc->dev, "CH%d -> HOST%d (HMR%d 0x%08x)\n", ch, host, idx, | |
167 | pruss_intc_read_reg(intc, PRU_INTC_HMR(idx))); | |
168 | } | |
169 | ||
170 | /** | |
171 | * pruss_intc_map() - configure the PRUSS INTC | |
172 | * @intc: PRUSS interrupt controller pointer | |
173 | * @hwirq: the system event number | |
174 | * | |
175 | * Configures the PRUSS INTC with the provided configuration from the one parsed | |
176 | * in the xlate function. | |
177 | */ | |
178 | static void pruss_intc_map(struct pruss_intc *intc, unsigned long hwirq) | |
179 | { | |
180 | struct device *dev = intc->dev; | |
181 | u8 ch, host, reg_idx; | |
182 | u32 val; | |
183 | ||
184 | mutex_lock(&intc->lock); | |
185 | ||
186 | intc->event_channel[hwirq].ref_count++; | |
187 | ||
188 | ch = intc->event_channel[hwirq].value; | |
189 | host = intc->channel_host[ch].value; | |
190 | ||
191 | pruss_intc_update_cmr(intc, hwirq, ch); | |
192 | ||
193 | reg_idx = hwirq / 32; | |
194 | val = BIT(hwirq % 32); | |
195 | ||
196 | /* clear and enable system event */ | |
197 | pruss_intc_write_reg(intc, PRU_INTC_ESR(reg_idx), val); | |
198 | pruss_intc_write_reg(intc, PRU_INTC_SECR(reg_idx), val); | |
199 | ||
200 | if (++intc->channel_host[ch].ref_count == 1) { | |
201 | pruss_intc_update_hmr(intc, ch, host); | |
202 | ||
203 | /* enable host interrupts */ | |
204 | pruss_intc_write_reg(intc, PRU_INTC_HIEISR, host); | |
205 | } | |
206 | ||
207 | dev_dbg(dev, "mapped system_event = %lu channel = %d host = %d", | |
208 | hwirq, ch, host); | |
209 | ||
210 | mutex_unlock(&intc->lock); | |
211 | } | |
212 | ||
213 | /** | |
214 | * pruss_intc_unmap() - unconfigure the PRUSS INTC | |
215 | * @intc: PRUSS interrupt controller pointer | |
216 | * @hwirq: the system event number | |
217 | * | |
218 | * Undo whatever was done in pruss_intc_map() for a PRU core. | |
219 | * Mappings are reference counted, so resources are only disabled when there | |
220 | * are no longer any users. | |
221 | */ | |
222 | static void pruss_intc_unmap(struct pruss_intc *intc, unsigned long hwirq) | |
223 | { | |
224 | u8 ch, host, reg_idx; | |
225 | u32 val; | |
226 | ||
227 | mutex_lock(&intc->lock); | |
228 | ||
229 | ch = intc->event_channel[hwirq].value; | |
230 | host = intc->channel_host[ch].value; | |
231 | ||
232 | if (--intc->channel_host[ch].ref_count == 0) { | |
233 | /* disable host interrupts */ | |
234 | pruss_intc_write_reg(intc, PRU_INTC_HIDISR, host); | |
235 | ||
236 | /* clear the map using reset value 0 */ | |
237 | pruss_intc_update_hmr(intc, ch, 0); | |
238 | } | |
239 | ||
240 | intc->event_channel[hwirq].ref_count--; | |
241 | reg_idx = hwirq / 32; | |
242 | val = BIT(hwirq % 32); | |
243 | ||
244 | /* disable system events */ | |
245 | pruss_intc_write_reg(intc, PRU_INTC_ECR(reg_idx), val); | |
246 | /* clear any pending status */ | |
247 | pruss_intc_write_reg(intc, PRU_INTC_SECR(reg_idx), val); | |
248 | ||
249 | /* clear the map using reset value 0 */ | |
250 | pruss_intc_update_cmr(intc, hwirq, 0); | |
251 | ||
252 | dev_dbg(intc->dev, "unmapped system_event = %lu channel = %d host = %d\n", | |
253 | hwirq, ch, host); | |
254 | ||
255 | mutex_unlock(&intc->lock); | |
256 | } | |
257 | ||
258 | static void pruss_intc_init(struct pruss_intc *intc) | |
259 | { | |
260 | const struct pruss_intc_match_data *soc_config = intc->soc_config; | |
261 | int num_chnl_map_regs, num_host_intr_regs, num_event_type_regs, i; | |
262 | ||
263 | num_chnl_map_regs = DIV_ROUND_UP(soc_config->num_system_events, | |
264 | CMR_EVT_PER_REG); | |
265 | num_host_intr_regs = DIV_ROUND_UP(soc_config->num_host_events, | |
266 | HMR_CH_PER_REG); | |
267 | num_event_type_regs = DIV_ROUND_UP(soc_config->num_system_events, 32); | |
268 | ||
269 | /* | |
270 | * configure polarity (SIPR register) to active high and | |
271 | * type (SITR register) to level interrupt for all system events | |
272 | */ | |
273 | for (i = 0; i < num_event_type_regs; i++) { | |
274 | pruss_intc_write_reg(intc, PRU_INTC_SIPR(i), 0xffffffff); | |
275 | pruss_intc_write_reg(intc, PRU_INTC_SITR(i), 0); | |
276 | } | |
277 | ||
278 | /* clear all interrupt channel map registers, 4 events per register */ | |
279 | for (i = 0; i < num_chnl_map_regs; i++) | |
280 | pruss_intc_write_reg(intc, PRU_INTC_CMR(i), 0); | |
281 | ||
282 | /* clear all host interrupt map registers, 4 channels per register */ | |
283 | for (i = 0; i < num_host_intr_regs; i++) | |
284 | pruss_intc_write_reg(intc, PRU_INTC_HMR(i), 0); | |
285 | ||
286 | /* global interrupt enable */ | |
287 | pruss_intc_write_reg(intc, PRU_INTC_GER, 1); | |
288 | } | |
289 | ||
290 | static void pruss_intc_irq_ack(struct irq_data *data) | |
291 | { | |
292 | struct pruss_intc *intc = irq_data_get_irq_chip_data(data); | |
293 | unsigned int hwirq = data->hwirq; | |
294 | ||
295 | pruss_intc_write_reg(intc, PRU_INTC_SICR, hwirq); | |
296 | } | |
297 | ||
298 | static void pruss_intc_irq_mask(struct irq_data *data) | |
299 | { | |
300 | struct pruss_intc *intc = irq_data_get_irq_chip_data(data); | |
301 | unsigned int hwirq = data->hwirq; | |
302 | ||
303 | pruss_intc_write_reg(intc, PRU_INTC_EICR, hwirq); | |
304 | } | |
305 | ||
306 | static void pruss_intc_irq_unmask(struct irq_data *data) | |
307 | { | |
308 | struct pruss_intc *intc = irq_data_get_irq_chip_data(data); | |
309 | unsigned int hwirq = data->hwirq; | |
310 | ||
311 | pruss_intc_write_reg(intc, PRU_INTC_EISR, hwirq); | |
312 | } | |
313 | ||
314 | static int pruss_intc_irq_reqres(struct irq_data *data) | |
315 | { | |
316 | if (!try_module_get(THIS_MODULE)) | |
317 | return -ENODEV; | |
318 | ||
319 | return 0; | |
320 | } | |
321 | ||
322 | static void pruss_intc_irq_relres(struct irq_data *data) | |
323 | { | |
324 | module_put(THIS_MODULE); | |
325 | } | |
326 | ||
b1026e8a DL |
327 | static int pruss_intc_irq_get_irqchip_state(struct irq_data *data, |
328 | enum irqchip_irq_state which, | |
329 | bool *state) | |
330 | { | |
331 | struct pruss_intc *intc = irq_data_get_irq_chip_data(data); | |
332 | u32 reg, mask, srsr; | |
333 | ||
334 | if (which != IRQCHIP_STATE_PENDING) | |
335 | return -EINVAL; | |
336 | ||
337 | reg = PRU_INTC_SRSR(data->hwirq / 32); | |
338 | mask = BIT(data->hwirq % 32); | |
339 | ||
340 | srsr = pruss_intc_read_reg(intc, reg); | |
341 | ||
342 | *state = !!(srsr & mask); | |
343 | ||
344 | return 0; | |
345 | } | |
346 | ||
347 | static int pruss_intc_irq_set_irqchip_state(struct irq_data *data, | |
348 | enum irqchip_irq_state which, | |
349 | bool state) | |
350 | { | |
351 | struct pruss_intc *intc = irq_data_get_irq_chip_data(data); | |
352 | ||
353 | if (which != IRQCHIP_STATE_PENDING) | |
354 | return -EINVAL; | |
355 | ||
356 | if (state) | |
357 | pruss_intc_write_reg(intc, PRU_INTC_SISR, data->hwirq); | |
358 | else | |
359 | pruss_intc_write_reg(intc, PRU_INTC_SICR, data->hwirq); | |
360 | ||
361 | return 0; | |
362 | } | |
363 | ||
04e2d1e0 GJ |
364 | static struct irq_chip pruss_irqchip = { |
365 | .name = "pruss-intc", | |
366 | .irq_ack = pruss_intc_irq_ack, | |
367 | .irq_mask = pruss_intc_irq_mask, | |
368 | .irq_unmask = pruss_intc_irq_unmask, | |
369 | .irq_request_resources = pruss_intc_irq_reqres, | |
370 | .irq_release_resources = pruss_intc_irq_relres, | |
b1026e8a DL |
371 | .irq_get_irqchip_state = pruss_intc_irq_get_irqchip_state, |
372 | .irq_set_irqchip_state = pruss_intc_irq_set_irqchip_state, | |
04e2d1e0 GJ |
373 | }; |
374 | ||
375 | static int pruss_intc_validate_mapping(struct pruss_intc *intc, int event, | |
376 | int channel, int host) | |
377 | { | |
378 | struct device *dev = intc->dev; | |
379 | int ret = 0; | |
380 | ||
381 | mutex_lock(&intc->lock); | |
382 | ||
383 | /* check if sysevent already assigned */ | |
384 | if (intc->event_channel[event].ref_count > 0 && | |
385 | intc->event_channel[event].value != channel) { | |
386 | dev_err(dev, "event %d (req. ch %d) already assigned to channel %d\n", | |
387 | event, channel, intc->event_channel[event].value); | |
388 | ret = -EBUSY; | |
389 | goto unlock; | |
390 | } | |
391 | ||
392 | /* check if channel already assigned */ | |
393 | if (intc->channel_host[channel].ref_count > 0 && | |
394 | intc->channel_host[channel].value != host) { | |
395 | dev_err(dev, "channel %d (req. host %d) already assigned to host %d\n", | |
396 | channel, host, intc->channel_host[channel].value); | |
397 | ret = -EBUSY; | |
398 | goto unlock; | |
399 | } | |
400 | ||
401 | intc->event_channel[event].value = channel; | |
402 | intc->channel_host[channel].value = host; | |
403 | ||
404 | unlock: | |
405 | mutex_unlock(&intc->lock); | |
406 | return ret; | |
407 | } | |
408 | ||
409 | static int | |
410 | pruss_intc_irq_domain_xlate(struct irq_domain *d, struct device_node *node, | |
411 | const u32 *intspec, unsigned int intsize, | |
412 | unsigned long *out_hwirq, unsigned int *out_type) | |
413 | { | |
414 | struct pruss_intc *intc = d->host_data; | |
415 | struct device *dev = intc->dev; | |
416 | int ret, sys_event, channel, host; | |
417 | ||
418 | if (intsize < 3) | |
419 | return -EINVAL; | |
420 | ||
421 | sys_event = intspec[0]; | |
422 | if (sys_event < 0 || sys_event >= intc->soc_config->num_system_events) { | |
423 | dev_err(dev, "%d is not valid event number\n", sys_event); | |
424 | return -EINVAL; | |
425 | } | |
426 | ||
427 | channel = intspec[1]; | |
428 | if (channel < 0 || channel >= intc->soc_config->num_host_events) { | |
429 | dev_err(dev, "%d is not valid channel number", channel); | |
430 | return -EINVAL; | |
431 | } | |
432 | ||
433 | host = intspec[2]; | |
434 | if (host < 0 || host >= intc->soc_config->num_host_events) { | |
435 | dev_err(dev, "%d is not valid host irq number\n", host); | |
436 | return -EINVAL; | |
437 | } | |
438 | ||
439 | /* check if requested sys_event was already mapped, if so validate it */ | |
440 | ret = pruss_intc_validate_mapping(intc, sys_event, channel, host); | |
441 | if (ret) | |
442 | return ret; | |
443 | ||
444 | *out_hwirq = sys_event; | |
445 | *out_type = IRQ_TYPE_LEVEL_HIGH; | |
446 | ||
447 | return 0; | |
448 | } | |
449 | ||
450 | static int pruss_intc_irq_domain_map(struct irq_domain *d, unsigned int virq, | |
451 | irq_hw_number_t hw) | |
452 | { | |
453 | struct pruss_intc *intc = d->host_data; | |
454 | ||
455 | pruss_intc_map(intc, hw); | |
456 | ||
457 | irq_set_chip_data(virq, intc); | |
458 | irq_set_chip_and_handler(virq, &pruss_irqchip, handle_level_irq); | |
459 | ||
460 | return 0; | |
461 | } | |
462 | ||
463 | static void pruss_intc_irq_domain_unmap(struct irq_domain *d, unsigned int virq) | |
464 | { | |
465 | struct pruss_intc *intc = d->host_data; | |
466 | unsigned long hwirq = irqd_to_hwirq(irq_get_irq_data(virq)); | |
467 | ||
468 | irq_set_chip_and_handler(virq, NULL, NULL); | |
469 | irq_set_chip_data(virq, NULL); | |
470 | pruss_intc_unmap(intc, hwirq); | |
471 | } | |
472 | ||
473 | static const struct irq_domain_ops pruss_intc_irq_domain_ops = { | |
474 | .xlate = pruss_intc_irq_domain_xlate, | |
475 | .map = pruss_intc_irq_domain_map, | |
476 | .unmap = pruss_intc_irq_domain_unmap, | |
477 | }; | |
478 | ||
479 | static void pruss_intc_irq_handler(struct irq_desc *desc) | |
480 | { | |
481 | unsigned int irq = irq_desc_get_irq(desc); | |
482 | struct irq_chip *chip = irq_desc_get_chip(desc); | |
483 | struct pruss_host_irq_data *host_irq_data = irq_get_handler_data(irq); | |
484 | struct pruss_intc *intc = host_irq_data->intc; | |
485 | u8 host_irq = host_irq_data->host_irq + FIRST_PRU_HOST_INT; | |
486 | ||
487 | chained_irq_enter(chip, desc); | |
488 | ||
489 | while (true) { | |
490 | u32 hipir; | |
491 | unsigned int virq; | |
492 | int hwirq; | |
493 | ||
494 | /* get highest priority pending PRUSS system event */ | |
495 | hipir = pruss_intc_read_reg(intc, PRU_INTC_HIPIR(host_irq)); | |
496 | if (hipir & INTC_HIPIR_NONE_HINT) | |
497 | break; | |
498 | ||
499 | hwirq = hipir & GENMASK(9, 0); | |
500 | virq = irq_find_mapping(intc->domain, hwirq); | |
501 | ||
502 | /* | |
503 | * NOTE: manually ACK any system events that do not have a | |
504 | * handler mapped yet | |
505 | */ | |
506 | if (WARN_ON_ONCE(!virq)) | |
507 | pruss_intc_write_reg(intc, PRU_INTC_SICR, hwirq); | |
508 | else | |
509 | generic_handle_irq(virq); | |
510 | } | |
511 | ||
512 | chained_irq_exit(chip, desc); | |
513 | } | |
514 | ||
515 | static const char * const irq_names[MAX_NUM_HOST_IRQS] = { | |
516 | "host_intr0", "host_intr1", "host_intr2", "host_intr3", | |
517 | "host_intr4", "host_intr5", "host_intr6", "host_intr7", | |
518 | }; | |
519 | ||
520 | static int pruss_intc_probe(struct platform_device *pdev) | |
521 | { | |
522 | const struct pruss_intc_match_data *data; | |
523 | struct device *dev = &pdev->dev; | |
524 | struct pruss_intc *intc; | |
525 | struct pruss_host_irq_data *host_data; | |
526 | int i, irq, ret; | |
6016f32d | 527 | u8 max_system_events, irqs_reserved = 0; |
04e2d1e0 GJ |
528 | |
529 | data = of_device_get_match_data(dev); | |
530 | if (!data) | |
531 | return -ENODEV; | |
532 | ||
533 | max_system_events = data->num_system_events; | |
534 | ||
535 | intc = devm_kzalloc(dev, sizeof(*intc), GFP_KERNEL); | |
536 | if (!intc) | |
537 | return -ENOMEM; | |
538 | ||
539 | intc->soc_config = data; | |
540 | intc->dev = dev; | |
541 | platform_set_drvdata(pdev, intc); | |
542 | ||
543 | intc->base = devm_platform_ioremap_resource(pdev, 0); | |
544 | if (IS_ERR(intc->base)) | |
545 | return PTR_ERR(intc->base); | |
546 | ||
6016f32d SA |
547 | ret = of_property_read_u8(dev->of_node, "ti,irqs-reserved", |
548 | &irqs_reserved); | |
549 | ||
550 | /* | |
551 | * The irqs-reserved is used only for some SoC's therefore not having | |
552 | * this property is still valid | |
553 | */ | |
554 | if (ret < 0 && ret != -EINVAL) | |
555 | return ret; | |
556 | ||
04e2d1e0 GJ |
557 | pruss_intc_init(intc); |
558 | ||
559 | mutex_init(&intc->lock); | |
560 | ||
561 | intc->domain = irq_domain_add_linear(dev->of_node, max_system_events, | |
562 | &pruss_intc_irq_domain_ops, intc); | |
563 | if (!intc->domain) | |
564 | return -ENOMEM; | |
565 | ||
566 | for (i = 0; i < MAX_NUM_HOST_IRQS; i++) { | |
6016f32d SA |
567 | if (irqs_reserved & BIT(i)) |
568 | continue; | |
569 | ||
04e2d1e0 GJ |
570 | irq = platform_get_irq_byname(pdev, irq_names[i]); |
571 | if (irq <= 0) { | |
572 | ret = (irq == 0) ? -EINVAL : irq; | |
573 | goto fail_irq; | |
574 | } | |
575 | ||
576 | intc->irqs[i] = irq; | |
577 | ||
578 | host_data = devm_kzalloc(dev, sizeof(*host_data), GFP_KERNEL); | |
579 | if (!host_data) { | |
580 | ret = -ENOMEM; | |
581 | goto fail_irq; | |
582 | } | |
583 | ||
584 | host_data->intc = intc; | |
585 | host_data->host_irq = i; | |
586 | ||
587 | irq_set_handler_data(irq, host_data); | |
588 | irq_set_chained_handler(irq, pruss_intc_irq_handler); | |
589 | } | |
590 | ||
591 | return 0; | |
592 | ||
593 | fail_irq: | |
6016f32d SA |
594 | while (--i >= 0) { |
595 | if (intc->irqs[i]) | |
596 | irq_set_chained_handler_and_data(intc->irqs[i], NULL, | |
597 | NULL); | |
598 | } | |
04e2d1e0 GJ |
599 | |
600 | irq_domain_remove(intc->domain); | |
601 | ||
602 | return ret; | |
603 | } | |
604 | ||
605 | static int pruss_intc_remove(struct platform_device *pdev) | |
606 | { | |
607 | struct pruss_intc *intc = platform_get_drvdata(pdev); | |
608 | u8 max_system_events = intc->soc_config->num_system_events; | |
609 | unsigned int hwirq; | |
610 | int i; | |
611 | ||
6016f32d SA |
612 | for (i = 0; i < MAX_NUM_HOST_IRQS; i++) { |
613 | if (intc->irqs[i]) | |
614 | irq_set_chained_handler_and_data(intc->irqs[i], NULL, | |
615 | NULL); | |
616 | } | |
04e2d1e0 GJ |
617 | |
618 | for (hwirq = 0; hwirq < max_system_events; hwirq++) | |
619 | irq_dispose_mapping(irq_find_mapping(intc->domain, hwirq)); | |
620 | ||
621 | irq_domain_remove(intc->domain); | |
622 | ||
623 | return 0; | |
624 | } | |
625 | ||
626 | static const struct pruss_intc_match_data pruss_intc_data = { | |
627 | .num_system_events = 64, | |
628 | .num_host_events = 10, | |
629 | }; | |
630 | ||
7e92dee6 SA |
631 | static const struct pruss_intc_match_data icssg_intc_data = { |
632 | .num_system_events = 160, | |
633 | .num_host_events = 20, | |
634 | }; | |
635 | ||
04e2d1e0 GJ |
636 | static const struct of_device_id pruss_intc_of_match[] = { |
637 | { | |
638 | .compatible = "ti,pruss-intc", | |
639 | .data = &pruss_intc_data, | |
640 | }, | |
7e92dee6 SA |
641 | { |
642 | .compatible = "ti,icssg-intc", | |
643 | .data = &icssg_intc_data, | |
644 | }, | |
04e2d1e0 GJ |
645 | { /* sentinel */ }, |
646 | }; | |
647 | MODULE_DEVICE_TABLE(of, pruss_intc_of_match); | |
648 | ||
649 | static struct platform_driver pruss_intc_driver = { | |
650 | .driver = { | |
651 | .name = "pruss-intc", | |
652 | .of_match_table = pruss_intc_of_match, | |
653 | .suppress_bind_attrs = true, | |
654 | }, | |
655 | .probe = pruss_intc_probe, | |
656 | .remove = pruss_intc_remove, | |
657 | }; | |
658 | module_platform_driver(pruss_intc_driver); | |
659 | ||
660 | MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>"); | |
661 | MODULE_AUTHOR("Suman Anna <s-anna@ti.com>"); | |
662 | MODULE_AUTHOR("Grzegorz Jaszczyk <grzegorz.jaszczyk@linaro.org>"); | |
663 | MODULE_DESCRIPTION("TI PRU-ICSS INTC Driver"); | |
664 | MODULE_LICENSE("GPL v2"); |