18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * MSI support for PPC4xx SoCs using High Speed Transfer Assist (HSTA) for 48c2ecf20Sopenharmony_ci * generation of the interrupt. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Copyright © 2013 Alistair Popple <alistair@popple.id.au> IBM Corporation 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 118c2ecf20Sopenharmony_ci#include <linux/msi.h> 128c2ecf20Sopenharmony_ci#include <linux/of.h> 138c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 148c2ecf20Sopenharmony_ci#include <linux/pci.h> 158c2ecf20Sopenharmony_ci#include <linux/semaphore.h> 168c2ecf20Sopenharmony_ci#include <asm/msi_bitmap.h> 178c2ecf20Sopenharmony_ci#include <asm/ppc-pci.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistruct ppc4xx_hsta_msi { 208c2ecf20Sopenharmony_ci struct device *dev; 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci /* The ioremapped HSTA MSI IO space */ 238c2ecf20Sopenharmony_ci u32 __iomem *data; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci /* Physical address of HSTA MSI IO space */ 268c2ecf20Sopenharmony_ci u64 address; 278c2ecf20Sopenharmony_ci struct msi_bitmap bmp; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci /* An array mapping offsets to hardware IRQs */ 308c2ecf20Sopenharmony_ci int *irq_map; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci /* Number of hwirqs supported */ 338c2ecf20Sopenharmony_ci int irq_count; 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_cistatic struct ppc4xx_hsta_msi ppc4xx_hsta_msi; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic int hsta_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) 388c2ecf20Sopenharmony_ci{ 398c2ecf20Sopenharmony_ci struct msi_msg msg; 408c2ecf20Sopenharmony_ci struct msi_desc *entry; 418c2ecf20Sopenharmony_ci int irq, hwirq; 428c2ecf20Sopenharmony_ci u64 addr; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci /* We don't support MSI-X */ 458c2ecf20Sopenharmony_ci if (type == PCI_CAP_ID_MSIX) { 468c2ecf20Sopenharmony_ci pr_debug("%s: MSI-X not supported.\n", __func__); 478c2ecf20Sopenharmony_ci return -EINVAL; 488c2ecf20Sopenharmony_ci } 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci for_each_pci_msi_entry(entry, dev) { 518c2ecf20Sopenharmony_ci irq = msi_bitmap_alloc_hwirqs(&ppc4xx_hsta_msi.bmp, 1); 528c2ecf20Sopenharmony_ci if (irq < 0) { 538c2ecf20Sopenharmony_ci pr_debug("%s: Failed to allocate msi interrupt\n", 548c2ecf20Sopenharmony_ci __func__); 558c2ecf20Sopenharmony_ci return irq; 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci hwirq = ppc4xx_hsta_msi.irq_map[irq]; 598c2ecf20Sopenharmony_ci if (!hwirq) { 608c2ecf20Sopenharmony_ci pr_err("%s: Failed mapping irq %d\n", __func__, irq); 618c2ecf20Sopenharmony_ci return -EINVAL; 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci /* 658c2ecf20Sopenharmony_ci * HSTA generates interrupts on writes to 128-bit aligned 668c2ecf20Sopenharmony_ci * addresses. 678c2ecf20Sopenharmony_ci */ 688c2ecf20Sopenharmony_ci addr = ppc4xx_hsta_msi.address + irq*0x10; 698c2ecf20Sopenharmony_ci msg.address_hi = upper_32_bits(addr); 708c2ecf20Sopenharmony_ci msg.address_lo = lower_32_bits(addr); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci /* Data is not used by the HSTA. */ 738c2ecf20Sopenharmony_ci msg.data = 0; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci pr_debug("%s: Setup irq %d (0x%0llx)\n", __func__, hwirq, 768c2ecf20Sopenharmony_ci (((u64) msg.address_hi) << 32) | msg.address_lo); 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci if (irq_set_msi_desc(hwirq, entry)) { 798c2ecf20Sopenharmony_ci pr_err( 808c2ecf20Sopenharmony_ci "%s: Invalid hwirq %d specified in device tree\n", 818c2ecf20Sopenharmony_ci __func__, hwirq); 828c2ecf20Sopenharmony_ci msi_bitmap_free_hwirqs(&ppc4xx_hsta_msi.bmp, irq, 1); 838c2ecf20Sopenharmony_ci return -EINVAL; 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci pci_write_msi_msg(hwirq, &msg); 868c2ecf20Sopenharmony_ci } 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci return 0; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic int hsta_find_hwirq_offset(int hwirq) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci int irq; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci /* Find the offset given the hwirq */ 968c2ecf20Sopenharmony_ci for (irq = 0; irq < ppc4xx_hsta_msi.irq_count; irq++) 978c2ecf20Sopenharmony_ci if (ppc4xx_hsta_msi.irq_map[irq] == hwirq) 988c2ecf20Sopenharmony_ci return irq; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci return -EINVAL; 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic void hsta_teardown_msi_irqs(struct pci_dev *dev) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci struct msi_desc *entry; 1068c2ecf20Sopenharmony_ci int irq; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci for_each_pci_msi_entry(entry, dev) { 1098c2ecf20Sopenharmony_ci if (!entry->irq) 1108c2ecf20Sopenharmony_ci continue; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci irq = hsta_find_hwirq_offset(entry->irq); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci /* entry->irq should always be in irq_map */ 1158c2ecf20Sopenharmony_ci BUG_ON(irq < 0); 1168c2ecf20Sopenharmony_ci irq_set_msi_desc(entry->irq, NULL); 1178c2ecf20Sopenharmony_ci msi_bitmap_free_hwirqs(&ppc4xx_hsta_msi.bmp, irq, 1); 1188c2ecf20Sopenharmony_ci pr_debug("%s: Teardown IRQ %u (index %u)\n", __func__, 1198c2ecf20Sopenharmony_ci entry->irq, irq); 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic int hsta_msi_probe(struct platform_device *pdev) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1268c2ecf20Sopenharmony_ci struct resource *mem; 1278c2ecf20Sopenharmony_ci int irq, ret, irq_count; 1288c2ecf20Sopenharmony_ci struct pci_controller *phb; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1318c2ecf20Sopenharmony_ci if (!mem) { 1328c2ecf20Sopenharmony_ci dev_err(dev, "Unable to get mmio space\n"); 1338c2ecf20Sopenharmony_ci return -EINVAL; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci irq_count = of_irq_count(dev->of_node); 1378c2ecf20Sopenharmony_ci if (!irq_count) { 1388c2ecf20Sopenharmony_ci dev_err(dev, "Unable to find IRQ range\n"); 1398c2ecf20Sopenharmony_ci return -EINVAL; 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci ppc4xx_hsta_msi.dev = dev; 1438c2ecf20Sopenharmony_ci ppc4xx_hsta_msi.address = mem->start; 1448c2ecf20Sopenharmony_ci ppc4xx_hsta_msi.data = ioremap(mem->start, resource_size(mem)); 1458c2ecf20Sopenharmony_ci ppc4xx_hsta_msi.irq_count = irq_count; 1468c2ecf20Sopenharmony_ci if (!ppc4xx_hsta_msi.data) { 1478c2ecf20Sopenharmony_ci dev_err(dev, "Unable to map memory\n"); 1488c2ecf20Sopenharmony_ci return -ENOMEM; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci ret = msi_bitmap_alloc(&ppc4xx_hsta_msi.bmp, irq_count, dev->of_node); 1528c2ecf20Sopenharmony_ci if (ret) 1538c2ecf20Sopenharmony_ci goto out; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci ppc4xx_hsta_msi.irq_map = kmalloc_array(irq_count, sizeof(int), 1568c2ecf20Sopenharmony_ci GFP_KERNEL); 1578c2ecf20Sopenharmony_ci if (!ppc4xx_hsta_msi.irq_map) { 1588c2ecf20Sopenharmony_ci ret = -ENOMEM; 1598c2ecf20Sopenharmony_ci goto out1; 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci /* Setup a mapping from irq offsets to hardware irq numbers */ 1638c2ecf20Sopenharmony_ci for (irq = 0; irq < irq_count; irq++) { 1648c2ecf20Sopenharmony_ci ppc4xx_hsta_msi.irq_map[irq] = 1658c2ecf20Sopenharmony_ci irq_of_parse_and_map(dev->of_node, irq); 1668c2ecf20Sopenharmony_ci if (!ppc4xx_hsta_msi.irq_map[irq]) { 1678c2ecf20Sopenharmony_ci dev_err(dev, "Unable to map IRQ\n"); 1688c2ecf20Sopenharmony_ci ret = -EINVAL; 1698c2ecf20Sopenharmony_ci goto out2; 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci list_for_each_entry(phb, &hose_list, list_node) { 1748c2ecf20Sopenharmony_ci phb->controller_ops.setup_msi_irqs = hsta_setup_msi_irqs; 1758c2ecf20Sopenharmony_ci phb->controller_ops.teardown_msi_irqs = hsta_teardown_msi_irqs; 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci return 0; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ciout2: 1808c2ecf20Sopenharmony_ci kfree(ppc4xx_hsta_msi.irq_map); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ciout1: 1838c2ecf20Sopenharmony_ci msi_bitmap_free(&ppc4xx_hsta_msi.bmp); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ciout: 1868c2ecf20Sopenharmony_ci iounmap(ppc4xx_hsta_msi.data); 1878c2ecf20Sopenharmony_ci return ret; 1888c2ecf20Sopenharmony_ci} 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic const struct of_device_id hsta_msi_ids[] = { 1918c2ecf20Sopenharmony_ci { 1928c2ecf20Sopenharmony_ci .compatible = "ibm,hsta-msi", 1938c2ecf20Sopenharmony_ci }, 1948c2ecf20Sopenharmony_ci {} 1958c2ecf20Sopenharmony_ci}; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cistatic struct platform_driver hsta_msi_driver = { 1988c2ecf20Sopenharmony_ci .probe = hsta_msi_probe, 1998c2ecf20Sopenharmony_ci .driver = { 2008c2ecf20Sopenharmony_ci .name = "hsta-msi", 2018c2ecf20Sopenharmony_ci .of_match_table = hsta_msi_ids, 2028c2ecf20Sopenharmony_ci }, 2038c2ecf20Sopenharmony_ci}; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic int hsta_msi_init(void) 2068c2ecf20Sopenharmony_ci{ 2078c2ecf20Sopenharmony_ci return platform_driver_register(&hsta_msi_driver); 2088c2ecf20Sopenharmony_ci} 2098c2ecf20Sopenharmony_cisubsys_initcall(hsta_msi_init); 210