18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Annapurna Labs MSIX support services 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2016, Amazon.com, Inc. or its affiliates. All Rights Reserved. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Antoine Tenart <antoine.tenart@free-electrons.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public 98c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any 108c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <linux/irqchip.h> 168c2ecf20Sopenharmony_ci#include <linux/irqchip/arm-gic.h> 178c2ecf20Sopenharmony_ci#include <linux/msi.h> 188c2ecf20Sopenharmony_ci#include <linux/of.h> 198c2ecf20Sopenharmony_ci#include <linux/of_address.h> 208c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 218c2ecf20Sopenharmony_ci#include <linux/of_pci.h> 228c2ecf20Sopenharmony_ci#include <linux/pci.h> 238c2ecf20Sopenharmony_ci#include <linux/slab.h> 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#include <asm/irq.h> 268c2ecf20Sopenharmony_ci#include <asm/msi.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/* MSIX message address format: local GIC target */ 298c2ecf20Sopenharmony_ci#define ALPINE_MSIX_SPI_TARGET_CLUSTER0 BIT(16) 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistruct alpine_msix_data { 328c2ecf20Sopenharmony_ci spinlock_t msi_map_lock; 338c2ecf20Sopenharmony_ci phys_addr_t addr; 348c2ecf20Sopenharmony_ci u32 spi_first; /* The SGI number that MSIs start */ 358c2ecf20Sopenharmony_ci u32 num_spis; /* The number of SGIs for MSIs */ 368c2ecf20Sopenharmony_ci unsigned long *msi_map; 378c2ecf20Sopenharmony_ci}; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic void alpine_msix_mask_msi_irq(struct irq_data *d) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci pci_msi_mask_irq(d); 428c2ecf20Sopenharmony_ci irq_chip_mask_parent(d); 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic void alpine_msix_unmask_msi_irq(struct irq_data *d) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci pci_msi_unmask_irq(d); 488c2ecf20Sopenharmony_ci irq_chip_unmask_parent(d); 498c2ecf20Sopenharmony_ci} 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistatic struct irq_chip alpine_msix_irq_chip = { 528c2ecf20Sopenharmony_ci .name = "MSIx", 538c2ecf20Sopenharmony_ci .irq_mask = alpine_msix_mask_msi_irq, 548c2ecf20Sopenharmony_ci .irq_unmask = alpine_msix_unmask_msi_irq, 558c2ecf20Sopenharmony_ci .irq_eoi = irq_chip_eoi_parent, 568c2ecf20Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 578c2ecf20Sopenharmony_ci}; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic int alpine_msix_allocate_sgi(struct alpine_msix_data *priv, int num_req) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci int first; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci spin_lock(&priv->msi_map_lock); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci first = bitmap_find_next_zero_area(priv->msi_map, priv->num_spis, 0, 668c2ecf20Sopenharmony_ci num_req, 0); 678c2ecf20Sopenharmony_ci if (first >= priv->num_spis) { 688c2ecf20Sopenharmony_ci spin_unlock(&priv->msi_map_lock); 698c2ecf20Sopenharmony_ci return -ENOSPC; 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci bitmap_set(priv->msi_map, first, num_req); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci spin_unlock(&priv->msi_map_lock); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci return priv->spi_first + first; 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic void alpine_msix_free_sgi(struct alpine_msix_data *priv, unsigned sgi, 808c2ecf20Sopenharmony_ci int num_req) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci int first = sgi - priv->spi_first; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci spin_lock(&priv->msi_map_lock); 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci bitmap_clear(priv->msi_map, first, num_req); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci spin_unlock(&priv->msi_map_lock); 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic void alpine_msix_compose_msi_msg(struct irq_data *data, 928c2ecf20Sopenharmony_ci struct msi_msg *msg) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci struct alpine_msix_data *priv = irq_data_get_irq_chip_data(data); 958c2ecf20Sopenharmony_ci phys_addr_t msg_addr = priv->addr; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci msg_addr |= (data->hwirq << 3); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci msg->address_hi = upper_32_bits(msg_addr); 1008c2ecf20Sopenharmony_ci msg->address_lo = lower_32_bits(msg_addr); 1018c2ecf20Sopenharmony_ci msg->data = 0; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic struct msi_domain_info alpine_msix_domain_info = { 1058c2ecf20Sopenharmony_ci .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | 1068c2ecf20Sopenharmony_ci MSI_FLAG_PCI_MSIX, 1078c2ecf20Sopenharmony_ci .chip = &alpine_msix_irq_chip, 1088c2ecf20Sopenharmony_ci}; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic struct irq_chip middle_irq_chip = { 1118c2ecf20Sopenharmony_ci .name = "alpine_msix_middle", 1128c2ecf20Sopenharmony_ci .irq_mask = irq_chip_mask_parent, 1138c2ecf20Sopenharmony_ci .irq_unmask = irq_chip_unmask_parent, 1148c2ecf20Sopenharmony_ci .irq_eoi = irq_chip_eoi_parent, 1158c2ecf20Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 1168c2ecf20Sopenharmony_ci .irq_compose_msi_msg = alpine_msix_compose_msi_msg, 1178c2ecf20Sopenharmony_ci}; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic int alpine_msix_gic_domain_alloc(struct irq_domain *domain, 1208c2ecf20Sopenharmony_ci unsigned int virq, int sgi) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci struct irq_fwspec fwspec; 1238c2ecf20Sopenharmony_ci struct irq_data *d; 1248c2ecf20Sopenharmony_ci int ret; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci if (!is_of_node(domain->parent->fwnode)) 1278c2ecf20Sopenharmony_ci return -EINVAL; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci fwspec.fwnode = domain->parent->fwnode; 1308c2ecf20Sopenharmony_ci fwspec.param_count = 3; 1318c2ecf20Sopenharmony_ci fwspec.param[0] = 0; 1328c2ecf20Sopenharmony_ci fwspec.param[1] = sgi; 1338c2ecf20Sopenharmony_ci fwspec.param[2] = IRQ_TYPE_EDGE_RISING; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); 1368c2ecf20Sopenharmony_ci if (ret) 1378c2ecf20Sopenharmony_ci return ret; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci d = irq_domain_get_irq_data(domain->parent, virq); 1408c2ecf20Sopenharmony_ci d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci return 0; 1438c2ecf20Sopenharmony_ci} 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_cistatic int alpine_msix_middle_domain_alloc(struct irq_domain *domain, 1468c2ecf20Sopenharmony_ci unsigned int virq, 1478c2ecf20Sopenharmony_ci unsigned int nr_irqs, void *args) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci struct alpine_msix_data *priv = domain->host_data; 1508c2ecf20Sopenharmony_ci int sgi, err, i; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci sgi = alpine_msix_allocate_sgi(priv, nr_irqs); 1538c2ecf20Sopenharmony_ci if (sgi < 0) 1548c2ecf20Sopenharmony_ci return sgi; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci for (i = 0; i < nr_irqs; i++) { 1578c2ecf20Sopenharmony_ci err = alpine_msix_gic_domain_alloc(domain, virq + i, sgi + i); 1588c2ecf20Sopenharmony_ci if (err) 1598c2ecf20Sopenharmony_ci goto err_sgi; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci irq_domain_set_hwirq_and_chip(domain, virq + i, sgi + i, 1628c2ecf20Sopenharmony_ci &middle_irq_chip, priv); 1638c2ecf20Sopenharmony_ci } 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci return 0; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_cierr_sgi: 1688c2ecf20Sopenharmony_ci irq_domain_free_irqs_parent(domain, virq, i - 1); 1698c2ecf20Sopenharmony_ci alpine_msix_free_sgi(priv, sgi, nr_irqs); 1708c2ecf20Sopenharmony_ci return err; 1718c2ecf20Sopenharmony_ci} 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_cistatic void alpine_msix_middle_domain_free(struct irq_domain *domain, 1748c2ecf20Sopenharmony_ci unsigned int virq, 1758c2ecf20Sopenharmony_ci unsigned int nr_irqs) 1768c2ecf20Sopenharmony_ci{ 1778c2ecf20Sopenharmony_ci struct irq_data *d = irq_domain_get_irq_data(domain, virq); 1788c2ecf20Sopenharmony_ci struct alpine_msix_data *priv = irq_data_get_irq_chip_data(d); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci irq_domain_free_irqs_parent(domain, virq, nr_irqs); 1818c2ecf20Sopenharmony_ci alpine_msix_free_sgi(priv, d->hwirq, nr_irqs); 1828c2ecf20Sopenharmony_ci} 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_cistatic const struct irq_domain_ops alpine_msix_middle_domain_ops = { 1858c2ecf20Sopenharmony_ci .alloc = alpine_msix_middle_domain_alloc, 1868c2ecf20Sopenharmony_ci .free = alpine_msix_middle_domain_free, 1878c2ecf20Sopenharmony_ci}; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_cistatic int alpine_msix_init_domains(struct alpine_msix_data *priv, 1908c2ecf20Sopenharmony_ci struct device_node *node) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci struct irq_domain *middle_domain, *msi_domain, *gic_domain; 1938c2ecf20Sopenharmony_ci struct device_node *gic_node; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci gic_node = of_irq_find_parent(node); 1968c2ecf20Sopenharmony_ci if (!gic_node) { 1978c2ecf20Sopenharmony_ci pr_err("Failed to find the GIC node\n"); 1988c2ecf20Sopenharmony_ci return -ENODEV; 1998c2ecf20Sopenharmony_ci } 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci gic_domain = irq_find_host(gic_node); 2028c2ecf20Sopenharmony_ci of_node_put(gic_node); 2038c2ecf20Sopenharmony_ci if (!gic_domain) { 2048c2ecf20Sopenharmony_ci pr_err("Failed to find the GIC domain\n"); 2058c2ecf20Sopenharmony_ci return -ENXIO; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci middle_domain = irq_domain_add_tree(NULL, 2098c2ecf20Sopenharmony_ci &alpine_msix_middle_domain_ops, 2108c2ecf20Sopenharmony_ci priv); 2118c2ecf20Sopenharmony_ci if (!middle_domain) { 2128c2ecf20Sopenharmony_ci pr_err("Failed to create the MSIX middle domain\n"); 2138c2ecf20Sopenharmony_ci return -ENOMEM; 2148c2ecf20Sopenharmony_ci } 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci middle_domain->parent = gic_domain; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node), 2198c2ecf20Sopenharmony_ci &alpine_msix_domain_info, 2208c2ecf20Sopenharmony_ci middle_domain); 2218c2ecf20Sopenharmony_ci if (!msi_domain) { 2228c2ecf20Sopenharmony_ci pr_err("Failed to create MSI domain\n"); 2238c2ecf20Sopenharmony_ci irq_domain_remove(middle_domain); 2248c2ecf20Sopenharmony_ci return -ENOMEM; 2258c2ecf20Sopenharmony_ci } 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci return 0; 2288c2ecf20Sopenharmony_ci} 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_cistatic int alpine_msix_init(struct device_node *node, 2318c2ecf20Sopenharmony_ci struct device_node *parent) 2328c2ecf20Sopenharmony_ci{ 2338c2ecf20Sopenharmony_ci struct alpine_msix_data *priv; 2348c2ecf20Sopenharmony_ci struct resource res; 2358c2ecf20Sopenharmony_ci int ret; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci priv = kzalloc(sizeof(*priv), GFP_KERNEL); 2388c2ecf20Sopenharmony_ci if (!priv) 2398c2ecf20Sopenharmony_ci return -ENOMEM; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci spin_lock_init(&priv->msi_map_lock); 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci ret = of_address_to_resource(node, 0, &res); 2448c2ecf20Sopenharmony_ci if (ret) { 2458c2ecf20Sopenharmony_ci pr_err("Failed to allocate resource\n"); 2468c2ecf20Sopenharmony_ci goto err_priv; 2478c2ecf20Sopenharmony_ci } 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci /* 2508c2ecf20Sopenharmony_ci * The 20 least significant bits of addr provide direct information 2518c2ecf20Sopenharmony_ci * regarding the interrupt destination. 2528c2ecf20Sopenharmony_ci * 2538c2ecf20Sopenharmony_ci * To select the primary GIC as the target GIC, bits [18:17] must be set 2548c2ecf20Sopenharmony_ci * to 0x0. In this case, bit 16 (SPI_TARGET_CLUSTER0) must be set. 2558c2ecf20Sopenharmony_ci */ 2568c2ecf20Sopenharmony_ci priv->addr = res.start & GENMASK_ULL(63,20); 2578c2ecf20Sopenharmony_ci priv->addr |= ALPINE_MSIX_SPI_TARGET_CLUSTER0; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci if (of_property_read_u32(node, "al,msi-base-spi", &priv->spi_first)) { 2608c2ecf20Sopenharmony_ci pr_err("Unable to parse MSI base\n"); 2618c2ecf20Sopenharmony_ci ret = -EINVAL; 2628c2ecf20Sopenharmony_ci goto err_priv; 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci if (of_property_read_u32(node, "al,msi-num-spis", &priv->num_spis)) { 2668c2ecf20Sopenharmony_ci pr_err("Unable to parse MSI numbers\n"); 2678c2ecf20Sopenharmony_ci ret = -EINVAL; 2688c2ecf20Sopenharmony_ci goto err_priv; 2698c2ecf20Sopenharmony_ci } 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci priv->msi_map = kcalloc(BITS_TO_LONGS(priv->num_spis), 2728c2ecf20Sopenharmony_ci sizeof(*priv->msi_map), 2738c2ecf20Sopenharmony_ci GFP_KERNEL); 2748c2ecf20Sopenharmony_ci if (!priv->msi_map) { 2758c2ecf20Sopenharmony_ci ret = -ENOMEM; 2768c2ecf20Sopenharmony_ci goto err_priv; 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci pr_debug("Registering %d msixs, starting at %d\n", 2808c2ecf20Sopenharmony_ci priv->num_spis, priv->spi_first); 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci ret = alpine_msix_init_domains(priv, node); 2838c2ecf20Sopenharmony_ci if (ret) 2848c2ecf20Sopenharmony_ci goto err_map; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci return 0; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_cierr_map: 2898c2ecf20Sopenharmony_ci kfree(priv->msi_map); 2908c2ecf20Sopenharmony_cierr_priv: 2918c2ecf20Sopenharmony_ci kfree(priv); 2928c2ecf20Sopenharmony_ci return ret; 2938c2ecf20Sopenharmony_ci} 2948c2ecf20Sopenharmony_ciIRQCHIP_DECLARE(alpine_msix, "al,alpine-msix", alpine_msix_init); 295