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