diff options
Diffstat (limited to 'arch/powerpc/sysdev/uic.c')
-rw-r--r-- | arch/powerpc/sysdev/uic.c | 75 |
1 files changed, 63 insertions, 12 deletions
diff --git a/arch/powerpc/sysdev/uic.c b/arch/powerpc/sysdev/uic.c index 89059895a20..5149716c734 100644 --- a/arch/powerpc/sysdev/uic.c +++ b/arch/powerpc/sysdev/uic.c @@ -24,6 +24,7 @@ #include <linux/spinlock.h> #include <linux/irq.h> #include <linux/interrupt.h> +#include <linux/kernel_stat.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/prom.h> @@ -55,9 +56,6 @@ struct uic { /* For secondary UICs, the cascade interrupt's irqaction */ struct irqaction cascade; - - /* The device node of the interrupt controller */ - struct device_node *of_node; }; static void uic_unmask_irq(unsigned int virq) @@ -142,7 +140,7 @@ static int uic_set_irq_type(unsigned int virq, unsigned int flow_type) desc->status &= ~(IRQ_TYPE_SENSE_MASK | IRQ_LEVEL); desc->status |= flow_type & IRQ_TYPE_SENSE_MASK; - if (trigger) + if (!trigger) desc->status |= IRQ_LEVEL; spin_unlock_irqrestore(&uic->lock, flags); @@ -159,10 +157,62 @@ static struct irq_chip uic_irq_chip = { .set_type = uic_set_irq_type, }; -static int uic_host_match(struct irq_host *h, struct device_node *node) +/** + * handle_uic_irq - irq flow handler for UIC + * @irq: the interrupt number + * @desc: the interrupt description structure for this irq + * + * This is modified version of the generic handle_level_irq() suitable + * for the UIC. On the UIC, acking (i.e. clearing the SR bit) a level + * irq will have no effect if the interrupt is still asserted by the + * device, even if the interrupt is already masked. Therefore, unlike + * the standard handle_level_irq(), we must ack the interrupt *after* + * invoking the ISR (which should have de-asserted the interrupt in + * the external source). For edge interrupts we ack at the beginning + * instead of the end, to keep the window in which we can miss an + * interrupt as small as possible. + */ +void fastcall handle_uic_irq(unsigned int irq, struct irq_desc *desc) { - struct uic *uic = h->host_data; - return uic->of_node == node; + unsigned int cpu = smp_processor_id(); + struct irqaction *action; + irqreturn_t action_ret; + + spin_lock(&desc->lock); + if (desc->status & IRQ_LEVEL) + desc->chip->mask(irq); + else + desc->chip->mask_ack(irq); + + if (unlikely(desc->status & IRQ_INPROGRESS)) + goto out_unlock; + desc->status &= ~(IRQ_REPLAY | IRQ_WAITING); + kstat_cpu(cpu).irqs[irq]++; + + /* + * If its disabled or no action available + * keep it masked and get out of here + */ + action = desc->action; + if (unlikely(!action || (desc->status & IRQ_DISABLED))) { + desc->status |= IRQ_PENDING; + goto out_unlock; + } + + desc->status |= IRQ_INPROGRESS; + desc->status &= ~IRQ_PENDING; + spin_unlock(&desc->lock); + + action_ret = handle_IRQ_event(irq, action); + + spin_lock(&desc->lock); + desc->status &= ~IRQ_INPROGRESS; + if (desc->status & IRQ_LEVEL) + desc->chip->ack(irq); + if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask) + desc->chip->unmask(irq); +out_unlock: + spin_unlock(&desc->lock); } static int uic_host_map(struct irq_host *h, unsigned int virq, @@ -173,7 +223,7 @@ static int uic_host_map(struct irq_host *h, unsigned int virq, set_irq_chip_data(virq, uic); /* Despite the name, handle_level_irq() works for both level * and edge irqs on UIC. FIXME: check this is correct */ - set_irq_chip_and_handler(virq, &uic_irq_chip, handle_level_irq); + set_irq_chip_and_handler(virq, &uic_irq_chip, handle_uic_irq); /* Set default irq type */ set_irq_type(virq, IRQ_TYPE_NONE); @@ -194,7 +244,6 @@ static int uic_host_xlate(struct irq_host *h, struct device_node *ct, } static struct irq_host_ops uic_host_ops = { - .match = uic_host_match, .map = uic_host_map, .xlate = uic_host_xlate, }; @@ -207,6 +256,9 @@ irqreturn_t uic_cascade(int virq, void *data) int subvirq; msr = mfdcr(uic->dcrbase + UIC_MSR); + if (!msr) /* spurious interrupt */ + return IRQ_HANDLED; + src = 32 - ffs(msr); subvirq = irq_linear_revmap(uic->irqhost, src); @@ -229,7 +281,6 @@ static struct uic * __init uic_init_one(struct device_node *node) memset(uic, 0, sizeof(*uic)); spin_lock_init(&uic->lock); - uic->of_node = of_node_get(node); indexp = of_get_property(node, "cell-index", &len); if (!indexp || (len != sizeof(u32))) { printk(KERN_ERR "uic: Device node %s has missing or invalid " @@ -246,8 +297,8 @@ static struct uic * __init uic_init_one(struct device_node *node) } uic->dcrbase = *dcrreg; - uic->irqhost = irq_alloc_host(IRQ_HOST_MAP_LINEAR, NR_UIC_INTS, - &uic_host_ops, -1); + uic->irqhost = irq_alloc_host(of_node_get(node), IRQ_HOST_MAP_LINEAR, + NR_UIC_INTS, &uic_host_ops, -1); if (! uic->irqhost) { of_node_put(node); return NULL; /* FIXME: panic? */ |