18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Altera PCIe MSI support 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Author: Ley Foon Tan <lftan@altera.com> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Copyright Altera Corporation (C) 2013-2015. All rights reserved 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 118c2ecf20Sopenharmony_ci#include <linux/irqchip/chained_irq.h> 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/msi.h> 158c2ecf20Sopenharmony_ci#include <linux/of_address.h> 168c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 178c2ecf20Sopenharmony_ci#include <linux/of_pci.h> 188c2ecf20Sopenharmony_ci#include <linux/pci.h> 198c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 208c2ecf20Sopenharmony_ci#include <linux/slab.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define MSI_STATUS 0x0 238c2ecf20Sopenharmony_ci#define MSI_ERROR 0x4 248c2ecf20Sopenharmony_ci#define MSI_INTMASK 0x8 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define MAX_MSI_VECTORS 32 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistruct altera_msi { 298c2ecf20Sopenharmony_ci DECLARE_BITMAP(used, MAX_MSI_VECTORS); 308c2ecf20Sopenharmony_ci struct mutex lock; /* protect "used" bitmap */ 318c2ecf20Sopenharmony_ci struct platform_device *pdev; 328c2ecf20Sopenharmony_ci struct irq_domain *msi_domain; 338c2ecf20Sopenharmony_ci struct irq_domain *inner_domain; 348c2ecf20Sopenharmony_ci void __iomem *csr_base; 358c2ecf20Sopenharmony_ci void __iomem *vector_base; 368c2ecf20Sopenharmony_ci phys_addr_t vector_phy; 378c2ecf20Sopenharmony_ci u32 num_of_vectors; 388c2ecf20Sopenharmony_ci int irq; 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic inline void msi_writel(struct altera_msi *msi, const u32 value, 428c2ecf20Sopenharmony_ci const u32 reg) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci writel_relaxed(value, msi->csr_base + reg); 458c2ecf20Sopenharmony_ci} 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic inline u32 msi_readl(struct altera_msi *msi, const u32 reg) 488c2ecf20Sopenharmony_ci{ 498c2ecf20Sopenharmony_ci return readl_relaxed(msi->csr_base + reg); 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic void altera_msi_isr(struct irq_desc *desc) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(desc); 558c2ecf20Sopenharmony_ci struct altera_msi *msi; 568c2ecf20Sopenharmony_ci unsigned long status; 578c2ecf20Sopenharmony_ci u32 bit; 588c2ecf20Sopenharmony_ci u32 virq; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci chained_irq_enter(chip, desc); 618c2ecf20Sopenharmony_ci msi = irq_desc_get_handler_data(desc); 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci while ((status = msi_readl(msi, MSI_STATUS)) != 0) { 648c2ecf20Sopenharmony_ci for_each_set_bit(bit, &status, msi->num_of_vectors) { 658c2ecf20Sopenharmony_ci /* Dummy read from vector to clear the interrupt */ 668c2ecf20Sopenharmony_ci readl_relaxed(msi->vector_base + (bit * sizeof(u32))); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci virq = irq_find_mapping(msi->inner_domain, bit); 698c2ecf20Sopenharmony_ci if (virq) 708c2ecf20Sopenharmony_ci generic_handle_irq(virq); 718c2ecf20Sopenharmony_ci else 728c2ecf20Sopenharmony_ci dev_err(&msi->pdev->dev, "unexpected MSI\n"); 738c2ecf20Sopenharmony_ci } 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci chained_irq_exit(chip, desc); 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic struct irq_chip altera_msi_irq_chip = { 808c2ecf20Sopenharmony_ci .name = "Altera PCIe MSI", 818c2ecf20Sopenharmony_ci .irq_mask = pci_msi_mask_irq, 828c2ecf20Sopenharmony_ci .irq_unmask = pci_msi_unmask_irq, 838c2ecf20Sopenharmony_ci}; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic struct msi_domain_info altera_msi_domain_info = { 868c2ecf20Sopenharmony_ci .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | 878c2ecf20Sopenharmony_ci MSI_FLAG_PCI_MSIX), 888c2ecf20Sopenharmony_ci .chip = &altera_msi_irq_chip, 898c2ecf20Sopenharmony_ci}; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic void altera_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci struct altera_msi *msi = irq_data_get_irq_chip_data(data); 948c2ecf20Sopenharmony_ci phys_addr_t addr = msi->vector_phy + (data->hwirq * sizeof(u32)); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci msg->address_lo = lower_32_bits(addr); 978c2ecf20Sopenharmony_ci msg->address_hi = upper_32_bits(addr); 988c2ecf20Sopenharmony_ci msg->data = data->hwirq; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci dev_dbg(&msi->pdev->dev, "msi#%d address_hi %#x address_lo %#x\n", 1018c2ecf20Sopenharmony_ci (int)data->hwirq, msg->address_hi, msg->address_lo); 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic int altera_msi_set_affinity(struct irq_data *irq_data, 1058c2ecf20Sopenharmony_ci const struct cpumask *mask, bool force) 1068c2ecf20Sopenharmony_ci{ 1078c2ecf20Sopenharmony_ci return -EINVAL; 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic struct irq_chip altera_msi_bottom_irq_chip = { 1118c2ecf20Sopenharmony_ci .name = "Altera MSI", 1128c2ecf20Sopenharmony_ci .irq_compose_msi_msg = altera_compose_msi_msg, 1138c2ecf20Sopenharmony_ci .irq_set_affinity = altera_msi_set_affinity, 1148c2ecf20Sopenharmony_ci}; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_cistatic int altera_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, 1178c2ecf20Sopenharmony_ci unsigned int nr_irqs, void *args) 1188c2ecf20Sopenharmony_ci{ 1198c2ecf20Sopenharmony_ci struct altera_msi *msi = domain->host_data; 1208c2ecf20Sopenharmony_ci unsigned long bit; 1218c2ecf20Sopenharmony_ci u32 mask; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci WARN_ON(nr_irqs != 1); 1248c2ecf20Sopenharmony_ci mutex_lock(&msi->lock); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci bit = find_first_zero_bit(msi->used, msi->num_of_vectors); 1278c2ecf20Sopenharmony_ci if (bit >= msi->num_of_vectors) { 1288c2ecf20Sopenharmony_ci mutex_unlock(&msi->lock); 1298c2ecf20Sopenharmony_ci return -ENOSPC; 1308c2ecf20Sopenharmony_ci } 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci set_bit(bit, msi->used); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci mutex_unlock(&msi->lock); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci irq_domain_set_info(domain, virq, bit, &altera_msi_bottom_irq_chip, 1378c2ecf20Sopenharmony_ci domain->host_data, handle_simple_irq, 1388c2ecf20Sopenharmony_ci NULL, NULL); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci mask = msi_readl(msi, MSI_INTMASK); 1418c2ecf20Sopenharmony_ci mask |= 1 << bit; 1428c2ecf20Sopenharmony_ci msi_writel(msi, mask, MSI_INTMASK); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci return 0; 1458c2ecf20Sopenharmony_ci} 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_cistatic void altera_irq_domain_free(struct irq_domain *domain, 1488c2ecf20Sopenharmony_ci unsigned int virq, unsigned int nr_irqs) 1498c2ecf20Sopenharmony_ci{ 1508c2ecf20Sopenharmony_ci struct irq_data *d = irq_domain_get_irq_data(domain, virq); 1518c2ecf20Sopenharmony_ci struct altera_msi *msi = irq_data_get_irq_chip_data(d); 1528c2ecf20Sopenharmony_ci u32 mask; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci mutex_lock(&msi->lock); 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci if (!test_bit(d->hwirq, msi->used)) { 1578c2ecf20Sopenharmony_ci dev_err(&msi->pdev->dev, "trying to free unused MSI#%lu\n", 1588c2ecf20Sopenharmony_ci d->hwirq); 1598c2ecf20Sopenharmony_ci } else { 1608c2ecf20Sopenharmony_ci __clear_bit(d->hwirq, msi->used); 1618c2ecf20Sopenharmony_ci mask = msi_readl(msi, MSI_INTMASK); 1628c2ecf20Sopenharmony_ci mask &= ~(1 << d->hwirq); 1638c2ecf20Sopenharmony_ci msi_writel(msi, mask, MSI_INTMASK); 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci mutex_unlock(&msi->lock); 1678c2ecf20Sopenharmony_ci} 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_cistatic const struct irq_domain_ops msi_domain_ops = { 1708c2ecf20Sopenharmony_ci .alloc = altera_irq_domain_alloc, 1718c2ecf20Sopenharmony_ci .free = altera_irq_domain_free, 1728c2ecf20Sopenharmony_ci}; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_cistatic int altera_allocate_domains(struct altera_msi *msi) 1758c2ecf20Sopenharmony_ci{ 1768c2ecf20Sopenharmony_ci struct fwnode_handle *fwnode = of_node_to_fwnode(msi->pdev->dev.of_node); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci msi->inner_domain = irq_domain_add_linear(NULL, msi->num_of_vectors, 1798c2ecf20Sopenharmony_ci &msi_domain_ops, msi); 1808c2ecf20Sopenharmony_ci if (!msi->inner_domain) { 1818c2ecf20Sopenharmony_ci dev_err(&msi->pdev->dev, "failed to create IRQ domain\n"); 1828c2ecf20Sopenharmony_ci return -ENOMEM; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci msi->msi_domain = pci_msi_create_irq_domain(fwnode, 1868c2ecf20Sopenharmony_ci &altera_msi_domain_info, msi->inner_domain); 1878c2ecf20Sopenharmony_ci if (!msi->msi_domain) { 1888c2ecf20Sopenharmony_ci dev_err(&msi->pdev->dev, "failed to create MSI domain\n"); 1898c2ecf20Sopenharmony_ci irq_domain_remove(msi->inner_domain); 1908c2ecf20Sopenharmony_ci return -ENOMEM; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci return 0; 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cistatic void altera_free_domains(struct altera_msi *msi) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci irq_domain_remove(msi->msi_domain); 1998c2ecf20Sopenharmony_ci irq_domain_remove(msi->inner_domain); 2008c2ecf20Sopenharmony_ci} 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic int altera_msi_remove(struct platform_device *pdev) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci struct altera_msi *msi = platform_get_drvdata(pdev); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci msi_writel(msi, 0, MSI_INTMASK); 2078c2ecf20Sopenharmony_ci irq_set_chained_handler(msi->irq, NULL); 2088c2ecf20Sopenharmony_ci irq_set_handler_data(msi->irq, NULL); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci altera_free_domains(msi); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, NULL); 2138c2ecf20Sopenharmony_ci return 0; 2148c2ecf20Sopenharmony_ci} 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_cistatic int altera_msi_probe(struct platform_device *pdev) 2178c2ecf20Sopenharmony_ci{ 2188c2ecf20Sopenharmony_ci struct altera_msi *msi; 2198c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 2208c2ecf20Sopenharmony_ci struct resource *res; 2218c2ecf20Sopenharmony_ci int ret; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci msi = devm_kzalloc(&pdev->dev, sizeof(struct altera_msi), 2248c2ecf20Sopenharmony_ci GFP_KERNEL); 2258c2ecf20Sopenharmony_ci if (!msi) 2268c2ecf20Sopenharmony_ci return -ENOMEM; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci mutex_init(&msi->lock); 2298c2ecf20Sopenharmony_ci msi->pdev = pdev; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci msi->csr_base = devm_platform_ioremap_resource_byname(pdev, "csr"); 2328c2ecf20Sopenharmony_ci if (IS_ERR(msi->csr_base)) { 2338c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to map csr memory\n"); 2348c2ecf20Sopenharmony_ci return PTR_ERR(msi->csr_base); 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci res = platform_get_resource_byname(pdev, IORESOURCE_MEM, 2388c2ecf20Sopenharmony_ci "vector_slave"); 2398c2ecf20Sopenharmony_ci msi->vector_base = devm_ioremap_resource(&pdev->dev, res); 2408c2ecf20Sopenharmony_ci if (IS_ERR(msi->vector_base)) { 2418c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to map vector_slave memory\n"); 2428c2ecf20Sopenharmony_ci return PTR_ERR(msi->vector_base); 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci msi->vector_phy = res->start; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci if (of_property_read_u32(np, "num-vectors", &msi->num_of_vectors)) { 2488c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to parse the number of vectors\n"); 2498c2ecf20Sopenharmony_ci return -EINVAL; 2508c2ecf20Sopenharmony_ci } 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci ret = altera_allocate_domains(msi); 2538c2ecf20Sopenharmony_ci if (ret) 2548c2ecf20Sopenharmony_ci return ret; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci msi->irq = platform_get_irq(pdev, 0); 2578c2ecf20Sopenharmony_ci if (msi->irq < 0) { 2588c2ecf20Sopenharmony_ci ret = msi->irq; 2598c2ecf20Sopenharmony_ci goto err; 2608c2ecf20Sopenharmony_ci } 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci irq_set_chained_handler_and_data(msi->irq, altera_msi_isr, msi); 2638c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, msi); 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci return 0; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_cierr: 2688c2ecf20Sopenharmony_ci altera_msi_remove(pdev); 2698c2ecf20Sopenharmony_ci return ret; 2708c2ecf20Sopenharmony_ci} 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_cistatic const struct of_device_id altera_msi_of_match[] = { 2738c2ecf20Sopenharmony_ci { .compatible = "altr,msi-1.0", NULL }, 2748c2ecf20Sopenharmony_ci { }, 2758c2ecf20Sopenharmony_ci}; 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_cistatic struct platform_driver altera_msi_driver = { 2788c2ecf20Sopenharmony_ci .driver = { 2798c2ecf20Sopenharmony_ci .name = "altera-msi", 2808c2ecf20Sopenharmony_ci .of_match_table = altera_msi_of_match, 2818c2ecf20Sopenharmony_ci }, 2828c2ecf20Sopenharmony_ci .probe = altera_msi_probe, 2838c2ecf20Sopenharmony_ci .remove = altera_msi_remove, 2848c2ecf20Sopenharmony_ci}; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_cistatic int __init altera_msi_init(void) 2878c2ecf20Sopenharmony_ci{ 2888c2ecf20Sopenharmony_ci return platform_driver_register(&altera_msi_driver); 2898c2ecf20Sopenharmony_ci} 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_cistatic void __exit altera_msi_exit(void) 2928c2ecf20Sopenharmony_ci{ 2938c2ecf20Sopenharmony_ci platform_driver_unregister(&altera_msi_driver); 2948c2ecf20Sopenharmony_ci} 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_cisubsys_initcall(altera_msi_init); 2978c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, altera_msi_of_match); 2988c2ecf20Sopenharmony_cimodule_exit(altera_msi_exit); 2998c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 300