diff options
Diffstat (limited to 'drivers/irqchip/irq-bcm7120-l2.c')
-rw-r--r-- | drivers/irqchip/irq-bcm7120-l2.c | 78 |
1 files changed, 54 insertions, 24 deletions
diff --git a/drivers/irqchip/irq-bcm7120-l2.c b/drivers/irqchip/irq-bcm7120-l2.c index 3ba5cc780..61b18ab33 100644 --- a/drivers/irqchip/irq-bcm7120-l2.c +++ b/drivers/irqchip/irq-bcm7120-l2.c @@ -26,10 +26,9 @@ #include <linux/irqdomain.h> #include <linux/reboot.h> #include <linux/bitops.h> +#include <linux/irqchip.h> #include <linux/irqchip/chained_irq.h> -#include "irqchip.h" - /* Register offset in the L2 interrupt controller */ #define IRQEN 0x00 #define IRQSTAT 0x04 @@ -38,6 +37,11 @@ #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]; @@ -47,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) +static void bcm7120_l2_intc_irq_handle(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; @@ -69,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) { @@ -81,11 +87,10 @@ static void bcm7120_l2_intc_irq_handle(unsigned int irq, struct irq_desc *desc) chained_irq_exit(chip, desc); } -static void bcm7120_l2_intc_suspend(struct irq_data *d) +static void bcm7120_l2_intc_suspend(struct irq_chip_generic *gc) { - struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); - struct irq_chip_type *ct = irq_data_get_chip_type(d); struct bcm7120_l2_intc_data *b = gc->private; + struct irq_chip_type *ct = gc->chip_types; irq_gc_lock(gc); if (b->can_wake) @@ -94,10 +99,9 @@ static void bcm7120_l2_intc_suspend(struct irq_data *d) irq_gc_unlock(gc); } -static void bcm7120_l2_intc_resume(struct irq_data *d) +static void bcm7120_l2_intc_resume(struct irq_chip_generic *gc) { - struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); - struct irq_chip_type *ct = irq_data_get_chip_type(d); + struct irq_chip_type *ct = gc->chip_types; /* Restore the saved mask */ irq_gc_lock(gc); @@ -107,8 +111,9 @@ static void bcm7120_l2_intc_resume(struct irq_data *d) 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; @@ -120,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_handler_data(parent_irq, data); - irq_set_chained_handler(parent_irq, bcm7120_l2_intc_irq_handle); + l1_data->b = data; + irq_set_chained_handler_and_data(parent_irq, + bcm7120_l2_intc_irq_handle, l1_data); return 0; } @@ -214,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) @@ -226,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], @@ -237,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 @@ -270,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; @@ -280,8 +301,15 @@ int __init bcm7120_l2_intc_probe(struct device_node *dn, ct->chip.irq_mask = irq_gc_mask_clr_bit; ct->chip.irq_unmask = irq_gc_mask_set_bit; ct->chip.irq_ack = irq_gc_noop; - ct->chip.irq_suspend = bcm7120_l2_intc_suspend; - ct->chip.irq_resume = bcm7120_l2_intc_resume; + gc->suspend = bcm7120_l2_intc_suspend; + gc->resume = bcm7120_l2_intc_resume; + + /* + * Initialize mask-cache, in case we need it for + * saving/restoring fwd mask even w/o any child interrupts + * installed + */ + gc->mask_cache = irq_reg_readl(gc, ct->regs.mask); if (data->can_wake) { /* This IRQ chip can wake the system, set all @@ -300,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]) |