Merge branch 'i2c-mux/for-next' of https://github.com/peda-r/i2c-mux into i2c/for-4.11

This commit is contained in:
Wolfram Sang 2017-02-10 12:51:23 +01:00
commit 465b2c4ab8
2 changed files with 159 additions and 5 deletions

View File

@ -19,7 +19,14 @@ Optional Properties:
- i2c-mux-idle-disconnect: Boolean; if defined, forces mux to disconnect all - i2c-mux-idle-disconnect: Boolean; if defined, forces mux to disconnect all
children in idle state. This is necessary for example, if there are several children in idle state. This is necessary for example, if there are several
multiplexers on the bus and the devices behind them use same I2C addresses. multiplexers on the bus and the devices behind them use same I2C addresses.
- interrupt-parent: Phandle for the interrupt controller that services
interrupts for this device.
- interrupts: Interrupt mapping for IRQ.
- interrupt-controller: Marks the device node as an interrupt controller.
- #interrupt-cells : Should be two.
- first cell is the pin number
- second cell is used to specify flags.
See also Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
Example: Example:
@ -29,6 +36,11 @@ Example:
#size-cells = <0>; #size-cells = <0>;
reg = <0x74>; reg = <0x74>;
interrupt-parent = <&ipic>;
interrupts = <17 IRQ_TYPE_LEVEL_LOW>;
interrupt-controller;
#interrupt-cells = <2>;
i2c@2 { i2c@2 {
#address-cells = <1>; #address-cells = <1>;
#size-cells = <0>; #size-cells = <0>;

View File

@ -41,14 +41,20 @@
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/i2c-mux.h> #include <linux/i2c-mux.h>
#include <linux/i2c/pca954x.h> #include <linux/i2c/pca954x.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/pm.h> #include <linux/pm.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/spinlock.h>
#define PCA954X_MAX_NCHANS 8 #define PCA954X_MAX_NCHANS 8
#define PCA954X_IRQ_OFFSET 4
enum pca_type { enum pca_type {
pca_9540, pca_9540,
pca_9542, pca_9542,
@ -63,6 +69,7 @@ enum pca_type {
struct chip_desc { struct chip_desc {
u8 nchans; u8 nchans;
u8 enable; /* used for muxes only */ u8 enable; /* used for muxes only */
u8 has_irq;
enum muxtype { enum muxtype {
pca954x_ismux = 0, pca954x_ismux = 0,
pca954x_isswi pca954x_isswi
@ -75,6 +82,10 @@ struct pca954x {
u8 last_chan; /* last register value */ u8 last_chan; /* last register value */
u8 deselect; u8 deselect;
struct i2c_client *client; struct i2c_client *client;
struct irq_domain *irq;
unsigned int irq_mask;
spinlock_t lock;
}; };
/* Provide specs for the PCA954x types we know about */ /* Provide specs for the PCA954x types we know about */
@ -84,17 +95,26 @@ static const struct chip_desc chips[] = {
.enable = 0x4, .enable = 0x4,
.muxtype = pca954x_ismux, .muxtype = pca954x_ismux,
}, },
[pca_9542] = {
.nchans = 2,
.enable = 0x4,
.has_irq = 1,
.muxtype = pca954x_ismux,
},
[pca_9543] = { [pca_9543] = {
.nchans = 2, .nchans = 2,
.has_irq = 1,
.muxtype = pca954x_isswi, .muxtype = pca954x_isswi,
}, },
[pca_9544] = { [pca_9544] = {
.nchans = 4, .nchans = 4,
.enable = 0x4, .enable = 0x4,
.has_irq = 1,
.muxtype = pca954x_ismux, .muxtype = pca954x_ismux,
}, },
[pca_9545] = { [pca_9545] = {
.nchans = 4, .nchans = 4,
.has_irq = 1,
.muxtype = pca954x_isswi, .muxtype = pca954x_isswi,
}, },
[pca_9547] = { [pca_9547] = {
@ -110,7 +130,7 @@ static const struct chip_desc chips[] = {
static const struct i2c_device_id pca954x_id[] = { static const struct i2c_device_id pca954x_id[] = {
{ "pca9540", pca_9540 }, { "pca9540", pca_9540 },
{ "pca9542", pca_9540 }, { "pca9542", pca_9542 },
{ "pca9543", pca_9543 }, { "pca9543", pca_9543 },
{ "pca9544", pca_9544 }, { "pca9544", pca_9544 },
{ "pca9545", pca_9545 }, { "pca9545", pca_9545 },
@ -124,7 +144,7 @@ MODULE_DEVICE_TABLE(i2c, pca954x_id);
#ifdef CONFIG_ACPI #ifdef CONFIG_ACPI
static const struct acpi_device_id pca954x_acpi_ids[] = { static const struct acpi_device_id pca954x_acpi_ids[] = {
{ .id = "PCA9540", .driver_data = pca_9540 }, { .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 = "PCA9543", .driver_data = pca_9543 },
{ .id = "PCA9544", .driver_data = pca_9544 }, { .id = "PCA9544", .driver_data = pca_9544 },
{ .id = "PCA9545", .driver_data = pca_9545 }, { .id = "PCA9545", .driver_data = pca_9545 },
@ -218,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); 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 * I2C init/probing/exit functions
*/ */
@ -282,6 +410,10 @@ static int pca954x_probe(struct i2c_client *client,
idle_disconnect_dt = of_node && idle_disconnect_dt = of_node &&
of_property_read_bool(of_node, "i2c-mux-idle-disconnect"); 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 */ /* Now create an adapter for each channel */
for (num = 0; num < data->chip->nchans; num++) { for (num = 0; num < data->chip->nchans; num++) {
bool idle_disconnect_pd = false; bool idle_disconnect_pd = false;
@ -307,7 +439,7 @@ static int pca954x_probe(struct i2c_client *client,
dev_err(&client->dev, dev_err(&client->dev,
"failed to register multiplexed adapter" "failed to register multiplexed adapter"
" %d as bus %d\n", num, force); " %d as bus %d\n", num, force);
goto virt_reg_failed; goto fail_del_adapters;
} }
} }
@ -318,7 +450,7 @@ static int pca954x_probe(struct i2c_client *client,
return 0; return 0;
virt_reg_failed: fail_del_adapters:
i2c_mux_del_adapters(muxc); i2c_mux_del_adapters(muxc);
return ret; return ret;
} }
@ -326,6 +458,16 @@ virt_reg_failed:
static int pca954x_remove(struct i2c_client *client) static int pca954x_remove(struct i2c_client *client)
{ {
struct i2c_mux_core *muxc = i2c_get_clientdata(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); i2c_mux_del_adapters(muxc);
return 0; return 0;