Messages in this thread Patch in this message | | | From | Stephen Warren <> | Subject | [PATCH 3/3] regmap: enhance regmap-irq to handle 1 IRQ feeding n chips | Date | Fri, 27 Jul 2012 13:01:56 -0600 |
| |
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, ®maps_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, ®maps_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
| |