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