diff mbox

[v7,3/3] Implement MSI support for Mobiveil PCIe Host Bridge IP device driver

Message ID 1524580023-4587-1-git-send-email-l.subrahmanya@mobiveil.co.in (mailing list archive)
State New, archived
Delegated to: Bjorn Helgaas
Headers show

Commit Message

Subrahmanya Lingappa April 24, 2018, 2:27 p.m. UTC
Signed-off-by: Subrahmanya Lingappa <l.subrahmanya@mobiveil.co.in>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Cc: Marc Zyngier <marc.zyngier@arm.com>
Cc: linux-pci@vger.kernel.org
---
 drivers/pci/host/pcie-mobiveil.c | 278 ++++++++++++++++++++++++++++++++-------
 1 file changed, 233 insertions(+), 45 deletions(-)
diff mbox

Patch

diff --git a/drivers/pci/host/pcie-mobiveil.c b/drivers/pci/host/pcie-mobiveil.c
index 28f488b..2a12abe 100644
--- a/drivers/pci/host/pcie-mobiveil.c
+++ b/drivers/pci/host/pcie-mobiveil.c
@@ -2,18 +2,19 @@ 
  * PCIe host controller driver for Mobiveil PCIe Host controller
  *
  * SPDX-License-Identifier: GPL-2.0
- * Copyright (c) 2017 Mobiveil Inc.
+ * Copyright (c) 2018 Mobiveil Inc.
  * Author: Subrahmanya Lingappa <l.subrahmanya@mobiveil.co.in>
  */
 
 #include <linux/delay.h>
-#include <linux/interrupt.h>
 #include <linux/init.h>
+#include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/irqchip/chained_irq.h>
 #include <linux/irqdomain.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/msi.h>
 #include <linux/of_address.h>
 #include <linux/of_irq.h>
 #include <linux/of_platform.h>
@@ -56,6 +57,7 @@ 
 #define PAB_INTP_AMBA_MISC_ENB	0x0b0c
 #define PAB_INTP_AMBA_MISC_STAT	0x0b1c
 #define  PAB_INTP_INTX_MASK	0x1e0
+#define  PAB_INTP_MSI_MASK	0x8
 
 #define PAB_AXI_AMAP_CTRL(win)	PAB_REG_ADDR(0x0ba0, win)
 #define  WIN_ENABLE_SHIFT	0
@@ -64,7 +66,7 @@ 
 #define PAB_EXT_AXI_AMAP_SIZE(win)	PAB_EXT_REG_ADDR(0xbaf0, win)
 
 #define PAB_AXI_AMAP_AXI_WIN(win)	PAB_REG_ADDR(0x0ba4, win)
-#define  AXI_WINDOW_BASE_SHIFT		2
+#define  AXI_WINDOW_ALIGN_MASK		3
 
 #define PAB_AXI_AMAP_PEX_WIN_L(win)	PAB_REG_ADDR(0x0ba8, win)
 #define  PAB_BUS_SHIFT			24
@@ -84,8 +86,18 @@ 
 #define PAB_PEX_AMAP_PEX_WIN_H(win)	PAB_REG_ADDR(0x4bac, win)
 
 /* supported number of interrupts */
-#define PCI_NUM_INTX    4
-#define PAB_INTA_POS    5
+#define PAB_INTX_START		5
+#define PCI_NUM_MSI			16
+
+/* MSI registers */
+#define MSI_BASE_LO_OFFSET	0x04
+#define MSI_BASE_HI_OFFSET	0x08
+#define MSI_SIZE_OFFSET		0x0c
+#define MSI_ENABLE_OFFSET	0x14
+#define MSI_STATUS_OFFSET	0x18
+#define MSI_DATA_OFFSET		0x20
+#define MSI_ADDR_L_OFFSET	0x24
+#define MSI_ADDR_H_OFFSET	0x28
 
 /* outbound and inbound window definitions */
 #define WIN_NUM_0		0
