]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/i2c/muxes/i2c-mux-pca954x.c
Merge branch 'i2c-mux/for-next' of https://github.com/peda-r/i2c-mux into i2c/for...
[karo-tx-linux.git] / drivers / i2c / muxes / i2c-mux-pca954x.c
index dd18b9ccb1f40b4f6ddf5904e1f6d529931801df..dfc1c0e37c4022b66da0facee93af030c70c4e73 100644 (file)
 #include <linux/i2c.h>
 #include <linux/i2c-mux.h>
 #include <linux/i2c/pca954x.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
+#include <linux/of_irq.h>
 #include <linux/pm.h>
 #include <linux/slab.h>
+#include <linux/spinlock.h>
 
 #define PCA954X_MAX_NCHANS 8
 
+#define PCA954X_IRQ_OFFSET 4
+
 enum pca_type {
        pca_9540,
        pca_9542,
@@ -63,6 +69,7 @@ enum pca_type {
 struct chip_desc {
        u8 nchans;
        u8 enable;      /* used for muxes only */
+       u8 has_irq;
        enum muxtype {
                pca954x_ismux = 0,
                pca954x_isswi
@@ -75,6 +82,10 @@ struct pca954x {
        u8 last_chan;           /* last register value */
        u8 deselect;
        struct i2c_client *client;
+
+       struct irq_domain *irq;
+       unsigned int irq_mask;
+       spinlock_t lock;
 };
 
 /* Provide specs for the PCA954x types we know about */
@@ -84,17 +95,26 @@ static const struct chip_desc chips[] = {
                .enable = 0x4,
                .muxtype = pca954x_ismux,
        },
+       [pca_9542] = {
+               .nchans = 2,
+               .enable = 0x4,
+               .has_irq = 1,
+               .muxtype = pca954x_ismux,
+       },
        [pca_9543] = {
                .nchans = 2,
+               .has_irq = 1,
                .muxtype = pca954x_isswi,
        },
        [pca_9544] = {
                .nchans = 4,
                .enable = 0x4,
+               .has_irq = 1,
                .muxtype = pca954x_ismux,
        },
        [pca_9545] = {
                .nchans = 4,
+               .has_irq = 1,
                .muxtype = pca954x_isswi,
        },
        [pca_9547] = {
@@ -110,7 +130,7 @@ static const struct chip_desc chips[] = {
 
 static const struct i2c_device_id pca954x_id[] = {
        { "pca9540", pca_9540 },
-       { "pca9542", pca_9540 },
+       { "pca9542", pca_9542 },
        { "pca9543", pca_9543 },
        { "pca9544", pca_9544 },
        { "pca9545", pca_9545 },
@@ -124,7 +144,7 @@ MODULE_DEVICE_TABLE(i2c, pca954x_id);
 #ifdef CONFIG_ACPI
 static const struct acpi_device_id pca954x_acpi_ids[] = {
        { .id = "PCA9540", .driver_data = pca_9540 },
-       { .id = "PCA9542", .driver_data = pca_9540 },
+       { .id = "PCA9542", .driver_data = pca_9542 },
        { .id = "PCA9543", .driver_data = pca_9543 },
        { .id = "PCA9544", .driver_data = pca_9544 },
        { .id = "PCA9545", .driver_data = pca_9545 },
@@ -148,6 +168,7 @@ static const struct of_device_id pca954x_of_match[] = {
        { .compatible = "nxp,pca9548", .data = &chips[pca_9548] },
        {}
 };
+MODULE_DEVICE_TABLE(of, pca954x_of_match);
 #endif
 
 /* Write to mux register. Don't use i2c_transfer()/i2c_smbus_xfer()
@@ -217,6 +238,114 @@ static int pca954x_deselect_mux(struct i2c_mux_core *muxc, u32 chan)
        return pca954x_reg_write(muxc->parent, client, data->last_chan);
 }
 
+static irqreturn_t pca954x_irq_handler(int irq, void *dev_id)
+{
+       struct pca954x *data = dev_id;
+       unsigned int child_irq;
+       int ret, i, handled = 0;
+
+       ret = i2c_smbus_read_byte(data->client);
+       if (ret < 0)
+               return IRQ_NONE;
+
+       for (i = 0; i < data->chip->nchans; i++) {
+               if (ret & BIT(PCA954X_IRQ_OFFSET + i)) {
+                       child_irq = irq_linear_revmap(data->irq, i);
+                       handle_nested_irq(child_irq);
+                       handled++;
+               }
+       }
+       return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static void pca954x_irq_mask(struct irq_data *idata)
+{
+       struct pca954x *data = irq_data_get_irq_chip_data(idata);
+       unsigned int pos = idata->hwirq;
+       unsigned long flags;
+
+       spin_lock_irqsave(&data->lock, flags);
+
+       data->irq_mask &= ~BIT(pos);
+       if (!data->irq_mask)
+               disable_irq(data->client->irq);
+
+       spin_unlock_irqrestore(&data->lock, flags);
+}
+
+static void pca954x_irq_unmask(struct irq_data *idata)
+{
+       struct pca954x *data = irq_data_get_irq_chip_data(idata);
+       unsigned int pos = idata->hwirq;
+       unsigned long flags;
+
+       spin_lock_irqsave(&data->lock, flags);
+
+       if (!data->irq_mask)
+               enable_irq(data->client->irq);
+       data->irq_mask |= BIT(pos);
+
+       spin_unlock_irqrestore(&data->lock, flags);
+}
+
+static int pca954x_irq_set_type(struct irq_data *idata, unsigned int type)
+{
+       if ((type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_LOW)
+               return -EINVAL;
+       return 0;
+}
+
+static struct irq_chip pca954x_irq_chip = {
+       .name = "i2c-mux-pca954x",
+       .irq_mask = pca954x_irq_mask,
+       .irq_unmask = pca954x_irq_unmask,
+       .irq_set_type = pca954x_irq_set_type,
+};
+
+static int pca954x_irq_setup(struct i2c_mux_core *muxc)
+{
+       struct pca954x *data = i2c_mux_priv(muxc);
+       struct i2c_client *client = data->client;
+       int c, err, irq;
+
+       if (!data->chip->has_irq || client->irq <= 0)
+               return 0;
+
+       spin_lock_init(&data->lock);
+
+       data->irq = irq_domain_add_linear(client->dev.of_node,
+                                         data->chip->nchans,
+                                         &irq_domain_simple_ops, data);
+       if (!data->irq)
+               return -ENODEV;
+
+       for (c = 0; c < data->chip->nchans; c++) {
+               irq = irq_create_mapping(data->irq, c);
+               irq_set_chip_data(irq, data);
+               irq_set_chip_and_handler(irq, &pca954x_irq_chip,
+                       handle_simple_irq);
+       }
+
+       err = devm_request_threaded_irq(&client->dev, data->client->irq, NULL,
+                                       pca954x_irq_handler,
+                                       IRQF_ONESHOT | IRQF_SHARED,
+                                       "pca954x", data);
+       if (err)
+               goto err_req_irq;
+
+       disable_irq(data->client->irq);
+
+       return 0;
+err_req_irq:
+       for (c = 0; c < data->chip->nchans; c++) {
+               irq = irq_find_mapping(data->irq, c);
+               irq_dispose_mapping(irq);
+       }
+       irq_domain_remove(data->irq);
+
+       return err;
+}
+
 /*
  * I2C init/probing/exit functions
  */
@@ -281,6 +410,10 @@ static int pca954x_probe(struct i2c_client *client,
        idle_disconnect_dt = of_node &&
                of_property_read_bool(of_node, "i2c-mux-idle-disconnect");
 
+       ret = pca954x_irq_setup(muxc);
+       if (ret)
+               goto fail_del_adapters;
+
        /* Now create an adapter for each channel */
        for (num = 0; num < data->chip->nchans; num++) {
                bool idle_disconnect_pd = false;
@@ -306,7 +439,7 @@ static int pca954x_probe(struct i2c_client *client,
                        dev_err(&client->dev,
                                "failed to register multiplexed adapter"
                                " %d as bus %d\n", num, force);
-                       goto virt_reg_failed;
+                       goto fail_del_adapters;
                }
        }
 
@@ -317,7 +450,7 @@ static int pca954x_probe(struct i2c_client *client,
 
        return 0;
 
-virt_reg_failed:
+fail_del_adapters:
        i2c_mux_del_adapters(muxc);
        return ret;
 }
@@ -325,6 +458,16 @@ virt_reg_failed:
 static int pca954x_remove(struct i2c_client *client)
 {
        struct i2c_mux_core *muxc = i2c_get_clientdata(client);
+       struct pca954x *data = i2c_mux_priv(muxc);
+       int c, irq;
+
+       if (data->irq) {
+               for (c = 0; c < data->chip->nchans; c++) {
+                       irq = irq_find_mapping(data->irq, c);
+                       irq_dispose_mapping(irq);
+               }
+               irq_domain_remove(data->irq);
+       }
 
        i2c_mux_del_adapters(muxc);
        return 0;