From 921042423f86f26a256175ad4897076d73412746 Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Wed, 23 Jan 2013 18:06:07 -0700 Subject: pcie: add MSI support Signed-off-by: Troy Kisky --- arch/arm/mach-mx6/Kconfig | 1 + arch/arm/mach-mx6/Makefile | 1 + arch/arm/mach-mx6/irq.c | 4 + arch/arm/mach-mx6/msi.c | 147 ++++++++++++++++++++++++++++++++++ arch/arm/mach-mx6/msi.h | 25 ++++++ arch/arm/mach-mx6/pcie.c | 69 ++++++++++++++++ arch/arm/plat-mxc/include/mach/irqs.h | 9 ++- 7 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 arch/arm/mach-mx6/msi.c create mode 100644 arch/arm/mach-mx6/msi.h 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 #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 +#include +#include +#include +#include +#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 #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); -- cgit v1.2.3