[POWERPC] 4xx: UIC add mask_ack callback
[safe/jmp/linux-2.6] / arch / powerpc / sysdev / uic.c
index 8905989..847a549 100644 (file)
@@ -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)
@@ -99,6 +97,22 @@ static void uic_ack_irq(unsigned int virq)
        spin_unlock_irqrestore(&uic->lock, flags);
 }
 
+static void uic_mask_ack_irq(unsigned int virq)
+{
+       struct uic *uic = get_irq_chip_data(virq);
+       unsigned int src = uic_irq_to_hw(virq);
+       unsigned long flags;
+       u32 er, sr;
+
+       sr = 1 << (31-src);
+       spin_lock_irqsave(&uic->lock, flags);
+       er = mfdcr(uic->dcrbase + UIC_ER);
+       er &= ~sr;
+       mtdcr(uic->dcrbase + UIC_ER, er);
+       mtdcr(uic->dcrbase + UIC_SR, sr);
+       spin_unlock_irqrestore(&uic->lock, flags);
+}
+
 static int uic_set_irq_type(unsigned int virq, unsigned int flow_type)
 {
        struct uic *uic = get_irq_chip_data(virq);
@@ -142,7 +156,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);
@@ -154,15 +168,67 @@ static struct irq_chip uic_irq_chip = {
        .typename       = " UIC  ",
        .unmask         = uic_unmask_irq,
        .mask           = uic_mask_irq,
-/*     .mask_ack       = uic_mask_irq_and_ack, */
+       .mask_ack       = uic_mask_ack_irq,
        .ack            = uic_ack_irq,
        .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 +239,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 +260,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 +272,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 +297,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 +313,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? */