lkml.org 
[lkml]   [2012]   [Jul]   [27]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCH 3/3] regmap: enhance regmap-irq to handle 1 IRQ feeding n chips
Date
From: Stephen Warren <swarren@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 <swarren@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 <broonie@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


\
 
 \ /
  Last update: 2012-07-27 21:41    [W:0.092 / U:0.120 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site