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