irqchip/bcm7120-l2: Fix interrupt status for multiple parent IRQs
[sfrench/cifs-2.6.git] / drivers / irqchip / irq-bcm7120-l2.c
index c885a5c4632a3c820c4f66cb95fae406afe86596..d3f976913a6fbd9bb2af3ff4c4b1e906f5bae61e 100644 (file)
 #define MAX_MAPPINGS   (MAX_WORDS * 2)
 #define IRQS_PER_WORD  32
 
+struct bcm7120_l1_intc_data {
+       struct bcm7120_l2_intc_data *b;
+       u32 irq_map_mask[MAX_WORDS];
+};
+
 struct bcm7120_l2_intc_data {
        unsigned int n_words;
        void __iomem *map_base[MAX_MAPPINGS];
@@ -46,14 +51,15 @@ struct bcm7120_l2_intc_data {
        struct irq_domain *domain;
        bool can_wake;
        u32 irq_fwd_mask[MAX_WORDS];
-       u32 irq_map_mask[MAX_WORDS];
+       struct bcm7120_l1_intc_data *l1_data;
        int num_parent_irqs;
        const __be32 *map_mask_prop;
 };
 
 static void bcm7120_l2_intc_irq_handle(unsigned int irq, struct irq_desc *desc)
 {
-       struct bcm7120_l2_intc_data *b = irq_desc_get_handler_data(desc);
+       struct bcm7120_l1_intc_data *data = irq_desc_get_handler_data(desc);
+       struct bcm7120_l2_intc_data *b = data->b;
        struct irq_chip *chip = irq_desc_get_chip(desc);
        unsigned int idx;
 
@@ -68,7 +74,8 @@ static void bcm7120_l2_intc_irq_handle(unsigned int irq, struct irq_desc *desc)
 
                irq_gc_lock(gc);
                pending = irq_reg_readl(gc, b->stat_offset[idx]) &
-                                           gc->mask_cache;
+                                           gc->mask_cache &
+                                           data->irq_map_mask[idx];
                irq_gc_unlock(gc);
 
                for_each_set_bit(hwirq, &pending, IRQS_PER_WORD) {
@@ -104,8 +111,9 @@ static void bcm7120_l2_intc_resume(struct irq_chip_generic *gc)
 
 static int bcm7120_l2_intc_init_one(struct device_node *dn,
                                        struct bcm7120_l2_intc_data *data,
-                                       int irq)
+                                       int irq, u32 *valid_mask)
 {
+       struct bcm7120_l1_intc_data *l1_data = &data->l1_data[irq];
        int parent_irq;
        unsigned int idx;
 
@@ -117,20 +125,28 @@ static int bcm7120_l2_intc_init_one(struct device_node *dn,
 
        /* For multiple parent IRQs with multiple words, this looks like:
         * <irq0_w0 irq0_w1 irq1_w0 irq1_w1 ...>
+        *
+        * We need to associate a given parent interrupt with its corresponding
+        * map_mask in order to mask the status register with it because we
+        * have the same handler being called for multiple parent interrupts.
+        *
+        * This is typically something needed on BCM7xxx (STB chips).
         */
        for (idx = 0; idx < data->n_words; idx++) {
                if (data->map_mask_prop) {
-                       data->irq_map_mask[idx] |=
+                       l1_data->irq_map_mask[idx] |=
                                be32_to_cpup(data->map_mask_prop +
                                             irq * data->n_words + idx);
                } else {
-                       data->irq_map_mask[idx] = 0xffffffff;
+                       l1_data->irq_map_mask[idx] = 0xffffffff;
                }
+               valid_mask[idx] |= l1_data->irq_map_mask[idx];
        }
 
-       irq_set_chained_handler_and_data(parent_irq,
-                                        bcm7120_l2_intc_irq_handle, data);
+       l1_data->b = data;
 
+       irq_set_chained_handler_and_data(parent_irq,
+                                        bcm7120_l2_intc_irq_handle, l1_data);
        return 0;
 }
 
@@ -211,6 +227,7 @@ int __init bcm7120_l2_intc_probe(struct device_node *dn,
        struct irq_chip_type *ct;
        int ret = 0;
        unsigned int idx, irq, flags;
+       u32 valid_mask[MAX_WORDS] = { };
 
        data = kzalloc(sizeof(*data), GFP_KERNEL);
        if (!data)
@@ -223,9 +240,16 @@ int __init bcm7120_l2_intc_probe(struct device_node *dn,
                goto out_unmap;
        }
 
+       data->l1_data = kcalloc(data->num_parent_irqs, sizeof(*data->l1_data),
+                               GFP_KERNEL);
+       if (!data->l1_data) {
+               ret = -ENOMEM;
+               goto out_free_l1_data;
+       }
+
        ret = iomap_regs_fn(dn, data);
        if (ret < 0)
-               goto out_unmap;
+               goto out_free_l1_data;
 
        for (idx = 0; idx < data->n_words; idx++) {
                __raw_writel(data->irq_fwd_mask[idx],
@@ -234,16 +258,16 @@ int __init bcm7120_l2_intc_probe(struct device_node *dn,
        }
 
        for (irq = 0; irq < data->num_parent_irqs; irq++) {
-               ret = bcm7120_l2_intc_init_one(dn, data, irq);
+               ret = bcm7120_l2_intc_init_one(dn, data, irq, valid_mask);
                if (ret)
-                       goto out_unmap;
+                       goto out_free_l1_data;
        }
 
        data->domain = irq_domain_add_linear(dn, IRQS_PER_WORD * data->n_words,
                                             &irq_generic_chip_ops, NULL);
        if (!data->domain) {
                ret = -ENOMEM;
-               goto out_unmap;
+               goto out_free_l1_data;
        }
 
        /* MIPS chips strapped for BE will automagically configure the
@@ -267,7 +291,7 @@ int __init bcm7120_l2_intc_probe(struct device_node *dn,
                irq = idx * IRQS_PER_WORD;
                gc = irq_get_domain_generic_chip(data->domain, irq);
 
-               gc->unused = 0xffffffff & ~data->irq_map_mask[idx];
+               gc->unused = 0xffffffff & ~valid_mask[idx];
                gc->private = data;
                ct = gc->chip_types;
 
@@ -304,6 +328,8 @@ int __init bcm7120_l2_intc_probe(struct device_node *dn,
 
 out_free_domain:
        irq_domain_remove(data->domain);
+out_free_l1_data:
+       kfree(data->l1_data);
 out_unmap:
        for (idx = 0; idx < MAX_MAPPINGS; idx++) {
                if (data->map_base[idx])