irq: irqdomain: Add API to find free irq range

When registering irq chip drivers, one common requirement is to
specify the irq_domain irq_base and nr_irq. These fields represent
the system wide logical interrupt range the domain occupies.
For systems with only one interrupt controller, it's trivial to
know these values. But for systems with several irq chip drivers,
it becomes painful to keep track of interrupt ranges in platform
defines. These create needless compile time dependencies, of
which the Device Tree aims to solve.

irq_alloc_desc() can search for a free irq, but is very
inefficient for determining the availability of large ranges.
Additionally, some irq chip drivers allocate irq descriptors
lazily. For example, portions of the Device Tree may not be parsed
until a particular bus is probed. But of_irq_init() is intended to
be run at init time, and this is a natural time to allocate irq
domains. Thus by the time we allocate our irq descriptors, we already
need to know a range of acceptable irqs to use for the domain.

To solve these problems, let's introduce
irq_domain_find_free_range(), which will return to the caller the
first available irq domain range not used already by the system.
This range can then be specified with irq_domain_add().

Change-Id: I8b0f5d25b173c76b8fc5d4f46b3fe9c6bf5c3c8f
Signed-off-by: Michael Bohan <mbohan@codeaurora.org>
diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h
index 905b877..60ee260 100644
--- a/include/linux/irqdomain.h
+++ b/include/linux/irqdomain.h
@@ -95,6 +95,7 @@
 extern void irq_domain_register_irq(struct irq_domain *domain, int hwirq);
 extern void irq_domain_unregister(struct irq_domain *domain);
 extern void irq_domain_unregister_irq(struct irq_domain *domain, int hwirq);
+extern int irq_domain_find_free_range(unsigned int from, unsigned int cnt);
 
 #endif /* CONFIG_IRQ_DOMAIN */
 
diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c
index 31853d3..3b5340b 100644
--- a/kernel/irq/irqdomain.c
+++ b/kernel/irq/irqdomain.c
@@ -143,6 +143,46 @@
 	d->domain = NULL;
 }
 
+/**
+ * irq_domain_find_free_range() - Find an available irq range
+ * @from: lowest logical irq number to request from
+ * @cnt: number of interrupts to search for
+ *
+ * Finds an available logical irq range from the domains specified
+ * on the system. The from parameter can be used to allocate a range
+ * at least as great as the specified irq number.
+ */
+int irq_domain_find_free_range(unsigned int from, unsigned int cnt)
+{
+	struct irq_domain *curr, *prev = NULL;
+
+	if (list_empty(&irq_domain_list))
+		return from;
+
+	list_for_each_entry(curr, &irq_domain_list, list) {
+		if (prev == NULL) {
+			if ((from + cnt - 1) < curr->irq_base)
+				return from;
+		} else {
+			uint32_t p_next_irq = prev->irq_base + prev->nr_irq;
+			uint32_t start_irq;
+			if (from >= curr->irq_base)
+				continue;
+			if (from < p_next_irq)
+				start_irq = p_next_irq;
+			else
+				start_irq = from;
+			if ((curr->irq_base - start_irq) >= cnt)
+				return p_next_irq;
+		}
+		prev = curr;
+	}
+	curr = list_entry(curr->list.prev, struct irq_domain, list);
+
+	return from > curr->irq_base + curr->nr_irq ?
+	       from : curr->irq_base + curr->nr_irq;
+}
+
 #if defined(CONFIG_OF_IRQ)
 /**
  * irq_create_of_mapping() - Map a linux irq number from a DT interrupt spec