@@ -101,21 +113,32 @@ 
 #define LINK_WAIT_MIN		90000
 #define LINK_WAIT_MAX		100000
 
+struct mobiveil_msi {			/* MSI information */
+	struct mutex lock;		/* protect bitmap variable */
+	struct irq_domain *msi_domain;
+	struct irq_domain *dev_domain;
+	phys_addr_t msi_pages_phys;
+	int num_of_vectors;
+	DECLARE_BITMAP(msi_irq_in_use, PCI_NUM_MSI);
+};
+
 struct mobiveil_pcie {
 	struct platform_device *pdev;
 	struct list_head resources;
 	void __iomem *config_axi_slave_base;	/* endpoint config base */
-	void __iomem *csr_axi_slave_base;   /* root port config base */
-	void __iomem *pcie_reg_base;    /* Physical PCIe Controller Base */
+	void __iomem *csr_axi_slave_base;	/* root port config base */
+	void __iomem *apb_csr_base;	/* MSI register base */
+	void __iomem *pcie_reg_base;	/* Physical PCIe Controller Base */
 	struct irq_domain *intx_domain;
 	raw_spinlock_t intx_mask_lock;
 	int irq;
 	int apio_wins;
 	int ppio_wins;
-	int ob_wins_configured;	/*  configured outbound windows */
-	int ib_wins_configured;	/*  configured inbound windows */
+	int ob_wins_configured;		/* configured outbound windows */
+	int ib_wins_configured;		/* configured inbound windows */
 	struct resource *ob_io_res;
 	char root_bus_nr;
+	struct mobiveil_msi msi;
 };
 
 static inline void csr_writel(struct mobiveil_pcie *pcie, const u32 value,
@@ -204,9 +227,11 @@  static void mobiveil_pcie_isr(struct irq_desc *desc)
 	struct irq_chip *chip = irq_desc_get_chip(desc);
 	struct mobiveil_pcie *pcie = irq_desc_get_handler_data(desc);
 	struct device *dev = &pcie->pdev->dev;
+	struct mobiveil_msi *msi = &pcie->msi;
+	u32 msi_data, msi_addr_lo, msi_addr_hi;
+	u32 intr_status, msi_status;
 	unsigned long shifted_status;
-	u32 bit, virq;
-	u32 val, mask;
+	u32 bit, virq, val, mask;
 
 	/*
 	 * The core provides a single interrupt for both INTx/MSI messages.
@@ -223,7 +248,7 @@  static void mobiveil_pcie_isr(struct irq_desc *desc)
 	/* Handle INTx */
 	if (intr_status & PAB_INTP_INTX_MASK) {
 		shifted_status = csr_readl(pcie, PAB_INTP_AMBA_MISC_STAT) >>
-			PAB_INTA_POS;
+			PAB_INTX_START;
 		do {
 			for_each_set_bit(bit, &shifted_status, PCI_NUM_INTX) {
 				virq = irq_find_mapping(pcie->intx_domain,
@@ -235,10 +260,44 @@  static void mobiveil_pcie_isr(struct irq_desc *desc)
 						"unexpected IRQ, INT%d\n", bit);
 
 				/* clear interrupt */
-				csr_writel(pcie, shifted_status << PAB_INTA_POS,
-						PAB_INTP_AMBA_MISC_STAT);
+				csr_writel(pcie,
+					shifted_status << PAB_INTX_START,
+					PAB_INTP_AMBA_MISC_STAT);
 			}
-		} while ((shifted_status >>  PAB_INTA_POS) != 0);
+		} while ((shifted_status >> PAB_INTX_START) != 0);
+	}
+
+	/* read extra MSI status register */
+	msi_status = readl_relaxed(pcie->apb_csr_base + MSI_STATUS_OFFSET);
+
+	/* handle MSI interrupts */
+	if (msi_status & 1) {
+		do {
+			msi_data = readl_relaxed(pcie->apb_csr_base
+					+ MSI_DATA_OFFSET);
+
+			/*
+			 * MSI_STATUS_OFFSET register gets updated to zero
+			 * once we pop not only the MSI data but also address
+			 * from MSI hardware FIFO. So keeping these following
+			 * two dummy reads.
+			 */
+			msi_addr_lo = readl_relaxed(pcie->apb_csr_base +
+					MSI_ADDR_L_OFFSET);
+			msi_addr_hi = readl_relaxed(pcie->apb_csr_base +
+					MSI_ADDR_H_OFFSET);
+			dev_dbg(dev,
+				"MSI registers, data: %08x, addr: %08x:%08x\n",
+				msi_data, msi_addr_hi, msi_addr_lo);
+
+				virq = irq_find_mapping(msi->dev_domain,
+					msi_data);
+				if (virq)
+					generic_handle_irq(virq);
+
+			msi_status = readl_relaxed(pcie->apb_csr_base +
+					MSI_STATUS_OFFSET);
+		} while (msi_status & 1);
 	}
 
 	/* Clear the interrupt status */
