irq: irqdomain: Add support for dynamic irq_desc allocations

The current irq_domain_add() design assumes the underlying
irq_descs are valid when irq_domain_add() is called. It also
assumes that every hwirq enlisted in the irq_domain is valid.
Often, these assumptions are not correct.

Typically, irq_domain_add() is called at board_init time, but
sometimes interrupt chip drivers are not initialized until an
underlying bus is probed. When using SPARSE_IRQ, it's quite
natural to hold off allocating irq_descs until the board
topology specifies them as included.

And in such device topologies, there's no guarantee that chip
interrupt numbers will be contiguous. For example, each hwirq may
map to a particular device, but that device may or may not be
present in the actual hardware configuration. In order to support
this, we need the concept of an irq_domain that supports holes
in it.

In order to solve these problems, let's break the irq_domain
creation from the irq registration process. The irq_domain
creation can occur at init time and then have no dependencies. An
interrupt is not 'registered' with the domain until its resources
have already been allocated by the chip driver. For simple cases,
an additional API is provided to register the entire
logical irq_domain range to provide feature parity with what
already exists.

Change-Id: Ie3340d07ba8663196dc87d1185e3bb7fbed4be4d
Signed-off-by: Michael Bohan <mbohan@codeaurora.org>
diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c
index 200ce83..8d27501 100644
--- a/kernel/irq/irqdomain.c
+++ b/kernel/irq/irqdomain.c
@@ -13,20 +13,30 @@
  * irq_domain_add() - Register an irq_domain
  * @domain: ptr to initialized irq_domain structure
  *
- * Registers an irq_domain structure.  The irq_domain must at a minimum be
+ * Adds a irq_domain structure.  The irq_domain must at a minimum be
  * initialized with an ops structure pointer, and either a ->to_irq hook or
  * a valid irq_base value.  Everything else is optional.
  */
 void irq_domain_add(struct irq_domain *domain)
 {
+	mutex_lock(&irq_domain_mutex);
+	list_add(&domain->list, &irq_domain_list);
+	mutex_unlock(&irq_domain_mutex);
+}
+
+/**
+ * irq_domain_register() - Register an entire irq_domain
+ * @domain: ptr to initialized irq_domain structure
+ *
+ * Registers the entire irq_domain.  The irq_domain must at a minimum be
+ * initialized with an ops structure pointer, and either a ->to_irq hook or
+ * a valid irq_base value.  Everything else is optional.
+ */
+void irq_domain_register(struct irq_domain *domain)
+{
 	struct irq_data *d;
 	int hwirq, irq;
 
-	/*
-	 * This assumes that the irq_domain owner has already allocated
-	 * the irq_descs.  This block will be removed when support for dynamic
-	 * allocation of irq_descs is added to irq_domain.
-	 */
 	irq_domain_for_each_irq(domain, hwirq, irq) {
 		d = irq_get_irq_data(irq);
 		if (!d) {
@@ -41,24 +51,55 @@
 		d->domain = domain;
 		d->hwirq = hwirq;
 	}
-
-	mutex_lock(&irq_domain_mutex);
-	list_add(&domain->list, &irq_domain_list);
-	mutex_unlock(&irq_domain_mutex);
 }
 
 /**
- * irq_domain_del() - Unregister an irq_domain
+ * irq_domain_register_irq() - Register an irq_domain
+ * @domain: ptr to initialized irq_domain structure
+ * @hwirq: irq_domain hwirq to register
+ *
+ * Registers a specific hwirq within the irq_domain.  The irq_domain
+ * must at a minimum be initialized with an ops structure pointer, and
+ * either a ->to_irq hook or a valid irq_base value.  Everything else is
+ * optional.
+ */
+void irq_domain_register_irq(struct irq_domain *domain, int hwirq)
+{
+	struct irq_data *d;
+
+	d = irq_get_irq_data(irq_domain_to_irq(domain, hwirq));
+	if (!d) {
+		WARN(1, "error: assigning domain to non existant irq_desc");
+		return;
+	}
+	if (d->domain) {
+		/* things are broken; just report, don't clean up */
+		WARN(1, "error: irq_desc already assigned to a domain");
+		return;
+	}
+	d->domain = domain;
+	d->hwirq = hwirq;
+}
+
+/**
+ * irq_domain_del() - Removes a irq_domain from the system
  * @domain: ptr to registered irq_domain.
  */
 void irq_domain_del(struct irq_domain *domain)
 {
-	struct irq_data *d;
-	int hwirq, irq;
-
 	mutex_lock(&irq_domain_mutex);
 	list_del(&domain->list);
 	mutex_unlock(&irq_domain_mutex);
+}
+
+/**
+ * irq_domain_unregister() - Unregister an irq_domain
+ * @domain: ptr to registered irq_domain.
+ */
+void irq_domain_unregister(struct irq_domain *domain)
+{
+	struct irq_data *d;
+	int hwirq, irq;
 
 	/* Clear the irq_domain assignments */
 	irq_domain_for_each_irq(domain, hwirq, irq) {
@@ -67,6 +108,20 @@
 	}
 }
 
+/**
+ * irq_domain_unregister_irq() - Unregister a hwirq within a irq_domain
+ * @domain: ptr to registered irq_domain.
+ * @hwirq: irq_domain hwirq to unregister.
+ */
+void irq_domain_unregister_irq(struct irq_domain *domain, int hwirq)
+{
+	struct irq_data *d;
+
+	/* Clear the irq_domain assignment */
+	d = irq_get_irq_data(irq_domain_to_irq(domain, hwirq));
+	d->domain = NULL;
+}
+
 #if defined(CONFIG_OF_IRQ)
 /**
  * irq_create_of_mapping() - Map a linux irq number from a DT interrupt spec
@@ -165,6 +220,7 @@
 	domain->of_node = of_node_get(controller);
 	domain->ops = &irq_domain_simple_ops;
 	irq_domain_add(domain);
+	irq_domain_register(domain);
 }
 EXPORT_SYMBOL_GPL(irq_domain_add_simple);