| /* |
| * PQ2 ADS-style PCI interrupt controller |
| * |
| * Copyright 2007 Freescale Semiconductor, Inc. |
| * Author: Scott Wood <scottwood@freescale.com> |
| * |
| * Loosely based on mpc82xx ADS support by Vitaly Bordug <vbordug@ru.mvista.com> |
| * Copyright (c) 2006 MontaVista Software, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 as published |
| * by the Free Software Foundation. |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/spinlock.h> |
| #include <linux/irq.h> |
| #include <linux/types.h> |
| #include <linux/bootmem.h> |
| |
| #include <asm/io.h> |
| #include <asm/prom.h> |
| #include <asm/cpm2.h> |
| |
| #include "pq2.h" |
| |
| static DEFINE_SPINLOCK(pci_pic_lock); |
| |
| struct pq2ads_pci_pic { |
| struct device_node *node; |
| struct irq_host *host; |
| |
| struct { |
| u32 stat; |
| u32 mask; |
| } __iomem *regs; |
| }; |
| |
| #define NUM_IRQS 32 |
| |
| static void pq2ads_pci_mask_irq(unsigned int virq) |
| { |
| struct pq2ads_pci_pic *priv = get_irq_chip_data(virq); |
| int irq = NUM_IRQS - virq_to_hw(virq) - 1; |
| |
| if (irq != -1) { |
| unsigned long flags; |
| spin_lock_irqsave(&pci_pic_lock, flags); |
| |
| setbits32(&priv->regs->mask, 1 << irq); |
| mb(); |
| |
| spin_unlock_irqrestore(&pci_pic_lock, flags); |
| } |
| } |
| |
| static void pq2ads_pci_unmask_irq(unsigned int virq) |
| { |
| struct pq2ads_pci_pic *priv = get_irq_chip_data(virq); |
| int irq = NUM_IRQS - virq_to_hw(virq) - 1; |
| |
| if (irq != -1) { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&pci_pic_lock, flags); |
| clrbits32(&priv->regs->mask, 1 << irq); |
| spin_unlock_irqrestore(&pci_pic_lock, flags); |
| } |
| } |
| |
| static struct irq_chip pq2ads_pci_ic = { |
| .name = "PQ2 ADS PCI", |
| .end = pq2ads_pci_unmask_irq, |
| .mask = pq2ads_pci_mask_irq, |
| .mask_ack = pq2ads_pci_mask_irq, |
| .ack = pq2ads_pci_mask_irq, |
| .unmask = pq2ads_pci_unmask_irq, |
| .enable = pq2ads_pci_unmask_irq, |
| .disable = pq2ads_pci_mask_irq |
| }; |
| |
| static void pq2ads_pci_irq_demux(unsigned int irq, struct irq_desc *desc) |
| { |
| struct pq2ads_pci_pic *priv = desc->handler_data; |
| u32 stat, mask, pend; |
| int bit; |
| |
| for (;;) { |
| stat = in_be32(&priv->regs->stat); |
| mask = in_be32(&priv->regs->mask); |
| |
| pend = stat & ~mask; |
| |
| if (!pend) |
| break; |
| |
| for (bit = 0; pend != 0; ++bit, pend <<= 1) { |
| if (pend & 0x80000000) { |
| int virq = irq_linear_revmap(priv->host, bit); |
| generic_handle_irq(virq); |
| } |
| } |
| } |
| } |
| |
| static int pci_pic_host_map(struct irq_host *h, unsigned int virq, |
| irq_hw_number_t hw) |
| { |
| irq_to_desc(virq)->status |= IRQ_LEVEL; |
| set_irq_chip_data(virq, h->host_data); |
| set_irq_chip_and_handler(virq, &pq2ads_pci_ic, handle_level_irq); |
| return 0; |
| } |
| |
| static void pci_host_unmap(struct irq_host *h, unsigned int virq) |
| { |
| /* remove chip and handler */ |
| set_irq_chip_data(virq, NULL); |
| set_irq_chip(virq, NULL); |
| } |
| |
| static struct irq_host_ops pci_pic_host_ops = { |
| .map = pci_pic_host_map, |
| .unmap = pci_host_unmap, |
| }; |
| |
| int __init pq2ads_pci_init_irq(void) |
| { |
| struct pq2ads_pci_pic *priv; |
| struct irq_host *host; |
| struct device_node *np; |
| int ret = -ENODEV; |
| int irq; |
| |
| np = of_find_compatible_node(NULL, NULL, "fsl,pq2ads-pci-pic"); |
| if (!np) { |
| printk(KERN_ERR "No pci pic node in device tree.\n"); |
| of_node_put(np); |
| goto out; |
| } |
| |
| irq = irq_of_parse_and_map(np, 0); |
| if (irq == NO_IRQ) { |
| printk(KERN_ERR "No interrupt in pci pic node.\n"); |
| of_node_put(np); |
| goto out; |
| } |
| |
| priv = kzalloc(sizeof(*priv), GFP_KERNEL); |
| if (!priv) { |
| of_node_put(np); |
| ret = -ENOMEM; |
| goto out_unmap_irq; |
| } |
| |
| /* PCI interrupt controller registers: status and mask */ |
| priv->regs = of_iomap(np, 0); |
| if (!priv->regs) { |
| printk(KERN_ERR "Cannot map PCI PIC registers.\n"); |
| goto out_free_bootmem; |
| } |
| |
| /* mask all PCI interrupts */ |
| out_be32(&priv->regs->mask, ~0); |
| mb(); |
| |
| host = irq_alloc_host(np, IRQ_HOST_MAP_LINEAR, NUM_IRQS, |
| &pci_pic_host_ops, NUM_IRQS); |
| if (!host) { |
| ret = -ENOMEM; |
| goto out_unmap_regs; |
| } |
| |
| host->host_data = priv; |
| |
| priv->host = host; |
| host->host_data = priv; |
| set_irq_data(irq, priv); |
| set_irq_chained_handler(irq, pq2ads_pci_irq_demux); |
| |
| of_node_put(np); |
| return 0; |
| |
| out_unmap_regs: |
| iounmap(priv->regs); |
| out_free_bootmem: |
| free_bootmem((unsigned long)priv, |
| sizeof(struct pq2ads_pci_pic)); |
| of_node_put(np); |
| out_unmap_irq: |
| irq_dispose_mapping(irq); |
| out: |
| return ret; |
| } |