@@ -261,21 +320,27 @@  static int mobiveil_pcie_parse_dt(struct mobiveil_pcie *pcie)
 	}
 
 	/* map config resource */
-	res = platform_get_resource_byname(pdev,
-		IORESOURCE_MEM, "config_axi_slave");
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+			"config_axi_slave");
 	pcie->config_axi_slave_base = devm_pci_remap_cfg_resource(dev, res);
 	if (IS_ERR(pcie->config_axi_slave_base))
 		return PTR_ERR(pcie->config_axi_slave_base);
 	pcie->ob_io_res = res;
 
 	/* map csr resource */
-	res = platform_get_resource_byname(pdev,
-		IORESOURCE_MEM, "csr_axi_slave");
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+			"csr_axi_slave");
 	pcie->csr_axi_slave_base = devm_pci_remap_cfg_resource(dev, res);
 	if (IS_ERR(pcie->csr_axi_slave_base))
 		return PTR_ERR(pcie->csr_axi_slave_base);
 	pcie->pcie_reg_base = res->start;
 
+	/* map MSI config resource */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "apb_csr");
+	pcie->apb_csr_base = devm_pci_remap_cfg_resource(dev, res);
+	if (IS_ERR(pcie->apb_csr_base))
+		return PTR_ERR(pcie->apb_csr_base);
+
 	/* read the number of windows requested */
 	if (of_property_read_u32(node, "apio-wins", &pcie->apio_wins))
 		pcie->apio_wins = MAX_PIO_WINDOWS;
