162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * MSI support for PPC4xx SoCs using High Speed Transfer Assist (HSTA) for 462306a36Sopenharmony_ci * generation of the interrupt. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright © 2013 Alistair Popple <alistair@popple.id.au> IBM Corporation 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/interrupt.h> 1162306a36Sopenharmony_ci#include <linux/msi.h> 1262306a36Sopenharmony_ci#include <linux/of.h> 1362306a36Sopenharmony_ci#include <linux/of_irq.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/pci.h> 1662306a36Sopenharmony_ci#include <linux/semaphore.h> 1762306a36Sopenharmony_ci#include <asm/msi_bitmap.h> 1862306a36Sopenharmony_ci#include <asm/ppc-pci.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistruct ppc4xx_hsta_msi { 2162306a36Sopenharmony_ci struct device *dev; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci /* The ioremapped HSTA MSI IO space */ 2462306a36Sopenharmony_ci u32 __iomem *data; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci /* Physical address of HSTA MSI IO space */ 2762306a36Sopenharmony_ci u64 address; 2862306a36Sopenharmony_ci struct msi_bitmap bmp; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci /* An array mapping offsets to hardware IRQs */ 3162306a36Sopenharmony_ci int *irq_map; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci /* Number of hwirqs supported */ 3462306a36Sopenharmony_ci int irq_count; 3562306a36Sopenharmony_ci}; 3662306a36Sopenharmony_cistatic struct ppc4xx_hsta_msi ppc4xx_hsta_msi; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic int hsta_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci struct msi_msg msg; 4162306a36Sopenharmony_ci struct msi_desc *entry; 4262306a36Sopenharmony_ci int irq, hwirq; 4362306a36Sopenharmony_ci u64 addr; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci /* We don't support MSI-X */ 4662306a36Sopenharmony_ci if (type == PCI_CAP_ID_MSIX) { 4762306a36Sopenharmony_ci pr_debug("%s: MSI-X not supported.\n", __func__); 4862306a36Sopenharmony_ci return -EINVAL; 4962306a36Sopenharmony_ci } 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci msi_for_each_desc(entry, &dev->dev, MSI_DESC_NOTASSOCIATED) { 5262306a36Sopenharmony_ci irq = msi_bitmap_alloc_hwirqs(&ppc4xx_hsta_msi.bmp, 1); 5362306a36Sopenharmony_ci if (irq < 0) { 5462306a36Sopenharmony_ci pr_debug("%s: Failed to allocate msi interrupt\n", 5562306a36Sopenharmony_ci __func__); 5662306a36Sopenharmony_ci return irq; 5762306a36Sopenharmony_ci } 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci hwirq = ppc4xx_hsta_msi.irq_map[irq]; 6062306a36Sopenharmony_ci if (!hwirq) { 6162306a36Sopenharmony_ci pr_err("%s: Failed mapping irq %d\n", __func__, irq); 6262306a36Sopenharmony_ci return -EINVAL; 6362306a36Sopenharmony_ci } 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci /* 6662306a36Sopenharmony_ci * HSTA generates interrupts on writes to 128-bit aligned 6762306a36Sopenharmony_ci * addresses. 6862306a36Sopenharmony_ci */ 6962306a36Sopenharmony_ci addr = ppc4xx_hsta_msi.address + irq*0x10; 7062306a36Sopenharmony_ci msg.address_hi = upper_32_bits(addr); 7162306a36Sopenharmony_ci msg.address_lo = lower_32_bits(addr); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci /* Data is not used by the HSTA. */ 7462306a36Sopenharmony_ci msg.data = 0; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci pr_debug("%s: Setup irq %d (0x%0llx)\n", __func__, hwirq, 7762306a36Sopenharmony_ci (((u64) msg.address_hi) << 32) | msg.address_lo); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci if (irq_set_msi_desc(hwirq, entry)) { 8062306a36Sopenharmony_ci pr_err( 8162306a36Sopenharmony_ci "%s: Invalid hwirq %d specified in device tree\n", 8262306a36Sopenharmony_ci __func__, hwirq); 8362306a36Sopenharmony_ci msi_bitmap_free_hwirqs(&ppc4xx_hsta_msi.bmp, irq, 1); 8462306a36Sopenharmony_ci return -EINVAL; 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci pci_write_msi_msg(hwirq, &msg); 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci return 0; 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistatic int hsta_find_hwirq_offset(int hwirq) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci int irq; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci /* Find the offset given the hwirq */ 9762306a36Sopenharmony_ci for (irq = 0; irq < ppc4xx_hsta_msi.irq_count; irq++) 9862306a36Sopenharmony_ci if (ppc4xx_hsta_msi.irq_map[irq] == hwirq) 9962306a36Sopenharmony_ci return irq; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci return -EINVAL; 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic void hsta_teardown_msi_irqs(struct pci_dev *dev) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct msi_desc *entry; 10762306a36Sopenharmony_ci int irq; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci msi_for_each_desc(entry, &dev->dev, MSI_DESC_ASSOCIATED) { 11062306a36Sopenharmony_ci irq = hsta_find_hwirq_offset(entry->irq); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci /* entry->irq should always be in irq_map */ 11362306a36Sopenharmony_ci BUG_ON(irq < 0); 11462306a36Sopenharmony_ci irq_set_msi_desc(entry->irq, NULL); 11562306a36Sopenharmony_ci msi_bitmap_free_hwirqs(&ppc4xx_hsta_msi.bmp, irq, 1); 11662306a36Sopenharmony_ci pr_debug("%s: Teardown IRQ %u (index %u)\n", __func__, 11762306a36Sopenharmony_ci entry->irq, irq); 11862306a36Sopenharmony_ci entry->irq = 0; 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic int hsta_msi_probe(struct platform_device *pdev) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci struct device *dev = &pdev->dev; 12562306a36Sopenharmony_ci struct resource *mem; 12662306a36Sopenharmony_ci int irq, ret, irq_count; 12762306a36Sopenharmony_ci struct pci_controller *phb; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 13062306a36Sopenharmony_ci if (!mem) { 13162306a36Sopenharmony_ci dev_err(dev, "Unable to get mmio space\n"); 13262306a36Sopenharmony_ci return -EINVAL; 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci irq_count = of_irq_count(dev->of_node); 13662306a36Sopenharmony_ci if (!irq_count) { 13762306a36Sopenharmony_ci dev_err(dev, "Unable to find IRQ range\n"); 13862306a36Sopenharmony_ci return -EINVAL; 13962306a36Sopenharmony_ci } 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci ppc4xx_hsta_msi.dev = dev; 14262306a36Sopenharmony_ci ppc4xx_hsta_msi.address = mem->start; 14362306a36Sopenharmony_ci ppc4xx_hsta_msi.data = ioremap(mem->start, resource_size(mem)); 14462306a36Sopenharmony_ci ppc4xx_hsta_msi.irq_count = irq_count; 14562306a36Sopenharmony_ci if (!ppc4xx_hsta_msi.data) { 14662306a36Sopenharmony_ci dev_err(dev, "Unable to map memory\n"); 14762306a36Sopenharmony_ci return -ENOMEM; 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci ret = msi_bitmap_alloc(&ppc4xx_hsta_msi.bmp, irq_count, dev->of_node); 15162306a36Sopenharmony_ci if (ret) 15262306a36Sopenharmony_ci goto out; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci ppc4xx_hsta_msi.irq_map = kmalloc_array(irq_count, sizeof(int), 15562306a36Sopenharmony_ci GFP_KERNEL); 15662306a36Sopenharmony_ci if (!ppc4xx_hsta_msi.irq_map) { 15762306a36Sopenharmony_ci ret = -ENOMEM; 15862306a36Sopenharmony_ci goto out1; 15962306a36Sopenharmony_ci } 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci /* Setup a mapping from irq offsets to hardware irq numbers */ 16262306a36Sopenharmony_ci for (irq = 0; irq < irq_count; irq++) { 16362306a36Sopenharmony_ci ppc4xx_hsta_msi.irq_map[irq] = 16462306a36Sopenharmony_ci irq_of_parse_and_map(dev->of_node, irq); 16562306a36Sopenharmony_ci if (!ppc4xx_hsta_msi.irq_map[irq]) { 16662306a36Sopenharmony_ci dev_err(dev, "Unable to map IRQ\n"); 16762306a36Sopenharmony_ci ret = -EINVAL; 16862306a36Sopenharmony_ci goto out2; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci list_for_each_entry(phb, &hose_list, list_node) { 17362306a36Sopenharmony_ci phb->controller_ops.setup_msi_irqs = hsta_setup_msi_irqs; 17462306a36Sopenharmony_ci phb->controller_ops.teardown_msi_irqs = hsta_teardown_msi_irqs; 17562306a36Sopenharmony_ci } 17662306a36Sopenharmony_ci return 0; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ciout2: 17962306a36Sopenharmony_ci kfree(ppc4xx_hsta_msi.irq_map); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ciout1: 18262306a36Sopenharmony_ci msi_bitmap_free(&ppc4xx_hsta_msi.bmp); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ciout: 18562306a36Sopenharmony_ci iounmap(ppc4xx_hsta_msi.data); 18662306a36Sopenharmony_ci return ret; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic const struct of_device_id hsta_msi_ids[] = { 19062306a36Sopenharmony_ci { 19162306a36Sopenharmony_ci .compatible = "ibm,hsta-msi", 19262306a36Sopenharmony_ci }, 19362306a36Sopenharmony_ci {} 19462306a36Sopenharmony_ci}; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic struct platform_driver hsta_msi_driver = { 19762306a36Sopenharmony_ci .probe = hsta_msi_probe, 19862306a36Sopenharmony_ci .driver = { 19962306a36Sopenharmony_ci .name = "hsta-msi", 20062306a36Sopenharmony_ci .of_match_table = hsta_msi_ids, 20162306a36Sopenharmony_ci }, 20262306a36Sopenharmony_ci}; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistatic int hsta_msi_init(void) 20562306a36Sopenharmony_ci{ 20662306a36Sopenharmony_ci return platform_driver_register(&hsta_msi_driver); 20762306a36Sopenharmony_ci} 20862306a36Sopenharmony_cisubsys_initcall(hsta_msi_init); 209