/* * PQ2 ADS-style PCI interrupt controller * * Copyright 2007 Freescale Semiconductor, Inc. * Author: Scott Wood * * Loosely based on mpc82xx ADS support by Vitaly Bordug * 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 #include #include #include #include #include #include #include #include #include "pq2.h" static DEFINE_RAW_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; raw_spin_lock_irqsave(&pci_pic_lock, flags); setbits32(&priv->regs->mask, 1 << irq); mb(); raw_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; raw_spin_lock_irqsave(&pci_pic_lock, flags); clrbits32(&priv->regs->mask, 1 << irq); raw_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; }