diff options
Diffstat (limited to 'arch/powerpc/sysdev/ppc4xx_msi.c')
-rwxr-xr-x | arch/powerpc/sysdev/ppc4xx_msi.c | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/arch/powerpc/sysdev/ppc4xx_msi.c b/arch/powerpc/sysdev/ppc4xx_msi.c new file mode 100755 index 00000000000..8bec6fff4ce --- /dev/null +++ b/arch/powerpc/sysdev/ppc4xx_msi.c @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2009 Applied Micro Circuits corporation. + * + * Author: Feng Kan <fkan@amcc.com> + * Tirumala Marri <tmarri@amcc.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; version 2 of the + * License. + */ +#include <linux/irq.h> +#include <linux/bootmem.h> +#include <linux/pci.h> +#include <linux/msi.h> +#include <linux/of_platform.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <asm/prom.h> +#include <asm/hw_irq.h> +#include <asm/ppc-pci.h> +#include <asm/dcr.h> +#include <asm/dcr-regs.h> +#include <linux/proc_fs.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include "ppc4xx_msi.h" + +#define MSI_DEBUG 0 + +#define MSI_PROC_ROOT "driver/msi" +#define MSI_PROC_NAME "term" + + +void __iomem *msi_virt; +static struct ppc4xx_msi *ppc4xx_msi_data; + +/* Can use this register address for catching the MSI message */ +#if defined(CONFIG_460EX) || defined (CONFIG_APM82181) + +#ifdef MSI_DEBUG + +#define MSI_TERM_ADDR 0xc0ec70000ull +#else +#define MSI_TERM_ADDR 0xc10000080ull +#endif /*MSI_DEBUG*/ +#endif /*defined(CONFIG_460EX) || defined (CONFIG_APM82181) */ + +static int ppc4xx_msi_init_allocator(struct ppc4xx_msi *msi_data) +{ + int rc; + + rc = msi_bitmap_alloc(&msi_data->bitmap, NR_MSI_IRQS, + msi_data->irqhost->of_node); + if (rc) + return rc; + rc = msi_bitmap_reserve_dt_hwirqs(&msi_data->bitmap); + if (rc < 0) { + msi_bitmap_free(&msi_data->bitmap); + return rc; + } + return 0; +} + + +static void ppc4xx_msi_cascade(unsigned int irq, struct irq_desc *desc) +{ + unsigned int cascade_irq; + struct ppc4xx_msi *msi_data = ppc4xx_msi_data; + int msir_index = -1; +#ifdef MSI_DEBUG + printk("PCIE-MSI: cascade %s called irq %d %d\n", + __func__, irq, (int)virq_to_hw(irq)); +#endif + spin_lock(&desc->lock); + if (desc->chip->mask_ack) { + desc->chip->mask_ack(irq); + } else { + desc->chip->mask(irq); + desc->chip->ack(irq); + } + + if (unlikely(desc->status & IRQ_INPROGRESS)) + goto unlock; + + msir_index = (int)desc->handler_data; + + if (msir_index >= NR_MSI_IRQS) + cascade_irq = NO_IRQ; + + desc->status |= IRQ_INPROGRESS; + + cascade_irq = irq_linear_revmap(msi_data->irqhost, msir_index); + if (cascade_irq != NO_IRQ) + generic_handle_irq(cascade_irq); + else + printk("%s: no cacade handler found\n", __FUNCTION__); + desc->status &= ~IRQ_INPROGRESS; + + if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask) + desc->chip->unmask(irq); +unlock: + spin_unlock(&desc->lock); +} + +static void ppc4xx_compose_msi_msg(struct pci_dev *pdev, int hwirq, + struct msi_msg *msg) +{ + struct ppc4xx_msi *msi_data = ppc4xx_msi_data; + + msg->address_lo = msi_data->msi_addr_lo; + msg->address_hi = msi_data->msi_addr_hi; + msg->data = hwirq | MSI_DATA_PATTERN; +} + + +int ppc4xx_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) +{ + struct msi_desc *entry; + int rc, hwirq; + unsigned int virq; + struct msi_msg msg; + struct ppc4xx_msi *msi_data = ppc4xx_msi_data; + + printk("PCIE-MSI: vendor %x\n", dev->vendor); + list_for_each_entry(entry, &dev->msi_list, list) { + hwirq = msi_bitmap_alloc_hwirqs(&msi_data->bitmap, 1); + if (hwirq < 0) { + rc = hwirq; + pr_debug("%s: fail allocating msi interrupt\n", + __func__); + goto out_free; + } + virq = irq_create_mapping(msi_data->irqhost, hwirq); + printk("PCIE-MSI: hwirq requested %d, virt %d\n", hwirq, virq); + if (virq == NO_IRQ) { + pr_debug("%s: fail mapping irq\n", __func__); + rc = -ENOSPC; + goto out_free; + } + set_irq_msi(virq, entry); + ppc4xx_compose_msi_msg(dev, hwirq, &msg); + printk("PCIE-MSI: message: h %08x l %08x data %08x\n", + msg.address_hi, msg.address_lo, msg.data); + + write_msi_msg(virq, &msg); + } + + return 0; +out_free: + return rc; +} + +void ppc4xx_teardown_msi_irqs(struct pci_dev *dev) +{ + struct msi_desc *entry; + struct ppc4xx_msi *msi_data = ppc4xx_msi_data; + + printk("PCIE-MSI: tearing down msi irqs for %p\n", dev); + + list_for_each_entry(entry, &dev->msi_list, list) { + if (entry->irq == NO_IRQ) + continue; + set_irq_msi(entry->irq, NULL); + msi_bitmap_free_hwirqs(&msi_data->bitmap, + virq_to_hw(entry->irq), 1); + irq_dispose_mapping(entry->irq); + + } + + return; +} + +static int ppc4xx_msi_check_device(struct pci_dev *pdev, int nvec, int type) +{ + printk("PCIE-MSI:%s called. vec %x type %d\n", + __func__, nvec, type); + return 0; +} + +/* Stub, unneeded. */ +static void ppc4xx_msi_end_irq(unsigned int virq) +{ +} + + +static struct irq_chip ppc4xx_msi_chip = { + .mask = mask_msi_irq, + .unmask = unmask_msi_irq, + .ack = ppc4xx_msi_end_irq, + .name = " UIC", +}; + +static int ppc4xx_msi_host_map(struct irq_host *h, unsigned int virq, + irq_hw_number_t hw) +{ + struct irq_chip *chip = &ppc4xx_msi_chip; + + irq_to_desc(virq)->status |= IRQ_TYPE_EDGE_RISING; + + set_irq_chip_and_handler(virq, chip, handle_edge_irq); + + return 0; +} + +static struct irq_host_ops ppc4xx_msi_host_ops = { + .map = ppc4xx_msi_host_map, +}; + + +static int __devinit ppc4xx_msi_probe(struct of_device *dev, + const struct of_device_id *match) +{ + struct ppc4xx_msi *msi; + struct resource res, rmsi; + int i; + int rc; + int virt_msir = 0; + const u32 *sdr_base; + dma_addr_t msi_phys = 0; + + printk("PCIE-MSI: Setting up MSI ...\n"); + msi = kzalloc(sizeof(struct ppc4xx_msi), GFP_KERNEL); + if (!msi) { + dev_err(&dev->dev, "No memory for MSI structure\n"); + rc = -ENOMEM; + goto error_out; + } + + /* TODO: parse device tree interrupts early to get dynamic IRQ count */ + msi->irqhost = irq_alloc_host(dev->node, IRQ_HOST_MAP_LINEAR, + NR_MSI_IRQS, &ppc4xx_msi_host_ops, 0); + if (msi->irqhost == NULL) { + dev_err(&dev->dev, "No memory for MSI irqhost\n"); + rc = -ENOMEM; + goto error_out; + } + + /* Get MSI ranges */ + rc = of_address_to_resource(dev->node, 0, &rmsi); + if (rc) { + printk("%s rmsi resource error!\n", + dev->node->full_name); + goto error_out; + } + + /* Get the MSI reg base */ + rc = of_address_to_resource(dev->node, 1, &res); + printk("PCIE-MSI: MSI reg base %llx - %llx\n", res.start, res.end); + if (rc) { + dev_err(&dev->dev, "%s regbase resource error!\n", + dev->node->full_name); + goto error_out; + } + /* Get the sdr-base */ + sdr_base = (u32 *)of_get_property(dev->node, "sdr-base", NULL); + printk("PCIE-MSI: MSI sdr base %08x\n", *sdr_base); + if (sdr_base == NULL) { + pr_debug("%s: %s - no sdr-base property found!\n", __func__, + dev->node->full_name); + goto error_out; + } + msi->sdr_base = *sdr_base; + mtdcri(SDR0, msi->sdr_base + PE_IHS1_OFF, RES_TO_U32_HIGH(res.start)); + mtdcri(SDR0, msi->sdr_base + PE_IHS2_OFF, RES_TO_U32_LOW(res.start)| + PE_IHS2_BREV_EN); + + printk("PCIE-MSI: PE_IHS h %08x l %08x\n", + mfdcri(SDR0, msi->sdr_base + PE_IHS1_OFF), + mfdcri(SDR0, msi->sdr_base + PE_IHS2_OFF)); + + msi->msi_regs = ioremap(res.start, res.end - res.start + 1); + if (!msi->msi_regs) { + pr_debug("%s: PIH mapping failed\n", __func__); + goto error_out; + } +#if defined(CONFIG_405EX) || defined(CONFIG_440SPe) || defined(CONFIG_460SX) + /* PIMs setup for first 2GB */ + msi_virt = dma_alloc_coherent(&dev->dev, 64, &msi_phys, GFP_KERNEL); + if (msi_virt == NULL) { + pr_debug("%s: No memory for MSI trigger\n", __func__); + rc = -ENOMEM; + goto error_out; + } + msi->msi_addr_hi = 0; + msi->msi_addr_lo = (u32)msi_phys; + +#elif defined(CONFIG_460EX) || defined (CONFIG_APM82181) + msi->msi_addr_hi = (u32)(MSI_TERM_ADDR>>32); + msi->msi_addr_lo = (u32)(MSI_TERM_ADDR & 0xffffffff); +#ifdef MSI_DEBUG + msi_virt = ioremap(MSI_TERM_ADDR, 0x20); + if(!msi_virt) { + printk("ERROR: Ioremap failed %llx\n", MSI_TERM_ADDR); + goto error_out; + } +#endif /*MSI_DEBUG*/ + +#endif + /* Progam the Interrupt handler Termination addr registers */ + out_be32(msi->msi_regs + PEIH_TERMADH, msi->msi_addr_hi); + out_be32(msi->msi_regs + PEIH_TERMADL, msi->msi_addr_lo); + + /* Program MSI Expected data and Mask bits */ + out_be32(msi->msi_regs + PEIH_MSIED, MSI_DATA_PATTERN); + out_be32(msi->msi_regs + PEIH_MSIMK, MSI_MASK_PATTERN); + + iounmap(msi->msi_regs); + + msi->irqhost->host_data = msi; + + if (ppc4xx_msi_init_allocator(msi)) { + pr_debug("%s: Error allocating MSI bitmap\n", __func__); + goto error_out; + } + + for (i = 0; i < NR_MSI_IRQS; i++) { + virt_msir = irq_of_parse_and_map(dev->node, i); + if (virt_msir != NO_IRQ) { + set_irq_data(virt_msir, (void *)i); + set_irq_chained_handler(virt_msir, ppc4xx_msi_cascade); + } else { + printk("No IRQ!\n"); + } + } + + ppc4xx_msi_data = msi; + + WARN_ON(ppc_md.setup_msi_irqs); + ppc_md.setup_msi_irqs = ppc4xx_setup_msi_irqs; + ppc_md.teardown_msi_irqs = ppc4xx_teardown_msi_irqs; + ppc_md.msi_check_device = ppc4xx_msi_check_device; + return 0; +error_out: + if (msi_virt) + dma_free_coherent(&dev->dev, 64, msi_virt, msi_phys); + if (msi) + kfree(msi); + return rc; +} + +int msi_proc_read (char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + printk("PCIE-MSI: Read from Term Address\n"); +#ifdef MSI_DEBUG + printk("message %08x\n", in_le32(msi_virt)); + + out_le32(msi_virt, 0x0); +#endif + return 0; +} + +int msi_proc_write (struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + char tmp[2]; + unsigned long val; + + /* Verify MSIx from user space by echo "x" > /proc/4xx_msi/test */ + if (copy_from_user(tmp, buffer, count)) + return -EFAULT; + val = simple_strtoul(tmp, NULL, 16); + + printk("PCIE-MSI: Testing interrupt:%d\n", (int)val); + +#ifdef MSI_DEBUG + printk("Write to term address with data=0x%08x\n",(u32)(MSI_DATA_PATTERN | val)); + out_le32(msi_virt, MSI_DATA_PATTERN | val); + mdelay(10); +#endif + return count; + + +} + +static const struct of_device_id ppc4xx_msi_ids[] = { + { + .compatible = "amcc,ppc4xx-msi", + }, + {} +}; + +static struct of_platform_driver ppc4xx_msi_driver = { + .name = "ppc4xx-msi", + .match_table = ppc4xx_msi_ids, + .probe = ppc4xx_msi_probe, +}; + +static __init int ppc4xx_msi_init(void) +{ + struct proc_dir_entry *p ,*root=NULL; + + root = proc_mkdir(MSI_PROC_ROOT, NULL); + if (!root) { + printk("%s: failed to create proc entry\n",__func__); + goto err; + } + + p = create_proc_entry(MSI_PROC_NAME, 0, root); + if (p) { + p->read_proc = msi_proc_read; + p->write_proc = msi_proc_write; + } +err: + return of_register_platform_driver(&ppc4xx_msi_driver); + +} +subsys_initcall(ppc4xx_msi_init); |