diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/i2c/muxes/i2c-mux-pca954x.c | 141 |
1 files changed, 139 insertions, 2 deletions
diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c index bbf088e08d73..06fd9cc85b23 100644 --- a/drivers/i2c/muxes/i2c-mux-pca954x.c +++ b/drivers/i2c/muxes/i2c-mux-pca954x.c @@ -41,14 +41,20 @@ #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 */ @@ -87,19 +98,23 @@ static const struct chip_desc chips[] = { [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] = { @@ -222,6 +237,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 */ @@ -286,6 +409,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; @@ -311,7 +438,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; } } @@ -322,7 +449,7 @@ static int pca954x_probe(struct i2c_client *client, return 0; -virt_reg_failed: +fail_del_adapters: i2c_mux_del_adapters(muxc); return ret; } @@ -330,6 +457,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; |