summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTroy Kisky <troy.kisky@boundarydevices.com>2013-01-23 18:06:07 -0700
committerEric Nelson <eric.nelson@boundarydevices.com>2013-02-12 13:32:51 -0700
commit921042423f86f26a256175ad4897076d73412746 (patch)
treefe9f75c6de95bb75eea84663fc04a92787f55226
parent617c2e7cc0d0fd2b637863d0aa45dfef757ba2df (diff)
pcie: add MSI support
Signed-off-by: Troy Kisky <troy.kisky@boundarydevices.com>
-rw-r--r--arch/arm/mach-mx6/Kconfig1
-rw-r--r--arch/arm/mach-mx6/Makefile1
-rw-r--r--arch/arm/mach-mx6/irq.c4
-rw-r--r--arch/arm/mach-mx6/msi.c147
-rw-r--r--arch/arm/mach-mx6/msi.h25
-rw-r--r--arch/arm/mach-mx6/pcie.c69
-rw-r--r--arch/arm/plat-mxc/include/mach/irqs.h9
7 files changed, 255 insertions, 1 deletions
diff --git a/arch/arm/mach-mx6/Kconfig b/arch/arm/mach-mx6/Kconfig
index 481075b5c4df..3fa29a06941c 100644
--- a/arch/arm/mach-mx6/Kconfig
+++ b/arch/arm/mach-mx6/Kconfig
@@ -5,6 +5,7 @@ config ARCH_MX6Q
select USB_ARCH_HAS_EHCI
select ARCH_MXC_IOMUX_V3
select ARCH_MXC_AUDMUX_V2
+ select ARCH_SUPPORTS_MSI
select ARM_GIC
select ARCH_HAS_CPUFREQ
select OC_ETM
diff --git a/arch/arm/mach-mx6/Makefile b/arch/arm/mach-mx6/Makefile
index 5cac9bc5db03..977853a089ce 100644
--- a/arch/arm/mach-mx6/Makefile
+++ b/arch/arm/mach-mx6/Makefile
@@ -19,3 +19,4 @@ obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o
obj-$(CONFIG_IMX_PCIE) += pcie.o
obj-$(CONFIG_USB_FSL_ARC_OTG) += usb_dr.o
obj-$(CONFIG_USB_EHCI_ARC_H1) += usb_h1.o
+obj-$(CONFIG_PCI_MSI) += msi.o \ No newline at end of file
diff --git a/arch/arm/mach-mx6/irq.c b/arch/arm/mach-mx6/irq.c
index 3281693224be..7f0eb6e5de4b 100644
--- a/arch/arm/mach-mx6/irq.c
+++ b/arch/arm/mach-mx6/irq.c
@@ -26,6 +26,7 @@
#ifdef CONFIG_CPU_FREQ_GOV_INTERACTIVE
#include <linux/cpufreq.h>
#endif
+#include "msi.h"
int mx6q_register_gpios(void);
unsigned int gpc_wake_irq[4];
@@ -131,4 +132,7 @@ void mx6_init_irq(void)
for (i = 0; i < ARRAY_SIZE(mxc_irq_tuner); i++)
cpufreq_gov_irq_tuner_register(mxc_irq_tuner[i]);
#endif
+#ifdef CONFIG_PCI_MSI
+ imx_msi_init();
+#endif
}
diff --git a/arch/arm/mach-mx6/msi.c b/arch/arm/mach-mx6/msi.c
new file mode 100644
index 000000000000..cd3f3a0eaa35
--- /dev/null
+++ b/arch/arm/mach-mx6/msi.c
@@ -0,0 +1,147 @@
+/*
+ * arch/arm/mach-mx6/msi.c
+ *
+ * PCI MSI support for the imx processor
+ *
+ * Copyright (c) 2013, Boundary Devices.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place - Suite 330, Boston, MA 02111-1307 USA.
+ *
+ */
+#include <linux/pci.h>
+#include <linux/msi.h>
+#include <asm/bitops.h>
+#include <asm/mach/irq.h>
+#include <asm/irq.h>
+#include "msi.h"
+
+
+#define IMX_NUM_MSI_IRQS 128
+static DECLARE_BITMAP(msi_irq_in_use, IMX_NUM_MSI_IRQS);
+
+static void imx_msi_handler(unsigned int irq, struct irq_desc *desc)
+{
+ int i, j;
+ unsigned status;
+ struct irq_chip *chip = irq_get_chip(irq);
+ unsigned base_irq = IRQ_IMX_MSI_0;
+
+ chained_irq_enter(chip, desc);
+ for (i = 0; i < 8; i++) {
+ status = imx_pcie_msi_pending(i);
+ while (status) {
+ j = __fls(status);
+ generic_handle_irq(base_irq + j);
+ status &= ~(1 << j);
+ }
+ base_irq += 32;
+ }
+ chained_irq_exit(chip, desc);
+}
+
+/*
+ * Dynamic irq allocate and deallocation
+ */
+int create_irq(void)
+{
+ int irq, pos;
+
+ do {
+ pos = find_first_zero_bit(msi_irq_in_use, IMX_NUM_MSI_IRQS);
+ if ((unsigned)pos >= IMX_NUM_MSI_IRQS)
+ return -ENOSPC;
+ /* test_and_set_bit operates on 32-bits at a time */
+ } while (test_and_set_bit(pos, msi_irq_in_use));
+
+ irq = IRQ_IMX_MSI_0 + pos;
+ dynamic_irq_init(irq);
+ return irq;
+}
+
+void destroy_irq(unsigned int irq)
+{
+ int pos = irq - IRQ_IMX_MSI_0;
+
+ dynamic_irq_cleanup(irq);
+ clear_bit(pos, msi_irq_in_use);
+}
+
+void arch_teardown_msi_irq(unsigned int irq)
+{
+ destroy_irq(irq);
+}
+
+static void imx_msi_irq_ack(struct irq_data *d)
+{
+ return;
+}
+
+static void imx_msi_irq_enable(struct irq_data *d)
+{
+ imx_pcie_enable_irq(d->irq - IRQ_IMX_MSI_0, 1);
+ return unmask_msi_irq(d);
+}
+
+static void imx_msi_irq_disable(struct irq_data *d)
+{
+ imx_pcie_enable_irq(d->irq - IRQ_IMX_MSI_0, 0);
+ return mask_msi_irq(d);
+}
+
+static void imx_msi_irq_mask(struct irq_data *d)
+{
+ imx_pcie_mask_irq(d->irq - IRQ_IMX_MSI_0, 1);
+ return mask_msi_irq(d);
+}
+
+static void imx_msi_irq_unmask(struct irq_data *d)
+{
+ imx_pcie_mask_irq(d->irq - IRQ_IMX_MSI_0, 0);
+ return unmask_msi_irq(d);
+}
+
+static struct irq_chip imx_msi_chip = {
+ .name = "PCIe-MSI",
+ .irq_ack = imx_msi_irq_ack,
+ .irq_enable = imx_msi_irq_enable,
+ .irq_disable = imx_msi_irq_disable,
+ .irq_mask = imx_msi_irq_mask,
+ .irq_unmask = imx_msi_irq_unmask,
+};
+
+int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc)
+{
+ int irq = create_irq();
+ struct msi_msg msg;
+
+ if (irq < 0)
+ return irq;
+
+ irq_set_msi_desc(irq, desc);
+
+ msg.address_hi = 0x0;
+ msg.address_lo = MSI_MATCH_ADDR;
+ msg.data = (mxc_cpu_type << 15) | ((irq - IRQ_IMX_MSI_0) & 0xff);
+
+ write_msi_msg(irq, &msg);
+ irq_set_chip_and_handler(irq, &imx_msi_chip, handle_simple_irq);
+ set_irq_flags(irq, IRQF_VALID);
+ pr_info("%s: %d of %d\n", __func__, irq, NR_IRQS);
+ return 0;
+}
+
+void imx_msi_init(void)
+{
+ irq_set_chained_handler(MXC_INT_PCIE_0, imx_msi_handler);
+}
diff --git a/arch/arm/mach-mx6/msi.h b/arch/arm/mach-mx6/msi.h
new file mode 100644
index 000000000000..27302b110c08
--- /dev/null
+++ b/arch/arm/mach-mx6/msi.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 Boundary Devices, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+void imx_pcie_enable_irq(unsigned pos, int set);
+void imx_pcie_mask_irq(unsigned pos, int set);
+unsigned imx_pcie_msi_pending(unsigned index);
+
+#define MSI_MATCH_ADDR 0x10000050
+
+void imx_msi_init(void);
diff --git a/arch/arm/mach-mx6/pcie.c b/arch/arm/mach-mx6/pcie.c
index 71de7217e51f..e3abda1a2c26 100644
--- a/arch/arm/mach-mx6/pcie.c
+++ b/arch/arm/mach-mx6/pcie.c
@@ -37,6 +37,7 @@
#include <asm/sizes.h>
#include "crm_regs.h"
+#include "msi.h"
/* Register Definitions */
#define PRT_LOG_R_BaseAddress 0x700
@@ -57,6 +58,16 @@
#define DB_R1_RegisterResetMask 0xFFFFFFFF
/* End of Register Definition for DB_R1 */
+#define PCIE_PL_MSICA 0x820
+#define PCIE_PL_MSICUA 0x824
+#define PCIE_PL_MSIC_INT 0x828
+
+#define MSIC_INT_EN 0x0
+#define MSIC_INT_MASK 0x4
+#define MSIC_INT_STATUS 0x8
+
+#define PCIE_PL_MSIC_GPIO 0x888
+
#define ATU_R_BaseAddress 0x900
#define ATU_VIEWPORT_R (ATU_R_BaseAddress + 0x0)
#define ATU_REGION_CTRL1_R (ATU_R_BaseAddress + 0x4)
@@ -296,7 +307,9 @@ static int imx_pcie_link_up(void __iomem *dbi_base)
static void imx_pcie_regions_setup(void __iomem *dbi_base)
{
unsigned bus;
+ unsigned i;
unsigned untranslated_base = PCIE_ARB_END_ADDR +1 - SZ_1M;
+ void __iomem *p = dbi_base + PCIE_PL_MSIC_INT;
/*
* i.MX6 defines 16MB in the AXI address map for PCIe.
*
@@ -339,6 +352,62 @@ static void imx_pcie_regions_setup(void __iomem *dbi_base)
dbi_base + ATU_REGION_CTRL1_R);
writel((1<<31), dbi_base + ATU_REGION_CTRL2_R);
}
+
+ writel(MSI_MATCH_ADDR, dbi_base + PCIE_PL_MSICA);
+ writel(0, dbi_base + PCIE_PL_MSICUA);
+ for (i = 0; i < 8 ; i++) {
+ writel(0, p + MSIC_INT_EN);
+ writel(0xffffffff, p + MSIC_INT_MASK);
+ writel(0xffffffff, p + MSIC_INT_STATUS);
+ p += 12;
+ }
+}
+
+void imx_pcie_mask_irq(unsigned pos, int set)
+{
+ unsigned mask = 1 << (pos & 0x1f);
+ unsigned val, newval;
+ void __iomem *p = dbi_base + PCIE_PL_MSIC_INT + MSIC_INT_MASK + ((pos >> 5) * 12);
+ if (pos >= (8 * 32))
+ return;
+ val = readl(p);
+ if (set)
+ newval = val | mask;
+ else
+ newval = val & ~mask;
+ if (val != newval)
+ writel(newval, p);
+}
+
+void imx_pcie_enable_irq(unsigned pos, int set)
+{
+ unsigned mask = 1 << (pos & 0x1f);
+ unsigned val, newval;
+ void __iomem *p = dbi_base + PCIE_PL_MSIC_INT + MSIC_INT_EN + ((pos >> 5) * 12);
+ if (pos >= (8 * 32))
+ return;
+ val = readl(p);
+ if (set)
+ newval = val | mask;
+ else
+ newval = val & ~mask;
+ if (val != newval)
+ writel(newval, p);
+ if (set && (val != newval))
+ imx_pcie_mask_irq(pos, 0); /* unmask when enabled */
+}
+
+unsigned imx_pcie_msi_pending(unsigned index)
+{
+ unsigned val, mask;
+ void __iomem *p = dbi_base + PCIE_PL_MSIC_INT + (index * 12);
+ if (index >= 8)
+ return 0;
+ val = readl(p + MSIC_INT_STATUS);
+ mask = readl(p + MSIC_INT_MASK);
+ val &= ~mask;
+ writel(val, p + MSIC_INT_STATUS);
+ return val;
}
static u32 get_cfg_addr(struct pci_bus *bus, u32 devfn, int where)
diff --git a/arch/arm/plat-mxc/include/mach/irqs.h b/arch/arm/plat-mxc/include/mach/irqs.h
index 9e44eaa4824c..0968b66e4705 100644
--- a/arch/arm/plat-mxc/include/mach/irqs.h
+++ b/arch/arm/plat-mxc/include/mach/irqs.h
@@ -72,7 +72,14 @@
#define MX5_IPU_IRQS 0
#endif
-#define NR_IRQS (MXC_IPU_IRQ_START + MX3_IPU_IRQS + MX5_IPU_IRQS)
+#ifdef CONFIG_ARCH_MX6
+#define MX6_MSI_IRQS 128
+#else
+#define MX6_MSI_IRQS 0
+#endif
+
+#define IRQ_IMX_MSI_0 (MXC_IPU_IRQ_START + MX3_IPU_IRQS + MX5_IPU_IRQS)
+#define NR_IRQS IRQ_IMX_MSI_0 + MX6_MSI_IRQS
extern int imx_irq_set_priority(unsigned char irq, unsigned char prio);