diff options
-rw-r--r-- | drivers/mfd/twl6030-irq.c | 119 |
1 files changed, 69 insertions, 50 deletions
diff --git a/drivers/mfd/twl6030-irq.c b/drivers/mfd/twl6030-irq.c index f7da2614de80..1b03ce9e9f15 100644 --- a/drivers/mfd/twl6030-irq.c +++ b/drivers/mfd/twl6030-irq.c | |||
@@ -86,11 +86,11 @@ static int twl6030_interrupt_mapping[24] = { | |||
86 | }; | 86 | }; |
87 | /*----------------------------------------------------------------------*/ | 87 | /*----------------------------------------------------------------------*/ |
88 | 88 | ||
89 | static unsigned twl6030_irq_base; | ||
90 | static int twl_irq; | 89 | static int twl_irq; |
91 | static bool twl_irq_wake_enabled; | 90 | static bool twl_irq_wake_enabled; |
92 | 91 | ||
93 | static atomic_t twl6030_wakeirqs = ATOMIC_INIT(0); | 92 | static atomic_t twl6030_wakeirqs = ATOMIC_INIT(0); |
93 | struct irq_domain *irq_domain; | ||
94 | 94 | ||
95 | static int twl6030_irq_pm_notifier(struct notifier_block *notifier, | 95 | static int twl6030_irq_pm_notifier(struct notifier_block *notifier, |
96 | unsigned long pm_event, void *unused) | 96 | unsigned long pm_event, void *unused) |
@@ -138,6 +138,7 @@ static struct notifier_block twl6030_irq_pm_notifier_block = { | |||
138 | static irqreturn_t twl6030_irq_thread(int irq, void *data) | 138 | static irqreturn_t twl6030_irq_thread(int irq, void *data) |
139 | { | 139 | { |
140 | int i, ret; | 140 | int i, ret; |
141 | struct irq_domain *irq_domain = (struct irq_domain *)data; | ||
141 | union { | 142 | union { |
142 | u8 bytes[4]; | 143 | u8 bytes[4]; |
143 | u32 int_sts; | 144 | u32 int_sts; |
@@ -161,9 +162,14 @@ static irqreturn_t twl6030_irq_thread(int irq, void *data) | |||
161 | 162 | ||
162 | for (i = 0; sts.int_sts; sts.int_sts >>= 1, i++) | 163 | for (i = 0; sts.int_sts; sts.int_sts >>= 1, i++) |
163 | if (sts.int_sts & 0x1) { | 164 | if (sts.int_sts & 0x1) { |
164 | int module_irq = twl6030_irq_base + | 165 | int module_irq = |
165 | twl6030_interrupt_mapping[i]; | 166 | irq_find_mapping(irq_domain, |
166 | handle_nested_irq(module_irq); | 167 | twl6030_interrupt_mapping[i]); |
168 | if (module_irq) | ||
169 | handle_nested_irq(module_irq); | ||
170 | else | ||
171 | pr_err("twl6030_irq: Unmapped PIH ISR %u detected\n", | ||
172 | i); | ||
167 | pr_debug("twl6030_irq: PIH ISR %u, virq%u\n", | 173 | pr_debug("twl6030_irq: PIH ISR %u, virq%u\n", |
168 | i, module_irq); | 174 | i, module_irq); |
169 | } | 175 | } |
@@ -186,19 +192,6 @@ static irqreturn_t twl6030_irq_thread(int irq, void *data) | |||
186 | 192 | ||
187 | /*----------------------------------------------------------------------*/ | 193 | /*----------------------------------------------------------------------*/ |
188 | 194 | ||
189 | static inline void activate_irq(int irq) | ||
190 | { | ||
191 | #ifdef CONFIG_ARM | ||
192 | /* ARM requires an extra step to clear IRQ_NOREQUEST, which it | ||
193 | * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE. | ||
194 | */ | ||
195 | set_irq_flags(irq, IRQF_VALID); | ||
196 | #else | ||
197 | /* same effect on other architectures */ | ||
198 | irq_set_noprobe(irq); | ||
199 | #endif | ||
200 | } | ||
201 | |||
202 | static int twl6030_irq_set_wake(struct irq_data *d, unsigned int on) | 195 | static int twl6030_irq_set_wake(struct irq_data *d, unsigned int on) |
203 | { | 196 | { |
204 | if (on) | 197 | if (on) |
@@ -279,7 +272,7 @@ int twl6030_mmc_card_detect_config(void) | |||
279 | return ret; | 272 | return ret; |
280 | } | 273 | } |
281 | 274 | ||
282 | return twl6030_irq_base + MMCDETECT_INTR_OFFSET; | 275 | return irq_find_mapping(irq_domain, MMCDETECT_INTR_OFFSET); |
283 | } | 276 | } |
284 | EXPORT_SYMBOL(twl6030_mmc_card_detect_config); | 277 | EXPORT_SYMBOL(twl6030_mmc_card_detect_config); |
285 | 278 | ||
@@ -308,28 +301,54 @@ int twl6030_mmc_card_detect(struct device *dev, int slot) | |||
308 | } | 301 | } |
309 | EXPORT_SYMBOL(twl6030_mmc_card_detect); | 302 | EXPORT_SYMBOL(twl6030_mmc_card_detect); |
310 | 303 | ||
304 | static struct irq_chip twl6030_irq_chip; | ||
305 | |||
306 | static int twl6030_irq_map(struct irq_domain *d, unsigned int virq, | ||
307 | irq_hw_number_t hwirq) | ||
308 | { | ||
309 | irq_set_chip_data(virq, &twl6030_irq_chip); | ||
310 | irq_set_chip_and_handler(virq, &twl6030_irq_chip, handle_simple_irq); | ||
311 | irq_set_nested_thread(virq, true); | ||
312 | irq_set_parent(virq, twl_irq); | ||
313 | |||
314 | #ifdef CONFIG_ARM | ||
315 | /* | ||
316 | * ARM requires an extra step to clear IRQ_NOREQUEST, which it | ||
317 | * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE. | ||
318 | */ | ||
319 | set_irq_flags(virq, IRQF_VALID); | ||
320 | #else | ||
321 | /* same effect on other architectures */ | ||
322 | irq_set_noprobe(virq); | ||
323 | #endif | ||
324 | |||
325 | return 0; | ||
326 | } | ||
327 | |||
328 | static void twl6030_irq_unmap(struct irq_domain *d, unsigned int virq) | ||
329 | { | ||
330 | #ifdef CONFIG_ARM | ||
331 | set_irq_flags(virq, 0); | ||
332 | #endif | ||
333 | irq_set_chip_and_handler(virq, NULL, NULL); | ||
334 | irq_set_chip_data(virq, NULL); | ||
335 | } | ||
336 | |||
337 | static struct irq_domain_ops twl6030_irq_domain_ops = { | ||
338 | .map = twl6030_irq_map, | ||
339 | .unmap = twl6030_irq_unmap, | ||
340 | .xlate = irq_domain_xlate_onetwocell, | ||
341 | }; | ||
342 | |||
311 | int twl6030_init_irq(struct device *dev, int irq_num) | 343 | int twl6030_init_irq(struct device *dev, int irq_num) |
312 | { | 344 | { |
313 | struct device_node *node = dev->of_node; | 345 | struct device_node *node = dev->of_node; |
314 | int nr_irqs, irq_base, irq_end; | 346 | int nr_irqs; |
315 | static struct irq_chip twl6030_irq_chip; | ||
316 | int status; | 347 | int status; |
317 | int i; | ||
318 | u8 mask[3]; | 348 | u8 mask[3]; |
319 | 349 | ||
320 | nr_irqs = TWL6030_NR_IRQS; | 350 | nr_irqs = TWL6030_NR_IRQS; |
321 | 351 | ||
322 | irq_base = irq_alloc_descs(-1, 0, nr_irqs, 0); | ||
323 | if (IS_ERR_VALUE(irq_base)) { | ||
324 | dev_err(dev, "Fail to allocate IRQ descs\n"); | ||
325 | return irq_base; | ||
326 | } | ||
327 | |||
328 | irq_domain_add_legacy(node, nr_irqs, irq_base, 0, | ||
329 | &irq_domain_simple_ops, NULL); | ||
330 | |||
331 | irq_end = irq_base + nr_irqs; | ||
332 | |||
333 | mask[0] = 0xFF; | 352 | mask[0] = 0xFF; |
334 | mask[1] = 0xFF; | 353 | mask[1] = 0xFF; |
335 | mask[2] = 0xFF; | 354 | mask[2] = 0xFF; |
@@ -346,8 +365,6 @@ int twl6030_init_irq(struct device *dev, int irq_num) | |||
346 | return status; | 365 | return status; |
347 | } | 366 | } |
348 | 367 | ||
349 | twl6030_irq_base = irq_base; | ||
350 | |||
351 | /* | 368 | /* |
352 | * install an irq handler for each of the modules; | 369 | * install an irq handler for each of the modules; |
353 | * clone dummy irq_chip since PIH can't *do* anything | 370 | * clone dummy irq_chip since PIH can't *do* anything |
@@ -357,21 +374,18 @@ int twl6030_init_irq(struct device *dev, int irq_num) | |||
357 | twl6030_irq_chip.irq_set_type = NULL; | 374 | twl6030_irq_chip.irq_set_type = NULL; |
358 | twl6030_irq_chip.irq_set_wake = twl6030_irq_set_wake; | 375 | twl6030_irq_chip.irq_set_wake = twl6030_irq_set_wake; |
359 | 376 | ||
360 | for (i = irq_base; i < irq_end; i++) { | 377 | irq_domain = irq_domain_add_linear(node, nr_irqs, |
361 | irq_set_chip_and_handler(i, &twl6030_irq_chip, | 378 | &twl6030_irq_domain_ops, NULL); |
362 | handle_simple_irq); | 379 | if (!irq_domain) { |
363 | irq_set_chip_data(i, (void *)irq_num); | 380 | dev_err(dev, "Can't add irq_domain\n"); |
364 | irq_set_nested_thread(i, true); | 381 | return -ENOMEM; |
365 | irq_set_parent(i, irq_num); | ||
366 | activate_irq(i); | ||
367 | } | 382 | } |
368 | 383 | ||
369 | dev_info(dev, "PIH (irq %d) nested IRQs %d..%d\n", | 384 | dev_info(dev, "PIH (irq %d) nested IRQs\n", irq_num); |
370 | irq_num, irq_base, irq_end); | ||
371 | 385 | ||
372 | /* install an irq handler to demultiplex the TWL6030 interrupt */ | 386 | /* install an irq handler to demultiplex the TWL6030 interrupt */ |
373 | status = request_threaded_irq(irq_num, NULL, twl6030_irq_thread, | 387 | status = request_threaded_irq(irq_num, NULL, twl6030_irq_thread, |
374 | IRQF_ONESHOT, "TWL6030-PIH", NULL); | 388 | IRQF_ONESHOT, "TWL6030-PIH", irq_domain); |
375 | if (status < 0) { | 389 | if (status < 0) { |
376 | dev_err(dev, "could not claim irq %d: %d\n", irq_num, status); | 390 | dev_err(dev, "could not claim irq %d: %d\n", irq_num, status); |
377 | goto fail_irq; | 391 | goto fail_irq; |
@@ -379,23 +393,28 @@ int twl6030_init_irq(struct device *dev, int irq_num) | |||
379 | 393 | ||
380 | twl_irq = irq_num; | 394 | twl_irq = irq_num; |
381 | register_pm_notifier(&twl6030_irq_pm_notifier_block); | 395 | register_pm_notifier(&twl6030_irq_pm_notifier_block); |
382 | return irq_base; | 396 | return 0; |
383 | 397 | ||
384 | fail_irq: | 398 | fail_irq: |
385 | for (i = irq_base; i < irq_end; i++) | 399 | irq_domain_remove(irq_domain); |
386 | irq_set_chip_and_handler(i, NULL, NULL); | ||
387 | |||
388 | return status; | 400 | return status; |
389 | } | 401 | } |
390 | 402 | ||
391 | int twl6030_exit_irq(void) | 403 | int twl6030_exit_irq(void) |
392 | { | 404 | { |
393 | |||
394 | if (twl_irq) { | 405 | if (twl_irq) { |
395 | unregister_pm_notifier(&twl6030_irq_pm_notifier_block); | 406 | unregister_pm_notifier(&twl6030_irq_pm_notifier_block); |
396 | free_irq(twl_irq, NULL); | 407 | free_irq(twl_irq, NULL); |
408 | /* | ||
409 | * TODO: IRQ domain and allocated nested IRQ descriptors | ||
410 | * should be freed somehow here. Now It can't be done, because | ||
411 | * child devices will not be deleted during removing of | ||
412 | * TWL Core driver and they will still contain allocated | ||
413 | * virt IRQs in their Resources tables. | ||
414 | * The same prevents us from using devm_request_threaded_irq() | ||
415 | * in this module. | ||
416 | */ | ||
397 | } | 417 | } |
398 | |||
399 | return 0; | 418 | return 0; |
400 | } | 419 | } |
401 | 420 | ||