18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ARM GIC v2m MSI(-X) support 48c2ecf20Sopenharmony_ci * Support for Message Signaled Interrupts for systems that 58c2ecf20Sopenharmony_ci * implement ARM Generic Interrupt Controller: GICv2m. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Copyright (C) 2014 Advanced Micro Devices, Inc. 88c2ecf20Sopenharmony_ci * Authors: Suravee Suthikulpanit <suravee.suthikulpanit@amd.com> 98c2ecf20Sopenharmony_ci * Harish Kasiviswanathan <harish.kasiviswanathan@amd.com> 108c2ecf20Sopenharmony_ci * Brandon Anderson <brandon.anderson@amd.com> 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "GICv2m: " fmt 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <linux/acpi.h> 168c2ecf20Sopenharmony_ci#include <linux/dma-iommu.h> 178c2ecf20Sopenharmony_ci#include <linux/irq.h> 188c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 198c2ecf20Sopenharmony_ci#include <linux/kernel.h> 208c2ecf20Sopenharmony_ci#include <linux/pci.h> 218c2ecf20Sopenharmony_ci#include <linux/msi.h> 228c2ecf20Sopenharmony_ci#include <linux/of_address.h> 238c2ecf20Sopenharmony_ci#include <linux/of_pci.h> 248c2ecf20Sopenharmony_ci#include <linux/slab.h> 258c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 268c2ecf20Sopenharmony_ci#include <linux/irqchip/arm-gic.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/* 298c2ecf20Sopenharmony_ci* MSI_TYPER: 308c2ecf20Sopenharmony_ci* [31:26] Reserved 318c2ecf20Sopenharmony_ci* [25:16] lowest SPI assigned to MSI 328c2ecf20Sopenharmony_ci* [15:10] Reserved 338c2ecf20Sopenharmony_ci* [9:0] Numer of SPIs assigned to MSI 348c2ecf20Sopenharmony_ci*/ 358c2ecf20Sopenharmony_ci#define V2M_MSI_TYPER 0x008 368c2ecf20Sopenharmony_ci#define V2M_MSI_TYPER_BASE_SHIFT 16 378c2ecf20Sopenharmony_ci#define V2M_MSI_TYPER_BASE_MASK 0x3FF 388c2ecf20Sopenharmony_ci#define V2M_MSI_TYPER_NUM_MASK 0x3FF 398c2ecf20Sopenharmony_ci#define V2M_MSI_SETSPI_NS 0x040 408c2ecf20Sopenharmony_ci#define V2M_MIN_SPI 32 418c2ecf20Sopenharmony_ci#define V2M_MAX_SPI 1019 428c2ecf20Sopenharmony_ci#define V2M_MSI_IIDR 0xFCC 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#define V2M_MSI_TYPER_BASE_SPI(x) \ 458c2ecf20Sopenharmony_ci (((x) >> V2M_MSI_TYPER_BASE_SHIFT) & V2M_MSI_TYPER_BASE_MASK) 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci#define V2M_MSI_TYPER_NUM_SPI(x) ((x) & V2M_MSI_TYPER_NUM_MASK) 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci/* APM X-Gene with GICv2m MSI_IIDR register value */ 508c2ecf20Sopenharmony_ci#define XGENE_GICV2M_MSI_IIDR 0x06000170 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci/* Broadcom NS2 GICv2m MSI_IIDR register value */ 538c2ecf20Sopenharmony_ci#define BCM_NS2_GICV2M_MSI_IIDR 0x0000013f 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci/* List of flags for specific v2m implementation */ 568c2ecf20Sopenharmony_ci#define GICV2M_NEEDS_SPI_OFFSET 0x00000001 578c2ecf20Sopenharmony_ci#define GICV2M_GRAVITON_ADDRESS_ONLY 0x00000002 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic LIST_HEAD(v2m_nodes); 608c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(v2m_lock); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistruct v2m_data { 638c2ecf20Sopenharmony_ci struct list_head entry; 648c2ecf20Sopenharmony_ci struct fwnode_handle *fwnode; 658c2ecf20Sopenharmony_ci struct resource res; /* GICv2m resource */ 668c2ecf20Sopenharmony_ci void __iomem *base; /* GICv2m virt address */ 678c2ecf20Sopenharmony_ci u32 spi_start; /* The SPI number that MSIs start */ 688c2ecf20Sopenharmony_ci u32 nr_spis; /* The number of SPIs for MSIs */ 698c2ecf20Sopenharmony_ci u32 spi_offset; /* offset to be subtracted from SPI number */ 708c2ecf20Sopenharmony_ci unsigned long *bm; /* MSI vector bitmap */ 718c2ecf20Sopenharmony_ci u32 flags; /* v2m flags for specific implementation */ 728c2ecf20Sopenharmony_ci}; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic void gicv2m_mask_msi_irq(struct irq_data *d) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci pci_msi_mask_irq(d); 778c2ecf20Sopenharmony_ci irq_chip_mask_parent(d); 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic void gicv2m_unmask_msi_irq(struct irq_data *d) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci pci_msi_unmask_irq(d); 838c2ecf20Sopenharmony_ci irq_chip_unmask_parent(d); 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic struct irq_chip gicv2m_msi_irq_chip = { 878c2ecf20Sopenharmony_ci .name = "MSI", 888c2ecf20Sopenharmony_ci .irq_mask = gicv2m_mask_msi_irq, 898c2ecf20Sopenharmony_ci .irq_unmask = gicv2m_unmask_msi_irq, 908c2ecf20Sopenharmony_ci .irq_eoi = irq_chip_eoi_parent, 918c2ecf20Sopenharmony_ci .irq_write_msi_msg = pci_msi_domain_write_msg, 928c2ecf20Sopenharmony_ci}; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistatic struct msi_domain_info gicv2m_msi_domain_info = { 958c2ecf20Sopenharmony_ci .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | 968c2ecf20Sopenharmony_ci MSI_FLAG_PCI_MSIX | MSI_FLAG_MULTI_PCI_MSI), 978c2ecf20Sopenharmony_ci .chip = &gicv2m_msi_irq_chip, 988c2ecf20Sopenharmony_ci}; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic phys_addr_t gicv2m_get_msi_addr(struct v2m_data *v2m, int hwirq) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) 1038c2ecf20Sopenharmony_ci return v2m->res.start | ((hwirq - 32) << 3); 1048c2ecf20Sopenharmony_ci else 1058c2ecf20Sopenharmony_ci return v2m->res.start + V2M_MSI_SETSPI_NS; 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic void gicv2m_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci struct v2m_data *v2m = irq_data_get_irq_chip_data(data); 1118c2ecf20Sopenharmony_ci phys_addr_t addr = gicv2m_get_msi_addr(v2m, data->hwirq); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci msg->address_hi = upper_32_bits(addr); 1148c2ecf20Sopenharmony_ci msg->address_lo = lower_32_bits(addr); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) 1178c2ecf20Sopenharmony_ci msg->data = 0; 1188c2ecf20Sopenharmony_ci else 1198c2ecf20Sopenharmony_ci msg->data = data->hwirq; 1208c2ecf20Sopenharmony_ci if (v2m->flags & GICV2M_NEEDS_SPI_OFFSET) 1218c2ecf20Sopenharmony_ci msg->data -= v2m->spi_offset; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci iommu_dma_compose_msi_msg(irq_data_get_msi_desc(data), msg); 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_cistatic struct irq_chip gicv2m_irq_chip = { 1278c2ecf20Sopenharmony_ci .name = "GICv2m", 1288c2ecf20Sopenharmony_ci .irq_mask = irq_chip_mask_parent, 1298c2ecf20Sopenharmony_ci .irq_unmask = irq_chip_unmask_parent, 1308c2ecf20Sopenharmony_ci .irq_eoi = irq_chip_eoi_parent, 1318c2ecf20Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 1328c2ecf20Sopenharmony_ci .irq_compose_msi_msg = gicv2m_compose_msi_msg, 1338c2ecf20Sopenharmony_ci}; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic int gicv2m_irq_gic_domain_alloc(struct irq_domain *domain, 1368c2ecf20Sopenharmony_ci unsigned int virq, 1378c2ecf20Sopenharmony_ci irq_hw_number_t hwirq) 1388c2ecf20Sopenharmony_ci{ 1398c2ecf20Sopenharmony_ci struct irq_fwspec fwspec; 1408c2ecf20Sopenharmony_ci struct irq_data *d; 1418c2ecf20Sopenharmony_ci int err; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci if (is_of_node(domain->parent->fwnode)) { 1448c2ecf20Sopenharmony_ci fwspec.fwnode = domain->parent->fwnode; 1458c2ecf20Sopenharmony_ci fwspec.param_count = 3; 1468c2ecf20Sopenharmony_ci fwspec.param[0] = 0; 1478c2ecf20Sopenharmony_ci fwspec.param[1] = hwirq - 32; 1488c2ecf20Sopenharmony_ci fwspec.param[2] = IRQ_TYPE_EDGE_RISING; 1498c2ecf20Sopenharmony_ci } else if (is_fwnode_irqchip(domain->parent->fwnode)) { 1508c2ecf20Sopenharmony_ci fwspec.fwnode = domain->parent->fwnode; 1518c2ecf20Sopenharmony_ci fwspec.param_count = 2; 1528c2ecf20Sopenharmony_ci fwspec.param[0] = hwirq; 1538c2ecf20Sopenharmony_ci fwspec.param[1] = IRQ_TYPE_EDGE_RISING; 1548c2ecf20Sopenharmony_ci } else { 1558c2ecf20Sopenharmony_ci return -EINVAL; 1568c2ecf20Sopenharmony_ci } 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); 1598c2ecf20Sopenharmony_ci if (err) 1608c2ecf20Sopenharmony_ci return err; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci /* Configure the interrupt line to be edge */ 1638c2ecf20Sopenharmony_ci d = irq_domain_get_irq_data(domain->parent, virq); 1648c2ecf20Sopenharmony_ci d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING); 1658c2ecf20Sopenharmony_ci return 0; 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cistatic void gicv2m_unalloc_msi(struct v2m_data *v2m, unsigned int hwirq, 1698c2ecf20Sopenharmony_ci int nr_irqs) 1708c2ecf20Sopenharmony_ci{ 1718c2ecf20Sopenharmony_ci spin_lock(&v2m_lock); 1728c2ecf20Sopenharmony_ci bitmap_release_region(v2m->bm, hwirq - v2m->spi_start, 1738c2ecf20Sopenharmony_ci get_count_order(nr_irqs)); 1748c2ecf20Sopenharmony_ci spin_unlock(&v2m_lock); 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cistatic int gicv2m_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, 1788c2ecf20Sopenharmony_ci unsigned int nr_irqs, void *args) 1798c2ecf20Sopenharmony_ci{ 1808c2ecf20Sopenharmony_ci msi_alloc_info_t *info = args; 1818c2ecf20Sopenharmony_ci struct v2m_data *v2m = NULL, *tmp; 1828c2ecf20Sopenharmony_ci int hwirq, offset, i, err = 0; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci spin_lock(&v2m_lock); 1858c2ecf20Sopenharmony_ci list_for_each_entry(tmp, &v2m_nodes, entry) { 1868c2ecf20Sopenharmony_ci offset = bitmap_find_free_region(tmp->bm, tmp->nr_spis, 1878c2ecf20Sopenharmony_ci get_count_order(nr_irqs)); 1888c2ecf20Sopenharmony_ci if (offset >= 0) { 1898c2ecf20Sopenharmony_ci v2m = tmp; 1908c2ecf20Sopenharmony_ci break; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci spin_unlock(&v2m_lock); 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci if (!v2m) 1968c2ecf20Sopenharmony_ci return -ENOSPC; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci hwirq = v2m->spi_start + offset; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci err = iommu_dma_prepare_msi(info->desc, 2018c2ecf20Sopenharmony_ci gicv2m_get_msi_addr(v2m, hwirq)); 2028c2ecf20Sopenharmony_ci if (err) 2038c2ecf20Sopenharmony_ci return err; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci for (i = 0; i < nr_irqs; i++) { 2068c2ecf20Sopenharmony_ci err = gicv2m_irq_gic_domain_alloc(domain, virq + i, hwirq + i); 2078c2ecf20Sopenharmony_ci if (err) 2088c2ecf20Sopenharmony_ci goto fail; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, 2118c2ecf20Sopenharmony_ci &gicv2m_irq_chip, v2m); 2128c2ecf20Sopenharmony_ci } 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci return 0; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_cifail: 2178c2ecf20Sopenharmony_ci irq_domain_free_irqs_parent(domain, virq, nr_irqs); 2188c2ecf20Sopenharmony_ci gicv2m_unalloc_msi(v2m, hwirq, nr_irqs); 2198c2ecf20Sopenharmony_ci return err; 2208c2ecf20Sopenharmony_ci} 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_cistatic void gicv2m_irq_domain_free(struct irq_domain *domain, 2238c2ecf20Sopenharmony_ci unsigned int virq, unsigned int nr_irqs) 2248c2ecf20Sopenharmony_ci{ 2258c2ecf20Sopenharmony_ci struct irq_data *d = irq_domain_get_irq_data(domain, virq); 2268c2ecf20Sopenharmony_ci struct v2m_data *v2m = irq_data_get_irq_chip_data(d); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci gicv2m_unalloc_msi(v2m, d->hwirq, nr_irqs); 2298c2ecf20Sopenharmony_ci irq_domain_free_irqs_parent(domain, virq, nr_irqs); 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cistatic const struct irq_domain_ops gicv2m_domain_ops = { 2338c2ecf20Sopenharmony_ci .alloc = gicv2m_irq_domain_alloc, 2348c2ecf20Sopenharmony_ci .free = gicv2m_irq_domain_free, 2358c2ecf20Sopenharmony_ci}; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_cistatic bool is_msi_spi_valid(u32 base, u32 num) 2388c2ecf20Sopenharmony_ci{ 2398c2ecf20Sopenharmony_ci if (base < V2M_MIN_SPI) { 2408c2ecf20Sopenharmony_ci pr_err("Invalid MSI base SPI (base:%u)\n", base); 2418c2ecf20Sopenharmony_ci return false; 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci if ((num == 0) || (base + num > V2M_MAX_SPI)) { 2458c2ecf20Sopenharmony_ci pr_err("Number of SPIs (%u) exceed maximum (%u)\n", 2468c2ecf20Sopenharmony_ci num, V2M_MAX_SPI - V2M_MIN_SPI + 1); 2478c2ecf20Sopenharmony_ci return false; 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci return true; 2518c2ecf20Sopenharmony_ci} 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_cistatic struct irq_chip gicv2m_pmsi_irq_chip = { 2548c2ecf20Sopenharmony_ci .name = "pMSI", 2558c2ecf20Sopenharmony_ci}; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_cistatic struct msi_domain_ops gicv2m_pmsi_ops = { 2588c2ecf20Sopenharmony_ci}; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_cistatic struct msi_domain_info gicv2m_pmsi_domain_info = { 2618c2ecf20Sopenharmony_ci .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS), 2628c2ecf20Sopenharmony_ci .ops = &gicv2m_pmsi_ops, 2638c2ecf20Sopenharmony_ci .chip = &gicv2m_pmsi_irq_chip, 2648c2ecf20Sopenharmony_ci}; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cistatic void gicv2m_teardown(void) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci struct v2m_data *v2m, *tmp; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci list_for_each_entry_safe(v2m, tmp, &v2m_nodes, entry) { 2718c2ecf20Sopenharmony_ci list_del(&v2m->entry); 2728c2ecf20Sopenharmony_ci kfree(v2m->bm); 2738c2ecf20Sopenharmony_ci iounmap(v2m->base); 2748c2ecf20Sopenharmony_ci of_node_put(to_of_node(v2m->fwnode)); 2758c2ecf20Sopenharmony_ci if (is_fwnode_irqchip(v2m->fwnode)) 2768c2ecf20Sopenharmony_ci irq_domain_free_fwnode(v2m->fwnode); 2778c2ecf20Sopenharmony_ci kfree(v2m); 2788c2ecf20Sopenharmony_ci } 2798c2ecf20Sopenharmony_ci} 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_cistatic int gicv2m_allocate_domains(struct irq_domain *parent) 2828c2ecf20Sopenharmony_ci{ 2838c2ecf20Sopenharmony_ci struct irq_domain *inner_domain, *pci_domain, *plat_domain; 2848c2ecf20Sopenharmony_ci struct v2m_data *v2m; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci v2m = list_first_entry_or_null(&v2m_nodes, struct v2m_data, entry); 2878c2ecf20Sopenharmony_ci if (!v2m) 2888c2ecf20Sopenharmony_ci return 0; 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci inner_domain = irq_domain_create_tree(v2m->fwnode, 2918c2ecf20Sopenharmony_ci &gicv2m_domain_ops, v2m); 2928c2ecf20Sopenharmony_ci if (!inner_domain) { 2938c2ecf20Sopenharmony_ci pr_err("Failed to create GICv2m domain\n"); 2948c2ecf20Sopenharmony_ci return -ENOMEM; 2958c2ecf20Sopenharmony_ci } 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS); 2988c2ecf20Sopenharmony_ci inner_domain->parent = parent; 2998c2ecf20Sopenharmony_ci pci_domain = pci_msi_create_irq_domain(v2m->fwnode, 3008c2ecf20Sopenharmony_ci &gicv2m_msi_domain_info, 3018c2ecf20Sopenharmony_ci inner_domain); 3028c2ecf20Sopenharmony_ci plat_domain = platform_msi_create_irq_domain(v2m->fwnode, 3038c2ecf20Sopenharmony_ci &gicv2m_pmsi_domain_info, 3048c2ecf20Sopenharmony_ci inner_domain); 3058c2ecf20Sopenharmony_ci if (!pci_domain || !plat_domain) { 3068c2ecf20Sopenharmony_ci pr_err("Failed to create MSI domains\n"); 3078c2ecf20Sopenharmony_ci if (plat_domain) 3088c2ecf20Sopenharmony_ci irq_domain_remove(plat_domain); 3098c2ecf20Sopenharmony_ci if (pci_domain) 3108c2ecf20Sopenharmony_ci irq_domain_remove(pci_domain); 3118c2ecf20Sopenharmony_ci irq_domain_remove(inner_domain); 3128c2ecf20Sopenharmony_ci return -ENOMEM; 3138c2ecf20Sopenharmony_ci } 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci return 0; 3168c2ecf20Sopenharmony_ci} 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_cistatic int __init gicv2m_init_one(struct fwnode_handle *fwnode, 3198c2ecf20Sopenharmony_ci u32 spi_start, u32 nr_spis, 3208c2ecf20Sopenharmony_ci struct resource *res, u32 flags) 3218c2ecf20Sopenharmony_ci{ 3228c2ecf20Sopenharmony_ci int ret; 3238c2ecf20Sopenharmony_ci struct v2m_data *v2m; 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci v2m = kzalloc(sizeof(struct v2m_data), GFP_KERNEL); 3268c2ecf20Sopenharmony_ci if (!v2m) { 3278c2ecf20Sopenharmony_ci pr_err("Failed to allocate struct v2m_data.\n"); 3288c2ecf20Sopenharmony_ci return -ENOMEM; 3298c2ecf20Sopenharmony_ci } 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&v2m->entry); 3328c2ecf20Sopenharmony_ci v2m->fwnode = fwnode; 3338c2ecf20Sopenharmony_ci v2m->flags = flags; 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci memcpy(&v2m->res, res, sizeof(struct resource)); 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci v2m->base = ioremap(v2m->res.start, resource_size(&v2m->res)); 3388c2ecf20Sopenharmony_ci if (!v2m->base) { 3398c2ecf20Sopenharmony_ci pr_err("Failed to map GICv2m resource\n"); 3408c2ecf20Sopenharmony_ci ret = -ENOMEM; 3418c2ecf20Sopenharmony_ci goto err_free_v2m; 3428c2ecf20Sopenharmony_ci } 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci if (spi_start && nr_spis) { 3458c2ecf20Sopenharmony_ci v2m->spi_start = spi_start; 3468c2ecf20Sopenharmony_ci v2m->nr_spis = nr_spis; 3478c2ecf20Sopenharmony_ci } else { 3488c2ecf20Sopenharmony_ci u32 typer; 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci /* Graviton should always have explicit spi_start/nr_spis */ 3518c2ecf20Sopenharmony_ci if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) { 3528c2ecf20Sopenharmony_ci ret = -EINVAL; 3538c2ecf20Sopenharmony_ci goto err_iounmap; 3548c2ecf20Sopenharmony_ci } 3558c2ecf20Sopenharmony_ci typer = readl_relaxed(v2m->base + V2M_MSI_TYPER); 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci v2m->spi_start = V2M_MSI_TYPER_BASE_SPI(typer); 3588c2ecf20Sopenharmony_ci v2m->nr_spis = V2M_MSI_TYPER_NUM_SPI(typer); 3598c2ecf20Sopenharmony_ci } 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_ci if (!is_msi_spi_valid(v2m->spi_start, v2m->nr_spis)) { 3628c2ecf20Sopenharmony_ci ret = -EINVAL; 3638c2ecf20Sopenharmony_ci goto err_iounmap; 3648c2ecf20Sopenharmony_ci } 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci /* 3678c2ecf20Sopenharmony_ci * APM X-Gene GICv2m implementation has an erratum where 3688c2ecf20Sopenharmony_ci * the MSI data needs to be the offset from the spi_start 3698c2ecf20Sopenharmony_ci * in order to trigger the correct MSI interrupt. This is 3708c2ecf20Sopenharmony_ci * different from the standard GICv2m implementation where 3718c2ecf20Sopenharmony_ci * the MSI data is the absolute value within the range from 3728c2ecf20Sopenharmony_ci * spi_start to (spi_start + num_spis). 3738c2ecf20Sopenharmony_ci * 3748c2ecf20Sopenharmony_ci * Broadcom NS2 GICv2m implementation has an erratum where the MSI data 3758c2ecf20Sopenharmony_ci * is 'spi_number - 32' 3768c2ecf20Sopenharmony_ci * 3778c2ecf20Sopenharmony_ci * Reading that register fails on the Graviton implementation 3788c2ecf20Sopenharmony_ci */ 3798c2ecf20Sopenharmony_ci if (!(v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY)) { 3808c2ecf20Sopenharmony_ci switch (readl_relaxed(v2m->base + V2M_MSI_IIDR)) { 3818c2ecf20Sopenharmony_ci case XGENE_GICV2M_MSI_IIDR: 3828c2ecf20Sopenharmony_ci v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; 3838c2ecf20Sopenharmony_ci v2m->spi_offset = v2m->spi_start; 3848c2ecf20Sopenharmony_ci break; 3858c2ecf20Sopenharmony_ci case BCM_NS2_GICV2M_MSI_IIDR: 3868c2ecf20Sopenharmony_ci v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; 3878c2ecf20Sopenharmony_ci v2m->spi_offset = 32; 3888c2ecf20Sopenharmony_ci break; 3898c2ecf20Sopenharmony_ci } 3908c2ecf20Sopenharmony_ci } 3918c2ecf20Sopenharmony_ci v2m->bm = kcalloc(BITS_TO_LONGS(v2m->nr_spis), sizeof(long), 3928c2ecf20Sopenharmony_ci GFP_KERNEL); 3938c2ecf20Sopenharmony_ci if (!v2m->bm) { 3948c2ecf20Sopenharmony_ci ret = -ENOMEM; 3958c2ecf20Sopenharmony_ci goto err_iounmap; 3968c2ecf20Sopenharmony_ci } 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_ci list_add_tail(&v2m->entry, &v2m_nodes); 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_ci pr_info("range%pR, SPI[%d:%d]\n", res, 4018c2ecf20Sopenharmony_ci v2m->spi_start, (v2m->spi_start + v2m->nr_spis - 1)); 4028c2ecf20Sopenharmony_ci return 0; 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_cierr_iounmap: 4058c2ecf20Sopenharmony_ci iounmap(v2m->base); 4068c2ecf20Sopenharmony_cierr_free_v2m: 4078c2ecf20Sopenharmony_ci kfree(v2m); 4088c2ecf20Sopenharmony_ci return ret; 4098c2ecf20Sopenharmony_ci} 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_cistatic struct of_device_id gicv2m_device_id[] = { 4128c2ecf20Sopenharmony_ci { .compatible = "arm,gic-v2m-frame", }, 4138c2ecf20Sopenharmony_ci {}, 4148c2ecf20Sopenharmony_ci}; 4158c2ecf20Sopenharmony_ci 4168c2ecf20Sopenharmony_cistatic int __init gicv2m_of_init(struct fwnode_handle *parent_handle, 4178c2ecf20Sopenharmony_ci struct irq_domain *parent) 4188c2ecf20Sopenharmony_ci{ 4198c2ecf20Sopenharmony_ci int ret = 0; 4208c2ecf20Sopenharmony_ci struct device_node *node = to_of_node(parent_handle); 4218c2ecf20Sopenharmony_ci struct device_node *child; 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_ci for (child = of_find_matching_node(node, gicv2m_device_id); child; 4248c2ecf20Sopenharmony_ci child = of_find_matching_node(child, gicv2m_device_id)) { 4258c2ecf20Sopenharmony_ci u32 spi_start = 0, nr_spis = 0; 4268c2ecf20Sopenharmony_ci struct resource res; 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci if (!of_find_property(child, "msi-controller", NULL)) 4298c2ecf20Sopenharmony_ci continue; 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci ret = of_address_to_resource(child, 0, &res); 4328c2ecf20Sopenharmony_ci if (ret) { 4338c2ecf20Sopenharmony_ci pr_err("Failed to allocate v2m resource.\n"); 4348c2ecf20Sopenharmony_ci break; 4358c2ecf20Sopenharmony_ci } 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_ci if (!of_property_read_u32(child, "arm,msi-base-spi", 4388c2ecf20Sopenharmony_ci &spi_start) && 4398c2ecf20Sopenharmony_ci !of_property_read_u32(child, "arm,msi-num-spis", &nr_spis)) 4408c2ecf20Sopenharmony_ci pr_info("DT overriding V2M MSI_TYPER (base:%u, num:%u)\n", 4418c2ecf20Sopenharmony_ci spi_start, nr_spis); 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci ret = gicv2m_init_one(&child->fwnode, spi_start, nr_spis, 4448c2ecf20Sopenharmony_ci &res, 0); 4458c2ecf20Sopenharmony_ci if (ret) { 4468c2ecf20Sopenharmony_ci of_node_put(child); 4478c2ecf20Sopenharmony_ci break; 4488c2ecf20Sopenharmony_ci } 4498c2ecf20Sopenharmony_ci } 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_ci if (!ret) 4528c2ecf20Sopenharmony_ci ret = gicv2m_allocate_domains(parent); 4538c2ecf20Sopenharmony_ci if (ret) 4548c2ecf20Sopenharmony_ci gicv2m_teardown(); 4558c2ecf20Sopenharmony_ci return ret; 4568c2ecf20Sopenharmony_ci} 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_ci#ifdef CONFIG_ACPI 4598c2ecf20Sopenharmony_cistatic int acpi_num_msi; 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_cistatic struct fwnode_handle *gicv2m_get_fwnode(struct device *dev) 4628c2ecf20Sopenharmony_ci{ 4638c2ecf20Sopenharmony_ci struct v2m_data *data; 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci if (WARN_ON(acpi_num_msi <= 0)) 4668c2ecf20Sopenharmony_ci return NULL; 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_ci /* We only return the fwnode of the first MSI frame. */ 4698c2ecf20Sopenharmony_ci data = list_first_entry_or_null(&v2m_nodes, struct v2m_data, entry); 4708c2ecf20Sopenharmony_ci if (!data) 4718c2ecf20Sopenharmony_ci return NULL; 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci return data->fwnode; 4748c2ecf20Sopenharmony_ci} 4758c2ecf20Sopenharmony_ci 4768c2ecf20Sopenharmony_cistatic bool acpi_check_amazon_graviton_quirks(void) 4778c2ecf20Sopenharmony_ci{ 4788c2ecf20Sopenharmony_ci static struct acpi_table_madt *madt; 4798c2ecf20Sopenharmony_ci acpi_status status; 4808c2ecf20Sopenharmony_ci bool rc = false; 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci#define ACPI_AMZN_OEM_ID "AMAZON" 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_ci status = acpi_get_table(ACPI_SIG_MADT, 0, 4858c2ecf20Sopenharmony_ci (struct acpi_table_header **)&madt); 4868c2ecf20Sopenharmony_ci 4878c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status) || !madt) 4888c2ecf20Sopenharmony_ci return rc; 4898c2ecf20Sopenharmony_ci rc = !memcmp(madt->header.oem_id, ACPI_AMZN_OEM_ID, ACPI_OEM_ID_SIZE); 4908c2ecf20Sopenharmony_ci acpi_put_table((struct acpi_table_header *)madt); 4918c2ecf20Sopenharmony_ci 4928c2ecf20Sopenharmony_ci return rc; 4938c2ecf20Sopenharmony_ci} 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_cistatic int __init 4968c2ecf20Sopenharmony_ciacpi_parse_madt_msi(union acpi_subtable_headers *header, 4978c2ecf20Sopenharmony_ci const unsigned long end) 4988c2ecf20Sopenharmony_ci{ 4998c2ecf20Sopenharmony_ci int ret; 5008c2ecf20Sopenharmony_ci struct resource res; 5018c2ecf20Sopenharmony_ci u32 spi_start = 0, nr_spis = 0; 5028c2ecf20Sopenharmony_ci struct acpi_madt_generic_msi_frame *m; 5038c2ecf20Sopenharmony_ci struct fwnode_handle *fwnode; 5048c2ecf20Sopenharmony_ci u32 flags = 0; 5058c2ecf20Sopenharmony_ci 5068c2ecf20Sopenharmony_ci m = (struct acpi_madt_generic_msi_frame *)header; 5078c2ecf20Sopenharmony_ci if (BAD_MADT_ENTRY(m, end)) 5088c2ecf20Sopenharmony_ci return -EINVAL; 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci res.start = m->base_address; 5118c2ecf20Sopenharmony_ci res.end = m->base_address + SZ_4K - 1; 5128c2ecf20Sopenharmony_ci res.flags = IORESOURCE_MEM; 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci if (acpi_check_amazon_graviton_quirks()) { 5158c2ecf20Sopenharmony_ci pr_info("applying Amazon Graviton quirk\n"); 5168c2ecf20Sopenharmony_ci res.end = res.start + SZ_8K - 1; 5178c2ecf20Sopenharmony_ci flags |= GICV2M_GRAVITON_ADDRESS_ONLY; 5188c2ecf20Sopenharmony_ci gicv2m_msi_domain_info.flags &= ~MSI_FLAG_MULTI_PCI_MSI; 5198c2ecf20Sopenharmony_ci } 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci if (m->flags & ACPI_MADT_OVERRIDE_SPI_VALUES) { 5228c2ecf20Sopenharmony_ci spi_start = m->spi_base; 5238c2ecf20Sopenharmony_ci nr_spis = m->spi_count; 5248c2ecf20Sopenharmony_ci 5258c2ecf20Sopenharmony_ci pr_info("ACPI overriding V2M MSI_TYPER (base:%u, num:%u)\n", 5268c2ecf20Sopenharmony_ci spi_start, nr_spis); 5278c2ecf20Sopenharmony_ci } 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_ci fwnode = irq_domain_alloc_fwnode(&res.start); 5308c2ecf20Sopenharmony_ci if (!fwnode) { 5318c2ecf20Sopenharmony_ci pr_err("Unable to allocate GICv2m domain token\n"); 5328c2ecf20Sopenharmony_ci return -EINVAL; 5338c2ecf20Sopenharmony_ci } 5348c2ecf20Sopenharmony_ci 5358c2ecf20Sopenharmony_ci ret = gicv2m_init_one(fwnode, spi_start, nr_spis, &res, flags); 5368c2ecf20Sopenharmony_ci if (ret) 5378c2ecf20Sopenharmony_ci irq_domain_free_fwnode(fwnode); 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_ci return ret; 5408c2ecf20Sopenharmony_ci} 5418c2ecf20Sopenharmony_ci 5428c2ecf20Sopenharmony_cistatic int __init gicv2m_acpi_init(struct irq_domain *parent) 5438c2ecf20Sopenharmony_ci{ 5448c2ecf20Sopenharmony_ci int ret; 5458c2ecf20Sopenharmony_ci 5468c2ecf20Sopenharmony_ci if (acpi_num_msi > 0) 5478c2ecf20Sopenharmony_ci return 0; 5488c2ecf20Sopenharmony_ci 5498c2ecf20Sopenharmony_ci acpi_num_msi = acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_MSI_FRAME, 5508c2ecf20Sopenharmony_ci acpi_parse_madt_msi, 0); 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_ci if (acpi_num_msi <= 0) 5538c2ecf20Sopenharmony_ci goto err_out; 5548c2ecf20Sopenharmony_ci 5558c2ecf20Sopenharmony_ci ret = gicv2m_allocate_domains(parent); 5568c2ecf20Sopenharmony_ci if (ret) 5578c2ecf20Sopenharmony_ci goto err_out; 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci pci_msi_register_fwnode_provider(&gicv2m_get_fwnode); 5608c2ecf20Sopenharmony_ci 5618c2ecf20Sopenharmony_ci return 0; 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_cierr_out: 5648c2ecf20Sopenharmony_ci gicv2m_teardown(); 5658c2ecf20Sopenharmony_ci return -EINVAL; 5668c2ecf20Sopenharmony_ci} 5678c2ecf20Sopenharmony_ci#else /* CONFIG_ACPI */ 5688c2ecf20Sopenharmony_cistatic int __init gicv2m_acpi_init(struct irq_domain *parent) 5698c2ecf20Sopenharmony_ci{ 5708c2ecf20Sopenharmony_ci return -EINVAL; 5718c2ecf20Sopenharmony_ci} 5728c2ecf20Sopenharmony_ci#endif /* CONFIG_ACPI */ 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_ciint __init gicv2m_init(struct fwnode_handle *parent_handle, 5758c2ecf20Sopenharmony_ci struct irq_domain *parent) 5768c2ecf20Sopenharmony_ci{ 5778c2ecf20Sopenharmony_ci if (is_of_node(parent_handle)) 5788c2ecf20Sopenharmony_ci return gicv2m_of_init(parent_handle, parent); 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_ci return gicv2m_acpi_init(parent); 5818c2ecf20Sopenharmony_ci} 582