summaryrefslogtreecommitdiff
path: root/drivers/sh
diff options
context:
space:
mode:
authorPaul Mundt <lethal@linux-sh.org>2009-11-02 10:30:26 +0900
committerPaul Mundt <lethal@linux-sh.org>2009-11-02 10:30:26 +0900
commit1ce7b039b5029ab698f9d64c0ad603794bc31ae7 (patch)
treed116ee849d48b470730ff209125e7ce3d2315582 /drivers/sh
parent58ee987e2fd8acff6263d194d8fa43267cc8b1c9 (diff)
downloadlinux-3.10-1ce7b039b5029ab698f9d64c0ad603794bc31ae7.tar.gz
linux-3.10-1ce7b039b5029ab698f9d64c0ad603794bc31ae7.tar.bz2
linux-3.10-1ce7b039b5029ab698f9d64c0ad603794bc31ae7.zip
sh: intc: dynamic IRQ support.
This adds support for dynamic IRQ allocation/deallocation for all parts using the SH-style vectored IRQs. While this is not inherently INTC-specific, the INTC code is the main tie-in for vectored IRQ registration, and is the only place that a full view of the utilized vector map is possible. The implementation is fairly straightforward, implementing a flat IRQ map where each registered vector is reserved, allowing us to scan for holes and dynamically wire up IRQs lazily later on in the boot stage. This piggybacks on top of sparseirq in order to make the best use of the available vector space. Dynamic IRQs can be used for any number of things, ranging from MSI in the SH-X3 PCIe case down to demux vectors for board FPGAs and system controllers that presently allocate an arbitrary range. In the latter case, this also allows those platforms to use sparseirq without blowing up, which brings us one step closer to enabling sparseirq as the default for all platform and CPU combinations. Signed-off-by: Paul Mundt <lethal@linux-sh.org>
Diffstat (limited to 'drivers/sh')
-rw-r--r--drivers/sh/intc.c84
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);
+}