From: SangeethaRao <sangeetha....@lsi.com> Signed-off-by: SangeethaRao <sangeetha....@lsi.com> --- arch/powerpc/boot/dts/acp35xx.dts | 34 +++- arch/powerpc/sysdev/Kconfig | 11 +- arch/powerpc/sysdev/Makefile | 5 +- arch/powerpc/sysdev/lsi_msi.c | 389 ++++++++++++++++++++++++++++++++++++++ arch/powerpc/sysdev/lsi_msi.h | 21 ++ arch/powerpc/sysdev/lsi_pci.c | 52 +++-- 6 files changed, 488 insertions(+), 24 deletions(-) create mode 100644 arch/powerpc/sysdev/lsi_msi.c create mode 100644 arch/powerpc/sysdev/lsi_msi.h
diff --git a/arch/powerpc/boot/dts/acp35xx.dts b/arch/powerpc/boot/dts/acp35xx.dts index ec4fc75..1a5a33e 100644 --- a/arch/powerpc/boot/dts/acp35xx.dts +++ b/arch/powerpc/boot/dts/acp35xx.dts @@ -307,6 +307,39 @@ sbb-reg = <0x00500000 0x8000>; tzc-reg = <0x00541000 0x1000>; }; + MSI0: msi0 { + compatible = "lsi,msi"; + device_type = "lsi_msi"; + status = "ok"; + enabled = <0x1>; + port = <0>; + #address-cells = <2>; + #size-cells = <1>; + /* config space access MPAGE7 registers*/ + reg = <0x580000 0x2000>; + }; + MSI1: msi1 { + compatible = "lsi,msi"; + device_type = "lsi_msi"; + status = "ok"; + enabled = <0x1>; + port = <1>; + #address-cells = <2>; + #size-cells = <1>; + /* config space access MPAGE7 registers*/ + reg = <0x588000 0x2000>; + }; + MSI2: msi2 { + compatible = "lsi,msi"; + device_type = "lsi_msi"; + status = "ok"; + enabled = <0x1>; + port = <2>; + #address-cells = <2>; + #size-cells = <1>; + /* config space access MPAGE7 registers*/ + reg = <0x590000 0x2000>; + }; }; }; @@ -361,7 +394,6 @@ 0000 0 0 4 &MPIC 52 >; }; - PCIE1: pei1 { compatible = "lsi,plb-pciex"; device_type = "pci"; diff --git a/arch/powerpc/sysdev/Kconfig b/arch/powerpc/sysdev/Kconfig index 1ea267b..ded32ef 100644 --- a/arch/powerpc/sysdev/Kconfig +++ b/arch/powerpc/sysdev/Kconfig @@ -3,10 +3,17 @@ # config LSI_PPC_PCI - bool "LSI ACP34XX PCIe support" + bool "LSI ACP34XX/AXM35XX PCIe support" default y help - PCIe driver support for LSI's ACP34xx + PCIe driver support for LSI's ACP34xx/AXM35xx + +config LSI_PPC_PCI_MSI + bool "LSI ACP34XX/AXM35XX PCIe MSI support" + depends on PCI_MSI + default y + help + PCIe MSI driver support for LSI's ACP34xx/AXM35xx config PPC4xx_PCI_EXPRESS bool diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile index b46b432..63dea41 100644 --- a/arch/powerpc/sysdev/Makefile +++ b/arch/powerpc/sysdev/Makefile @@ -1,4 +1,4 @@ -subdir-ccflags-$(CONFIG_PPC_WERROR) := -Werror +#subdir-ccflags-$(CONFIG_PPC_WERROR) := -Werror ccflags-$(CONFIG_PPC64) := -mno-minimal-toc @@ -41,6 +41,7 @@ obj-$(CONFIG_4xx_SOC) += ppc4xx_soc.o obj-$(CONFIG_XILINX_VIRTEX) += xilinx_intc.o obj-$(CONFIG_XILINX_PCI) += xilinx_pci.o obj-$(CONFIG_LSI_PPC_PCI) += lsi_pci.o +obj-$(CONFIG_LSI_PPC_PCI_MSI) += lsi_msi.o obj-$(CONFIG_OF_RTC) += of_rtc.o ifeq ($(CONFIG_PCI),y) ifeq ($(CONFIG_LSI_PCI),n) @@ -67,7 +68,7 @@ endif obj-$(CONFIG_PPC_SCOM) += scom.o -subdir-ccflags-$(CONFIG_PPC_WERROR) := -Werror +#subdir-ccflags-$(CONFIG_PPC_WERROR) := -Werror obj-$(CONFIG_PPC_XICS) += xics/ diff --git a/arch/powerpc/sysdev/lsi_msi.c b/arch/powerpc/sysdev/lsi_msi.c new file mode 100644 index 0000000..f89ba7a --- /dev/null +++ b/arch/powerpc/sysdev/lsi_msi.c @@ -0,0 +1,389 @@ +/* +* LSI specific PCI-Express MSI support; +*/ + +#undef DEBUG + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/bootmem.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/module.h> + +#include <linux/io.h> +#include <asm/pci-bridge.h> +#include <asm/machdep.h> +#include <asm/dcr.h> +#include <asm/dcr-regs.h> +#include <mm/mmu_decl.h> + +#include "../../drivers/misc/lsi-ncr.h" +#include "lsi_msi.h" + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/bitmap.h> +#include <linux/msi.h> +#include <linux/of_platform.h> +#include <asm/prom.h> +#include <asm/hw_irq.h> +#include <asm/ppc-pci.h> + +#define MAX_NUMBER_PCI_SLOTS 3 +static struct acp_pci_msi *acp_pci_msi[MAX_NUMBER_PCI_SLOTS]; + +static void acp_pci_msi_end_irq(struct irq_data *d) +{ +} + +static struct irq_chip acp_pci_msi_chip = { + .irq_mask = mask_msi_irq, + .irq_unmask = unmask_msi_irq, + .irq_ack = acp_pci_msi_end_irq, + .name = " ACP-MSI ", +}; + +static int acp_pci_msi_host_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw) +{ + struct irq_chip *chip = &acp_pci_msi_chip; + irq_set_status_flags(virq, IRQ_TYPE_EDGE_FALLING); + irq_set_chip_and_handler(virq, chip, handle_edge_irq); + return 0; +} + +static struct irq_domain_ops acp_pci_msi_host_ops = { + .map = acp_pci_msi_host_map, +}; + +static void acp_pci_msi_free_hwirqs(struct acp_pci_msi *msi, int offset, + int num) +{ + unsigned long flags; + int order = get_count_order(num); + + pr_debug("%s: freeing 0x%x (2^%d) at offset 0x%x\n", + __func__, num, order, offset); + + spin_lock_irqsave(&msi->bitmap_lock, flags); + bitmap_release_region(msi->acp_pci_msi_bitmap, offset, order); + spin_unlock_irqrestore(&msi->bitmap_lock, flags); +} + +static int acp_pci_msi_free_dt_hwirqs(struct acp_pci_msi *msi_data) +{ + bitmap_allocate_region(msi_data->acp_pci_msi_bitmap, 0, + get_count_order(NR_MSI_IRQS)); + + acp_pci_msi_free_hwirqs(msi_data, 0, 0x100); + return 0; +} + +static int acp_pci_msi_init_allocator(struct acp_pci_msi *msi_data) +{ + int rc; + int size = BITS_TO_LONGS(NR_MSI_IRQS) * sizeof(u32); + + msi_data->acp_pci_msi_bitmap = kzalloc(size, GFP_KERNEL); + if (msi_data->acp_pci_msi_bitmap == NULL) { + pr_debug("%s: out of memory at line %d\n", + __func__, __LINE__); + return -ENOMEM; + } + + rc = acp_pci_msi_free_dt_hwirqs(msi_data); + if (rc) + goto out_free; + + return 0; +out_free: + kfree(msi_data->acp_pci_msi_bitmap); + msi_data->acp_pci_msi_bitmap = NULL; + return rc; +} + +static int acp_pci_msi_hw_init(struct acp_pci_msi *msi, + struct platform_device *pdev) +{ + u32 int_enable; + u32 msg_phys_adrs; + int rc, i; + + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)) != 0) { + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)) != 0) { + dev_err(&pdev->dev, "No memory for MSI table\n"); + rc = -ENOMEM; + return rc; + } else { + dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + } + } else { + dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64)); + } + + msi->msg_buf = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, + &msi->msg_buf_dma, GFP_KERNEL); + if (!msi->msg_buf) { + pr_debug("%s: out of memory at line %d\n", __func__, __LINE__); + rc = -ENOMEM; + return rc; + } + + msi->msi_addr_hi = 0; + /* Align address to 1K */ + if (msi->msg_buf_dma & 0x3f) + msi->msg_buf_dma = (msi->msg_buf_dma + 0x400) & 0xfffffc00; + msi->msi_addr_lo = msi->msg_buf_dma; + + /* Set MSI buffer address. */ + msg_phys_adrs = msi->msg_buf_dma & 0x0fffffff; + + pr_debug("%s: MSI buffer address = x%x\n", __func__, msg_phys_adrs); + printk(KERN_INFO "%s: MSI buffer address = x%x\n", __func__, + msg_phys_adrs); + out_le32(msi->utl_base + 0x1190, (msg_phys_adrs) >> 10); + + /* Enable MSI interrupts. */ + int_enable = in_le32(msi->utl_base + 0x10C4); + int_enable |= (1 << 31); + out_le32(msi->utl_base + 0x10C4, int_enable); + out_le32(msi->utl_base + 0x1234, 0xffff); + for (i = 0; i < 16; i++) + out_le32(msi->utl_base + 0x1240 + (i*0xc), 0xffff); + + return 0; +} + +static int acp_pci_msi_setup_irqs(struct pci_dev *pdev, int nvec, int type); +static void acp_pci_msi_teardown_irqs(struct pci_dev *pdev); +static int acp_pci_msi_check_device(struct pci_dev *pdev, int nvec, int type); + +static int acp_pci_msi_init(struct platform_device *pdev, int port) +{ + int rc; + struct acp_pci_msi *msi; + + msi = acp_pci_msi[port]; + + msi->irqhost = irq_domain_add_linear(msi->node, + NR_MSI_IRQS, &acp_pci_msi_host_ops, 0); + + if (msi->irqhost == NULL) { + pr_debug("%s: out of memory at line %d\n", + __func__, __LINE__); + rc = -ENOMEM; + goto error_out; + } + + rc = acp_pci_msi_init_allocator(msi); + if (rc) + goto error_out; + + rc = acp_pci_msi_hw_init(msi, pdev); + if (rc != 0) + goto error_out; + + spin_lock_init(&msi->bitmap_lock); + acp_pci_msi[port] = msi; + ppc_md.setup_msi_irqs = acp_pci_msi_setup_irqs; + ppc_md.teardown_msi_irqs = acp_pci_msi_teardown_irqs; + ppc_md.msi_check_device = acp_pci_msi_check_device; + + return 0; + +error_out: + kfree(msi); + return rc; +} + +static int __devinit lsi_msi_probe(struct platform_device *pdev) +{ + + const u32 *pval; + int rc = 0; + struct device_node *np = pdev->dev.of_node; + struct acp_pci_msi *pci_msi; + u32 int_enable; + + pci_msi = kzalloc(sizeof(struct acp_pci_msi), GFP_KERNEL); + if (!pci_msi) { + rc = -ENOMEM; + return -1; + } + + pci_msi->node = of_node_get(np); + + /* Get the port number from the device-tree */ + pval = of_get_property(np, "port", NULL); + if (pval == NULL) { + printk(KERN_ERR "PCIE: Can't find port number for %s\n", + np->full_name); + return -1; + } + pci_msi->port = *pval; + + pci_msi->utl_base = of_iomap(np, 0); + if (!pci_msi->utl_base) { + printk(KERN_ERR "No memory for utl_base\n"); + return -1; + } + int_enable = in_le32(pci_msi->utl_base + 0x1004); + + acp_pci_msi[pci_msi->port] = pci_msi; + + acp_pci_msi_init(pdev, pci_msi->port); + + return 0; +} + +static int lsi_msi_remove(struct platform_device *pdev) +{ + platform_device_unregister(pdev); + return 0; +} + +int acp_pci_msi_irq_handle(int port) +{ + struct acp_pci_msi *msi = acp_pci_msi[port]; + u32 *msg = (u32 *) msi->msg_buf; + int rc = -1; + int i; + + /* + * Check each message slot. If the slot is not zero, + * then handle the associated virq. + */ + for (i = 0; i < 256; i++) { + if (msg[i] != 0) { + pr_debug("%s msg[%d] = x%x\n", __func__, i, msg[i]); + msg[i] = 0; + generic_handle_irq(i); + rc = 0; + } + } + /* Clear MSI interrupts */ + out_le32(msi->utl_base + 0x1230, 0xffff); + for (i = 0; i < 16; i++) + out_le32(msi->utl_base + 0x123c + (i*0xc), 0xffff); + return rc; +} +EXPORT_SYMBOL_GPL(acp_pci_msi_irq_handle); + +static irq_hw_number_t acp_pci_msi_alloc_hwirqs(struct acp_pci_msi *msi, + int num) +{ + unsigned long flags; + int order = get_count_order(num); + int offset; + + spin_lock_irqsave(&msi->bitmap_lock, flags); + offset = bitmap_find_free_region(msi->acp_pci_msi_bitmap, + NR_MSI_IRQS, order); + + spin_unlock_irqrestore(&msi->bitmap_lock, flags); + + pr_debug("%s: allocated 0x%x (2^%d) at offset 0x%x\n", + __func__, num, order, offset); + + return offset; +} + +static int acp_pci_msi_check_device(struct pci_dev *pdev, int nvec, int type) +{ + int rc = 0; + return rc; +} + +static void acp_pci_msi_teardown_irqs(struct pci_dev *pdev) +{ + struct msi_desc *entry; + struct pci_bus *bus = pdev->bus; + struct pci_controller *hose = (struct pci_controller *) bus->sysdata; + struct acp_pci_msi *msi_data = acp_pci_msi[hose->indirect_type]; + + list_for_each_entry(entry, &pdev->msi_list, list) { + if (entry->irq == NO_IRQ) + continue; + irq_set_msi_desc(entry->irq, NULL); + acp_pci_msi_free_hwirqs(msi_data, virq_to_hw(entry->irq), 1); + irq_dispose_mapping(entry->irq); + } + return; +} + +static int acp_pci_msi_setup_irqs(struct pci_dev *pdev, int nvec, int type) +{ + irq_hw_number_t hwirq; + int rc; + unsigned int virq; + struct msi_desc *entry; + struct msi_msg msg; + struct pci_bus *bus = pdev->bus; + struct pci_controller *hose = (struct pci_controller *) bus->sysdata; + struct acp_pci_msi *msi = acp_pci_msi[hose->indirect_type]; + + list_for_each_entry(entry, &pdev->msi_list, list) { + hwirq = acp_pci_msi_alloc_hwirqs(msi, 1); + if (hwirq < 0) { + rc = hwirq; + pr_debug("%s: fail allocating msi interrupt\n", + __func__); + goto out_free; + } + + virq = irq_create_mapping(msi->irqhost, hwirq); + if ((virq == NO_IRQ) || (virq >= NR_MSI_IRQS)) { + pr_debug("%s: fail mapping hwirq 0x%lx\n", + __func__, hwirq); + acp_pci_msi_free_hwirqs(msi, hwirq, 1); + rc = -ENOSPC; + goto out_free; + } + + pr_debug("%s: hwirq = %d, virq = %d entry = %p\n", __func__, + (int) hwirq, virq, entry); + printk(KERN_INFO "%s: hwirq = %d, virq = %d entry = %p\n", + __func__, + (int) hwirq, virq, entry); + irq_set_msi_desc(virq, entry); + + msg.address_hi = msi->msi_addr_hi; + msg.address_lo = msi->msi_addr_lo + (virq * sizeof(u32)); + msg.data = MSI_MSG_DATA; + pr_debug("%s: msi_msg: hi = x%x, lo = x%x, data = x%x\n", + __func__, msg.address_hi, msg.address_lo, msg.data); + printk(KERN_INFO "%s: msi_msg: hi = x%x, lo = x%x, data = x%x\n", + __func__, msg.address_hi, msg.address_lo, msg.data); + write_msi_msg(virq, &msg); + } + return 0; + +out_free: + return rc; +} + +static const struct of_device_id lsi_msi_match[] = { + {.compatible = "lsi,msi"}, + {} +}; + +MODULE_DEVICE_TABLE(of, lsi_msi_match); + +static struct platform_driver lsi_msi_driver = { + .driver = { + .name = "lsi_msi", + .owner = THIS_MODULE, + .of_match_table = lsi_msi_match, + }, + .probe = lsi_msi_probe, + .remove = lsi_msi_remove, +}; + +module_platform_driver(lsi_msi_driver); +MODULE_AUTHOR("Sangeetha Rao <sangeetha....@avagotech.com>"); +MODULE_DESCRIPTION("LSI/Avago PCIe MSI Driver"); + diff --git a/arch/powerpc/sysdev/lsi_msi.h b/arch/powerpc/sysdev/lsi_msi.h new file mode 100644 index 0000000..25e3b83 --- /dev/null +++ b/arch/powerpc/sysdev/lsi_msi.h @@ -0,0 +1,21 @@ +/* +* LSI specific PCI-Express support; +*/ + +#define NR_MSI_IRQS (256) +#define MSI_MSG_DATA (0xcece) + +struct acp_pci_msi { + struct irq_domain *irqhost; + u32 msi_addr_lo; + u32 msi_addr_hi; + unsigned long *acp_pci_msi_bitmap; + spinlock_t bitmap_lock; + void *msg_buf; + void __iomem *utl_base; + struct device_node *node; + dma_addr_t msg_buf_dma; + int port; +}; +int acp_pci_msi_irq_handle(int port); + diff --git a/arch/powerpc/sysdev/lsi_pci.c b/arch/powerpc/sysdev/lsi_pci.c index 1236fdc..417488a 100644 --- a/arch/powerpc/sysdev/lsi_pci.c +++ b/arch/powerpc/sysdev/lsi_pci.c @@ -20,15 +20,41 @@ #include <mm/mmu_decl.h> #include "ppc4xx_pci.h" +#include "lsi_msi.h" #include "../../../drivers/misc/lsi-ncr.h" #include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/bitmap.h> +#include <linux/msi.h> +#include <linux/of_platform.h> +#include <asm/prom.h> +#include <asm/hw_irq.h> +#include <asm/ppc-pci.h> static int acp_plx; #undef PRINT_CONFIG_ACCESSES /*#define PRINT_CONFIG_ACCESSES*/ +struct pciex_port { + struct pci_controller *hose; + struct device_node *node; + unsigned int index; + int endpoint; + int link; + int has_ibpre; + unsigned int sdr_base; + dcr_host_t dcrs; + struct resource cfg_space; + struct resource utl_regs; + void __iomem *utl_base; + int acpChipType; +}; + +static struct pciex_port *acp_pciex_ports; +static unsigned int acp_pciex_port_count; + static int dma_offset_set; static u32 last_mpage; static u32 last_port; @@ -188,24 +214,6 @@ out: #define MAX_PCIE_BUS_MAPPED 0x40 -struct pciex_port { - struct pci_controller *hose; - struct device_node *node; - unsigned int index; - int endpoint; - int link; - int has_ibpre; - unsigned int sdr_base; - dcr_host_t dcrs; - struct resource cfg_space; - struct resource utl_regs; - void __iomem *utl_base; - int acpChipType; -}; - -static struct pciex_port *acp_pciex_ports; -static unsigned int acp_pciex_port_count; - struct pciex_hwops { bool want_sdr; int (*core_init)(struct device_node *np); @@ -854,8 +862,14 @@ acp_pcie_isr(int irq, void *arg) /* read the PEI interrupt status register */ intr_status = in_le32(mbase + 0x10c0); - in_le32(mbase + 0x10c4); + if (intr_status & 0x80000000) { + printk(KERN_INFO "Received MSI interrupt\n"); + acp_pci_msi_irq_handle(hose->indirect_type); + /* Clear MSI interrupt */ + out_le32(mbase + 0x10c0, 0x80000000); + return IRQ_HANDLED; + } /* check if this is a PCIe message from an external device */ if (intr_status & 0x00000010) { externalPciIntr = 1; -- 1.8.1.4 -- _______________________________________________ linux-yocto mailing list linux-yocto@yoctoproject.org https://lists.yoctoproject.org/listinfo/linux-yocto