|
19 | 19 | */
|
20 | 20 |
|
21 | 21 | #include <linux/errno.h>
|
| 22 | +#include <linux/list.h> |
22 | 23 | #include <linux/module.h>
|
23 | 24 | #include <linux/of.h>
|
24 | 25 | #include <linux/of_irq.h>
|
25 | 26 | #include <linux/string.h>
|
| 27 | +#include <linux/slab.h> |
26 | 28 |
|
27 | 29 | /* For archs that don't support NO_IRQ (such as x86), provide a dummy value */
|
28 | 30 | #ifndef NO_IRQ
|
@@ -386,3 +388,108 @@ int of_irq_to_resource_table(struct device_node *dev, struct resource *res,
|
386 | 388 |
|
387 | 389 | return i;
|
388 | 390 | }
|
| 391 | + |
| 392 | +struct intc_desc { |
| 393 | + struct list_head list; |
| 394 | + struct device_node *dev; |
| 395 | + struct device_node *interrupt_parent; |
| 396 | +}; |
| 397 | + |
| 398 | +/** |
| 399 | + * of_irq_init - Scan and init matching interrupt controllers in DT |
| 400 | + * @matches: 0 terminated array of nodes to match and init function to call |
| 401 | + * |
| 402 | + * This function scans the device tree for matching interrupt controller nodes, |
| 403 | + * and calls their initialization functions in order with parents first. |
| 404 | + */ |
| 405 | +void __init of_irq_init(const struct of_device_id *matches) |
| 406 | +{ |
| 407 | + struct device_node *np, *parent = NULL; |
| 408 | + struct intc_desc *desc, *temp_desc; |
| 409 | + struct list_head intc_desc_list, intc_parent_list; |
| 410 | + |
| 411 | + INIT_LIST_HEAD(&intc_desc_list); |
| 412 | + INIT_LIST_HEAD(&intc_parent_list); |
| 413 | + |
| 414 | + for_each_matching_node(np, matches) { |
| 415 | + if (!of_find_property(np, "interrupt-controller", NULL)) |
| 416 | + continue; |
| 417 | + /* |
| 418 | + * Here, we allocate and populate an intc_desc with the node |
| 419 | + * pointer, interrupt-parent device_node etc. |
| 420 | + */ |
| 421 | + desc = kzalloc(sizeof(*desc), GFP_KERNEL); |
| 422 | + if (WARN_ON(!desc)) |
| 423 | + goto err; |
| 424 | + |
| 425 | + desc->dev = np; |
| 426 | + desc->interrupt_parent = of_irq_find_parent(np); |
| 427 | + list_add_tail(&desc->list, &intc_desc_list); |
| 428 | + } |
| 429 | + |
| 430 | + /* |
| 431 | + * The root irq controller is the one without an interrupt-parent. |
| 432 | + * That one goes first, followed by the controllers that reference it, |
| 433 | + * followed by the ones that reference the 2nd level controllers, etc. |
| 434 | + */ |
| 435 | + while (!list_empty(&intc_desc_list)) { |
| 436 | + /* |
| 437 | + * Process all controllers with the current 'parent'. |
| 438 | + * First pass will be looking for NULL as the parent. |
| 439 | + * The assumption is that NULL parent means a root controller. |
| 440 | + */ |
| 441 | + list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) { |
| 442 | + const struct of_device_id *match; |
| 443 | + int ret; |
| 444 | + of_irq_init_cb_t irq_init_cb; |
| 445 | + |
| 446 | + if (desc->interrupt_parent != parent) |
| 447 | + continue; |
| 448 | + |
| 449 | + list_del(&desc->list); |
| 450 | + match = of_match_node(matches, desc->dev); |
| 451 | + if (WARN(!match->data, |
| 452 | + "of_irq_init: no init function for %s\n", |
| 453 | + match->compatible)) { |
| 454 | + kfree(desc); |
| 455 | + continue; |
| 456 | + } |
| 457 | + |
| 458 | + pr_debug("of_irq_init: init %s @ %p, parent %p\n", |
| 459 | + match->compatible, |
| 460 | + desc->dev, desc->interrupt_parent); |
| 461 | + irq_init_cb = match->data; |
| 462 | + ret = irq_init_cb(desc->dev, desc->interrupt_parent); |
| 463 | + if (ret) { |
| 464 | + kfree(desc); |
| 465 | + continue; |
| 466 | + } |
| 467 | + |
| 468 | + /* |
| 469 | + * This one is now set up; add it to the parent list so |
| 470 | + * its children can get processed in a subsequent pass. |
| 471 | + */ |
| 472 | + list_add_tail(&desc->list, &intc_parent_list); |
| 473 | + } |
| 474 | + |
| 475 | + /* Get the next pending parent that might have children */ |
| 476 | + desc = list_first_entry(&intc_parent_list, typeof(*desc), list); |
| 477 | + if (list_empty(&intc_parent_list) || !desc) { |
| 478 | + pr_err("of_irq_init: children remain, but no parents\n"); |
| 479 | + break; |
| 480 | + } |
| 481 | + list_del(&desc->list); |
| 482 | + parent = desc->dev; |
| 483 | + kfree(desc); |
| 484 | + } |
| 485 | + |
| 486 | + list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) { |
| 487 | + list_del(&desc->list); |
| 488 | + kfree(desc); |
| 489 | + } |
| 490 | +err: |
| 491 | + list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) { |
| 492 | + list_del(&desc->list); |
| 493 | + kfree(desc); |
| 494 | + } |
| 495 | +} |
0 commit comments