Message ID | 1400849668-11708-1-git-send-email-ike.pan@canonical.com |
---|---|
State | New |
Headers | show |
On 05/23/2014 02:54 PM, Ike Panhc wrote: > From: Tanmay Inamdar <tinamdar@apm.com> > > BugLink: http://launchpad.net/bugs/1318977 > > Signed-off-by: Tanmay Inamdar <tinamdar@apm.com> > Signed-off-by: Ike Panhc <ike.pan@canonical.com> > --- > arch/arm64/boot/dts/apm-storm.dtsi | 41 ++- > arch/arm64/include/asm/msi_bitmap.h | 36 ++ > arch/arm64/kernel/Makefile | 1 + > arch/arm64/kernel/msi_bitmap.c | 134 +++++++ > debian.master/config/config.common.ubuntu | 1 + > drivers/pci/host/Kconfig | 4 + > drivers/pci/host/Makefile | 1 + > drivers/pci/host/pci-xgene-msi.c | 593 ++++++++++++++++++++++++++++++ > drivers/pci/host/pci-xgene.c | 6 +- > 9 files changed, 807 insertions(+), 10 deletions(-) > create mode 100644 arch/arm64/include/asm/msi_bitmap.h > create mode 100644 arch/arm64/kernel/msi_bitmap.c > create mode 100644 drivers/pci/host/pci-xgene-msi.c > > diff --git a/arch/arm64/boot/dts/apm-storm.dtsi b/arch/arm64/boot/dts/apm-storm.dtsi > index aae496f..6f22fd8 100644 > --- a/arch/arm64/boot/dts/apm-storm.dtsi > +++ b/arch/arm64/boot/dts/apm-storm.dtsi > @@ -333,6 +333,28 @@ > }; > }; > > + msi: msi@79000000 { > + compatible = "xgene,gic-msi"; > + reg = <0x00 0x79000000 0x0 0x900000>; > + msi-available-ranges = <0x0 0x1000>; > + interrupts = < 0x0 0x10 0x4 > + 0x0 0x11 0x4 > + 0x0 0x12 0x4 > + 0x0 0x13 0x4 > + 0x0 0x14 0x4 > + 0x0 0x15 0x4 > + 0x0 0x16 0x4 > + 0x0 0x17 0x4 > + 0x0 0x18 0x4 > + 0x0 0x19 0x4 > + 0x0 0x1a 0x4 > + 0x0 0x1b 0x4 > + 0x0 0x1c 0x4 > + 0x0 0x1d 0x4 > + 0x0 0x1e 0x4 > + 0x0 0x1f 0x4>; > + }; > + > pcie0: pcie@1f2b0000 { > status = "disabled"; > device_type = "pci"; > @@ -345,7 +367,8 @@ > reg-names = "csr", "cfg"; > ranges = <0x01000000 0x00 0x00000000 0xe0 0x00000000 0x00 0x00010000 /* io */ > 0x02000000 0x00 0x10000000 0xe0 0x10000000 0x00 0x80000000>; /* mem */ > - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; > + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ > + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ > interrupt-map-mask = <0x0 0x0 0x0 0x7>; > interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xc2 0x1 > 0x0 0x0 0x0 0x2 &gic 0x0 0xc3 0x1 > @@ -366,7 +389,8 @@ > reg-names = "csr", "cfg"; > ranges = <0x01000000 0x0 0x00000000 0xd0 0x00000000 0x00 0x00010000 /* io */ > 0x02000000 0x0 0x10000000 0xd0 0x10000000 0x00 0x80000000>; /* mem */ > - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; > + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ > + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ > interrupt-map-mask = <0x0 0x0 0x0 0x7>; > interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xc8 0x1 > 0x0 0x0 0x0 0x2 &gic 0x0 0xc9 0x1 > @@ -387,7 +411,8 @@ > reg-names = "csr", "cfg"; > ranges = <0x01000000 0x0 0x00000000 0x90 0x00000000 0x0 0x00010000 /* io */ > 0x02000000 0x0 0x10000000 0x90 0x10000000 0x0 0x80000000>; /* mem */ > - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; > + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ > + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ > interrupt-map-mask = <0x0 0x0 0x0 0x7>; > interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xce 0x1 > 0x0 0x0 0x0 0x2 &gic 0x0 0xcf 0x1 > @@ -406,9 +431,10 @@ > reg = < 0x00 0x1f500000 0x0 0x00010000 /* Controller registers */ > 0xa0 0xd0000000 0x0 0x00200000>; /* PCI config space */ > reg-names = "csr", "cfg"; > - ranges = <0x01000000 0x0 0x00000000 0xa0 0x00000000 0x0 0x00010000 /* mem */ > - 0x02000000 0x0 0x10000000 0xa0 0x10000000 0x0 0x80000000>; /* io */ > - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; > + ranges = <0x01000000 0x0 0x00000000 0xa0 0x00000000 0x0 0x00010000 /* io */ > + 0x02000000 0x0 0x10000000 0xa0 0x10000000 0x0 0x80000000>; /* mem */ > + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ > + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ > interrupt-map-mask = <0x0 0x0 0x0 0x7>; > interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xd4 0x1 > 0x0 0x0 0x0 0x2 &gic 0x0 0xd5 0x1 > @@ -429,7 +455,8 @@ > reg-names = "csr", "cfg"; > ranges = <0x01000000 0x0 0x00000000 0xc0 0x00000000 0x0 0x00010000 /* io */ > 0x02000000 0x0 0x10000000 0xc0 0x10000000 0x0 0x80000000>; /* mem */ > - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; > + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ > + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ > interrupt-map-mask = <0x0 0x0 0x0 0x7>; > interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xda 0x1 > 0x0 0x0 0x0 0x2 &gic 0x0 0xdb 0x1 > diff --git a/arch/arm64/include/asm/msi_bitmap.h b/arch/arm64/include/asm/msi_bitmap.h > new file mode 100644 > index 0000000..dfaa256 > --- /dev/null > +++ b/arch/arm64/include/asm/msi_bitmap.h > @@ -0,0 +1,36 @@ > +#ifndef MSI_BITMAP_H > +#define MSI_BITMAP_H > + > +/* > + * Copyright 2008, Michael Ellerman, IBM Corporation. > + * > + * 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; version 2 of the > + * License. > + * > + * Borrowed from powerpc arch > + */ > + > +#include <linux/of.h> > +#include <asm/irq.h> > + > +struct msi_bitmap { > + struct device_node *of_node; > + unsigned long *bitmap; > + spinlock_t lock; > + unsigned int irq_count; > +}; > + > +int msi_bitmap_alloc_hwirqs(struct msi_bitmap *bmp, int num); > +void msi_bitmap_free_hwirqs(struct msi_bitmap *bmp, unsigned int offset, > + unsigned int num); > +void msi_bitmap_reserve_hwirq(struct msi_bitmap *bmp, unsigned int hwirq); > + > +int msi_bitmap_reserve_dt_hwirqs(struct msi_bitmap *bmp); > + > +int msi_bitmap_alloc(struct msi_bitmap *bmp, unsigned int irq_count, > + struct device_node *of_node); > +void msi_bitmap_free(struct msi_bitmap *bmp); > + > +#endif /* MSI_BITMAP_H */ > diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile > index 9e9c208..69900a2 100644 > --- a/arch/arm64/kernel/Makefile > +++ b/arch/arm64/kernel/Makefile > @@ -20,6 +20,7 @@ arm64-obj-$(CONFIG_HAVE_HW_BREAKPOINT)+= hw_breakpoint.o > arm64-obj-$(CONFIG_EARLY_PRINTK) += early_printk.o > > obj-$(CONFIG_PCI) += pcibios.o > +obj-$(CONFIG_PCI_MSI) += msi_bitmap.o > obj-y += $(arm64-obj-y) vdso/ > obj-m += $(arm64-obj-m) > head-y := head.o > diff --git a/arch/arm64/kernel/msi_bitmap.c b/arch/arm64/kernel/msi_bitmap.c > new file mode 100644 > index 0000000..5d851fe > --- /dev/null > +++ b/arch/arm64/kernel/msi_bitmap.c > @@ -0,0 +1,134 @@ > +/* > + * Copyright 2006-2008, Michael Ellerman, IBM Corporation. > + * > + * 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; version 2 of the > + * License. > + * > + * Borrowed from powerpc arch > + */ > + > +#include <linux/slab.h> > +#include <linux/kernel.h> > +#include <linux/bitmap.h> > +#include <asm/msi_bitmap.h> > +#include <asm/setup.h> > + > +int msi_bitmap_alloc_hwirqs(struct msi_bitmap *bmp, int num) > +{ > + unsigned long flags; > + int offset, order = get_count_order(num); > + > + spin_lock_irqsave(&bmp->lock, flags); > + /* > + * This is fast, but stricter than we need. We might want to add > + * a fallback routine which does a linear search with no alignment. > + */ > + offset = bitmap_find_free_region(bmp->bitmap, bmp->irq_count, order); > + spin_unlock_irqrestore(&bmp->lock, flags); > + > + pr_debug("msi_bitmap: allocated 0x%x (2^%d) at offset 0x%x\n", > + num, order, offset); > + > + return offset; > +} > + > +void msi_bitmap_free_hwirqs(struct msi_bitmap *bmp, unsigned int offset, > + unsigned int num) > +{ > + unsigned long flags; > + int order = get_count_order(num); > + > + pr_debug("msi_bitmap: freeing 0x%x (2^%d) at offset 0x%x\n", > + num, order, offset); > + > + spin_lock_irqsave(&bmp->lock, flags); > + bitmap_release_region(bmp->bitmap, offset, order); > + spin_unlock_irqrestore(&bmp->lock, flags); > +} > + > +void msi_bitmap_reserve_hwirq(struct msi_bitmap *bmp, unsigned int hwirq) > +{ > + unsigned long flags; > + > + pr_debug("msi_bitmap: reserving hwirq 0x%x\n", hwirq); > + > + spin_lock_irqsave(&bmp->lock, flags); > + bitmap_allocate_region(bmp->bitmap, hwirq, 0); > + spin_unlock_irqrestore(&bmp->lock, flags); > +} > + > +/** > + * msi_bitmap_reserve_dt_hwirqs - Reserve irqs specified in the device tree. > + * @bmp: pointer to the MSI bitmap. > + * > + * Looks in the device tree to see if there is a property specifying which > + * irqs can be used for MSI. If found those irqs reserved in the device tree > + * are reserved in the bitmap. > + * > + * Returns 0 for success, < 0 if there was an error, and > 0 if no property > + * was found in the device tree. > + **/ > +int msi_bitmap_reserve_dt_hwirqs(struct msi_bitmap *bmp) > +{ > + int i, j, len = 2; > + u32 array[2]; > + u32 *p = array; > + int ret; > + > + if (!bmp->of_node) > + return 1; > + > + ret = of_property_read_u32_array(bmp->of_node, > + "msi-available-ranges", p, len); > + if (ret) > + return ret; > + > + bitmap_allocate_region(bmp->bitmap, 0, get_count_order(bmp->irq_count)); > + > + spin_lock(&bmp->lock); > + > + /* Format is: (<u32 start> <u32 count>)+ */ > + len /= 2 * sizeof(u32); > + for (i = 0; i < len; i++, p += 2) { > + for (j = 0; j < *(p + 1); j++) > + bitmap_release_region(bmp->bitmap, *p + j, 0); > + } > + > + spin_unlock(&bmp->lock); > + > + return 0; > +} > + > +int msi_bitmap_alloc(struct msi_bitmap *bmp, unsigned int irq_count, > + struct device_node *of_node) > +{ > + int size; > + > + if (!irq_count) > + return -EINVAL; > + > + size = BITS_TO_LONGS(irq_count) * sizeof(long); > + pr_debug("msi_bitmap: allocator bitmap size is 0x%x bytes\n", size); > + > + bmp->bitmap = kzalloc(size, GFP_KERNEL); > + if (!bmp->bitmap) { > + pr_debug("msi_bitmap: ENOMEM allocating allocator bitmap!\n"); > + return -ENOMEM; > + } > + > + /* We zalloc'ed the bitmap, so all irqs are free by default */ > + spin_lock_init(&bmp->lock); > + bmp->of_node = of_node_get(of_node); > + bmp->irq_count = irq_count; > + > + return 0; > +} > + > +void msi_bitmap_free(struct msi_bitmap *bmp) > +{ > + /* we can't free the bitmap we don't know if it's bootmem etc. */ > + of_node_put(bmp->of_node); > + bmp->bitmap = NULL; > +} > diff --git a/debian.master/config/config.common.ubuntu b/debian.master/config/config.common.ubuntu > index b44d8a2..2f785a5 100644 > --- a/debian.master/config/config.common.ubuntu > +++ b/debian.master/config/config.common.ubuntu > @@ -4819,6 +4819,7 @@ CONFIG_PCI_STUB=m > CONFIG_PCI_TEGRA=y > CONFIG_PCI_XEN=y > CONFIG_PCI_XGENE=y > +CONFIG_PCI_XGENE_MSI=y > CONFIG_PCMCIA=m > CONFIG_PCMCIA_3C574=m > CONFIG_PCMCIA_3C589=m > diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig > index 19ce97d..a7d746a 100644 > --- a/drivers/pci/host/Kconfig > +++ b/drivers/pci/host/Kconfig > @@ -33,11 +33,15 @@ config PCI_RCAR_GEN2 > There are 3 internal PCI controllers available with a single > built-in EHCI/OHCI host controller present on each one. > > +config PCI_XGENE_MSI > + bool > + > config PCI_XGENE > bool "X-Gene PCIe controller" > depends on ARCH_XGENE > depends on OF > select PCIEPORTBUS > + select PCI_XGENE_MSI if PCI_MSI > help > Say Y here if you want internal PCI support on APM X-Gene SoC. > There are 5 internal PCIe ports available. Each port is GEN3 capable > diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile > index 34c7c36..ddac195 100644 > --- a/drivers/pci/host/Makefile > +++ b/drivers/pci/host/Makefile > @@ -5,3 +5,4 @@ obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o > obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o > obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o > obj-$(CONFIG_PCI_XGENE) += pci-xgene.o > +obj-$(CONFIG_PCI_XGENE_MSI) += pci-xgene-msi.o > diff --git a/drivers/pci/host/pci-xgene-msi.c b/drivers/pci/host/pci-xgene-msi.c > new file mode 100644 > index 0000000..f21c459 > --- /dev/null > +++ b/drivers/pci/host/pci-xgene-msi.c > @@ -0,0 +1,593 @@ > +/* > + * XGene MSI Driver > + * > + * Copyright (c) 2010, Applied Micro Circuits Corporation > + * Author: Tanmay Inamdar <tinamdar@apm.com> > + * Tuan Phan <tphan@apm.com> > + * Jim Hull <jim.hull@hp.com> > + * > + * 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., 59 Temple Place, Suite 330, Boston, > + * MA 02111-1307 USA > + * > + */ > +#include <linux/platform_device.h> > +#include <linux/interrupt.h> > +#include <linux/irq.h> > +#include <linux/irqdomain.h> > +#include <linux/bootmem.h> > +#include <linux/msi.h> > +#include <linux/pci.h> > +#include <linux/slab.h> > +#include <linux/export.h> > +#include <linux/of.h> > +#include <linux/of_platform.h> > +#include <linux/of_irq.h> > +#include <linux/of_address.h> > +#include <linux/acpi.h> > +#include <linux/efi.h> > +#include <asm/hw_irq.h> > +#include <asm/io.h> > +#include <asm/msi_bitmap.h> > + > +#define NR_MSI_REG 16 > +#define IRQS_PER_MSI_INDEX 32 > +#define IRQS_PER_MSI_REG 256 > +#define NR_MSI_IRQS (NR_MSI_REG * IRQS_PER_MSI_REG) > + > +#define XGENE_PIC_IP_MASK 0x0000000F > +#define XGENE_PIC_IP_GIC 0x00000001 > + > +/* PCIe MSI Index Registers */ > +#define MSI0IR0 0x000000 > +#define MSIFIR7 0x7F0000 > + > +/* PCIe MSI Interrupt Register */ > +#define MSI1INT0 0x800000 > +#define MSI1INTF 0x8F0000 > + > +struct xgene_msi { > + struct irq_domain *irqhost; > + unsigned long cascade_irq; > + u32 msiir_offset; > + u32 msi_addr_lo; > + u32 msi_addr_hi; > + void __iomem *msi_regs; > + u32 feature; > + int msi_virqs[NR_MSI_REG]; > + struct msi_bitmap bitmap; > + struct list_head list; /* support multiple MSI banks */ > + phandle phandle; > +}; > + > +#ifdef CONFIG_ARCH_MSLIM > +static inline u64 xgene_pcie_get_iof_addr(u64 addr) > +{ > + return mslim_pa_to_iof_axi(lower_32_bits(addr)); > +} > +#else > +#define xgene_pcie_get_iof_addr(addr) addr > +#endif > + > +#define MSI_DRIVER_VERSION "0.1" > + > +struct xgene_msi_feature { > + u32 xgene_pic_ip; > + u32 msiir_offset; /* Offset of MSIIR, relative to start of MSIR bank */ > +}; > + > +struct xgene_msi_cascade_data { > + struct xgene_msi *msi_data; > + int index; > +}; > + > +LIST_HEAD(msi_head); > + > +static const struct xgene_msi_feature gic_msi_feature = { > + .xgene_pic_ip = XGENE_PIC_IP_GIC, > + .msiir_offset = 0, > +}; > + > +irq_hw_number_t virq_to_hw(unsigned int virq) > +{ > + struct irq_data *irq_data = irq_get_irq_data(virq); > + return WARN_ON(!irq_data) ? 0 : irq_data->hwirq; > +} > + > +static inline u32 xgene_msi_intr_read(phys_addr_t __iomem *base, > + unsigned int reg) > +{ > + u32 irq_reg = MSI1INT0 + (reg << 16); > + pr_debug("base = %p irq_reg = 0x%x , reg = 0x%x\n", base, irq_reg, reg); > + return readl((void *)((phys_addr_t) base + irq_reg)); > +} > + > +static inline u32 xgene_msi_read(phys_addr_t __iomem *base, unsigned int group, > + unsigned int reg) > +{ > + u32 irq_reg = MSI0IR0 + (group << 19) + (reg << 16); > + pr_debug("base %p irq_reg 0x%x, group 0x%x, reg 0x%x\n", > + base, irq_reg, group, reg); > + return readl((void *)((phys_addr_t) base + irq_reg)); > +} > + > +/* > + * We do not need this actually. The MSIR register has been read once > + * in the cascade interrupt. So, this MSI interrupt has been acked > +*/ > +static void xgene_msi_end_irq(struct irq_data *d) > +{ > +} > + > +#ifdef CONFIG_SMP > +static int xgene_msi_set_affinity(struct irq_data *d, > + const struct cpumask *mask_val, bool force) > +{ > + u64 virt_msir; > + > + virt_msir = (u64)irq_get_handler_data(d->irq); > + return irq_set_affinity(virt_msir, mask_val); > +} > +#endif > + > +static struct irq_chip xgene_msi_chip = { > + .irq_mask = mask_msi_irq, > + .irq_unmask = unmask_msi_irq, > + .irq_ack = xgene_msi_end_irq, > +#ifdef CONFIG_SMP > + .irq_set_affinity = xgene_msi_set_affinity, > +#endif > + .name = "xgene-msi", > +}; > + > +static int xgene_msi_host_map(struct irq_domain *h, unsigned int virq, > + irq_hw_number_t hw) > +{ > + struct xgene_msi *msi_data = h->host_data; > + struct irq_chip *chip = &xgene_msi_chip; > + > + pr_debug("\nENTER %s, virq=%u\n", __func__, virq); > + irq_set_status_flags(virq, IRQ_TYPE_LEVEL_HIGH); > + irq_set_chip_data(virq, msi_data); > + irq_set_chip_and_handler(virq, chip, handle_level_irq); > + > + return 0; > +} > + > +static const struct irq_domain_ops xgene_msi_host_ops = { > + .map = xgene_msi_host_map, > +}; > + > +static int xgene_msi_init_allocator(struct xgene_msi *msi_data) > +{ > +#ifdef CONFIG_ACPI > + if (efi_enabled(EFI_BOOT)) > + return msi_bitmap_alloc(&msi_data->bitmap, NR_MSI_IRQS, NULL); > + else > +#endif > + return msi_bitmap_alloc(&msi_data->bitmap, NR_MSI_IRQS, > + msi_data->irqhost->of_node); > +} > + > +int arch_msi_check_device(struct pci_dev *dev, int nvec, int type) > +{ > + pr_debug("ENTER %s\n", __func__); > + return 0; > +} > + > +void arch_teardown_msi_irqs(struct pci_dev *dev) > +{ > + struct msi_desc *entry; > + struct xgene_msi *msi_data; > + > + pr_debug("ENTER %s\n", __func__); > + list_for_each_entry(entry, &dev->msi_list, list) { > + if (entry->irq == 0) > + continue; > + > + msi_data = irq_get_chip_data(entry->irq); > + irq_set_msi_desc(entry->irq, NULL); > + msi_bitmap_free_hwirqs(&msi_data->bitmap, > + virq_to_hw(entry->irq), 1); > + irq_dispose_mapping(entry->irq); > + } > +} > + > +static void xgene_compose_msi_msg(struct pci_dev *dev, int hwirq, > + struct msi_msg *msg, > + struct xgene_msi *msi_data) > +{ > + int reg_set, group; > + > + group = hwirq % NR_MSI_REG; > + reg_set = hwirq / (NR_MSI_REG * IRQS_PER_MSI_INDEX); > + > + pr_debug("group = %d, reg_set : 0x%x\n", group, reg_set); > + msg->address_lo = msi_data->msi_addr_lo + > + (((8 * group) + reg_set) << 16); > + msg->address_hi = msi_data->msi_addr_hi; > + msg->data = (hwirq / NR_MSI_REG) % IRQS_PER_MSI_INDEX; > + pr_debug("addr : 0x%08x, data : 0x%x\n", msg->address_lo, msg->data); > +} > + > +int arch_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type) > +{ > + struct device_node *np; > + struct msi_desc *entry; > + struct msi_msg msg; > + u64 gic_irq; > + unsigned int virq; > + struct xgene_msi *msi_data; > + phandle phandle = 0; > + int rc = 0; > + int hwirq = -1; > + > + pr_debug("ENTER %s - nvec = %d, type = %d\n", __func__, nvec, type); > +#ifdef CONFIG_ACPI > + if (!efi_enabled(EFI_BOOT)) > +#endif > + { > + np = pci_device_to_OF_node(pdev); > + /* > + * If the PCI node has an xgene,msi property, > + * then we need to use it to find the specific MSI. > + */ > + np = of_parse_phandle(np, "xgene,msi", 0); > + if (np) { > + if (of_device_is_compatible(np, > + "xgene,gic-msi-cascade")) > + phandle = np->phandle; > + else { > + dev_err(&pdev->dev, > + "node %s has an invalid xgene,msi phandle %u\n", > + np->full_name, np->phandle); > + rc = -EINVAL; > + goto exit; > + } > + } else > + dev_info(&pdev->dev, "Found no xgene,msi phandle\n"); > + } > + > + list_for_each_entry(entry, &pdev->msi_list, list) { > + pr_debug("Loop over MSI devices\n"); > + /* > + * Loop over all the MSI devices until we find one that has an > + * available interrupt. > + */ > + list_for_each_entry(msi_data, &msi_head, list) { > + if (phandle && (phandle != msi_data->phandle)) > + continue; > + pr_debug("Xgene msi pointer : %p\n", msi_data); > + hwirq = msi_bitmap_alloc_hwirqs(&msi_data->bitmap, 1); > + break; > + } > + > + if (hwirq < 0) { > + dev_err(&pdev->dev, > + "could not allocate MSI interrupt\n"); > + rc = -ENOSPC; > + goto exit; > + } > + > + virq = irq_create_mapping(msi_data->irqhost, hwirq); > + if (virq == 0) { > + dev_err(&pdev->dev, "fail mapping hwirq %i\n", hwirq); > + msi_bitmap_free_hwirqs(&msi_data->bitmap, hwirq, 1); > + rc = -ENOSPC; > + goto exit; > + } > + > + gic_irq = msi_data->msi_virqs[hwirq % NR_MSI_REG]; > + pr_debug("Created Mapping HWIRQ %d on GIC IRQ %llu " > + "TO VIRQ %d\n", > + hwirq, gic_irq, virq); > + /* chip_data is msi_data via host->hostdata in host->map() */ > + irq_set_msi_desc(virq, entry); > + xgene_compose_msi_msg(pdev, hwirq, &msg, msi_data); > + irq_set_handler_data(virq, (void *)gic_irq); > + write_msi_msg(virq, &msg); > + } > + > +exit: > + return rc; > +} > + > +static void xgene_msi_cascade(unsigned int irq, struct irq_desc *desc) > +{ > + struct irq_chip *chip = irq_desc_get_chip(desc); > + struct irq_data *idata = irq_desc_get_irq_data(desc); > + struct xgene_msi_cascade_data *cascade_data; > + struct xgene_msi *msi_data; > + unsigned int cascade_irq; > + int msir_index = -1; > + u32 msir_value = 0; > + u32 intr_index = 0; > + u32 msi_intr_reg_value = 0; > + u32 msi_intr_reg; > + > + pr_debug("\nENTER %s, irq=%u\n", __func__, irq); > + cascade_data = irq_get_handler_data(irq); > + msi_data = cascade_data->msi_data; > + pr_debug("xgene_msi : 0x%p, irq = %u\n", msi_data, irq); > + > + raw_spin_lock(&desc->lock); > + if (unlikely(irqd_irq_inprogress(idata))) > + goto unlock; > + > + msi_intr_reg = cascade_data->index; > + pr_debug("msi_intr_reg : %d\n", msi_intr_reg); > + > + if (msi_intr_reg >= NR_MSI_REG) > + cascade_irq = 0; > + > + irqd_set_chained_irq_inprogress(idata); > + > + switch (msi_data->feature & XGENE_PIC_IP_MASK) { > + case XGENE_PIC_IP_GIC: > + msi_intr_reg_value = xgene_msi_intr_read(msi_data->msi_regs, > + msi_intr_reg); > + pr_debug("msi_intr_reg_value : 0x%08x\n", msi_intr_reg_value); > + break; > + } > + > + while (msi_intr_reg_value) { > + msir_index = ffs(msi_intr_reg_value) - 1; > + pr_debug("msir_index : %d\n", msir_index); > + msir_value = xgene_msi_read(msi_data->msi_regs, msi_intr_reg, > + msir_index); > + while (msir_value) { > + intr_index = ffs(msir_value) - 1; > + pr_debug("intr_index : %d\n", intr_index); > + cascade_irq = irq_linear_revmap(msi_data->irqhost, > + msir_index * IRQS_PER_MSI_INDEX * NR_MSI_REG + > + intr_index * NR_MSI_REG + msi_intr_reg); > + pr_debug("cascade_irq : %d\n", cascade_irq); > + if (cascade_irq != 0) > + generic_handle_irq(cascade_irq); > + msir_value &= ~(1 << intr_index); > + pr_debug("msir_value : 0x%08x\n", msir_value); > + } > + msi_intr_reg_value &= ~(1 << msir_index); > + pr_debug("msi_intr_reg_value : 0x%08x\n", msi_intr_reg_value); > + } > + irqd_clr_chained_irq_inprogress(idata); > + > + switch (msi_data->feature & XGENE_PIC_IP_MASK) { > + case XGENE_PIC_IP_GIC: > + chip->irq_eoi(idata); > + break; > + } > + > +unlock: > + raw_spin_unlock(&desc->lock); > + pr_debug("EXIT\n"); > +} > + > +static int xgene_msi_remove(struct platform_device *pdev) > +{ > + int virq, i; > + struct xgene_msi *msi = platform_get_drvdata(pdev); > + > + pr_debug("ENTER %s\n", __func__); > + for (i = 0; i < NR_MSI_REG; i++) { > + virq = msi->msi_virqs[i]; > + if (virq != 0) > + irq_dispose_mapping(virq); > + } > + > + if (msi->bitmap.bitmap) > + msi_bitmap_free(&msi->bitmap); > + > + return 0; > +} > + > +static int xgene_msi_setup_hwirq(struct xgene_msi *msi, > + struct platform_device *pdev, > + int offset, int irq_index) > +{ > + int virt_msir; > + cpumask_var_t mask; > + struct xgene_msi_cascade_data *cascade_data = NULL; > + > + virt_msir = platform_get_irq(pdev, irq_index); > + if (virt_msir < 0) { > + dev_err(&pdev->dev, "Cannot translate IRQ index %d\n", > + irq_index); > + return -EINVAL; > + } > + > + cascade_data = devm_kzalloc(&pdev->dev, > + sizeof(struct xgene_msi_cascade_data), GFP_KERNEL); > + if (!cascade_data) { > + dev_err(&pdev->dev, "No memory for MSI cascade data\n"); > + return -ENOMEM; > + } > + > + if (alloc_cpumask_var(&mask, GFP_KERNEL)) { > + cpumask_setall(mask); > + irq_set_affinity(virt_msir, mask); > + free_cpumask_var(mask); > + } > + > + msi->msi_virqs[irq_index] = virt_msir; > + cascade_data->index = offset; > + cascade_data->msi_data = msi; > + irq_set_handler_data(virt_msir, cascade_data); > + irq_set_chained_handler(virt_msir, xgene_msi_cascade); > + pr_debug("mapped phys irq %d\n", virt_msir); > + > + return 0; > +} > + > +static int xgene_msi_get_param(struct platform_device *pdev, const char *name, > + u32 *buf, int count) > +{ > + int rc = 0; > + > +#ifdef CONFIG_ACPI > + if (efi_enabled(EFI_BOOT)) { > + struct acpi_dsm_entry entry; > + > + if (acpi_dsm_lookup_value(ACPI_HANDLE(&pdev->dev), > + name, 0, &entry) || !entry.value) > + return -EINVAL; > + > + if (count == 2) > + sscanf(entry.value, "%d %d", &buf[0], &buf[1]); > + else > + rc = -EINVAL; > + > + kfree(entry.key); > + kfree(entry.value); > + } else > +#endif > + if (of_property_read_u32_array(pdev->dev.of_node, name, > + buf, count)) > + rc = -EINVAL; > + > + return rc; > +} > + > +static int xgene_msi_probe(struct platform_device *pdev) > +{ > + struct xgene_msi *msi; > + struct resource *res; > + phys_addr_t msiir_offset; > + int rc, j, irq_index, count; > + u32 offset; > + u32 buf[] = { 0, NR_MSI_IRQS}; > + > + msi = devm_kzalloc(&pdev->dev, sizeof(struct xgene_msi), GFP_KERNEL); > + if (!msi) { > + dev_err(&pdev->dev, "No memory for MSI structure\n"); > + return -ENOMEM; > + } > + > + platform_set_drvdata(pdev, msi); > + > +#ifdef CONFIG_ACPI > + if (efi_enabled(EFI_BOOT)) > + msi->irqhost = irq_domain_add_linear(NULL, > + NR_MSI_IRQS, &xgene_msi_host_ops, msi); > + else > +#endif > + msi->irqhost = irq_domain_add_linear(pdev->dev.of_node, > + NR_MSI_IRQS, &xgene_msi_host_ops, msi); > + if (msi->irqhost == NULL) { > + dev_err(&pdev->dev, "No memory for MSI irqhost\n"); > + rc = -ENOMEM; > + goto error; > + } > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + msi->msi_regs = devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(msi->msi_regs)) { > + dev_err(&pdev->dev, "no reg space\n"); > + rc = -EINVAL; > + goto error; > + } > + > + pr_debug("mapped 0x%08llx to 0x%p Size : 0x%08llx\n", res->start, > + msi->msi_regs, resource_size(res)); > + > + msiir_offset = lower_32_bits(xgene_pcie_get_iof_addr(res->start)); > + msi->msiir_offset = gic_msi_feature.msiir_offset + > + (msiir_offset & 0xfffff); > + msi->msi_addr_hi = upper_32_bits(res->start); > + msi->msi_addr_lo = gic_msi_feature.msiir_offset + > + (msiir_offset & 0xffffffff); > + msi->feature = gic_msi_feature.xgene_pic_ip; > + > +#ifdef CONFIG_ACPI > + if (efi_enabled(EFI_BOOT)) > + msi->phandle = 0; > + else > +#endif > + msi->phandle = pdev->dev.of_node->phandle; > + > + rc = xgene_msi_init_allocator(msi); > + if (rc) { > + dev_err(&pdev->dev, "Error allocating MSI bitmap\n"); > + goto error; > + } > + > + rc = xgene_msi_get_param(pdev, "msi-available-ranges", buf, 2); > + if (rc) { > + dev_err(&pdev->dev, "Error getting MSI ranges\n"); > + goto error; > + } > + > + pr_debug("buf[0] = 0x%x buf[1] = 0x%x\n", buf[0], buf[1]); > + if (buf[0] % IRQS_PER_MSI_REG || buf[1] % IRQS_PER_MSI_REG) { > + pr_err_once("msi available range of" > + "%u at %u is not IRQ-aligned\n", > + buf[1], buf[0]); > + rc = -EINVAL; > + goto error; > + } > + > + offset = buf[0] / IRQS_PER_MSI_REG; > + count = buf[1] / IRQS_PER_MSI_REG; > + pr_debug("offset = %d count = %d\n", offset, count); > + > + for (irq_index = 0, j = 0; j < count; j++, irq_index++) { > + rc = xgene_msi_setup_hwirq(msi, pdev, offset + j, irq_index); > + if (rc) > + goto error; > + } > + > + list_add_tail(&msi->list, &msi_head); > + > + pr_info("XGene: PCIe MSI driver v%s\n", MSI_DRIVER_VERSION); > + > + return 0; > + > +error: > + xgene_msi_remove(pdev); > + return rc; > +} > + > +static const struct of_device_id xgene_msi_of_ids[] = { > + { > + .compatible = "xgene,gic-msi", > + }, > + {} > +}; > + > +#ifdef CONFIG_ACPI > +static const struct acpi_device_id xgene_msi_acpi_ids[] = { > + {"APMC0D0E", 0}, > + {}, > +}; > +#endif > + > +static struct platform_driver xgene_msi_driver = { > + .driver = { > + .name = "xgene-msi", > + .owner = THIS_MODULE, > + .of_match_table = xgene_msi_of_ids, > +#ifdef CONFIG_ACPI > + .acpi_match_table = ACPI_PTR(xgene_msi_acpi_ids), > +#endif > + }, > + .probe = xgene_msi_probe, > + .remove = xgene_msi_remove, > +}; > + > +static __init int xgene_msi_init(void) > +{ > + return platform_driver_register(&xgene_msi_driver); > +} > + > +subsys_initcall_sync(xgene_msi_init); > diff --git a/drivers/pci/host/pci-xgene.c b/drivers/pci/host/pci-xgene.c > index 03be990..b787e76 100644 > --- a/drivers/pci/host/pci-xgene.c > +++ b/drivers/pci/host/pci-xgene.c > @@ -665,7 +665,7 @@ static int pci_dma_range_parser_init(struct of_pci_range_parser *parser, > parser->pna = of_n_addr_cells(node); > parser->np = parser->pna + na + ns; > > - parser->range = of_get_property(node, "dma-ranges", &rlen); > + parser->range = of_get_property(node, "ib-ranges", &rlen); > if (!parser->range) > return -ENOENT; > > @@ -682,11 +682,11 @@ static int xgene_pcie_parse_map_dma_ranges(struct xgene_pcie_port *port) > u8 ib_reg_mask = 0; > > if (pci_dma_range_parser_init(&parser, np)) { > - dev_err(dev, "missing dma-ranges property\n"); > + dev_err(dev, "missing ib-ranges property\n"); > return -EINVAL; > } > > - /* Get the dma-ranges from DT */ > + /* Get the ib-ranges from DT */ > for_each_of_pci_range(&parser, &range) { > u64 end = range.cpu_addr + range.size - 1; > dev_dbg(port->dev, "0x%08x 0x%016llx..0x%016llx -> 0x%016llx\n", >
On Fri, May 23, 2014 at 08:54:28PM +0800, Ike Panhc wrote: > From: Tanmay Inamdar <tinamdar@apm.com> > > BugLink: http://launchpad.net/bugs/1318977 > > Signed-off-by: Tanmay Inamdar <tinamdar@apm.com> > Signed-off-by: Ike Panhc <ike.pan@canonical.com> > --- > arch/arm64/boot/dts/apm-storm.dtsi | 41 ++- > arch/arm64/include/asm/msi_bitmap.h | 36 ++ > arch/arm64/kernel/Makefile | 1 + > arch/arm64/kernel/msi_bitmap.c | 134 +++++++ > debian.master/config/config.common.ubuntu | 1 + > drivers/pci/host/Kconfig | 4 + > drivers/pci/host/Makefile | 1 + > drivers/pci/host/pci-xgene-msi.c | 593 ++++++++++++++++++++++++++++++ > drivers/pci/host/pci-xgene.c | 6 +- > 9 files changed, 807 insertions(+), 10 deletions(-) > create mode 100644 arch/arm64/include/asm/msi_bitmap.h > create mode 100644 arch/arm64/kernel/msi_bitmap.c > create mode 100644 drivers/pci/host/pci-xgene-msi.c > > diff --git a/arch/arm64/boot/dts/apm-storm.dtsi b/arch/arm64/boot/dts/apm-storm.dtsi > index aae496f..6f22fd8 100644 > --- a/arch/arm64/boot/dts/apm-storm.dtsi > +++ b/arch/arm64/boot/dts/apm-storm.dtsi > @@ -333,6 +333,28 @@ > }; > }; > > + msi: msi@79000000 { > + compatible = "xgene,gic-msi"; > + reg = <0x00 0x79000000 0x0 0x900000>; > + msi-available-ranges = <0x0 0x1000>; > + interrupts = < 0x0 0x10 0x4 > + 0x0 0x11 0x4 > + 0x0 0x12 0x4 > + 0x0 0x13 0x4 > + 0x0 0x14 0x4 > + 0x0 0x15 0x4 > + 0x0 0x16 0x4 > + 0x0 0x17 0x4 > + 0x0 0x18 0x4 > + 0x0 0x19 0x4 > + 0x0 0x1a 0x4 > + 0x0 0x1b 0x4 > + 0x0 0x1c 0x4 > + 0x0 0x1d 0x4 > + 0x0 0x1e 0x4 > + 0x0 0x1f 0x4>; > + }; > + > pcie0: pcie@1f2b0000 { > status = "disabled"; > device_type = "pci"; > @@ -345,7 +367,8 @@ > reg-names = "csr", "cfg"; > ranges = <0x01000000 0x00 0x00000000 0xe0 0x00000000 0x00 0x00010000 /* io */ > 0x02000000 0x00 0x10000000 0xe0 0x10000000 0x00 0x80000000>; /* mem */ > - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; > + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ > + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ > interrupt-map-mask = <0x0 0x0 0x0 0x7>; > interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xc2 0x1 > 0x0 0x0 0x0 0x2 &gic 0x0 0xc3 0x1 > @@ -366,7 +389,8 @@ > reg-names = "csr", "cfg"; > ranges = <0x01000000 0x0 0x00000000 0xd0 0x00000000 0x00 0x00010000 /* io */ > 0x02000000 0x0 0x10000000 0xd0 0x10000000 0x00 0x80000000>; /* mem */ > - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; > + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ > + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ > interrupt-map-mask = <0x0 0x0 0x0 0x7>; > interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xc8 0x1 > 0x0 0x0 0x0 0x2 &gic 0x0 0xc9 0x1 > @@ -387,7 +411,8 @@ > reg-names = "csr", "cfg"; > ranges = <0x01000000 0x0 0x00000000 0x90 0x00000000 0x0 0x00010000 /* io */ > 0x02000000 0x0 0x10000000 0x90 0x10000000 0x0 0x80000000>; /* mem */ > - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; > + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ > + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ > interrupt-map-mask = <0x0 0x0 0x0 0x7>; > interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xce 0x1 > 0x0 0x0 0x0 0x2 &gic 0x0 0xcf 0x1 > @@ -406,9 +431,10 @@ > reg = < 0x00 0x1f500000 0x0 0x00010000 /* Controller registers */ > 0xa0 0xd0000000 0x0 0x00200000>; /* PCI config space */ > reg-names = "csr", "cfg"; > - ranges = <0x01000000 0x0 0x00000000 0xa0 0x00000000 0x0 0x00010000 /* mem */ > - 0x02000000 0x0 0x10000000 0xa0 0x10000000 0x0 0x80000000>; /* io */ > - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; > + ranges = <0x01000000 0x0 0x00000000 0xa0 0x00000000 0x0 0x00010000 /* io */ > + 0x02000000 0x0 0x10000000 0xa0 0x10000000 0x0 0x80000000>; /* mem */ > + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ > + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ > interrupt-map-mask = <0x0 0x0 0x0 0x7>; > interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xd4 0x1 > 0x0 0x0 0x0 0x2 &gic 0x0 0xd5 0x1 > @@ -429,7 +455,8 @@ > reg-names = "csr", "cfg"; > ranges = <0x01000000 0x0 0x00000000 0xc0 0x00000000 0x0 0x00010000 /* io */ > 0x02000000 0x0 0x10000000 0xc0 0x10000000 0x0 0x80000000>; /* mem */ > - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; > + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ > + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ > interrupt-map-mask = <0x0 0x0 0x0 0x7>; > interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xda 0x1 > 0x0 0x0 0x0 0x2 &gic 0x0 0xdb 0x1 > diff --git a/arch/arm64/include/asm/msi_bitmap.h b/arch/arm64/include/asm/msi_bitmap.h > new file mode 100644 > index 0000000..dfaa256 > --- /dev/null > +++ b/arch/arm64/include/asm/msi_bitmap.h > @@ -0,0 +1,36 @@ > +#ifndef MSI_BITMAP_H > +#define MSI_BITMAP_H > + > +/* > + * Copyright 2008, Michael Ellerman, IBM Corporation. > + * > + * 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; version 2 of the > + * License. > + * > + * Borrowed from powerpc arch > + */ > + > +#include <linux/of.h> > +#include <asm/irq.h> > + > +struct msi_bitmap { > + struct device_node *of_node; > + unsigned long *bitmap; > + spinlock_t lock; > + unsigned int irq_count; > +}; > + > +int msi_bitmap_alloc_hwirqs(struct msi_bitmap *bmp, int num); > +void msi_bitmap_free_hwirqs(struct msi_bitmap *bmp, unsigned int offset, > + unsigned int num); > +void msi_bitmap_reserve_hwirq(struct msi_bitmap *bmp, unsigned int hwirq); > + > +int msi_bitmap_reserve_dt_hwirqs(struct msi_bitmap *bmp); > + > +int msi_bitmap_alloc(struct msi_bitmap *bmp, unsigned int irq_count, > + struct device_node *of_node); > +void msi_bitmap_free(struct msi_bitmap *bmp); > + > +#endif /* MSI_BITMAP_H */ > diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile > index 9e9c208..69900a2 100644 > --- a/arch/arm64/kernel/Makefile > +++ b/arch/arm64/kernel/Makefile > @@ -20,6 +20,7 @@ arm64-obj-$(CONFIG_HAVE_HW_BREAKPOINT)+= hw_breakpoint.o > arm64-obj-$(CONFIG_EARLY_PRINTK) += early_printk.o > > obj-$(CONFIG_PCI) += pcibios.o > +obj-$(CONFIG_PCI_MSI) += msi_bitmap.o > obj-y += $(arm64-obj-y) vdso/ > obj-m += $(arm64-obj-m) > head-y := head.o > diff --git a/arch/arm64/kernel/msi_bitmap.c b/arch/arm64/kernel/msi_bitmap.c > new file mode 100644 > index 0000000..5d851fe > --- /dev/null > +++ b/arch/arm64/kernel/msi_bitmap.c > @@ -0,0 +1,134 @@ > +/* > + * Copyright 2006-2008, Michael Ellerman, IBM Corporation. > + * > + * 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; version 2 of the > + * License. > + * > + * Borrowed from powerpc arch > + */ > + > +#include <linux/slab.h> > +#include <linux/kernel.h> > +#include <linux/bitmap.h> > +#include <asm/msi_bitmap.h> > +#include <asm/setup.h> > + > +int msi_bitmap_alloc_hwirqs(struct msi_bitmap *bmp, int num) > +{ > + unsigned long flags; > + int offset, order = get_count_order(num); > + > + spin_lock_irqsave(&bmp->lock, flags); > + /* > + * This is fast, but stricter than we need. We might want to add > + * a fallback routine which does a linear search with no alignment. > + */ > + offset = bitmap_find_free_region(bmp->bitmap, bmp->irq_count, order); > + spin_unlock_irqrestore(&bmp->lock, flags); > + > + pr_debug("msi_bitmap: allocated 0x%x (2^%d) at offset 0x%x\n", > + num, order, offset); > + > + return offset; > +} > + > +void msi_bitmap_free_hwirqs(struct msi_bitmap *bmp, unsigned int offset, > + unsigned int num) > +{ > + unsigned long flags; > + int order = get_count_order(num); > + > + pr_debug("msi_bitmap: freeing 0x%x (2^%d) at offset 0x%x\n", > + num, order, offset); > + > + spin_lock_irqsave(&bmp->lock, flags); > + bitmap_release_region(bmp->bitmap, offset, order); > + spin_unlock_irqrestore(&bmp->lock, flags); > +} > + > +void msi_bitmap_reserve_hwirq(struct msi_bitmap *bmp, unsigned int hwirq) > +{ > + unsigned long flags; > + > + pr_debug("msi_bitmap: reserving hwirq 0x%x\n", hwirq); > + > + spin_lock_irqsave(&bmp->lock, flags); > + bitmap_allocate_region(bmp->bitmap, hwirq, 0); > + spin_unlock_irqrestore(&bmp->lock, flags); > +} > + > +/** > + * msi_bitmap_reserve_dt_hwirqs - Reserve irqs specified in the device tree. > + * @bmp: pointer to the MSI bitmap. > + * > + * Looks in the device tree to see if there is a property specifying which > + * irqs can be used for MSI. If found those irqs reserved in the device tree > + * are reserved in the bitmap. > + * > + * Returns 0 for success, < 0 if there was an error, and > 0 if no property > + * was found in the device tree. > + **/ > +int msi_bitmap_reserve_dt_hwirqs(struct msi_bitmap *bmp) > +{ > + int i, j, len = 2; > + u32 array[2]; > + u32 *p = array; > + int ret; > + > + if (!bmp->of_node) > + return 1; > + > + ret = of_property_read_u32_array(bmp->of_node, > + "msi-available-ranges", p, len); > + if (ret) > + return ret; > + > + bitmap_allocate_region(bmp->bitmap, 0, get_count_order(bmp->irq_count)); > + > + spin_lock(&bmp->lock); > + > + /* Format is: (<u32 start> <u32 count>)+ */ > + len /= 2 * sizeof(u32); > + for (i = 0; i < len; i++, p += 2) { > + for (j = 0; j < *(p + 1); j++) > + bitmap_release_region(bmp->bitmap, *p + j, 0); > + } > + > + spin_unlock(&bmp->lock); > + > + return 0; > +} > + > +int msi_bitmap_alloc(struct msi_bitmap *bmp, unsigned int irq_count, > + struct device_node *of_node) > +{ > + int size; > + > + if (!irq_count) > + return -EINVAL; > + > + size = BITS_TO_LONGS(irq_count) * sizeof(long); > + pr_debug("msi_bitmap: allocator bitmap size is 0x%x bytes\n", size); > + > + bmp->bitmap = kzalloc(size, GFP_KERNEL); > + if (!bmp->bitmap) { > + pr_debug("msi_bitmap: ENOMEM allocating allocator bitmap!\n"); > + return -ENOMEM; > + } > + > + /* We zalloc'ed the bitmap, so all irqs are free by default */ > + spin_lock_init(&bmp->lock); > + bmp->of_node = of_node_get(of_node); > + bmp->irq_count = irq_count; > + > + return 0; > +} > + > +void msi_bitmap_free(struct msi_bitmap *bmp) > +{ > + /* we can't free the bitmap we don't know if it's bootmem etc. */ > + of_node_put(bmp->of_node); > + bmp->bitmap = NULL; > +} > diff --git a/debian.master/config/config.common.ubuntu b/debian.master/config/config.common.ubuntu > index b44d8a2..2f785a5 100644 > --- a/debian.master/config/config.common.ubuntu > +++ b/debian.master/config/config.common.ubuntu > @@ -4819,6 +4819,7 @@ CONFIG_PCI_STUB=m > CONFIG_PCI_TEGRA=y > CONFIG_PCI_XEN=y > CONFIG_PCI_XGENE=y > +CONFIG_PCI_XGENE_MSI=y > CONFIG_PCMCIA=m > CONFIG_PCMCIA_3C574=m > CONFIG_PCMCIA_3C589=m > diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig > index 19ce97d..a7d746a 100644 > --- a/drivers/pci/host/Kconfig > +++ b/drivers/pci/host/Kconfig > @@ -33,11 +33,15 @@ config PCI_RCAR_GEN2 > There are 3 internal PCI controllers available with a single > built-in EHCI/OHCI host controller present on each one. > > +config PCI_XGENE_MSI > + bool > + > config PCI_XGENE > bool "X-Gene PCIe controller" > depends on ARCH_XGENE > depends on OF > select PCIEPORTBUS > + select PCI_XGENE_MSI if PCI_MSI > help > Say Y here if you want internal PCI support on APM X-Gene SoC. > There are 5 internal PCIe ports available. Each port is GEN3 capable > diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile > index 34c7c36..ddac195 100644 > --- a/drivers/pci/host/Makefile > +++ b/drivers/pci/host/Makefile > @@ -5,3 +5,4 @@ obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o > obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o > obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o > obj-$(CONFIG_PCI_XGENE) += pci-xgene.o > +obj-$(CONFIG_PCI_XGENE_MSI) += pci-xgene-msi.o > diff --git a/drivers/pci/host/pci-xgene-msi.c b/drivers/pci/host/pci-xgene-msi.c > new file mode 100644 > index 0000000..f21c459 > --- /dev/null > +++ b/drivers/pci/host/pci-xgene-msi.c > @@ -0,0 +1,593 @@ > +/* > + * XGene MSI Driver > + * > + * Copyright (c) 2010, Applied Micro Circuits Corporation > + * Author: Tanmay Inamdar <tinamdar@apm.com> > + * Tuan Phan <tphan@apm.com> > + * Jim Hull <jim.hull@hp.com> > + * > + * 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., 59 Temple Place, Suite 330, Boston, > + * MA 02111-1307 USA > + * > + */ > +#include <linux/platform_device.h> > +#include <linux/interrupt.h> > +#include <linux/irq.h> > +#include <linux/irqdomain.h> > +#include <linux/bootmem.h> > +#include <linux/msi.h> > +#include <linux/pci.h> > +#include <linux/slab.h> > +#include <linux/export.h> > +#include <linux/of.h> > +#include <linux/of_platform.h> > +#include <linux/of_irq.h> > +#include <linux/of_address.h> > +#include <linux/acpi.h> > +#include <linux/efi.h> > +#include <asm/hw_irq.h> > +#include <asm/io.h> > +#include <asm/msi_bitmap.h> > + > +#define NR_MSI_REG 16 > +#define IRQS_PER_MSI_INDEX 32 > +#define IRQS_PER_MSI_REG 256 > +#define NR_MSI_IRQS (NR_MSI_REG * IRQS_PER_MSI_REG) > + > +#define XGENE_PIC_IP_MASK 0x0000000F > +#define XGENE_PIC_IP_GIC 0x00000001 > + > +/* PCIe MSI Index Registers */ > +#define MSI0IR0 0x000000 > +#define MSIFIR7 0x7F0000 > + > +/* PCIe MSI Interrupt Register */ > +#define MSI1INT0 0x800000 > +#define MSI1INTF 0x8F0000 > + > +struct xgene_msi { > + struct irq_domain *irqhost; > + unsigned long cascade_irq; > + u32 msiir_offset; > + u32 msi_addr_lo; > + u32 msi_addr_hi; > + void __iomem *msi_regs; > + u32 feature; > + int msi_virqs[NR_MSI_REG]; > + struct msi_bitmap bitmap; > + struct list_head list; /* support multiple MSI banks */ > + phandle phandle; > +}; > + > +#ifdef CONFIG_ARCH_MSLIM > +static inline u64 xgene_pcie_get_iof_addr(u64 addr) > +{ > + return mslim_pa_to_iof_axi(lower_32_bits(addr)); > +} > +#else > +#define xgene_pcie_get_iof_addr(addr) addr > +#endif > + > +#define MSI_DRIVER_VERSION "0.1" > + > +struct xgene_msi_feature { > + u32 xgene_pic_ip; > + u32 msiir_offset; /* Offset of MSIIR, relative to start of MSIR bank */ > +}; > + > +struct xgene_msi_cascade_data { > + struct xgene_msi *msi_data; > + int index; > +}; > + > +LIST_HEAD(msi_head); > + > +static const struct xgene_msi_feature gic_msi_feature = { > + .xgene_pic_ip = XGENE_PIC_IP_GIC, > + .msiir_offset = 0, > +}; > + > +irq_hw_number_t virq_to_hw(unsigned int virq) > +{ > + struct irq_data *irq_data = irq_get_irq_data(virq); > + return WARN_ON(!irq_data) ? 0 : irq_data->hwirq; > +} > + > +static inline u32 xgene_msi_intr_read(phys_addr_t __iomem *base, > + unsigned int reg) > +{ > + u32 irq_reg = MSI1INT0 + (reg << 16); > + pr_debug("base = %p irq_reg = 0x%x , reg = 0x%x\n", base, irq_reg, reg); > + return readl((void *)((phys_addr_t) base + irq_reg)); > +} > + > +static inline u32 xgene_msi_read(phys_addr_t __iomem *base, unsigned int group, > + unsigned int reg) > +{ > + u32 irq_reg = MSI0IR0 + (group << 19) + (reg << 16); > + pr_debug("base %p irq_reg 0x%x, group 0x%x, reg 0x%x\n", > + base, irq_reg, group, reg); > + return readl((void *)((phys_addr_t) base + irq_reg)); > +} > + > +/* > + * We do not need this actually. The MSIR register has been read once > + * in the cascade interrupt. So, this MSI interrupt has been acked > +*/ > +static void xgene_msi_end_irq(struct irq_data *d) > +{ > +} > + > +#ifdef CONFIG_SMP > +static int xgene_msi_set_affinity(struct irq_data *d, > + const struct cpumask *mask_val, bool force) > +{ > + u64 virt_msir; > + > + virt_msir = (u64)irq_get_handler_data(d->irq); > + return irq_set_affinity(virt_msir, mask_val); > +} > +#endif > + > +static struct irq_chip xgene_msi_chip = { > + .irq_mask = mask_msi_irq, > + .irq_unmask = unmask_msi_irq, > + .irq_ack = xgene_msi_end_irq, > +#ifdef CONFIG_SMP > + .irq_set_affinity = xgene_msi_set_affinity, > +#endif > + .name = "xgene-msi", > +}; > + > +static int xgene_msi_host_map(struct irq_domain *h, unsigned int virq, > + irq_hw_number_t hw) > +{ > + struct xgene_msi *msi_data = h->host_data; > + struct irq_chip *chip = &xgene_msi_chip; > + > + pr_debug("\nENTER %s, virq=%u\n", __func__, virq); > + irq_set_status_flags(virq, IRQ_TYPE_LEVEL_HIGH); > + irq_set_chip_data(virq, msi_data); > + irq_set_chip_and_handler(virq, chip, handle_level_irq); > + > + return 0; > +} > + > +static const struct irq_domain_ops xgene_msi_host_ops = { > + .map = xgene_msi_host_map, > +}; > + > +static int xgene_msi_init_allocator(struct xgene_msi *msi_data) > +{ > +#ifdef CONFIG_ACPI > + if (efi_enabled(EFI_BOOT)) > + return msi_bitmap_alloc(&msi_data->bitmap, NR_MSI_IRQS, NULL); > + else > +#endif > + return msi_bitmap_alloc(&msi_data->bitmap, NR_MSI_IRQS, > + msi_data->irqhost->of_node); > +} > + > +int arch_msi_check_device(struct pci_dev *dev, int nvec, int type) > +{ > + pr_debug("ENTER %s\n", __func__); > + return 0; > +} > + > +void arch_teardown_msi_irqs(struct pci_dev *dev) > +{ > + struct msi_desc *entry; > + struct xgene_msi *msi_data; > + > + pr_debug("ENTER %s\n", __func__); > + list_for_each_entry(entry, &dev->msi_list, list) { > + if (entry->irq == 0) > + continue; > + > + msi_data = irq_get_chip_data(entry->irq); > + irq_set_msi_desc(entry->irq, NULL); > + msi_bitmap_free_hwirqs(&msi_data->bitmap, > + virq_to_hw(entry->irq), 1); > + irq_dispose_mapping(entry->irq); > + } > +} > + > +static void xgene_compose_msi_msg(struct pci_dev *dev, int hwirq, > + struct msi_msg *msg, > + struct xgene_msi *msi_data) > +{ > + int reg_set, group; > + > + group = hwirq % NR_MSI_REG; > + reg_set = hwirq / (NR_MSI_REG * IRQS_PER_MSI_INDEX); > + > + pr_debug("group = %d, reg_set : 0x%x\n", group, reg_set); > + msg->address_lo = msi_data->msi_addr_lo + > + (((8 * group) + reg_set) << 16); > + msg->address_hi = msi_data->msi_addr_hi; > + msg->data = (hwirq / NR_MSI_REG) % IRQS_PER_MSI_INDEX; > + pr_debug("addr : 0x%08x, data : 0x%x\n", msg->address_lo, msg->data); > +} > + > +int arch_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type) > +{ > + struct device_node *np; > + struct msi_desc *entry; > + struct msi_msg msg; > + u64 gic_irq; > + unsigned int virq; > + struct xgene_msi *msi_data; > + phandle phandle = 0; > + int rc = 0; > + int hwirq = -1; > + > + pr_debug("ENTER %s - nvec = %d, type = %d\n", __func__, nvec, type); > +#ifdef CONFIG_ACPI > + if (!efi_enabled(EFI_BOOT)) > +#endif > + { > + np = pci_device_to_OF_node(pdev); > + /* > + * If the PCI node has an xgene,msi property, > + * then we need to use it to find the specific MSI. > + */ > + np = of_parse_phandle(np, "xgene,msi", 0); > + if (np) { > + if (of_device_is_compatible(np, > + "xgene,gic-msi-cascade")) > + phandle = np->phandle; > + else { > + dev_err(&pdev->dev, > + "node %s has an invalid xgene,msi phandle %u\n", > + np->full_name, np->phandle); > + rc = -EINVAL; > + goto exit; > + } > + } else > + dev_info(&pdev->dev, "Found no xgene,msi phandle\n"); > + } > + > + list_for_each_entry(entry, &pdev->msi_list, list) { > + pr_debug("Loop over MSI devices\n"); > + /* > + * Loop over all the MSI devices until we find one that has an > + * available interrupt. > + */ > + list_for_each_entry(msi_data, &msi_head, list) { > + if (phandle && (phandle != msi_data->phandle)) > + continue; > + pr_debug("Xgene msi pointer : %p\n", msi_data); > + hwirq = msi_bitmap_alloc_hwirqs(&msi_data->bitmap, 1); > + break; > + } > + > + if (hwirq < 0) { > + dev_err(&pdev->dev, > + "could not allocate MSI interrupt\n"); > + rc = -ENOSPC; > + goto exit; > + } > + > + virq = irq_create_mapping(msi_data->irqhost, hwirq); > + if (virq == 0) { > + dev_err(&pdev->dev, "fail mapping hwirq %i\n", hwirq); > + msi_bitmap_free_hwirqs(&msi_data->bitmap, hwirq, 1); > + rc = -ENOSPC; > + goto exit; > + } > + > + gic_irq = msi_data->msi_virqs[hwirq % NR_MSI_REG]; > + pr_debug("Created Mapping HWIRQ %d on GIC IRQ %llu " > + "TO VIRQ %d\n", > + hwirq, gic_irq, virq); > + /* chip_data is msi_data via host->hostdata in host->map() */ > + irq_set_msi_desc(virq, entry); > + xgene_compose_msi_msg(pdev, hwirq, &msg, msi_data); > + irq_set_handler_data(virq, (void *)gic_irq); > + write_msi_msg(virq, &msg); > + } > + > +exit: > + return rc; > +} > + > +static void xgene_msi_cascade(unsigned int irq, struct irq_desc *desc) > +{ > + struct irq_chip *chip = irq_desc_get_chip(desc); > + struct irq_data *idata = irq_desc_get_irq_data(desc); > + struct xgene_msi_cascade_data *cascade_data; > + struct xgene_msi *msi_data; > + unsigned int cascade_irq; > + int msir_index = -1; > + u32 msir_value = 0; > + u32 intr_index = 0; > + u32 msi_intr_reg_value = 0; > + u32 msi_intr_reg; > + > + pr_debug("\nENTER %s, irq=%u\n", __func__, irq); > + cascade_data = irq_get_handler_data(irq); > + msi_data = cascade_data->msi_data; > + pr_debug("xgene_msi : 0x%p, irq = %u\n", msi_data, irq); > + > + raw_spin_lock(&desc->lock); > + if (unlikely(irqd_irq_inprogress(idata))) > + goto unlock; > + > + msi_intr_reg = cascade_data->index; > + pr_debug("msi_intr_reg : %d\n", msi_intr_reg); > + > + if (msi_intr_reg >= NR_MSI_REG) > + cascade_irq = 0; > + > + irqd_set_chained_irq_inprogress(idata); > + > + switch (msi_data->feature & XGENE_PIC_IP_MASK) { > + case XGENE_PIC_IP_GIC: > + msi_intr_reg_value = xgene_msi_intr_read(msi_data->msi_regs, > + msi_intr_reg); > + pr_debug("msi_intr_reg_value : 0x%08x\n", msi_intr_reg_value); > + break; > + } > + > + while (msi_intr_reg_value) { > + msir_index = ffs(msi_intr_reg_value) - 1; > + pr_debug("msir_index : %d\n", msir_index); > + msir_value = xgene_msi_read(msi_data->msi_regs, msi_intr_reg, > + msir_index); > + while (msir_value) { > + intr_index = ffs(msir_value) - 1; > + pr_debug("intr_index : %d\n", intr_index); > + cascade_irq = irq_linear_revmap(msi_data->irqhost, > + msir_index * IRQS_PER_MSI_INDEX * NR_MSI_REG + > + intr_index * NR_MSI_REG + msi_intr_reg); > + pr_debug("cascade_irq : %d\n", cascade_irq); > + if (cascade_irq != 0) > + generic_handle_irq(cascade_irq); > + msir_value &= ~(1 << intr_index); > + pr_debug("msir_value : 0x%08x\n", msir_value); > + } > + msi_intr_reg_value &= ~(1 << msir_index); > + pr_debug("msi_intr_reg_value : 0x%08x\n", msi_intr_reg_value); > + } > + irqd_clr_chained_irq_inprogress(idata); > + > + switch (msi_data->feature & XGENE_PIC_IP_MASK) { > + case XGENE_PIC_IP_GIC: > + chip->irq_eoi(idata); > + break; > + } > + > +unlock: > + raw_spin_unlock(&desc->lock); > + pr_debug("EXIT\n"); > +} > + > +static int xgene_msi_remove(struct platform_device *pdev) > +{ > + int virq, i; > + struct xgene_msi *msi = platform_get_drvdata(pdev); > + > + pr_debug("ENTER %s\n", __func__); > + for (i = 0; i < NR_MSI_REG; i++) { > + virq = msi->msi_virqs[i]; > + if (virq != 0) > + irq_dispose_mapping(virq); > + } > + > + if (msi->bitmap.bitmap) > + msi_bitmap_free(&msi->bitmap); > + > + return 0; > +} > + > +static int xgene_msi_setup_hwirq(struct xgene_msi *msi, > + struct platform_device *pdev, > + int offset, int irq_index) > +{ > + int virt_msir; > + cpumask_var_t mask; > + struct xgene_msi_cascade_data *cascade_data = NULL; > + > + virt_msir = platform_get_irq(pdev, irq_index); > + if (virt_msir < 0) { > + dev_err(&pdev->dev, "Cannot translate IRQ index %d\n", > + irq_index); > + return -EINVAL; > + } > + > + cascade_data = devm_kzalloc(&pdev->dev, > + sizeof(struct xgene_msi_cascade_data), GFP_KERNEL); > + if (!cascade_data) { > + dev_err(&pdev->dev, "No memory for MSI cascade data\n"); > + return -ENOMEM; > + } > + > + if (alloc_cpumask_var(&mask, GFP_KERNEL)) { > + cpumask_setall(mask); > + irq_set_affinity(virt_msir, mask); > + free_cpumask_var(mask); > + } > + > + msi->msi_virqs[irq_index] = virt_msir; > + cascade_data->index = offset; > + cascade_data->msi_data = msi; > + irq_set_handler_data(virt_msir, cascade_data); > + irq_set_chained_handler(virt_msir, xgene_msi_cascade); > + pr_debug("mapped phys irq %d\n", virt_msir); > + > + return 0; > +} > + > +static int xgene_msi_get_param(struct platform_device *pdev, const char *name, > + u32 *buf, int count) > +{ > + int rc = 0; > + > +#ifdef CONFIG_ACPI > + if (efi_enabled(EFI_BOOT)) { > + struct acpi_dsm_entry entry; > + > + if (acpi_dsm_lookup_value(ACPI_HANDLE(&pdev->dev), > + name, 0, &entry) || !entry.value) > + return -EINVAL; > + > + if (count == 2) > + sscanf(entry.value, "%d %d", &buf[0], &buf[1]); > + else > + rc = -EINVAL; > + > + kfree(entry.key); > + kfree(entry.value); > + } else > +#endif > + if (of_property_read_u32_array(pdev->dev.of_node, name, > + buf, count)) > + rc = -EINVAL; > + > + return rc; > +} > + > +static int xgene_msi_probe(struct platform_device *pdev) > +{ > + struct xgene_msi *msi; > + struct resource *res; > + phys_addr_t msiir_offset; > + int rc, j, irq_index, count; > + u32 offset; > + u32 buf[] = { 0, NR_MSI_IRQS}; > + > + msi = devm_kzalloc(&pdev->dev, sizeof(struct xgene_msi), GFP_KERNEL); > + if (!msi) { > + dev_err(&pdev->dev, "No memory for MSI structure\n"); > + return -ENOMEM; > + } > + > + platform_set_drvdata(pdev, msi); > + > +#ifdef CONFIG_ACPI > + if (efi_enabled(EFI_BOOT)) > + msi->irqhost = irq_domain_add_linear(NULL, > + NR_MSI_IRQS, &xgene_msi_host_ops, msi); > + else > +#endif > + msi->irqhost = irq_domain_add_linear(pdev->dev.of_node, > + NR_MSI_IRQS, &xgene_msi_host_ops, msi); > + if (msi->irqhost == NULL) { > + dev_err(&pdev->dev, "No memory for MSI irqhost\n"); > + rc = -ENOMEM; > + goto error; > + } > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + msi->msi_regs = devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(msi->msi_regs)) { > + dev_err(&pdev->dev, "no reg space\n"); > + rc = -EINVAL; > + goto error; > + } > + > + pr_debug("mapped 0x%08llx to 0x%p Size : 0x%08llx\n", res->start, > + msi->msi_regs, resource_size(res)); > + > + msiir_offset = lower_32_bits(xgene_pcie_get_iof_addr(res->start)); > + msi->msiir_offset = gic_msi_feature.msiir_offset + > + (msiir_offset & 0xfffff); > + msi->msi_addr_hi = upper_32_bits(res->start); > + msi->msi_addr_lo = gic_msi_feature.msiir_offset + > + (msiir_offset & 0xffffffff); > + msi->feature = gic_msi_feature.xgene_pic_ip; > + > +#ifdef CONFIG_ACPI > + if (efi_enabled(EFI_BOOT)) > + msi->phandle = 0; > + else > +#endif > + msi->phandle = pdev->dev.of_node->phandle; > + > + rc = xgene_msi_init_allocator(msi); > + if (rc) { > + dev_err(&pdev->dev, "Error allocating MSI bitmap\n"); > + goto error; > + } > + > + rc = xgene_msi_get_param(pdev, "msi-available-ranges", buf, 2); > + if (rc) { > + dev_err(&pdev->dev, "Error getting MSI ranges\n"); > + goto error; > + } > + > + pr_debug("buf[0] = 0x%x buf[1] = 0x%x\n", buf[0], buf[1]); > + if (buf[0] % IRQS_PER_MSI_REG || buf[1] % IRQS_PER_MSI_REG) { > + pr_err_once("msi available range of" > + "%u at %u is not IRQ-aligned\n", > + buf[1], buf[0]); > + rc = -EINVAL; > + goto error; > + } > + > + offset = buf[0] / IRQS_PER_MSI_REG; > + count = buf[1] / IRQS_PER_MSI_REG; > + pr_debug("offset = %d count = %d\n", offset, count); > + > + for (irq_index = 0, j = 0; j < count; j++, irq_index++) { > + rc = xgene_msi_setup_hwirq(msi, pdev, offset + j, irq_index); > + if (rc) > + goto error; > + } > + > + list_add_tail(&msi->list, &msi_head); > + > + pr_info("XGene: PCIe MSI driver v%s\n", MSI_DRIVER_VERSION); > + > + return 0; > + > +error: > + xgene_msi_remove(pdev); > + return rc; > +} > + > +static const struct of_device_id xgene_msi_of_ids[] = { > + { > + .compatible = "xgene,gic-msi", > + }, > + {} > +}; > + > +#ifdef CONFIG_ACPI > +static const struct acpi_device_id xgene_msi_acpi_ids[] = { > + {"APMC0D0E", 0}, > + {}, > +}; > +#endif > + > +static struct platform_driver xgene_msi_driver = { > + .driver = { > + .name = "xgene-msi", > + .owner = THIS_MODULE, > + .of_match_table = xgene_msi_of_ids, > +#ifdef CONFIG_ACPI > + .acpi_match_table = ACPI_PTR(xgene_msi_acpi_ids), > +#endif > + }, > + .probe = xgene_msi_probe, > + .remove = xgene_msi_remove, > +}; > + > +static __init int xgene_msi_init(void) > +{ > + return platform_driver_register(&xgene_msi_driver); > +} > + > +subsys_initcall_sync(xgene_msi_init); > diff --git a/drivers/pci/host/pci-xgene.c b/drivers/pci/host/pci-xgene.c > index 03be990..b787e76 100644 > --- a/drivers/pci/host/pci-xgene.c > +++ b/drivers/pci/host/pci-xgene.c > @@ -665,7 +665,7 @@ static int pci_dma_range_parser_init(struct of_pci_range_parser *parser, > parser->pna = of_n_addr_cells(node); > parser->np = parser->pna + na + ns; > > - parser->range = of_get_property(node, "dma-ranges", &rlen); > + parser->range = of_get_property(node, "ib-ranges", &rlen); > if (!parser->range) > return -ENOENT; > > @@ -682,11 +682,11 @@ static int xgene_pcie_parse_map_dma_ranges(struct xgene_pcie_port *port) > u8 ib_reg_mask = 0; > > if (pci_dma_range_parser_init(&parser, np)) { > - dev_err(dev, "missing dma-ranges property\n"); > + dev_err(dev, "missing ib-ranges property\n"); > return -EINVAL; > } > > - /* Get the dma-ranges from DT */ > + /* Get the ib-ranges from DT */ > for_each_of_pci_range(&parser, &range) { > u64 end = range.cpu_addr + range.size - 1; > dev_dbg(port->dev, "0x%08x 0x%016llx..0x%016llx -> 0x%016llx\n", > -- > 1.9.1 > > > -- > kernel-team mailing list > kernel-team@lists.ubuntu.com > https://lists.ubuntu.com/mailman/listinfo/kernel-team Cheers, -- Luís
diff --git a/arch/arm64/boot/dts/apm-storm.dtsi b/arch/arm64/boot/dts/apm-storm.dtsi index aae496f..6f22fd8 100644 --- a/arch/arm64/boot/dts/apm-storm.dtsi +++ b/arch/arm64/boot/dts/apm-storm.dtsi @@ -333,6 +333,28 @@ }; }; + msi: msi@79000000 { + compatible = "xgene,gic-msi"; + reg = <0x00 0x79000000 0x0 0x900000>; + msi-available-ranges = <0x0 0x1000>; + interrupts = < 0x0 0x10 0x4 + 0x0 0x11 0x4 + 0x0 0x12 0x4 + 0x0 0x13 0x4 + 0x0 0x14 0x4 + 0x0 0x15 0x4 + 0x0 0x16 0x4 + 0x0 0x17 0x4 + 0x0 0x18 0x4 + 0x0 0x19 0x4 + 0x0 0x1a 0x4 + 0x0 0x1b 0x4 + 0x0 0x1c 0x4 + 0x0 0x1d 0x4 + 0x0 0x1e 0x4 + 0x0 0x1f 0x4>; + }; + pcie0: pcie@1f2b0000 { status = "disabled"; device_type = "pci"; @@ -345,7 +367,8 @@ reg-names = "csr", "cfg"; ranges = <0x01000000 0x00 0x00000000 0xe0 0x00000000 0x00 0x00010000 /* io */ 0x02000000 0x00 0x10000000 0xe0 0x10000000 0x00 0x80000000>; /* mem */ - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ interrupt-map-mask = <0x0 0x0 0x0 0x7>; interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xc2 0x1 0x0 0x0 0x0 0x2 &gic 0x0 0xc3 0x1 @@ -366,7 +389,8 @@ reg-names = "csr", "cfg"; ranges = <0x01000000 0x0 0x00000000 0xd0 0x00000000 0x00 0x00010000 /* io */ 0x02000000 0x0 0x10000000 0xd0 0x10000000 0x00 0x80000000>; /* mem */ - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ interrupt-map-mask = <0x0 0x0 0x0 0x7>; interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xc8 0x1 0x0 0x0 0x0 0x2 &gic 0x0 0xc9 0x1 @@ -387,7 +411,8 @@ reg-names = "csr", "cfg"; ranges = <0x01000000 0x0 0x00000000 0x90 0x00000000 0x0 0x00010000 /* io */ 0x02000000 0x0 0x10000000 0x90 0x10000000 0x0 0x80000000>; /* mem */ - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ interrupt-map-mask = <0x0 0x0 0x0 0x7>; interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xce 0x1 0x0 0x0 0x0 0x2 &gic 0x0 0xcf 0x1 @@ -406,9 +431,10 @@ reg = < 0x00 0x1f500000 0x0 0x00010000 /* Controller registers */ 0xa0 0xd0000000 0x0 0x00200000>; /* PCI config space */ reg-names = "csr", "cfg"; - ranges = <0x01000000 0x0 0x00000000 0xa0 0x00000000 0x0 0x00010000 /* mem */ - 0x02000000 0x0 0x10000000 0xa0 0x10000000 0x0 0x80000000>; /* io */ - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; + ranges = <0x01000000 0x0 0x00000000 0xa0 0x00000000 0x0 0x00010000 /* io */ + 0x02000000 0x0 0x10000000 0xa0 0x10000000 0x0 0x80000000>; /* mem */ + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ interrupt-map-mask = <0x0 0x0 0x0 0x7>; interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xd4 0x1 0x0 0x0 0x0 0x2 &gic 0x0 0xd5 0x1 @@ -429,7 +455,8 @@ reg-names = "csr", "cfg"; ranges = <0x01000000 0x0 0x00000000 0xc0 0x00000000 0x0 0x00010000 /* io */ 0x02000000 0x0 0x10000000 0xc0 0x10000000 0x0 0x80000000>; /* mem */ - dma-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000>; + ib-ranges = <0x42000000 0x40 0x00000000 0x40 0x00000000 0x40 0x00000000 /* dma */ + 0x00000000 0x0 0x79000000 0x00 0x79000000 0x0 0x00800000>; /* msi */ interrupt-map-mask = <0x0 0x0 0x0 0x7>; interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xda 0x1 0x0 0x0 0x0 0x2 &gic 0x0 0xdb 0x1 diff --git a/arch/arm64/include/asm/msi_bitmap.h b/arch/arm64/include/asm/msi_bitmap.h new file mode 100644 index 0000000..dfaa256 --- /dev/null +++ b/arch/arm64/include/asm/msi_bitmap.h @@ -0,0 +1,36 @@ +#ifndef MSI_BITMAP_H +#define MSI_BITMAP_H + +/* + * Copyright 2008, Michael Ellerman, IBM Corporation. + * + * 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; version 2 of the + * License. + * + * Borrowed from powerpc arch + */ + +#include <linux/of.h> +#include <asm/irq.h> + +struct msi_bitmap { + struct device_node *of_node; + unsigned long *bitmap; + spinlock_t lock; + unsigned int irq_count; +}; + +int msi_bitmap_alloc_hwirqs(struct msi_bitmap *bmp, int num); +void msi_bitmap_free_hwirqs(struct msi_bitmap *bmp, unsigned int offset, + unsigned int num); +void msi_bitmap_reserve_hwirq(struct msi_bitmap *bmp, unsigned int hwirq); + +int msi_bitmap_reserve_dt_hwirqs(struct msi_bitmap *bmp); + +int msi_bitmap_alloc(struct msi_bitmap *bmp, unsigned int irq_count, + struct device_node *of_node); +void msi_bitmap_free(struct msi_bitmap *bmp); + +#endif /* MSI_BITMAP_H */ diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index 9e9c208..69900a2 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -20,6 +20,7 @@ arm64-obj-$(CONFIG_HAVE_HW_BREAKPOINT)+= hw_breakpoint.o arm64-obj-$(CONFIG_EARLY_PRINTK) += early_printk.o obj-$(CONFIG_PCI) += pcibios.o +obj-$(CONFIG_PCI_MSI) += msi_bitmap.o obj-y += $(arm64-obj-y) vdso/ obj-m += $(arm64-obj-m) head-y := head.o diff --git a/arch/arm64/kernel/msi_bitmap.c b/arch/arm64/kernel/msi_bitmap.c new file mode 100644 index 0000000..5d851fe --- /dev/null +++ b/arch/arm64/kernel/msi_bitmap.c @@ -0,0 +1,134 @@ +/* + * Copyright 2006-2008, Michael Ellerman, IBM Corporation. + * + * 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; version 2 of the + * License. + * + * Borrowed from powerpc arch + */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/bitmap.h> +#include <asm/msi_bitmap.h> +#include <asm/setup.h> + +int msi_bitmap_alloc_hwirqs(struct msi_bitmap *bmp, int num) +{ + unsigned long flags; + int offset, order = get_count_order(num); + + spin_lock_irqsave(&bmp->lock, flags); + /* + * This is fast, but stricter than we need. We might want to add + * a fallback routine which does a linear search with no alignment. + */ + offset = bitmap_find_free_region(bmp->bitmap, bmp->irq_count, order); + spin_unlock_irqrestore(&bmp->lock, flags); + + pr_debug("msi_bitmap: allocated 0x%x (2^%d) at offset 0x%x\n", + num, order, offset); + + return offset; +} + +void msi_bitmap_free_hwirqs(struct msi_bitmap *bmp, unsigned int offset, + unsigned int num) +{ + unsigned long flags; + int order = get_count_order(num); + + pr_debug("msi_bitmap: freeing 0x%x (2^%d) at offset 0x%x\n", + num, order, offset); + + spin_lock_irqsave(&bmp->lock, flags); + bitmap_release_region(bmp->bitmap, offset, order); + spin_unlock_irqrestore(&bmp->lock, flags); +} + +void msi_bitmap_reserve_hwirq(struct msi_bitmap *bmp, unsigned int hwirq) +{ + unsigned long flags; + + pr_debug("msi_bitmap: reserving hwirq 0x%x\n", hwirq); + + spin_lock_irqsave(&bmp->lock, flags); + bitmap_allocate_region(bmp->bitmap, hwirq, 0); + spin_unlock_irqrestore(&bmp->lock, flags); +} + +/** + * msi_bitmap_reserve_dt_hwirqs - Reserve irqs specified in the device tree. + * @bmp: pointer to the MSI bitmap. + * + * Looks in the device tree to see if there is a property specifying which + * irqs can be used for MSI. If found those irqs reserved in the device tree + * are reserved in the bitmap. + * + * Returns 0 for success, < 0 if there was an error, and > 0 if no property + * was found in the device tree. + **/ +int msi_bitmap_reserve_dt_hwirqs(struct msi_bitmap *bmp) +{ + int i, j, len = 2; + u32 array[2]; + u32 *p = array; + int ret; + + if (!bmp->of_node) + return 1; + + ret = of_property_read_u32_array(bmp->of_node, + "msi-available-ranges", p, len); + if (ret) + return ret; + + bitmap_allocate_region(bmp->bitmap, 0, get_count_order(bmp->irq_count)); + + spin_lock(&bmp->lock); + + /* Format is: (<u32 start> <u32 count>)+ */ + len /= 2 * sizeof(u32); + for (i = 0; i < len; i++, p += 2) { + for (j = 0; j < *(p + 1); j++) + bitmap_release_region(bmp->bitmap, *p + j, 0); + } + + spin_unlock(&bmp->lock); + + return 0; +} + +int msi_bitmap_alloc(struct msi_bitmap *bmp, unsigned int irq_count, + struct device_node *of_node) +{ + int size; + + if (!irq_count) + return -EINVAL; + + size = BITS_TO_LONGS(irq_count) * sizeof(long); + pr_debug("msi_bitmap: allocator bitmap size is 0x%x bytes\n", size); + + bmp->bitmap = kzalloc(size, GFP_KERNEL); + if (!bmp->bitmap) { + pr_debug("msi_bitmap: ENOMEM allocating allocator bitmap!\n"); + return -ENOMEM; + } + + /* We zalloc'ed the bitmap, so all irqs are free by default */ + spin_lock_init(&bmp->lock); + bmp->of_node = of_node_get(of_node); + bmp->irq_count = irq_count; + + return 0; +} + +void msi_bitmap_free(struct msi_bitmap *bmp) +{ + /* we can't free the bitmap we don't know if it's bootmem etc. */ + of_node_put(bmp->of_node); + bmp->bitmap = NULL; +} diff --git a/debian.master/config/config.common.ubuntu b/debian.master/config/config.common.ubuntu index b44d8a2..2f785a5 100644 --- a/debian.master/config/config.common.ubuntu +++ b/debian.master/config/config.common.ubuntu @@ -4819,6 +4819,7 @@ CONFIG_PCI_STUB=m CONFIG_PCI_TEGRA=y CONFIG_PCI_XEN=y CONFIG_PCI_XGENE=y +CONFIG_PCI_XGENE_MSI=y CONFIG_PCMCIA=m CONFIG_PCMCIA_3C574=m CONFIG_PCMCIA_3C589=m diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 19ce97d..a7d746a 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -33,11 +33,15 @@ config PCI_RCAR_GEN2 There are 3 internal PCI controllers available with a single built-in EHCI/OHCI host controller present on each one. +config PCI_XGENE_MSI + bool + config PCI_XGENE bool "X-Gene PCIe controller" depends on ARCH_XGENE depends on OF select PCIEPORTBUS + select PCI_XGENE_MSI if PCI_MSI help Say Y here if you want internal PCI support on APM X-Gene SoC. There are 5 internal PCIe ports available. Each port is GEN3 capable diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 34c7c36..ddac195 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o obj-$(CONFIG_PCI_XGENE) += pci-xgene.o +obj-$(CONFIG_PCI_XGENE_MSI) += pci-xgene-msi.o diff --git a/drivers/pci/host/pci-xgene-msi.c b/drivers/pci/host/pci-xgene-msi.c new file mode 100644 index 0000000..f21c459 --- /dev/null +++ b/drivers/pci/host/pci-xgene-msi.c @@ -0,0 +1,593 @@ +/* + * XGene MSI Driver + * + * Copyright (c) 2010, Applied Micro Circuits Corporation + * Author: Tanmay Inamdar <tinamdar@apm.com> + * Tuan Phan <tphan@apm.com> + * Jim Hull <jim.hull@hp.com> + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + */ +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/bootmem.h> +#include <linux/msi.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/export.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <linux/acpi.h> +#include <linux/efi.h> +#include <asm/hw_irq.h> +#include <asm/io.h> +#include <asm/msi_bitmap.h> + +#define NR_MSI_REG 16 +#define IRQS_PER_MSI_INDEX 32 +#define IRQS_PER_MSI_REG 256 +#define NR_MSI_IRQS (NR_MSI_REG * IRQS_PER_MSI_REG) + +#define XGENE_PIC_IP_MASK 0x0000000F +#define XGENE_PIC_IP_GIC 0x00000001 + +/* PCIe MSI Index Registers */ +#define MSI0IR0 0x000000 +#define MSIFIR7 0x7F0000 + +/* PCIe MSI Interrupt Register */ +#define MSI1INT0 0x800000 +#define MSI1INTF 0x8F0000 + +struct xgene_msi { + struct irq_domain *irqhost; + unsigned long cascade_irq; + u32 msiir_offset; + u32 msi_addr_lo; + u32 msi_addr_hi; + void __iomem *msi_regs; + u32 feature; + int msi_virqs[NR_MSI_REG]; + struct msi_bitmap bitmap; + struct list_head list; /* support multiple MSI banks */ + phandle phandle; +}; + +#ifdef CONFIG_ARCH_MSLIM +static inline u64 xgene_pcie_get_iof_addr(u64 addr) +{ + return mslim_pa_to_iof_axi(lower_32_bits(addr)); +} +#else +#define xgene_pcie_get_iof_addr(addr) addr +#endif + +#define MSI_DRIVER_VERSION "0.1" + +struct xgene_msi_feature { + u32 xgene_pic_ip; + u32 msiir_offset; /* Offset of MSIIR, relative to start of MSIR bank */ +}; + +struct xgene_msi_cascade_data { + struct xgene_msi *msi_data; + int index; +}; + +LIST_HEAD(msi_head); + +static const struct xgene_msi_feature gic_msi_feature = { + .xgene_pic_ip = XGENE_PIC_IP_GIC, + .msiir_offset = 0, +}; + +irq_hw_number_t virq_to_hw(unsigned int virq) +{ + struct irq_data *irq_data = irq_get_irq_data(virq); + return WARN_ON(!irq_data) ? 0 : irq_data->hwirq; +} + +static inline u32 xgene_msi_intr_read(phys_addr_t __iomem *base, + unsigned int reg) +{ + u32 irq_reg = MSI1INT0 + (reg << 16); + pr_debug("base = %p irq_reg = 0x%x , reg = 0x%x\n", base, irq_reg, reg); + return readl((void *)((phys_addr_t) base + irq_reg)); +} + +static inline u32 xgene_msi_read(phys_addr_t __iomem *base, unsigned int group, + unsigned int reg) +{ + u32 irq_reg = MSI0IR0 + (group << 19) + (reg << 16); + pr_debug("base %p irq_reg 0x%x, group 0x%x, reg 0x%x\n", + base, irq_reg, group, reg); + return readl((void *)((phys_addr_t) base + irq_reg)); +} + +/* + * We do not need this actually. The MSIR register has been read once + * in the cascade interrupt. So, this MSI interrupt has been acked +*/ +static void xgene_msi_end_irq(struct irq_data *d) +{ +} + +#ifdef CONFIG_SMP +static int xgene_msi_set_affinity(struct irq_data *d, + const struct cpumask *mask_val, bool force) +{ + u64 virt_msir; + + virt_msir = (u64)irq_get_handler_data(d->irq); + return irq_set_affinity(virt_msir, mask_val); +} +#endif + +static struct irq_chip xgene_msi_chip = { + .irq_mask = mask_msi_irq, + .irq_unmask = unmask_msi_irq, + .irq_ack = xgene_msi_end_irq, +#ifdef CONFIG_SMP + .irq_set_affinity = xgene_msi_set_affinity, +#endif + .name = "xgene-msi", +}; + +static int xgene_msi_host_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw) +{ + struct xgene_msi *msi_data = h->host_data; + struct irq_chip *chip = &xgene_msi_chip; + + pr_debug("\nENTER %s, virq=%u\n", __func__, virq); + irq_set_status_flags(virq, IRQ_TYPE_LEVEL_HIGH); + irq_set_chip_data(virq, msi_data); + irq_set_chip_and_handler(virq, chip, handle_level_irq); + + return 0; +} + +static const struct irq_domain_ops xgene_msi_host_ops = { + .map = xgene_msi_host_map, +}; + +static int xgene_msi_init_allocator(struct xgene_msi *msi_data) +{ +#ifdef CONFIG_ACPI + if (efi_enabled(EFI_BOOT)) + return msi_bitmap_alloc(&msi_data->bitmap, NR_MSI_IRQS, NULL); + else +#endif + return msi_bitmap_alloc(&msi_data->bitmap, NR_MSI_IRQS, + msi_data->irqhost->of_node); +} + +int arch_msi_check_device(struct pci_dev *dev, int nvec, int type) +{ + pr_debug("ENTER %s\n", __func__); + return 0; +} + +void arch_teardown_msi_irqs(struct pci_dev *dev) +{ + struct msi_desc *entry; + struct xgene_msi *msi_data; + + pr_debug("ENTER %s\n", __func__); + list_for_each_entry(entry, &dev->msi_list, list) { + if (entry->irq == 0) + continue; + + msi_data = irq_get_chip_data(entry->irq); + irq_set_msi_desc(entry->irq, NULL); + msi_bitmap_free_hwirqs(&msi_data->bitmap, + virq_to_hw(entry->irq), 1); + irq_dispose_mapping(entry->irq); + } +} + +static void xgene_compose_msi_msg(struct pci_dev *dev, int hwirq, + struct msi_msg *msg, + struct xgene_msi *msi_data) +{ + int reg_set, group; + + group = hwirq % NR_MSI_REG; + reg_set = hwirq / (NR_MSI_REG * IRQS_PER_MSI_INDEX); + + pr_debug("group = %d, reg_set : 0x%x\n", group, reg_set); + msg->address_lo = msi_data->msi_addr_lo + + (((8 * group) + reg_set) << 16); + msg->address_hi = msi_data->msi_addr_hi; + msg->data = (hwirq / NR_MSI_REG) % IRQS_PER_MSI_INDEX; + pr_debug("addr : 0x%08x, data : 0x%x\n", msg->address_lo, msg->data); +} + +int arch_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type) +{ + struct device_node *np; + struct msi_desc *entry; + struct msi_msg msg; + u64 gic_irq; + unsigned int virq; + struct xgene_msi *msi_data; + phandle phandle = 0; + int rc = 0; + int hwirq = -1; + + pr_debug("ENTER %s - nvec = %d, type = %d\n", __func__, nvec, type); +#ifdef CONFIG_ACPI + if (!efi_enabled(EFI_BOOT)) +#endif + { + np = pci_device_to_OF_node(pdev); + /* + * If the PCI node has an xgene,msi property, + * then we need to use it to find the specific MSI. + */ + np = of_parse_phandle(np, "xgene,msi", 0); + if (np) { + if (of_device_is_compatible(np, + "xgene,gic-msi-cascade")) + phandle = np->phandle; + else { + dev_err(&pdev->dev, + "node %s has an invalid xgene,msi phandle %u\n", + np->full_name, np->phandle); + rc = -EINVAL; + goto exit; + } + } else + dev_info(&pdev->dev, "Found no xgene,msi phandle\n"); + } + + list_for_each_entry(entry, &pdev->msi_list, list) { + pr_debug("Loop over MSI devices\n"); + /* + * Loop over all the MSI devices until we find one that has an + * available interrupt. + */ + list_for_each_entry(msi_data, &msi_head, list) { + if (phandle && (phandle != msi_data->phandle)) + continue; + pr_debug("Xgene msi pointer : %p\n", msi_data); + hwirq = msi_bitmap_alloc_hwirqs(&msi_data->bitmap, 1); + break; + } + + if (hwirq < 0) { + dev_err(&pdev->dev, + "could not allocate MSI interrupt\n"); + rc = -ENOSPC; + goto exit; + } + + virq = irq_create_mapping(msi_data->irqhost, hwirq); + if (virq == 0) { + dev_err(&pdev->dev, "fail mapping hwirq %i\n", hwirq); + msi_bitmap_free_hwirqs(&msi_data->bitmap, hwirq, 1); + rc = -ENOSPC; + goto exit; + } + + gic_irq = msi_data->msi_virqs[hwirq % NR_MSI_REG]; + pr_debug("Created Mapping HWIRQ %d on GIC IRQ %llu " + "TO VIRQ %d\n", + hwirq, gic_irq, virq); + /* chip_data is msi_data via host->hostdata in host->map() */ + irq_set_msi_desc(virq, entry); + xgene_compose_msi_msg(pdev, hwirq, &msg, msi_data); + irq_set_handler_data(virq, (void *)gic_irq); + write_msi_msg(virq, &msg); + } + +exit: + return rc; +} + +static void xgene_msi_cascade(unsigned int irq, struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct irq_data *idata = irq_desc_get_irq_data(desc); + struct xgene_msi_cascade_data *cascade_data; + struct xgene_msi *msi_data; + unsigned int cascade_irq; + int msir_index = -1; + u32 msir_value = 0; + u32 intr_index = 0; + u32 msi_intr_reg_value = 0; + u32 msi_intr_reg; + + pr_debug("\nENTER %s, irq=%u\n", __func__, irq); + cascade_data = irq_get_handler_data(irq); + msi_data = cascade_data->msi_data; + pr_debug("xgene_msi : 0x%p, irq = %u\n", msi_data, irq); + + raw_spin_lock(&desc->lock); + if (unlikely(irqd_irq_inprogress(idata))) + goto unlock; + + msi_intr_reg = cascade_data->index; + pr_debug("msi_intr_reg : %d\n", msi_intr_reg); + + if (msi_intr_reg >= NR_MSI_REG) + cascade_irq = 0; + + irqd_set_chained_irq_inprogress(idata); + + switch (msi_data->feature & XGENE_PIC_IP_MASK) { + case XGENE_PIC_IP_GIC: + msi_intr_reg_value = xgene_msi_intr_read(msi_data->msi_regs, + msi_intr_reg); + pr_debug("msi_intr_reg_value : 0x%08x\n", msi_intr_reg_value); + break; + } + + while (msi_intr_reg_value) { + msir_index = ffs(msi_intr_reg_value) - 1; + pr_debug("msir_index : %d\n", msir_index); + msir_value = xgene_msi_read(msi_data->msi_regs, msi_intr_reg, + msir_index); + while (msir_value) { + intr_index = ffs(msir_value) - 1; + pr_debug("intr_index : %d\n", intr_index); + cascade_irq = irq_linear_revmap(msi_data->irqhost, + msir_index * IRQS_PER_MSI_INDEX * NR_MSI_REG + + intr_index * NR_MSI_REG + msi_intr_reg); + pr_debug("cascade_irq : %d\n", cascade_irq); + if (cascade_irq != 0) + generic_handle_irq(cascade_irq); + msir_value &= ~(1 << intr_index); + pr_debug("msir_value : 0x%08x\n", msir_value); + } + msi_intr_reg_value &= ~(1 << msir_index); + pr_debug("msi_intr_reg_value : 0x%08x\n", msi_intr_reg_value); + } + irqd_clr_chained_irq_inprogress(idata); + + switch (msi_data->feature & XGENE_PIC_IP_MASK) { + case XGENE_PIC_IP_GIC: + chip->irq_eoi(idata); + break; + } + +unlock: + raw_spin_unlock(&desc->lock); + pr_debug("EXIT\n"); +} + +static int xgene_msi_remove(struct platform_device *pdev) +{ + int virq, i; + struct xgene_msi *msi = platform_get_drvdata(pdev); + + pr_debug("ENTER %s\n", __func__); + for (i = 0; i < NR_MSI_REG; i++) { + virq = msi->msi_virqs[i]; + if (virq != 0) + irq_dispose_mapping(virq); + } + + if (msi->bitmap.bitmap) + msi_bitmap_free(&msi->bitmap); + + return 0; +} + +static int xgene_msi_setup_hwirq(struct xgene_msi *msi, + struct platform_device *pdev, + int offset, int irq_index) +{ + int virt_msir; + cpumask_var_t mask; + struct xgene_msi_cascade_data *cascade_data = NULL; + + virt_msir = platform_get_irq(pdev, irq_index); + if (virt_msir < 0) { + dev_err(&pdev->dev, "Cannot translate IRQ index %d\n", + irq_index); + return -EINVAL; + } + + cascade_data = devm_kzalloc(&pdev->dev, + sizeof(struct xgene_msi_cascade_data), GFP_KERNEL); + if (!cascade_data) { + dev_err(&pdev->dev, "No memory for MSI cascade data\n"); + return -ENOMEM; + } + + if (alloc_cpumask_var(&mask, GFP_KERNEL)) { + cpumask_setall(mask); + irq_set_affinity(virt_msir, mask); + free_cpumask_var(mask); + } + + msi->msi_virqs[irq_index] = virt_msir; + cascade_data->index = offset; + cascade_data->msi_data = msi; + irq_set_handler_data(virt_msir, cascade_data); + irq_set_chained_handler(virt_msir, xgene_msi_cascade); + pr_debug("mapped phys irq %d\n", virt_msir); + + return 0; +} + +static int xgene_msi_get_param(struct platform_device *pdev, const char *name, + u32 *buf, int count) +{ + int rc = 0; + +#ifdef CONFIG_ACPI + if (efi_enabled(EFI_BOOT)) { + struct acpi_dsm_entry entry; + + if (acpi_dsm_lookup_value(ACPI_HANDLE(&pdev->dev), + name, 0, &entry) || !entry.value) + return -EINVAL; + + if (count == 2) + sscanf(entry.value, "%d %d", &buf[0], &buf[1]); + else + rc = -EINVAL; + + kfree(entry.key); + kfree(entry.value); + } else +#endif + if (of_property_read_u32_array(pdev->dev.of_node, name, + buf, count)) + rc = -EINVAL; + + return rc; +} + +static int xgene_msi_probe(struct platform_device *pdev) +{ + struct xgene_msi *msi; + struct resource *res; + phys_addr_t msiir_offset; + int rc, j, irq_index, count; + u32 offset; + u32 buf[] = { 0, NR_MSI_IRQS}; + + msi = devm_kzalloc(&pdev->dev, sizeof(struct xgene_msi), GFP_KERNEL); + if (!msi) { + dev_err(&pdev->dev, "No memory for MSI structure\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, msi); + +#ifdef CONFIG_ACPI + if (efi_enabled(EFI_BOOT)) + msi->irqhost = irq_domain_add_linear(NULL, + NR_MSI_IRQS, &xgene_msi_host_ops, msi); + else +#endif + msi->irqhost = irq_domain_add_linear(pdev->dev.of_node, + NR_MSI_IRQS, &xgene_msi_host_ops, msi); + if (msi->irqhost == NULL) { + dev_err(&pdev->dev, "No memory for MSI irqhost\n"); + rc = -ENOMEM; + goto error; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + msi->msi_regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(msi->msi_regs)) { + dev_err(&pdev->dev, "no reg space\n"); + rc = -EINVAL; + goto error; + } + + pr_debug("mapped 0x%08llx to 0x%p Size : 0x%08llx\n", res->start, + msi->msi_regs, resource_size(res)); + + msiir_offset = lower_32_bits(xgene_pcie_get_iof_addr(res->start)); + msi->msiir_offset = gic_msi_feature.msiir_offset + + (msiir_offset & 0xfffff); + msi->msi_addr_hi = upper_32_bits(res->start); + msi->msi_addr_lo = gic_msi_feature.msiir_offset + + (msiir_offset & 0xffffffff); + msi->feature = gic_msi_feature.xgene_pic_ip; + +#ifdef CONFIG_ACPI + if (efi_enabled(EFI_BOOT)) + msi->phandle = 0; + else +#endif + msi->phandle = pdev->dev.of_node->phandle; + + rc = xgene_msi_init_allocator(msi); + if (rc) { + dev_err(&pdev->dev, "Error allocating MSI bitmap\n"); + goto error; + } + + rc = xgene_msi_get_param(pdev, "msi-available-ranges", buf, 2); + if (rc) { + dev_err(&pdev->dev, "Error getting MSI ranges\n"); + goto error; + } + + pr_debug("buf[0] = 0x%x buf[1] = 0x%x\n", buf[0], buf[1]); + if (buf[0] % IRQS_PER_MSI_REG || buf[1] % IRQS_PER_MSI_REG) { + pr_err_once("msi available range of" + "%u at %u is not IRQ-aligned\n", + buf[1], buf[0]); + rc = -EINVAL; + goto error; + } + + offset = buf[0] / IRQS_PER_MSI_REG; + count = buf[1] / IRQS_PER_MSI_REG; + pr_debug("offset = %d count = %d\n", offset, count); + + for (irq_index = 0, j = 0; j < count; j++, irq_index++) { + rc = xgene_msi_setup_hwirq(msi, pdev, offset + j, irq_index); + if (rc) + goto error; + } + + list_add_tail(&msi->list, &msi_head); + + pr_info("XGene: PCIe MSI driver v%s\n", MSI_DRIVER_VERSION); + + return 0; + +error: + xgene_msi_remove(pdev); + return rc; +} + +static const struct of_device_id xgene_msi_of_ids[] = { + { + .compatible = "xgene,gic-msi", + }, + {} +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id xgene_msi_acpi_ids[] = { + {"APMC0D0E", 0}, + {}, +}; +#endif + +static struct platform_driver xgene_msi_driver = { + .driver = { + .name = "xgene-msi", + .owner = THIS_MODULE, + .of_match_table = xgene_msi_of_ids, +#ifdef CONFIG_ACPI + .acpi_match_table = ACPI_PTR(xgene_msi_acpi_ids), +#endif + }, + .probe = xgene_msi_probe, + .remove = xgene_msi_remove, +}; + +static __init int xgene_msi_init(void) +{ + return platform_driver_register(&xgene_msi_driver); +} + +subsys_initcall_sync(xgene_msi_init); diff --git a/drivers/pci/host/pci-xgene.c b/drivers/pci/host/pci-xgene.c index 03be990..b787e76 100644 --- a/drivers/pci/host/pci-xgene.c +++ b/drivers/pci/host/pci-xgene.c @@ -665,7 +665,7 @@ static int pci_dma_range_parser_init(struct of_pci_range_parser *parser, parser->pna = of_n_addr_cells(node); parser->np = parser->pna + na + ns; - parser->range = of_get_property(node, "dma-ranges", &rlen); + parser->range = of_get_property(node, "ib-ranges", &rlen); if (!parser->range) return -ENOENT; @@ -682,11 +682,11 @@ static int xgene_pcie_parse_map_dma_ranges(struct xgene_pcie_port *port) u8 ib_reg_mask = 0; if (pci_dma_range_parser_init(&parser, np)) { - dev_err(dev, "missing dma-ranges property\n"); + dev_err(dev, "missing ib-ranges property\n"); return -EINVAL; } - /* Get the dma-ranges from DT */ + /* Get the ib-ranges from DT */ for_each_of_pci_range(&parser, &range) { u64 end = range.cpu_addr + range.size - 1; dev_dbg(port->dev, "0x%08x 0x%016llx..0x%016llx -> 0x%016llx\n",