mfd: tps65217: Add support for IRQs
[cascardo/linux.git] / drivers / mfd / tps65217.c
index 049a6fc..41b5d59 100644 (file)
  * GNU General Public License for more details.
  */
 
-#include <linux/kernel.h>
 #include <linux/device.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
+#include <linux/err.h>
 #include <linux/init.h>
+#include <linux/interrupt.h>
 #include <linux/i2c.h>
-#include <linux/slab.h>
-#include <linux/regmap.h>
-#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
 
 #include <linux/mfd/core.h>
 #include <linux/mfd/tps65217.h>
 
-static const struct mfd_cell tps65217s[] = {
+static struct resource charger_resources[] = {
+       DEFINE_RES_IRQ_NAMED(TPS65217_IRQ_AC, "AC"),
+       DEFINE_RES_IRQ_NAMED(TPS65217_IRQ_USB, "USB"),
+};
+
+struct tps65217_irq {
+       int mask;
+       int interrupt;
+};
+
+static const struct tps65217_irq tps65217_irqs[] = {
+       [TPS65217_IRQ_PB] = {
+               .mask = TPS65217_INT_PBM,
+               .interrupt = TPS65217_INT_PBI,
+       },
+       [TPS65217_IRQ_AC] = {
+               .mask = TPS65217_INT_ACM,
+               .interrupt = TPS65217_INT_ACI,
+       },
+       [TPS65217_IRQ_USB] = {
+               .mask = TPS65217_INT_USBM,
+               .interrupt = TPS65217_INT_USBI,
+       },
+};
+
+static void tps65217_irq_lock(struct irq_data *data)
+{
+       struct tps65217 *tps = irq_data_get_irq_chip_data(data);
+
+       mutex_lock(&tps->irq_lock);
+}
+
+static void tps65217_irq_sync_unlock(struct irq_data *data)
+{
+       struct tps65217 *tps = irq_data_get_irq_chip_data(data);
+       int ret;
+
+       ret = tps65217_reg_write(tps, TPS65217_REG_INT, tps->irq_mask,
+                               TPS65217_PROTECT_NONE);
+       if (ret != 0)
+               dev_err(tps->dev, "Failed to sync IRQ masks\n");
+
+       mutex_unlock(&tps->irq_lock);
+}
+
+static const inline struct tps65217_irq *
+irq_to_tps65217_irq(struct tps65217 *tps, struct irq_data *data)
+{
+       return &tps65217_irqs[data->hwirq];
+}
+
+static void tps65217_irq_enable(struct irq_data *data)
+{
+       struct tps65217 *tps = irq_data_get_irq_chip_data(data);
+       const struct tps65217_irq *irq_data = irq_to_tps65217_irq(tps, data);
+
+       tps->irq_mask &= ~irq_data->mask;
+}
+
+static void tps65217_irq_disable(struct irq_data *data)
+{
+       struct tps65217 *tps = irq_data_get_irq_chip_data(data);
+       const struct tps65217_irq *irq_data = irq_to_tps65217_irq(tps, data);
+
+       tps->irq_mask |= irq_data->mask;
+}
+
+static struct irq_chip tps65217_irq_chip = {
+       .irq_bus_lock           = tps65217_irq_lock,
+       .irq_bus_sync_unlock    = tps65217_irq_sync_unlock,
+       .irq_enable             = tps65217_irq_enable,
+       .irq_disable            = tps65217_irq_disable,
+};
+
+static struct mfd_cell tps65217s[] = {
        {
                .name = "tps65217-pmic",
                .of_compatible = "ti,tps65217-pmic",
@@ -41,10 +118,90 @@ static const struct mfd_cell tps65217s[] = {
        },
        {
                .name = "tps65217-charger",
+               .num_resources = ARRAY_SIZE(charger_resources),
+               .resources = charger_resources,
                .of_compatible = "ti,tps65217-charger",
        },
 };
 
+static irqreturn_t tps65217_irq_thread(int irq, void *data)
+{
+       struct tps65217 *tps = data;
+       unsigned int status;
+       bool handled = false;
+       int i;
+       int ret;
+
+       ret = tps65217_reg_read(tps, TPS65217_REG_INT, &status);
+       if (ret < 0) {
+               dev_err(tps->dev, "Failed to read IRQ status: %d\n",
+                       ret);
+               return IRQ_NONE;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(tps65217_irqs); i++) {
+               if (status & tps65217_irqs[i].interrupt) {
+                       handle_nested_irq(irq_find_mapping(tps->irq_domain, i));
+                       handled = true;
+               }
+       }
+
+       if (handled)
+               return IRQ_HANDLED;
+
+       return IRQ_NONE;
+}
+
+static int tps65217_irq_map(struct irq_domain *h, unsigned int virq,
+                       irq_hw_number_t hw)
+{
+       struct tps65217 *tps = h->host_data;
+
+       irq_set_chip_data(virq, tps);
+       irq_set_chip_and_handler(virq, &tps65217_irq_chip, handle_edge_irq);
+       irq_set_nested_thread(virq, 1);
+       irq_set_parent(virq, tps->irq);
+       irq_set_noprobe(virq);
+
+       return 0;
+}
+
+static const struct irq_domain_ops tps65217_irq_domain_ops = {
+       .map = tps65217_irq_map,
+};
+
+static int tps65217_irq_init(struct tps65217 *tps, int irq)
+{
+       int ret;
+
+       mutex_init(&tps->irq_lock);
+       tps->irq = irq;
+
+       /* Mask all interrupt sources */
+       tps->irq_mask = (TPS65217_INT_RESERVEDM | TPS65217_INT_PBM
+                       | TPS65217_INT_ACM | TPS65217_INT_USBM);
+       tps65217_reg_write(tps, TPS65217_REG_INT, tps->irq_mask,
+                       TPS65217_PROTECT_NONE);
+
+       tps->irq_domain = irq_domain_add_linear(tps->dev->of_node,
+               TPS65217_NUM_IRQ, &tps65217_irq_domain_ops, tps);
+       if (!tps->irq_domain) {
+               dev_err(tps->dev, "Could not create IRQ domain\n");
+               return -ENOMEM;
+       }
+
+       ret = devm_request_threaded_irq(tps->dev, irq, NULL,
+                                       tps65217_irq_thread, IRQF_ONESHOT,
+                                       "tps65217-irq", tps);
+       if (ret) {
+               dev_err(tps->dev, "Failed to request IRQ %d: %d\n",
+                       irq, ret);
+               return ret;
+       }
+
+       return 0;
+}
+
 /**
  * tps65217_reg_read: Read a single tps65217 register.
  *
@@ -149,11 +306,22 @@ int tps65217_clear_bits(struct tps65217 *tps, unsigned int reg,
 }
 EXPORT_SYMBOL_GPL(tps65217_clear_bits);
 
+static bool tps65217_volatile_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case TPS65217_REG_INT:
+               return true;
+       default:
+               return false;
+       }
+}
+
 static const struct regmap_config tps65217_regmap_config = {
        .reg_bits = 8,
        .val_bits = 8,
 
        .max_register = TPS65217_REG_MAX,
+       .volatile_reg = tps65217_volatile_reg,
 };
 
 static const struct of_device_id tps65217_of_match[] = {
@@ -205,8 +373,19 @@ static int tps65217_probe(struct i2c_client *client,
                return ret;
        }
 
+       if (client->irq) {
+               tps65217_irq_init(tps, client->irq);
+       } else {
+               int i;
+
+               /* Don't tell children about IRQ resources which won't fire */
+               for (i = 0; i < ARRAY_SIZE(tps65217s); i++)
+                       tps65217s[i].num_resources = 0;
+       }
+
        ret = devm_mfd_add_devices(tps->dev, -1, tps65217s,
-                                  ARRAY_SIZE(tps65217s), NULL, 0, NULL);
+                                  ARRAY_SIZE(tps65217s), NULL, 0,
+                                  tps->irq_domain);
        if (ret < 0) {
                dev_err(tps->dev, "mfd_add_devices failed: %d\n", ret);
                return ret;