From: Stephen Warren <swar...@nvidia.com>

Some devices contain a single interrupt output, and multiple separate
interrupt controllers that all trigger that interrupt output, yet provide
no top-level interrupt controller/registers to allow determination of
which child interrupt controller caused the interrupt.

In this case, a regmap irq_chip can be created for each of the individual
"child" interrupt controllers, alongside some infra-structure to hook the
interrupt and distribute it to each "child". This patch introduces
regmap_add_irq_chips() which sets up such infra-structure, and creates
a number of child regmap irq_chips.

Signed-off-by: Stephen Warren <swar...@nvidia.com>
---
 drivers/base/regmap/regmap-irq.c |  187 ++++++++++++++++++++++++++++++++++++++
 include/linux/regmap.h           |    9 ++
 2 files changed, 196 insertions(+), 0 deletions(-)

diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c
index 6bb4364..9d7894c 100644
--- a/drivers/base/regmap/regmap-irq.c
+++ b/drivers/base/regmap/regmap-irq.c
@@ -2,6 +2,7 @@
  * regmap based irq_chip
  *
  * Copyright 2011 Wolfson Microelectronics plc
+ * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
  *
  * Author: Mark Brown <broo...@opensource.wolfsonmicro.com>
  *
@@ -16,6 +17,7 @@
 #include <linux/irq.h>
 #include <linux/interrupt.h>
 #include <linux/irqdomain.h>
+#include <linux/pm_runtime.h>
 #include <linux/slab.h>
 
 #include "internal.h"
@@ -40,6 +42,14 @@ struct regmap_irq_chip_data {
        unsigned int irq_reg_stride;
 };
 
+struct regmap_irq_chips_data {
+       struct device *dev;
+       int irq;
+       int nchips;
+       struct irq_domain *irqdom;
+       struct regmap_irq_chip_data **datas;
+};
+
 static inline const
 struct regmap_irq *irq_to_regmap_irq(struct regmap_irq_chip_data *data,
                                     int irq)
@@ -463,3 +473,180 @@ int regmap_irq_get_virq(struct regmap_irq_chip_data 
*data, int irq)
        return irq_create_mapping(data->domain, irq);
 }
 EXPORT_SYMBOL_GPL(regmap_irq_get_virq);
