diff options
Diffstat (limited to 'drivers/sh')
-rw-r--r-- | drivers/sh/intc.c | 84 |
1 files changed, 83 insertions, 1 deletions
diff --git a/drivers/sh/intc.c b/drivers/sh/intc.c index 94e6e46ff82..4789df43c0f 100644 --- a/drivers/sh/intc.c +++ b/drivers/sh/intc.c @@ -2,6 +2,7 @@ * Shared interrupt handling code for IPR and INTC2 types of IRQs. * * Copyright (C) 2007, 2008 Magnus Damm + * Copyright (C) 2009 Paul Mundt * * Based on intc2.c and ipr.c * @@ -24,6 +25,7 @@ #include <linux/sysdev.h> #include <linux/list.h> #include <linux/topology.h> +#include <linux/bitmap.h> #define _INTC_MK(fn, mode, addr_e, addr_d, width, shift) \ ((shift) | ((width) << 5) | ((fn) << 9) | ((mode) << 13) | \ @@ -59,6 +61,20 @@ struct intc_desc_int { static LIST_HEAD(intc_list); +/* + * The intc_irq_map provides a global map of bound IRQ vectors for a + * given platform. Allocation of IRQs are either static through the CPU + * vector map, or dynamic in the case of board mux vectors or MSI. + * + * As this is a central point for all IRQ controllers on the system, + * each of the available sources are mapped out here. This combined with + * sparseirq makes it quite trivial to keep the vector map tightly packed + * when dynamically creating IRQs, as well as tying in to otherwise + * unused irq_desc positions in the sparse array. + */ +static DECLARE_BITMAP(intc_irq_map, NR_IRQS); +static DEFINE_SPINLOCK(vector_lock); + #ifdef CONFIG_SMP #define IS_SMP(x) x.smp #define INTC_REG(d, x, c) (d->reg[(x)] + ((d->smp[(x)] & 0xff) * c)) @@ -566,6 +582,11 @@ static void __init intc_register_irq(struct intc_desc *desc, struct intc_handle_int *hp; unsigned int data[2], primary; + /* + * Register the IRQ position with the global IRQ map + */ + set_bit(irq, intc_irq_map); + /* Prefer single interrupt source bitmap over other combinations: * 1. bitmap, single interrupt source * 2. priority, single interrupt source @@ -844,5 +865,66 @@ static int __init register_intc_sysdevs(void) return error; } - device_initcall(register_intc_sysdevs); + +/* + * Dynamic IRQ allocation and deallocation + */ +static unsigned int create_irq_on_node(unsigned int irq_want, int node) +{ + unsigned int irq = 0, new; + unsigned long flags; + struct irq_desc *desc; + + spin_lock_irqsave(&vector_lock, flags); + + /* + * First try the wanted IRQ, then scan. + */ + if (test_and_set_bit(irq_want, intc_irq_map)) { + new = find_first_zero_bit(intc_irq_map, nr_irqs); + if (unlikely(new == nr_irqs)) + goto out_unlock; + + desc = irq_to_desc_alloc_node(new, node); + if (unlikely(!desc)) { + pr_info("can't get irq_desc for %d\n", new); + goto out_unlock; + } + + desc = move_irq_desc(desc, node); + __set_bit(new, intc_irq_map); + irq = new; + } + +out_unlock: + spin_unlock_irqrestore(&vector_lock, flags); + + if (irq > 0) + dynamic_irq_init(irq); + + return irq; +} + +int create_irq(void) +{ + int nid = cpu_to_node(smp_processor_id()); + int irq; + + irq = create_irq_on_node(NR_IRQS_LEGACY, nid); + if (irq == 0) + irq = -1; + + return irq; +} + +void destroy_irq(unsigned int irq) +{ + unsigned long flags; + + dynamic_irq_cleanup(irq); + + spin_lock_irqsave(&vector_lock, flags); + __clear_bit(irq, intc_irq_map); + spin_unlock_irqrestore(&vector_lock, flags); +} |