@@ -335,11 +400,11 @@  static u32 read_paged_register(struct mobiveil_pcie *pcie, u32 offset)
 }
 
 static void program_ib_windows(struct mobiveil_pcie *pcie, int win_num,
-		int pci_addr, u32 type,	int size)
+		int pci_addr, u32 type,	u64 size)
 {
 	int pio_ctrl_val;
 	int amap_ctrl_dw;
-	u64 size64 =  ~(size-1);
+	u64 size64 = ~(size-1);
 
 	if ((pcie->ib_wins_configured + 1) > pcie->ppio_wins) {
 		dev_err(&pcie->pdev->dev,
@@ -369,12 +434,11 @@  static void program_ib_windows(struct mobiveil_pcie *pcie, int win_num,
  * routine to program the outbound windows
  */
 static void program_ob_windows(struct mobiveil_pcie *pcie, int win_num,
-		u64 cpu_addr, u64 pci_addr, u32 config_io_bit,
-		int size)
+		u64 cpu_addr, u64 pci_addr, u32 config_io_bit, u64 size)
 {
 
 	u32 value, type;
-	u64 size64 =  ~(size-1);
+	u64 size64 = ~(size-1);
 
 	if ((pcie->ob_wins_configured + 1) > pcie->apio_wins) {
 		dev_err(&pcie->pdev->dev,
@@ -388,11 +452,8 @@  static void program_ob_windows(struct mobiveil_pcie *pcie, int win_num,
 	 */
 	type = config_io_bit;
 	value = csr_readl(pcie, PAB_AXI_AMAP_CTRL(win_num));
-	csr_writel(pcie,
-			1 << WIN_ENABLE_SHIFT |
-			type << WIN_TYPE_SHIFT |
-			lower_32_bits(size64),
-			PAB_AXI_AMAP_CTRL(win_num));
+	csr_writel(pcie, 1 << WIN_ENABLE_SHIFT | type << WIN_TYPE_SHIFT |
+			lower_32_bits(size64), PAB_AXI_AMAP_CTRL(win_num));
 
 	write_paged_register(pcie, upper_32_bits(size64),
 				PAB_EXT_AXI_AMAP_SIZE(win_num));
@@ -402,9 +463,8 @@  static void program_ob_windows(struct mobiveil_pcie *pcie, int win_num,
 	 * PAB_AXI_AMAP_AXI_WIN0 register
 	 */
 	value = csr_readl(pcie, PAB_AXI_AMAP_AXI_WIN(win_num));
-	csr_writel(pcie,
-			cpu_addr >> AXI_WINDOW_BASE_SHIFT <<
-			AXI_WINDOW_BASE_SHIFT, PAB_AXI_AMAP_AXI_WIN(win_num));
+	csr_writel(pcie, cpu_addr & (~AXI_WINDOW_ALIGN_MASK),
+			PAB_AXI_AMAP_AXI_WIN(win_num));
 
 	value = csr_readl(pcie, PAB_AXI_AMAP_PEX_WIN_H(win_num));
 
@@ -431,11 +491,25 @@  static int mobiveil_bringup_link(struct mobiveil_pcie *pcie)
 	return -ETIMEDOUT;
 }
 
+static void mobiveil_pcie_enable_msi(struct mobiveil_pcie *pcie)
+{
+	phys_addr_t msg_addr = pcie->pcie_reg_base;
+	struct mobiveil_msi *msi = &pcie->msi;
+
+	pcie->msi.num_of_vectors = PCI_NUM_MSI;
+	msi->msi_pages_phys = (phys_addr_t)msg_addr;
+
+	writel_relaxed(lower_32_bits(msg_addr),
+		pcie->apb_csr_base + MSI_BASE_LO_OFFSET);
+	writel_relaxed(upper_32_bits(msg_addr),
+		pcie->apb_csr_base + MSI_BASE_HI_OFFSET);
+	writel_relaxed(4096, pcie->apb_csr_base + MSI_SIZE_OFFSET);
+	writel_relaxed(1, pcie->apb_csr_base + MSI_ENABLE_OFFSET);
+}
+
 static int mobiveil_host_init(struct mobiveil_pcie *pcie)
 {
-	u32 value;
-	u32 type = 0;
-	u32 pab_ctrl;
+	u32 value, pab_ctrl, type = 0;
 	int err;
 	struct resource_entry *win, *tmp;
 
@@ -451,7 +525,7 @@  static int mobiveil_host_init(struct mobiveil_pcie *pcie)
 	 */
 	value = csr_readl(pcie, PCI_COMMAND);
 	csr_writel(pcie, value | PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
-		PCI_COMMAND_MASTER,	PCI_COMMAND);
+		PCI_COMMAND_MASTER, PCI_COMMAND);
 
 	/*
 	 * program PIO Enable Bit to 1 (and PEX PIO Enable to 1) in PAB_CTRL
@@ -483,7 +557,7 @@  static int mobiveil_host_init(struct mobiveil_pcie *pcie)
 			pcie->ob_io_res->start,	0, CFG_WINDOW_TYPE,
 			resource_size(pcie->ob_io_res));
 
-	/* memory inbound  translation window */
+	/* memory inbound translation window */
 	program_ib_windows(pcie, WIN_NUM_1, 0, MEM_WINDOW_TYPE, IB_WIN_SIZE);
 
 	/* Get the I/O and memory ranges from DT */
@@ -501,6 +575,9 @@  static int mobiveil_host_init(struct mobiveil_pcie *pcie)
 		}
 	}
 
+	/* setup MSI hardware registers */
+	mobiveil_pcie_enable_msi(pcie);
+
 	return err;
 }
 
@@ -509,11 +586,10 @@  static void mobiveil_mask_intx_irq(struct irq_data *data)
 	struct irq_desc *desc = irq_to_desc(data->irq);
 	struct mobiveil_pcie *pcie;
 	unsigned long flags;
-	u32 mask;
-	u32 shifted_val;
+	u32 mask, shifted_val;
 
 	pcie = irq_desc_get_chip_data(desc);
-	mask = 1 << ((data->hwirq + PAB_INTA_POS) - 1);
+	mask = 1 << ((data->hwirq + PAB_INTX_START) - 1);
 	raw_spin_lock_irqsave(&pcie->intx_mask_lock, flags);
 	shifted_val = csr_readl(pcie, PAB_INTP_AMBA_MISC_ENB);
 	csr_writel(pcie, (shifted_val & (~mask)), PAB_INTP_AMBA_MISC_ENB);
@@ -525,11 +601,10 @@  static void mobiveil_unmask_intx_irq(struct irq_data *data)
 	struct irq_desc *desc = irq_to_desc(data->irq);
 	struct mobiveil_pcie *pcie;
 	unsigned long flags;
-	u32 shifted_val;
-	u32 mask;
+	u32 shifted_val, mask;
 
 	pcie = irq_desc_get_chip_data(desc);
-	mask = 1 << ((data->hwirq + PAB_INTA_POS) - 1);
+	mask = 1 << ((data->hwirq + PAB_INTX_START) - 1);
 	raw_spin_lock_irqsave(&pcie->intx_mask_lock, flags);
 	shifted_val = csr_readl(pcie, PAB_INTP_AMBA_MISC_ENB);
 	csr_writel(pcie, (shifted_val | mask), PAB_INTP_AMBA_MISC_ENB);
@@ -559,6 +634,116 @@  static int mobiveil_pcie_intx_map(struct irq_domain *domain, unsigned int irq,
 	.xlate = pci_irqd_intx_xlate,
 };
 
+static struct irq_chip mobiveil_msi_irq_chip = {
+	.name = "Mobiveil PCIe MSI",
+	.irq_mask = pci_msi_mask_irq,
+	.irq_unmask = pci_msi_unmask_irq,
+};
+
+static struct msi_domain_info mobiveil_msi_domain_info = {
+	.flags	= (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
+		MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX),
+	.chip	= &mobiveil_msi_irq_chip,
+};
+
+static void mobiveil_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
+{
+	struct mobiveil_pcie *pcie = irq_data_get_irq_chip_data(data);
+	phys_addr_t addr = pcie->pcie_reg_base + (data->hwirq * sizeof(int));
+
+	msg->address_lo = lower_32_bits(addr);
+	msg->address_hi = upper_32_bits(addr);
+	msg->data = data->hwirq;
+
+	dev_dbg(&pcie->pdev->dev, "msi#%d address_hi %#x address_lo %#x\n",
+		(int)data->hwirq, msg->address_hi, msg->address_lo);
+}
+
+static int mobiveil_msi_set_affinity(struct irq_data *irq_data,
+		const struct cpumask *mask, bool force)
+{
+	return -EINVAL;
+}
+
+static struct irq_chip mobiveil_msi_bottom_irq_chip = {
+	.name			= "Mobiveil MSI",
+	.irq_compose_msi_msg	= mobiveil_compose_msi_msg,
+	.irq_set_affinity	= mobiveil_msi_set_affinity,
+};
+
+static int mobiveil_irq_msi_domain_alloc(struct irq_domain *domain,
+		unsigned int virq, unsigned int nr_irqs, void *args)
+{
+	struct mobiveil_pcie *pcie = domain->host_data;
+	struct mobiveil_msi *msi = &pcie->msi;
+	unsigned long bit;
+
+	WARN_ON(nr_irqs != 1);
+	mutex_lock(&msi->lock);
+
+	bit = find_first_zero_bit(msi->msi_irq_in_use, msi->num_of_vectors);
+	if (bit >= msi->num_of_vectors) {
+		mutex_unlock(&msi->lock);
+		return -ENOSPC;
+	}
+
+	set_bit(bit, msi->msi_irq_in_use);
+
+	mutex_unlock(&msi->lock);
+
+	irq_domain_set_info(domain, virq, bit, &mobiveil_msi_bottom_irq_chip,
+				domain->host_data, handle_level_irq,
+				NULL, NULL);
+	return 0;
+}
+
+static void mobiveil_irq_msi_domain_free(struct irq_domain *domain,
+		unsigned int virq, unsigned int nr_irqs)
+{
+	struct irq_data *d = irq_domain_get_irq_data(domain, virq);
+	struct mobiveil_pcie *pcie = irq_data_get_irq_chip_data(d);
+	struct mobiveil_msi *msi = &pcie->msi;
+
+	mutex_lock(&msi->lock);
+
+	if (!test_bit(d->hwirq, msi->msi_irq_in_use)) {
+		dev_err(&pcie->pdev->dev, "trying to free unused MSI#%lu\n",
+			d->hwirq);
+	} else {
+		__clear_bit(d->hwirq, msi->msi_irq_in_use);
+	}
+
+	mutex_unlock(&msi->lock);
+}
+static const struct irq_domain_ops msi_domain_ops = {
+	.alloc	= mobiveil_irq_msi_domain_alloc,
+	.free	= mobiveil_irq_msi_domain_free,
+};
+
+static int mobiveil_allocate_msi_domains(struct mobiveil_pcie *pcie)
+{
+	struct device *dev = &pcie->pdev->dev;
+	struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node);
+	struct mobiveil_msi *msi = &pcie->msi;
+
+	mutex_init(&pcie->msi.lock);
+	msi->dev_domain = irq_domain_add_linear(NULL, msi->num_of_vectors,
+						&msi_domain_ops, pcie);
+	if (!msi->dev_domain) {
+		dev_err(dev, "failed to create IRQ domain\n");
+		return -ENOMEM;
+	}
+
+	msi->msi_domain = pci_msi_create_irq_domain(fwnode,
+				&mobiveil_msi_domain_info, msi->dev_domain);
+	if (!msi->msi_domain) {
+		dev_err(dev, "failed to create MSI domain\n");
+		irq_domain_remove(msi->dev_domain);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
 static int mobiveil_pcie_init_irq_domain(struct mobiveil_pcie *pcie)
 {
 	struct device *dev = &pcie->pdev->dev;
@@ -576,6 +761,11 @@  static int mobiveil_pcie_init_irq_domain(struct mobiveil_pcie *pcie)
 
 	raw_spin_lock_init(&pcie->intx_mask_lock);
 
+	/* setup MSI */
+	ret = mobiveil_allocate_msi_domains(pcie);
+	if (ret)
+		return ret;
+
 	return 0;
 }
 
@@ -662,8 +852,6 @@  static int mobiveil_pcie_probe(struct platform_device *pdev)
 		pcie_bus_configure_settings(child);
 	pci_bus_add_devices(bus);
 
-	platform_set_drvdata(pdev, pcie);
-
 	return 0;
 error:
 	pci_free_resource_list(&pcie->resources);