+
+static void regmaps_irq_enable(struct irq_data *data)
+{
+}
+
+static void regmaps_irq_disable(struct irq_data *data)
+{
+}
+
+static struct irq_chip regmaps_irq_chip = {
+       .name           = "regmaps",
+       .irq_disable    = regmaps_irq_disable,
+       .irq_enable     = regmaps_irq_enable,
+};
+
+static int regmaps_irq_map(struct irq_domain *h, unsigned int virq,
+                          irq_hw_number_t hw)
+{
+       struct regmap_irq_chips_data *data = h->host_data;
+
+       irq_set_chip_data(virq, data);
+       irq_set_chip_and_handler(virq, &regmaps_irq_chip, handle_edge_irq);
+       irq_set_nested_thread(virq, 1);
+
+       /* ARM needs us to explicitly flag the IRQ as valid
+        * and will set them noprobe when we do so. */
+#ifdef CONFIG_ARM
+       set_irq_flags(virq, IRQF_VALID);
+#else
+       irq_set_noprobe(virq);
+#endif
+
+       return 0;
+}
+
+static struct irq_domain_ops regmaps_domain_ops = {
+       .map    = regmaps_irq_map,
+};
+
+static irqreturn_t regmaps_irq_thread(int irq, void *data)
+{
+       struct regmap_irq_chips_data *d = data;
+       int ret, i;
+
+       ret = pm_runtime_get_sync(d->dev);
+       if (ret < 0) {
+               dev_err(d->dev, "Failed to resume device: %d\n", ret);
+               return IRQ_NONE;
+       }
+
+       for (i = 0; i < d->nchips; i++)
+               handle_nested_irq(irq_find_mapping(d->irqdom, i));
+
+       pm_runtime_mark_last_busy(d->dev);
+       pm_runtime_put_autosuspend(d->dev);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * regmap_add_irq_chips(): Call regmap_add_irq_chip for n chips on one IRQ
+ *
+ * @irq:       Primary IRQ for the device
+ * @irq_flags: The IRQF_ flags to use for the primary interrupt.
+ * @nchips:    The number of IRQ chips attached to the interrupt.
+ * @maps:      The regmap for each IRQ chip.
+ * @irq_bases  The base Linux IRQ number for each chip, or NULL.
+ * @chips:     Configuration for each interrupt controller.
+ * @data:      Runtime data structure set of controllers, allocated on success
+ *
+ * Some devices contain a single interrupt output, and multiple separate
+ * interrupt controllers that all trigger that interrupt output, yet provide
+ * no top-level interrupt controller to allow determination of which child
+ * interrupt controller caused the interrupt. regmap_add_irq_chips() creates
+ * the required N regmap irq_chips, and handles demultiplexing this virtual
+ * top-level interrupt.
+ *
+ * Returns 0 on success or an errno on failure.
+ *
+ * In order for this to be efficient the chip really should use a
+ * register cache.  The chip driver is responsible for restoring the
+ * register values used by the IRQ controller over suspend and resume.
+ */
+int regmap_add_irq_chips(int irq, int irq_flags, int nchips,
+                        struct regmap **maps, int *irq_bases,
+                        const struct regmap_irq_chip **chips,
+                        struct regmap_irq_chips_data **data)
+{
+       int ret = -ENOMEM;
+       struct regmap_irq_chips_data *d;
+       int i;
+
+       d = kzalloc(sizeof(*d), GFP_KERNEL);
+       if (!d)
+               return -ENOMEM;
+
+       d->dev = maps[0]->dev;
+       d->irq = irq;
+       d->nchips = nchips;
+
+       d->datas = kzalloc(nchips * sizeof(*(d->datas)), GFP_KERNEL);
+       if (!d->datas)
+               goto err_alloc;
+
+       d->irqdom = irq_domain_add_linear(NULL, nchips, &regmaps_domain_ops, d);
+       if (!d->irqdom) {
+               ret = -EINVAL;
+               goto err_alloc;
+       }
+
+       for (i = 0; i < nchips; i++) {
+               ret = regmap_add_irq_chip(maps[i],
+                                         irq_create_mapping(d->irqdom, i),
+                                         IRQF_ONESHOT,
+                                         irq_bases ? irq_bases[i] : 0,
+                                         chips[i], &d->datas[i]);
+               if (ret != 0) {
+                       dev_err(maps[i]->dev,
+                               "Failed to add chip %d IRQs: %d\n", i, ret);
+                       goto err_chips;
+               }
+       }
+
+       ret = request_threaded_irq(irq, NULL, regmaps_irq_thread,
+                                  irq_flags, dev_name(d->dev), d);
+       if (ret != 0) {
+               dev_err(d->dev, "Failed to request IRQ %d: %d\n", irq, ret);
+               goto err_chips;
+       }
+
+       *data = d;
+
+       return 0;
+
+err_chips:
+       for (i--; i >= 0; i--)
+               regmap_del_irq_chip(irq_create_mapping(d->irqdom, i),
+                                   d->datas[i]);
+       /* We should unmap the domain but... */
+err_alloc:
+       kfree(d->datas);
+       kfree(d);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(regmap_add_irq_chips);
+
+/**
+ * regmap_del_irq_chips(): Undo regmap_add_irq_chips()
+ *
+ * @irq:    Primary IRQ for the device
+ * @d:      regmap_irq_chips_data allocated by regmap_add_irq_chips()
+ */
+void regmap_del_irq_chips(struct regmap_irq_chips_data *d)
+{
+       int i;
+
+       free_irq(d->irq, d);
+
+       for (i = d->nchips - 1; i >= 0; i--)
+               regmap_del_irq_chip(irq_create_mapping(d->irqdom, i),
+                                   d->datas[i]);
+
+       /* We should unmap the domain but... */
+
+       kfree(d->datas);
+       kfree(d);
+}
+EXPORT_SYMBOL_GPL(regmap_del_irq_chips);
+
+struct regmap_irq_chip_data *regmap_irq_chips_get_chip(
+                               struct regmap_irq_chips_data *d, int chip)
+{
+       if (chip >= d->nchips)
+               return NULL;
+       return d->datas[chip];
+}
+EXPORT_SYMBOL(regmap_irq_chips_get_chip);
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index 7f7e00d..b5d9699 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -307,6 +307,7 @@ struct regmap_irq_chip {
 };
 
 struct regmap_irq_chip_data;
+struct regmap_irq_chips_data;
 
 int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
                        int irq_base, const struct regmap_irq_chip *chip,
@@ -315,6 +316,14 @@ void regmap_del_irq_chip(int irq, struct 
regmap_irq_chip_data *data);
 int regmap_irq_chip_get_base(struct regmap_irq_chip_data *data);
 int regmap_irq_get_virq(struct regmap_irq_chip_data *data, int irq);
 
+int regmap_add_irq_chips(int irq, int irq_flags, int nchips,
+                        struct regmap **maps, int *irq_bases,
+                        const struct regmap_irq_chip **chips,
+                        struct regmap_irq_chips_data **data);
+void regmap_del_irq_chips(struct regmap_irq_chips_data *d);
+struct regmap_irq_chip_data *regmap_irq_chips_get_chip(
+                               struct regmap_irq_chips_data *d, int chip);
+
 #else
 
 /*
-- 
1.7.0.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to