diff options
Diffstat (limited to 'arch/powerpc/sysdev/uic.c')
-rw-r--r-- | arch/powerpc/sysdev/uic.c | 132 |
1 files changed, 42 insertions, 90 deletions
diff --git a/arch/powerpc/sysdev/uic.c b/arch/powerpc/sysdev/uic.c index 847a5496b86..625b275c379 100644 --- a/arch/powerpc/sysdev/uic.c +++ b/arch/powerpc/sysdev/uic.c @@ -53,21 +53,23 @@ struct uic { /* The remapper for this UIC */ struct irq_host *irqhost; - - /* For secondary UICs, the cascade interrupt's irqaction */ - struct irqaction cascade; }; static void uic_unmask_irq(unsigned int virq) { + struct irq_desc *desc = get_irq_desc(virq); struct uic *uic = get_irq_chip_data(virq); unsigned int src = uic_irq_to_hw(virq); unsigned long flags; - u32 er; + u32 er, sr; + sr = 1 << (31-src); spin_lock_irqsave(&uic->lock, flags); + /* ack level-triggered interrupts here */ + if (desc->status & IRQ_LEVEL) + mtdcr(uic->dcrbase + UIC_SR, sr); er = mfdcr(uic->dcrbase + UIC_ER); - er |= 1 << (31 - src); + er |= sr; mtdcr(uic->dcrbase + UIC_ER, er); spin_unlock_irqrestore(&uic->lock, flags); } @@ -99,6 +101,7 @@ static void uic_ack_irq(unsigned int virq) static void uic_mask_ack_irq(unsigned int virq) { + struct irq_desc *desc = get_irq_desc(virq); struct uic *uic = get_irq_chip_data(virq); unsigned int src = uic_irq_to_hw(virq); unsigned long flags; @@ -109,7 +112,16 @@ static void uic_mask_ack_irq(unsigned int virq) er = mfdcr(uic->dcrbase + UIC_ER); er &= ~sr; mtdcr(uic->dcrbase + UIC_ER, er); - mtdcr(uic->dcrbase + UIC_SR, sr); + /* 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 + * we only ack the egde interrupts here, while + * level interrupts are ack'ed after the actual + * isr call in the uic_unmask_irq() + */ + if (!(desc->status & IRQ_LEVEL)) + mtdcr(uic->dcrbase + UIC_SR, sr); spin_unlock_irqrestore(&uic->lock, flags); } @@ -173,64 +185,6 @@ static struct irq_chip uic_irq_chip = { .set_type = uic_set_irq_type, }; -/** - * 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) -{ - 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, irq_hw_number_t hw) { @@ -239,7 +193,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_uic_irq); + set_irq_chip_and_handler(virq, &uic_irq_chip, handle_level_irq); /* Set default irq type */ set_irq_type(virq, IRQ_TYPE_NONE); @@ -264,23 +218,36 @@ static struct irq_host_ops uic_host_ops = { .xlate = uic_host_xlate, }; -irqreturn_t uic_cascade(int virq, void *data) +void uic_irq_cascade(unsigned int virq, struct irq_desc *desc) { - struct uic *uic = data; + struct uic *uic = get_irq_data(virq); u32 msr; int src; int subvirq; + spin_lock(&desc->lock); + if (desc->status & IRQ_LEVEL) + desc->chip->mask(virq); + else + desc->chip->mask_ack(virq); + spin_unlock(&desc->lock); + msr = mfdcr(uic->dcrbase + UIC_MSR); if (!msr) /* spurious interrupt */ - return IRQ_HANDLED; + goto uic_irq_ret; src = 32 - ffs(msr); subvirq = irq_linear_revmap(uic->irqhost, src); generic_handle_irq(subvirq); - return IRQ_HANDLED; +uic_irq_ret: + spin_lock(&desc->lock); + if (desc->status & IRQ_LEVEL) + desc->chip->ack(virq); + if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask) + desc->chip->unmask(virq); + spin_unlock(&desc->lock); } static struct uic * __init uic_init_one(struct device_node *node) @@ -342,33 +309,27 @@ void __init uic_init_tree(void) const u32 *interrupts; /* First locate and initialize the top-level UIC */ - - np = of_find_compatible_node(NULL, NULL, "ibm,uic"); - while (np) { + for_each_compatible_node(np, NULL, "ibm,uic") { interrupts = of_get_property(np, "interrupts", NULL); - if (! interrupts) + if (!interrupts) break; - - np = of_find_compatible_node(np, NULL, "ibm,uic"); } BUG_ON(!np); /* uic_init_tree() assumes there's a UIC as the * top-level interrupt controller */ primary_uic = uic_init_one(np); - if (! primary_uic) + if (!primary_uic) panic("Unable to initialize primary UIC %s\n", np->full_name); irq_set_default_host(primary_uic->irqhost); of_node_put(np); /* The scan again for cascaded UICs */ - np = of_find_compatible_node(NULL, NULL, "ibm,uic"); - while (np) { + for_each_compatible_node(np, NULL, "ibm,uic") { interrupts = of_get_property(np, "interrupts", NULL); if (interrupts) { /* Secondary UIC */ int cascade_virq; - int ret; uic = uic_init_one(np); if (! uic) @@ -377,20 +338,11 @@ void __init uic_init_tree(void) cascade_virq = irq_of_parse_and_map(np, 0); - uic->cascade.handler = uic_cascade; - uic->cascade.name = "UIC cascade"; - uic->cascade.dev_id = uic; - - ret = setup_irq(cascade_virq, &uic->cascade); - if (ret) - printk(KERN_ERR "Failed to setup_irq(%d) for " - "UIC%d cascade\n", cascade_virq, - uic->index); + set_irq_data(cascade_virq, uic); + set_irq_chained_handler(cascade_virq, uic_irq_cascade); /* FIXME: setup critical cascade?? */ } - - np = of_find_compatible_node(np, NULL, "ibm,uic"); } } |