|
15 | 15 |
|
16 | 16 | #define pr_fmt(fmt) "GICv2m: " fmt
|
17 | 17 |
|
| 18 | +#include <linux/acpi.h> |
18 | 19 | #include <linux/irq.h>
|
19 | 20 | #include <linux/irqdomain.h>
|
20 | 21 | #include <linux/kernel.h>
|
| 22 | +#include <linux/msi.h> |
21 | 23 | #include <linux/of_address.h>
|
22 | 24 | #include <linux/of_pci.h>
|
23 | 25 | #include <linux/slab.h>
|
@@ -138,6 +140,11 @@ static int gicv2m_irq_gic_domain_alloc(struct irq_domain *domain,
|
138 | 140 | fwspec.param[0] = 0;
|
139 | 141 | fwspec.param[1] = hwirq - 32;
|
140 | 142 | fwspec.param[2] = IRQ_TYPE_EDGE_RISING;
|
| 143 | + } else if (is_fwnode_irqchip(domain->parent->fwnode)) { |
| 144 | + fwspec.fwnode = domain->parent->fwnode; |
| 145 | + fwspec.param_count = 2; |
| 146 | + fwspec.param[0] = hwirq; |
| 147 | + fwspec.param[1] = IRQ_TYPE_EDGE_RISING; |
141 | 148 | } else {
|
142 | 149 | return -EINVAL;
|
143 | 150 | }
|
@@ -255,6 +262,8 @@ static void gicv2m_teardown(void)
|
255 | 262 | kfree(v2m->bm);
|
256 | 263 | iounmap(v2m->base);
|
257 | 264 | of_node_put(to_of_node(v2m->fwnode));
|
| 265 | + if (is_fwnode_irqchip(v2m->fwnode)) |
| 266 | + irq_domain_free_fwnode(v2m->fwnode); |
258 | 267 | kfree(v2m);
|
259 | 268 | }
|
260 | 269 | }
|
@@ -373,9 +382,11 @@ static struct of_device_id gicv2m_device_id[] = {
|
373 | 382 | {},
|
374 | 383 | };
|
375 | 384 |
|
376 |
| -int __init gicv2m_of_init(struct device_node *node, struct irq_domain *parent) |
| 385 | +static int __init gicv2m_of_init(struct fwnode_handle *parent_handle, |
| 386 | + struct irq_domain *parent) |
377 | 387 | {
|
378 | 388 | int ret = 0;
|
| 389 | + struct device_node *node = to_of_node(parent_handle); |
379 | 390 | struct device_node *child;
|
380 | 391 |
|
381 | 392 | for (child = of_find_matching_node(node, gicv2m_device_id); child;
|
@@ -411,3 +422,100 @@ int __init gicv2m_of_init(struct device_node *node, struct irq_domain *parent)
|
411 | 422 | gicv2m_teardown();
|
412 | 423 | return ret;
|
413 | 424 | }
|
| 425 | + |
| 426 | +#ifdef CONFIG_ACPI |
| 427 | +static int acpi_num_msi; |
| 428 | + |
| 429 | +static struct fwnode_handle *gicv2m_get_fwnode(struct device *dev) |
| 430 | +{ |
| 431 | + struct v2m_data *data; |
| 432 | + |
| 433 | + if (WARN_ON(acpi_num_msi <= 0)) |
| 434 | + return NULL; |
| 435 | + |
| 436 | + /* We only return the fwnode of the first MSI frame. */ |
| 437 | + data = list_first_entry_or_null(&v2m_nodes, struct v2m_data, entry); |
| 438 | + if (!data) |
| 439 | + return NULL; |
| 440 | + |
| 441 | + return data->fwnode; |
| 442 | +} |
| 443 | + |
| 444 | +static int __init |
| 445 | +acpi_parse_madt_msi(struct acpi_subtable_header *header, |
| 446 | + const unsigned long end) |
| 447 | +{ |
| 448 | + int ret; |
| 449 | + struct resource res; |
| 450 | + u32 spi_start = 0, nr_spis = 0; |
| 451 | + struct acpi_madt_generic_msi_frame *m; |
| 452 | + struct fwnode_handle *fwnode; |
| 453 | + |
| 454 | + m = (struct acpi_madt_generic_msi_frame *)header; |
| 455 | + if (BAD_MADT_ENTRY(m, end)) |
| 456 | + return -EINVAL; |
| 457 | + |
| 458 | + res.start = m->base_address; |
| 459 | + res.end = m->base_address + SZ_4K; |
| 460 | + |
| 461 | + if (m->flags & ACPI_MADT_OVERRIDE_SPI_VALUES) { |
| 462 | + spi_start = m->spi_base; |
| 463 | + nr_spis = m->spi_count; |
| 464 | + |
| 465 | + pr_info("ACPI overriding V2M MSI_TYPER (base:%u, num:%u)\n", |
| 466 | + spi_start, nr_spis); |
| 467 | + } |
| 468 | + |
| 469 | + fwnode = irq_domain_alloc_fwnode((void *)m->base_address); |
| 470 | + if (!fwnode) { |
| 471 | + pr_err("Unable to allocate GICv2m domain token\n"); |
| 472 | + return -EINVAL; |
| 473 | + } |
| 474 | + |
| 475 | + ret = gicv2m_init_one(fwnode, spi_start, nr_spis, &res); |
| 476 | + if (ret) |
| 477 | + irq_domain_free_fwnode(fwnode); |
| 478 | + |
| 479 | + return ret; |
| 480 | +} |
| 481 | + |
| 482 | +static int __init gicv2m_acpi_init(struct irq_domain *parent) |
| 483 | +{ |
| 484 | + int ret; |
| 485 | + |
| 486 | + if (acpi_num_msi > 0) |
| 487 | + return 0; |
| 488 | + |
| 489 | + acpi_num_msi = acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_MSI_FRAME, |
| 490 | + acpi_parse_madt_msi, 0); |
| 491 | + |
| 492 | + if (acpi_num_msi <= 0) |
| 493 | + goto err_out; |
| 494 | + |
| 495 | + ret = gicv2m_allocate_domains(parent); |
| 496 | + if (ret) |
| 497 | + goto err_out; |
| 498 | + |
| 499 | + pci_msi_register_fwnode_provider(&gicv2m_get_fwnode); |
| 500 | + |
| 501 | + return 0; |
| 502 | + |
| 503 | +err_out: |
| 504 | + gicv2m_teardown(); |
| 505 | + return -EINVAL; |
| 506 | +} |
| 507 | +#else /* CONFIG_ACPI */ |
| 508 | +static int __init gicv2m_acpi_init(struct irq_domain *parent) |
| 509 | +{ |
| 510 | + return -EINVAL; |
| 511 | +} |
| 512 | +#endif /* CONFIG_ACPI */ |
| 513 | + |
| 514 | +int __init gicv2m_init(struct fwnode_handle *parent_handle, |
| 515 | + struct irq_domain *parent) |
| 516 | +{ |
| 517 | + if (is_of_node(parent_handle)) |
| 518 | + return gicv2m_of_init(parent_handle, parent); |
| 519 | + |
| 520 | + return gicv2m_acpi_init(parent); |
| 521 | +} |
0 commit comments