162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * Annapurna Labs MSIX support services 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (C) 2016, Amazon.com, Inc. or its affiliates. All Rights Reserved. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Antoine Tenart <antoine.tenart@free-electrons.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * This file is licensed under the terms of the GNU General Public 962306a36Sopenharmony_ci * License version 2. This program is licensed "as is" without any 1062306a36Sopenharmony_ci * warranty of any kind, whether express or implied. 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/irqchip.h> 1662306a36Sopenharmony_ci#include <linux/irqchip/arm-gic.h> 1762306a36Sopenharmony_ci#include <linux/msi.h> 1862306a36Sopenharmony_ci#include <linux/of.h> 1962306a36Sopenharmony_ci#include <linux/of_address.h> 2062306a36Sopenharmony_ci#include <linux/of_irq.h> 2162306a36Sopenharmony_ci#include <linux/of_pci.h> 2262306a36Sopenharmony_ci#include <linux/pci.h> 2362306a36Sopenharmony_ci#include <linux/slab.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#include <asm/irq.h> 2662306a36Sopenharmony_ci#include <asm/msi.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci/* MSIX message address format: local GIC target */ 2962306a36Sopenharmony_ci#define ALPINE_MSIX_SPI_TARGET_CLUSTER0 BIT(16) 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistruct alpine_msix_data { 3262306a36Sopenharmony_ci spinlock_t msi_map_lock; 3362306a36Sopenharmony_ci phys_addr_t addr; 3462306a36Sopenharmony_ci u32 spi_first; /* The SGI number that MSIs start */ 3562306a36Sopenharmony_ci u32 num_spis; /* The number of SGIs for MSIs */ 3662306a36Sopenharmony_ci unsigned long *msi_map; 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic void alpine_msix_mask_msi_irq(struct irq_data *d) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci pci_msi_mask_irq(d); 4262306a36Sopenharmony_ci irq_chip_mask_parent(d); 4362306a36Sopenharmony_ci} 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistatic void alpine_msix_unmask_msi_irq(struct irq_data *d) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci pci_msi_unmask_irq(d); 4862306a36Sopenharmony_ci irq_chip_unmask_parent(d); 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic struct irq_chip alpine_msix_irq_chip = { 5262306a36Sopenharmony_ci .name = "MSIx", 5362306a36Sopenharmony_ci .irq_mask = alpine_msix_mask_msi_irq, 5462306a36Sopenharmony_ci .irq_unmask = alpine_msix_unmask_msi_irq, 5562306a36Sopenharmony_ci .irq_eoi = irq_chip_eoi_parent, 5662306a36Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 5762306a36Sopenharmony_ci}; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic int alpine_msix_allocate_sgi(struct alpine_msix_data *priv, int num_req) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci int first; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci spin_lock(&priv->msi_map_lock); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci first = bitmap_find_next_zero_area(priv->msi_map, priv->num_spis, 0, 6662306a36Sopenharmony_ci num_req, 0); 6762306a36Sopenharmony_ci if (first >= priv->num_spis) { 6862306a36Sopenharmony_ci spin_unlock(&priv->msi_map_lock); 6962306a36Sopenharmony_ci return -ENOSPC; 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci bitmap_set(priv->msi_map, first, num_req); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci spin_unlock(&priv->msi_map_lock); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci return priv->spi_first + first; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic void alpine_msix_free_sgi(struct alpine_msix_data *priv, unsigned sgi, 8062306a36Sopenharmony_ci int num_req) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci int first = sgi - priv->spi_first; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci spin_lock(&priv->msi_map_lock); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci bitmap_clear(priv->msi_map, first, num_req); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci spin_unlock(&priv->msi_map_lock); 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic void alpine_msix_compose_msi_msg(struct irq_data *data, 9262306a36Sopenharmony_ci struct msi_msg *msg) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci struct alpine_msix_data *priv = irq_data_get_irq_chip_data(data); 9562306a36Sopenharmony_ci phys_addr_t msg_addr = priv->addr; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci msg_addr |= (data->hwirq << 3); 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci msg->address_hi = upper_32_bits(msg_addr); 10062306a36Sopenharmony_ci msg->address_lo = lower_32_bits(msg_addr); 10162306a36Sopenharmony_ci msg->data = 0; 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic struct msi_domain_info alpine_msix_domain_info = { 10562306a36Sopenharmony_ci .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | 10662306a36Sopenharmony_ci MSI_FLAG_PCI_MSIX, 10762306a36Sopenharmony_ci .chip = &alpine_msix_irq_chip, 10862306a36Sopenharmony_ci}; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cistatic struct irq_chip middle_irq_chip = { 11162306a36Sopenharmony_ci .name = "alpine_msix_middle", 11262306a36Sopenharmony_ci .irq_mask = irq_chip_mask_parent, 11362306a36Sopenharmony_ci .irq_unmask = irq_chip_unmask_parent, 11462306a36Sopenharmony_ci .irq_eoi = irq_chip_eoi_parent, 11562306a36Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 11662306a36Sopenharmony_ci .irq_compose_msi_msg = alpine_msix_compose_msi_msg, 11762306a36Sopenharmony_ci}; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic int alpine_msix_gic_domain_alloc(struct irq_domain *domain, 12062306a36Sopenharmony_ci unsigned int virq, int sgi) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct irq_fwspec fwspec; 12362306a36Sopenharmony_ci struct irq_data *d; 12462306a36Sopenharmony_ci int ret; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci if (!is_of_node(domain->parent->fwnode)) 12762306a36Sopenharmony_ci return -EINVAL; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci fwspec.fwnode = domain->parent->fwnode; 13062306a36Sopenharmony_ci fwspec.param_count = 3; 13162306a36Sopenharmony_ci fwspec.param[0] = 0; 13262306a36Sopenharmony_ci fwspec.param[1] = sgi; 13362306a36Sopenharmony_ci fwspec.param[2] = IRQ_TYPE_EDGE_RISING; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); 13662306a36Sopenharmony_ci if (ret) 13762306a36Sopenharmony_ci return ret; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci d = irq_domain_get_irq_data(domain->parent, virq); 14062306a36Sopenharmony_ci d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci return 0; 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic int alpine_msix_middle_domain_alloc(struct irq_domain *domain, 14662306a36Sopenharmony_ci unsigned int virq, 14762306a36Sopenharmony_ci unsigned int nr_irqs, void *args) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci struct alpine_msix_data *priv = domain->host_data; 15062306a36Sopenharmony_ci int sgi, err, i; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci sgi = alpine_msix_allocate_sgi(priv, nr_irqs); 15362306a36Sopenharmony_ci if (sgi < 0) 15462306a36Sopenharmony_ci return sgi; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci for (i = 0; i < nr_irqs; i++) { 15762306a36Sopenharmony_ci err = alpine_msix_gic_domain_alloc(domain, virq + i, sgi + i); 15862306a36Sopenharmony_ci if (err) 15962306a36Sopenharmony_ci goto err_sgi; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci irq_domain_set_hwirq_and_chip(domain, virq + i, sgi + i, 16262306a36Sopenharmony_ci &middle_irq_chip, priv); 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci return 0; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_cierr_sgi: 16862306a36Sopenharmony_ci irq_domain_free_irqs_parent(domain, virq, i - 1); 16962306a36Sopenharmony_ci alpine_msix_free_sgi(priv, sgi, nr_irqs); 17062306a36Sopenharmony_ci return err; 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic void alpine_msix_middle_domain_free(struct irq_domain *domain, 17462306a36Sopenharmony_ci unsigned int virq, 17562306a36Sopenharmony_ci unsigned int nr_irqs) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci struct irq_data *d = irq_domain_get_irq_data(domain, virq); 17862306a36Sopenharmony_ci struct alpine_msix_data *priv = irq_data_get_irq_chip_data(d); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci irq_domain_free_irqs_parent(domain, virq, nr_irqs); 18162306a36Sopenharmony_ci alpine_msix_free_sgi(priv, d->hwirq, nr_irqs); 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic const struct irq_domain_ops alpine_msix_middle_domain_ops = { 18562306a36Sopenharmony_ci .alloc = alpine_msix_middle_domain_alloc, 18662306a36Sopenharmony_ci .free = alpine_msix_middle_domain_free, 18762306a36Sopenharmony_ci}; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic int alpine_msix_init_domains(struct alpine_msix_data *priv, 19062306a36Sopenharmony_ci struct device_node *node) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci struct irq_domain *middle_domain, *msi_domain, *gic_domain; 19362306a36Sopenharmony_ci struct device_node *gic_node; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci gic_node = of_irq_find_parent(node); 19662306a36Sopenharmony_ci if (!gic_node) { 19762306a36Sopenharmony_ci pr_err("Failed to find the GIC node\n"); 19862306a36Sopenharmony_ci return -ENODEV; 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci gic_domain = irq_find_host(gic_node); 20262306a36Sopenharmony_ci of_node_put(gic_node); 20362306a36Sopenharmony_ci if (!gic_domain) { 20462306a36Sopenharmony_ci pr_err("Failed to find the GIC domain\n"); 20562306a36Sopenharmony_ci return -ENXIO; 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci middle_domain = irq_domain_add_hierarchy(gic_domain, 0, 0, NULL, 20962306a36Sopenharmony_ci &alpine_msix_middle_domain_ops, 21062306a36Sopenharmony_ci priv); 21162306a36Sopenharmony_ci if (!middle_domain) { 21262306a36Sopenharmony_ci pr_err("Failed to create the MSIX middle domain\n"); 21362306a36Sopenharmony_ci return -ENOMEM; 21462306a36Sopenharmony_ci } 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node), 21762306a36Sopenharmony_ci &alpine_msix_domain_info, 21862306a36Sopenharmony_ci middle_domain); 21962306a36Sopenharmony_ci if (!msi_domain) { 22062306a36Sopenharmony_ci pr_err("Failed to create MSI domain\n"); 22162306a36Sopenharmony_ci irq_domain_remove(middle_domain); 22262306a36Sopenharmony_ci return -ENOMEM; 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci return 0; 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cistatic int alpine_msix_init(struct device_node *node, 22962306a36Sopenharmony_ci struct device_node *parent) 23062306a36Sopenharmony_ci{ 23162306a36Sopenharmony_ci struct alpine_msix_data *priv; 23262306a36Sopenharmony_ci struct resource res; 23362306a36Sopenharmony_ci int ret; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci priv = kzalloc(sizeof(*priv), GFP_KERNEL); 23662306a36Sopenharmony_ci if (!priv) 23762306a36Sopenharmony_ci return -ENOMEM; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci spin_lock_init(&priv->msi_map_lock); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci ret = of_address_to_resource(node, 0, &res); 24262306a36Sopenharmony_ci if (ret) { 24362306a36Sopenharmony_ci pr_err("Failed to allocate resource\n"); 24462306a36Sopenharmony_ci goto err_priv; 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci /* 24862306a36Sopenharmony_ci * The 20 least significant bits of addr provide direct information 24962306a36Sopenharmony_ci * regarding the interrupt destination. 25062306a36Sopenharmony_ci * 25162306a36Sopenharmony_ci * To select the primary GIC as the target GIC, bits [18:17] must be set 25262306a36Sopenharmony_ci * to 0x0. In this case, bit 16 (SPI_TARGET_CLUSTER0) must be set. 25362306a36Sopenharmony_ci */ 25462306a36Sopenharmony_ci priv->addr = res.start & GENMASK_ULL(63,20); 25562306a36Sopenharmony_ci priv->addr |= ALPINE_MSIX_SPI_TARGET_CLUSTER0; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci if (of_property_read_u32(node, "al,msi-base-spi", &priv->spi_first)) { 25862306a36Sopenharmony_ci pr_err("Unable to parse MSI base\n"); 25962306a36Sopenharmony_ci ret = -EINVAL; 26062306a36Sopenharmony_ci goto err_priv; 26162306a36Sopenharmony_ci } 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci if (of_property_read_u32(node, "al,msi-num-spis", &priv->num_spis)) { 26462306a36Sopenharmony_ci pr_err("Unable to parse MSI numbers\n"); 26562306a36Sopenharmony_ci ret = -EINVAL; 26662306a36Sopenharmony_ci goto err_priv; 26762306a36Sopenharmony_ci } 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci priv->msi_map = bitmap_zalloc(priv->num_spis, GFP_KERNEL); 27062306a36Sopenharmony_ci if (!priv->msi_map) { 27162306a36Sopenharmony_ci ret = -ENOMEM; 27262306a36Sopenharmony_ci goto err_priv; 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci pr_debug("Registering %d msixs, starting at %d\n", 27662306a36Sopenharmony_ci priv->num_spis, priv->spi_first); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci ret = alpine_msix_init_domains(priv, node); 27962306a36Sopenharmony_ci if (ret) 28062306a36Sopenharmony_ci goto err_map; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci return 0; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_cierr_map: 28562306a36Sopenharmony_ci bitmap_free(priv->msi_map); 28662306a36Sopenharmony_cierr_priv: 28762306a36Sopenharmony_ci kfree(priv); 28862306a36Sopenharmony_ci return ret; 28962306a36Sopenharmony_ci} 29062306a36Sopenharmony_ciIRQCHIP_DECLARE(alpine_msix, "al,alpine-msix", alpine_msix_init); 291