You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
270 lines
7.3 KiB
270 lines
7.3 KiB
From eaa70d53f6b827f147d775a3de7ff3ef27d0fae6 Mon Sep 17 00:00:00 2001
|
|
From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
|
|
Date: Thu, 6 Jun 2013 18:25:16 +0200
|
|
Subject: [PATCH 075/203] irqchip: armada-370-xp: implement MSI support
|
|
|
|
This commit introduces the support for the MSI interrupts in the
|
|
armada-370-xp interrupt controller driver. It registers an MSI chip to
|
|
the MSI chip registry, which will be used by the Marvell PCIe host
|
|
controller driver.
|
|
|
|
The MSI interrupts use the 16 high doorbells, and are therefore
|
|
notified using IRQ1 of the main interrupt controller.
|
|
|
|
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
|
|
Acked-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
|
|
---
|
|
.../devicetree/bindings/arm/armada-370-xp-mpic.txt | 3 +
|
|
drivers/irqchip/irq-armada-370-xp.c | 182 ++++++++++++++++++++-
|
|
2 files changed, 184 insertions(+), 1 deletion(-)
|
|
|
|
--- a/Documentation/devicetree/bindings/arm/armada-370-xp-mpic.txt
|
|
+++ b/Documentation/devicetree/bindings/arm/armada-370-xp-mpic.txt
|
|
@@ -4,6 +4,8 @@ Marvell Armada 370 and Armada XP Interru
|
|
Required properties:
|
|
- compatible: Should be "marvell,mpic"
|
|
- interrupt-controller: Identifies the node as an interrupt controller.
|
|
+- msi-controller: Identifies the node as an PCI Message Signaled
|
|
+ Interrupt controller.
|
|
- #interrupt-cells: The number of cells to define the interrupts. Should be 1.
|
|
The cell is the IRQ number
|
|
|
|
@@ -24,6 +26,7 @@ Example:
|
|
#address-cells = <1>;
|
|
#size-cells = <1>;
|
|
interrupt-controller;
|
|
+ msi-controller;
|
|
reg = <0xd0020a00 0x1d0>,
|
|
<0xd0021070 0x58>;
|
|
};
|
|
--- a/drivers/irqchip/irq-armada-370-xp.c
|
|
+++ b/drivers/irqchip/irq-armada-370-xp.c
|
|
@@ -21,7 +21,10 @@
|
|
#include <linux/io.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
+#include <linux/of_pci.h>
|
|
#include <linux/irqdomain.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/msi.h>
|
|
#include <asm/mach/arch.h>
|
|
#include <asm/exception.h>
|
|
#include <asm/smp_plat.h>
|
|
@@ -51,12 +54,22 @@
|
|
#define IPI_DOORBELL_START (0)
|
|
#define IPI_DOORBELL_END (8)
|
|
#define IPI_DOORBELL_MASK 0xFF
|
|
+#define PCI_MSI_DOORBELL_START (16)
|
|
+#define PCI_MSI_DOORBELL_NR (16)
|
|
+#define PCI_MSI_DOORBELL_END (32)
|
|
+#define PCI_MSI_DOORBELL_MASK 0xFFFF0000
|
|
|
|
static DEFINE_RAW_SPINLOCK(irq_controller_lock);
|
|
|
|
static void __iomem *per_cpu_int_base;
|
|
static void __iomem *main_int_base;
|
|
static struct irq_domain *armada_370_xp_mpic_domain;
|
|
+#ifdef CONFIG_PCI_MSI
|
|
+static struct irq_domain *armada_370_xp_msi_domain;
|
|
+static DECLARE_BITMAP(msi_used, PCI_MSI_DOORBELL_NR);
|
|
+static DEFINE_MUTEX(msi_used_lock);
|
|
+static phys_addr_t msi_doorbell_addr;
|
|
+#endif
|
|
|
|
/*
|
|
* In SMP mode:
|
|
@@ -87,6 +100,144 @@ static void armada_370_xp_irq_unmask(str
|
|
ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
|
|
}
|
|
|
|
+#ifdef CONFIG_PCI_MSI
|
|
+
|
|
+static int armada_370_xp_alloc_msi(void)
|
|
+{
|
|
+ int hwirq;
|
|
+
|
|
+ mutex_lock(&msi_used_lock);
|
|
+ hwirq = find_first_zero_bit(&msi_used, PCI_MSI_DOORBELL_NR);
|
|
+ if (hwirq >= PCI_MSI_DOORBELL_NR)
|
|
+ hwirq = -ENOSPC;
|
|
+ else
|
|
+ set_bit(hwirq, msi_used);
|
|
+ mutex_unlock(&msi_used_lock);
|
|
+
|
|
+ return hwirq;
|
|
+}
|
|
+
|
|
+static void armada_370_xp_free_msi(int hwirq)
|
|
+{
|
|
+ mutex_lock(&msi_used_lock);
|
|
+ if (!test_bit(hwirq, msi_used))
|
|
+ pr_err("trying to free unused MSI#%d\n", hwirq);
|
|
+ else
|
|
+ clear_bit(hwirq, msi_used);
|
|
+ mutex_unlock(&msi_used_lock);
|
|
+}
|
|
+
|
|
+static int armada_370_xp_setup_msi_irq(struct msi_chip *chip,
|
|
+ struct pci_dev *pdev,
|
|
+ struct msi_desc *desc)
|
|
+{
|
|
+ struct msi_msg msg;
|
|
+ irq_hw_number_t hwirq;
|
|
+ int virq;
|
|
+
|
|
+ hwirq = armada_370_xp_alloc_msi();
|
|
+ if (hwirq < 0)
|
|
+ return hwirq;
|
|
+
|
|
+ virq = irq_create_mapping(armada_370_xp_msi_domain, hwirq);
|
|
+ if (!virq) {
|
|
+ armada_370_xp_free_msi(hwirq);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ irq_set_msi_desc(virq, desc);
|
|
+
|
|
+ msg.address_lo = msi_doorbell_addr;
|
|
+ msg.address_hi = 0;
|
|
+ msg.data = 0xf00 | (hwirq + 16);
|
|
+
|
|
+ write_msi_msg(virq, &msg);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void armada_370_xp_teardown_msi_irq(struct msi_chip *chip,
|
|
+ unsigned int irq)
|
|
+{
|
|
+ struct irq_data *d = irq_get_irq_data(irq);
|
|
+ irq_dispose_mapping(irq);
|
|
+ armada_370_xp_free_msi(d->hwirq);
|
|
+}
|
|
+
|
|
+static struct irq_chip armada_370_xp_msi_irq_chip = {
|
|
+ .name = "armada_370_xp_msi_irq",
|
|
+ .irq_enable = unmask_msi_irq,
|
|
+ .irq_disable = mask_msi_irq,
|
|
+ .irq_mask = mask_msi_irq,
|
|
+ .irq_unmask = unmask_msi_irq,
|
|
+};
|
|
+
|
|
+static int armada_370_xp_msi_map(struct irq_domain *domain, unsigned int virq,
|
|
+ irq_hw_number_t hw)
|
|
+{
|
|
+ irq_set_chip_and_handler(virq, &armada_370_xp_msi_irq_chip,
|
|
+ handle_simple_irq);
|
|
+ set_irq_flags(virq, IRQF_VALID);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct irq_domain_ops armada_370_xp_msi_irq_ops = {
|
|
+ .map = armada_370_xp_msi_map,
|
|
+};
|
|
+
|
|
+static int armada_370_xp_msi_init(struct device_node *node,
|
|
+ phys_addr_t main_int_phys_base)
|
|
+{
|
|
+ struct msi_chip *msi_chip;
|
|
+ u32 reg;
|
|
+ int ret;
|
|
+
|
|
+ msi_doorbell_addr = main_int_phys_base +
|
|
+ ARMADA_370_XP_SW_TRIG_INT_OFFS;
|
|
+
|
|
+ msi_chip = kzalloc(sizeof(*msi_chip), GFP_KERNEL);
|
|
+ if (!msi_chip)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ msi_chip->setup_irq = armada_370_xp_setup_msi_irq;
|
|
+ msi_chip->teardown_irq = armada_370_xp_teardown_msi_irq;
|
|
+ msi_chip->of_node = node;
|
|
+
|
|
+ armada_370_xp_msi_domain =
|
|
+ irq_domain_add_linear(NULL, PCI_MSI_DOORBELL_NR,
|
|
+ &armada_370_xp_msi_irq_ops,
|
|
+ NULL);
|
|
+ if (!armada_370_xp_msi_domain) {
|
|
+ kfree(msi_chip);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ ret = of_pci_msi_chip_add(msi_chip);
|
|
+ if (ret < 0) {
|
|
+ irq_domain_remove(armada_370_xp_msi_domain);
|
|
+ kfree(msi_chip);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS)
|
|
+ | PCI_MSI_DOORBELL_MASK;
|
|
+
|
|
+ writel(reg, per_cpu_int_base +
|
|
+ ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
|
|
+
|
|
+ /* Unmask IPI interrupt */
|
|
+ writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#else
|
|
+static inline int armada_370_xp_msi_init(struct device_node *node,
|
|
+ phys_addr_t main_int_phys_base)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
#ifdef CONFIG_SMP
|
|
static int armada_xp_set_affinity(struct irq_data *d,
|
|
const struct cpumask *mask_val, bool force)
|
|
@@ -214,12 +365,39 @@ armada_370_xp_handle_irq(struct pt_regs
|
|
if (irqnr > 1022)
|
|
break;
|
|
|
|
- if (irqnr > 0) {
|
|
+ if (irqnr > 1) {
|
|
irqnr = irq_find_mapping(armada_370_xp_mpic_domain,
|
|
irqnr);
|
|
handle_IRQ(irqnr, regs);
|
|
continue;
|
|
}
|
|
+
|
|
+#ifdef CONFIG_PCI_MSI
|
|
+ /* MSI handling */
|
|
+ if (irqnr == 1) {
|
|
+ u32 msimask, msinr;
|
|
+
|
|
+ msimask = readl_relaxed(per_cpu_int_base +
|
|
+ ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS)
|
|
+ & PCI_MSI_DOORBELL_MASK;
|
|
+
|
|
+ writel(~PCI_MSI_DOORBELL_MASK, per_cpu_int_base +
|
|
+ ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);
|
|
+
|
|
+ for (msinr = PCI_MSI_DOORBELL_START;
|
|
+ msinr < PCI_MSI_DOORBELL_END; msinr++) {
|
|
+ int irq;
|
|
+
|
|
+ if (!(msimask & BIT(msinr)))
|
|
+ continue;
|
|
+
|
|
+ irq = irq_find_mapping(armada_370_xp_msi_domain,
|
|
+ msinr - 16);
|
|
+ handle_IRQ(irq, regs);
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+
|
|
#ifdef CONFIG_SMP
|
|
/* IPI Handling */
|
|
if (irqnr == 0) {
|
|
@@ -292,6 +470,8 @@ static int __init armada_370_xp_mpic_of_
|
|
|
|
#endif
|
|
|
|
+ armada_370_xp_msi_init(node, main_int_res.start);
|
|
+
|
|
set_handle_irq(armada_370_xp_handle_irq);
|
|
|
|
return 0;
|
|
|