linux/drivers/mfd/twl6040-irq.c
<<
>>
Prefs
   1/*
   2 * Interrupt controller support for TWL6040
   3 *
   4 * Author:     Misael Lopez Cruz <misael.lopez@ti.com>
   5 *
   6 * Copyright:   (C) 2011 Texas Instruments, Inc.
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License version 2 as
  10 * published by the Free Software Foundation.
  11 *
  12 * This program is distributed in the hope that it will be useful, but
  13 * WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15 * General Public License for more details.
  16 *
  17 * You should have received a copy of the GNU General Public License
  18 * along with this program; if not, write to the Free Software
  19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  20 * 02110-1301 USA
  21 *
  22 */
  23
  24#include <linux/kernel.h>
  25#include <linux/module.h>
  26#include <linux/err.h>
  27#include <linux/irq.h>
  28#include <linux/of.h>
  29#include <linux/irqdomain.h>
  30#include <linux/interrupt.h>
  31#include <linux/mfd/core.h>
  32#include <linux/mfd/twl6040.h>
  33
  34struct twl6040_irq_data {
  35        int mask;
  36        int status;
  37};
  38
  39static struct twl6040_irq_data twl6040_irqs[] = {
  40        {
  41                .mask = TWL6040_THMSK,
  42                .status = TWL6040_THINT,
  43        },
  44        {
  45                .mask = TWL6040_PLUGMSK,
  46                .status = TWL6040_PLUGINT | TWL6040_UNPLUGINT,
  47        },
  48        {
  49                .mask = TWL6040_HOOKMSK,
  50                .status = TWL6040_HOOKINT,
  51        },
  52        {
  53                .mask = TWL6040_HFMSK,
  54                .status = TWL6040_HFINT,
  55        },
  56        {
  57                .mask = TWL6040_VIBMSK,
  58                .status = TWL6040_VIBINT,
  59        },
  60        {
  61                .mask = TWL6040_READYMSK,
  62                .status = TWL6040_READYINT,
  63        },
  64};
  65
  66static inline
  67struct twl6040_irq_data *irq_to_twl6040_irq(struct twl6040 *twl6040,
  68                                            int irq)
  69{
  70        return &twl6040_irqs[irq - twl6040->irq_base];
  71}
  72
  73static void twl6040_irq_lock(struct irq_data *data)
  74{
  75        struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
  76
  77        mutex_lock(&twl6040->irq_mutex);
  78}
  79
  80static void twl6040_irq_sync_unlock(struct irq_data *data)
  81{
  82        struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
  83
  84        /* write back to hardware any change in irq mask */
  85        if (twl6040->irq_masks_cur != twl6040->irq_masks_cache) {
  86                twl6040->irq_masks_cache = twl6040->irq_masks_cur;
  87                twl6040_reg_write(twl6040, TWL6040_REG_INTMR,
  88                                  twl6040->irq_masks_cur);
  89        }
  90
  91        mutex_unlock(&twl6040->irq_mutex);
  92}
  93
  94static void twl6040_irq_enable(struct irq_data *data)
  95{
  96        struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
  97        struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040,
  98                                                               data->irq);
  99
 100        twl6040->irq_masks_cur &= ~irq_data->mask;
 101}
 102
 103static void twl6040_irq_disable(struct irq_data *data)
 104{
 105        struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
 106        struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040,
 107                                                               data->irq);
 108
 109        twl6040->irq_masks_cur |= irq_data->mask;
 110}
 111
 112static struct irq_chip twl6040_irq_chip = {
 113        .name                   = "twl6040",
 114        .irq_bus_lock           = twl6040_irq_lock,
 115        .irq_bus_sync_unlock    = twl6040_irq_sync_unlock,
 116        .irq_enable             = twl6040_irq_enable,
 117        .irq_disable            = twl6040_irq_disable,
 118};
 119
 120static irqreturn_t twl6040_irq_thread(int irq, void *data)
 121{
 122        struct twl6040 *twl6040 = data;
 123        u8 intid;
 124        int i;
 125
 126        intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
 127
 128        /* apply masking and report (backwards to handle READYINT first) */
 129        for (i = ARRAY_SIZE(twl6040_irqs) - 1; i >= 0; i--) {
 130                if (twl6040->irq_masks_cur & twl6040_irqs[i].mask)
 131                        intid &= ~twl6040_irqs[i].status;
 132                if (intid & twl6040_irqs[i].status)
 133                        handle_nested_irq(twl6040->irq_base + i);
 134        }
 135
 136        /* ack unmasked irqs */
 137        twl6040_reg_write(twl6040, TWL6040_REG_INTID, intid);
 138
 139        return IRQ_HANDLED;
 140}
 141
 142int twl6040_irq_init(struct twl6040 *twl6040)
 143{
 144        struct device_node *node = twl6040->dev->of_node;
 145        int i, nr_irqs, irq_base, ret;
 146        u8 val;
 147
 148        mutex_init(&twl6040->irq_mutex);
 149
 150        /* mask the individual interrupt sources */
 151        twl6040->irq_masks_cur = TWL6040_ALLINT_MSK;
 152        twl6040->irq_masks_cache = TWL6040_ALLINT_MSK;
 153        twl6040_reg_write(twl6040, TWL6040_REG_INTMR, TWL6040_ALLINT_MSK);
 154
 155        nr_irqs = ARRAY_SIZE(twl6040_irqs);
 156
 157        irq_base = irq_alloc_descs(-1, 0, nr_irqs, 0);
 158        if (IS_ERR_VALUE(irq_base)) {
 159                dev_err(twl6040->dev, "Fail to allocate IRQ descs\n");
 160                return irq_base;
 161        }
 162        twl6040->irq_base = irq_base;
 163
 164        irq_domain_add_legacy(node, ARRAY_SIZE(twl6040_irqs), irq_base, 0,
 165                              &irq_domain_simple_ops, NULL);
 166
 167        /* Register them with genirq */
 168        for (i = irq_base; i < irq_base + nr_irqs; i++) {
 169                irq_set_chip_data(i, twl6040);
 170                irq_set_chip_and_handler(i, &twl6040_irq_chip,
 171                                         handle_level_irq);
 172                irq_set_nested_thread(i, 1);
 173
 174                /* ARM needs us to explicitly flag the IRQ as valid
 175                 * and will set them noprobe when we do so. */
 176#ifdef CONFIG_ARM
 177                set_irq_flags(i, IRQF_VALID);
 178#else
 179                irq_set_noprobe(i);
 180#endif
 181        }
 182
 183        ret = request_threaded_irq(twl6040->irq, NULL, twl6040_irq_thread,
 184                                   IRQF_ONESHOT, "twl6040", twl6040);
 185        if (ret) {
 186                dev_err(twl6040->dev, "failed to request IRQ %d: %d\n",
 187                        twl6040->irq, ret);
 188                return ret;
 189        }
 190
 191        /* reset interrupts */
 192        val = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
 193
 194        /* interrupts cleared on write */
 195        twl6040_clear_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_INTCLRMODE);
 196
 197        return 0;
 198}
 199EXPORT_SYMBOL(twl6040_irq_init);
 200
 201void twl6040_irq_exit(struct twl6040 *twl6040)
 202{
 203        free_irq(twl6040->irq, twl6040);
 204}
 205EXPORT_SYMBOL(twl6040_irq_exit);
 206
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.