diff options
Diffstat (limited to 'drivers/gpio/gpio-pcf857x.c')
| -rw-r--r-- | drivers/gpio/gpio-pcf857x.c | 291 |
1 files changed, 202 insertions, 89 deletions
diff --git a/drivers/gpio/gpio-pcf857x.c b/drivers/gpio/gpio-pcf857x.c index 2d1de9e7e9b..27b46751ea7 100644 --- a/drivers/gpio/gpio-pcf857x.c +++ b/drivers/gpio/gpio-pcf857x.c @@ -18,12 +18,18 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include <linux/kernel.h> -#include <linux/slab.h> #include <linux/gpio.h> #include <linux/i2c.h> #include <linux/i2c/pcf857x.h> +#include <linux/interrupt.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/slab.h> +#include <linux/spinlock.h> static const struct i2c_device_id pcf857x_id[] = { @@ -40,10 +46,32 @@ static const struct i2c_device_id pcf857x_id[] = { { "pca9675", 16 }, { "max7328", 8 }, { "max7329", 8 }, + { "tca9554", 8 }, { } }; MODULE_DEVICE_TABLE(i2c, pcf857x_id); +#ifdef CONFIG_OF +static const struct of_device_id pcf857x_of_table[] = { + { .compatible = "nxp,pcf8574" }, + { .compatible = "nxp,pcf8574a" }, + { .compatible = "nxp,pca8574" }, + { .compatible = "nxp,pca9670" }, + { .compatible = "nxp,pca9672" }, + { .compatible = "nxp,pca9674" }, + { .compatible = "nxp,pcf8575" }, + { .compatible = "nxp,pca8575" }, + { .compatible = "nxp,pca9671" }, + { .compatible = "nxp,pca9673" }, + { .compatible = "nxp,pca9675" }, + { .compatible = "maxim,max7328" }, + { .compatible = "maxim,max7329" }, + { .compatible = "ti,tca9554" }, + { } +}; +MODULE_DEVICE_TABLE(of, pcf857x_of_table); +#endif + /* * The pcf857x, pca857x, and pca967x chips only expose one read and one * write register. Writing a "one" bit (to match the reset state) lets @@ -60,62 +88,33 @@ struct pcf857x { struct gpio_chip chip; struct i2c_client *client; struct mutex lock; /* protect 'out' */ + struct irq_domain *irq_domain; /* for irq demux */ + spinlock_t slock; /* protect irq demux */ unsigned out; /* software latch */ + unsigned status; /* current status */ + unsigned irq_mapped; /* mapped gpio irqs */ + + int (*write)(struct i2c_client *client, unsigned data); + int (*read)(struct i2c_client *client); }; /*-------------------------------------------------------------------------*/ /* Talk to 8-bit I/O expander */ -static int pcf857x_input8(struct gpio_chip *chip, unsigned offset) +static int i2c_write_le8(struct i2c_client *client, unsigned data) { - struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); - int status; - - mutex_lock(&gpio->lock); - gpio->out |= (1 << offset); - status = i2c_smbus_write_byte(gpio->client, gpio->out); - mutex_unlock(&gpio->lock); - - return status; + return i2c_smbus_write_byte(client, data); } -static int pcf857x_get8(struct gpio_chip *chip, unsigned offset) +static int i2c_read_le8(struct i2c_client *client) { - struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); - s32 value; - - value = i2c_smbus_read_byte(gpio->client); - return (value < 0) ? 0 : (value & (1 << offset)); + return (int)i2c_smbus_read_byte(client); } -static int pcf857x_output8(struct gpio_chip *chip, unsigned offset, int value) -{ - struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); - unsigned bit = 1 << offset; - int status; - - mutex_lock(&gpio->lock); - if (value) - gpio->out |= bit; - else - gpio->out &= ~bit; - status = i2c_smbus_write_byte(gpio->client, gpio->out); - mutex_unlock(&gpio->lock); - - return status; -} - -static void pcf857x_set8(struct gpio_chip *chip, unsigned offset, int value) -{ - pcf857x_output8(chip, offset, value); -} - -/*-------------------------------------------------------------------------*/ - /* Talk to 16-bit I/O expander */ -static int i2c_write_le16(struct i2c_client *client, u16 word) +static int i2c_write_le16(struct i2c_client *client, unsigned word) { u8 buf[2] = { word & 0xff, word >> 8, }; int status; @@ -135,29 +134,31 @@ static int i2c_read_le16(struct i2c_client *client) return (buf[1] << 8) | buf[0]; } -static int pcf857x_input16(struct gpio_chip *chip, unsigned offset) +/*-------------------------------------------------------------------------*/ + +static int pcf857x_input(struct gpio_chip *chip, unsigned offset) { struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); int status; mutex_lock(&gpio->lock); gpio->out |= (1 << offset); - status = i2c_write_le16(gpio->client, gpio->out); + status = gpio->write(gpio->client, gpio->out); mutex_unlock(&gpio->lock); return status; } -static int pcf857x_get16(struct gpio_chip *chip, unsigned offset) +static int pcf857x_get(struct gpio_chip *chip, unsigned offset) { struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); int value; - value = i2c_read_le16(gpio->client); + value = gpio->read(gpio->client); return (value < 0) ? 0 : (value & (1 << offset)); } -static int pcf857x_output16(struct gpio_chip *chip, unsigned offset, int value) +static int pcf857x_output(struct gpio_chip *chip, unsigned offset, int value) { struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); unsigned bit = 1 << offset; @@ -168,15 +169,113 @@ static int pcf857x_output16(struct gpio_chip *chip, unsigned offset, int value) gpio->out |= bit; else gpio->out &= ~bit; - status = i2c_write_le16(gpio->client, gpio->out); + status = gpio->write(gpio->client, gpio->out); mutex_unlock(&gpio->lock); return status; } -static void pcf857x_set16(struct gpio_chip *chip, unsigned offset, int value) +static void pcf857x_set(struct gpio_chip *chip, unsigned offset, int value) { - pcf857x_output16(chip, offset, value); + pcf857x_output(chip, offset, value); +} + +/*-------------------------------------------------------------------------*/ + +static int pcf857x_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); + int ret; + + ret = irq_create_mapping(gpio->irq_domain, offset); + if (ret > 0) + gpio->irq_mapped |= (1 << offset); + + return ret; +} + +static irqreturn_t pcf857x_irq(int irq, void *data) +{ + struct pcf857x *gpio = data; + unsigned long change, i, status, flags; + + status = gpio->read(gpio->client); + + spin_lock_irqsave(&gpio->slock, flags); + + /* + * call the interrupt handler iff gpio is used as + * interrupt source, just to avoid bad irqs + */ + + change = ((gpio->status ^ status) & gpio->irq_mapped); + for_each_set_bit(i, &change, gpio->chip.ngpio) + generic_handle_irq(irq_find_mapping(gpio->irq_domain, i)); + gpio->status = status; + + spin_unlock_irqrestore(&gpio->slock, flags); + + return IRQ_HANDLED; +} + +static int pcf857x_irq_domain_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hw) +{ + struct pcf857x *gpio = domain->host_data; + + irq_set_chip_and_handler(irq, + &dummy_irq_chip, + handle_level_irq); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + gpio->irq_mapped |= (1 << hw); + + return 0; +} + +static struct irq_domain_ops pcf857x_irq_domain_ops = { + .map = pcf857x_irq_domain_map, +}; + +static void pcf857x_irq_domain_cleanup(struct pcf857x *gpio) +{ + if (gpio->irq_domain) + irq_domain_remove(gpio->irq_domain); + +} + +static int pcf857x_irq_domain_init(struct pcf857x *gpio, + struct i2c_client *client) +{ + int status; + + gpio->irq_domain = irq_domain_add_linear(client->dev.of_node, + gpio->chip.ngpio, + &pcf857x_irq_domain_ops, + gpio); + if (!gpio->irq_domain) + goto fail; + + /* enable real irq */ + status = devm_request_threaded_irq(&client->dev, client->irq, + NULL, pcf857x_irq, IRQF_ONESHOT | + IRQF_TRIGGER_FALLING | IRQF_SHARED, + dev_name(&client->dev), gpio); + + if (status) + goto fail; + + /* enable gpio_to_irq() */ + gpio->chip.to_irq = pcf857x_to_irq; + + return 0; + +fail: + pcf857x_irq_domain_cleanup(gpio); + return -EINVAL; } /*-------------------------------------------------------------------------*/ @@ -184,26 +283,45 @@ static void pcf857x_set16(struct gpio_chip *chip, unsigned offset, int value) static int pcf857x_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct pcf857x_platform_data *pdata; + struct pcf857x_platform_data *pdata = dev_get_platdata(&client->dev); + struct device_node *np = client->dev.of_node; struct pcf857x *gpio; + unsigned int n_latch = 0; int status; - pdata = client->dev.platform_data; - if (!pdata) { + if (IS_ENABLED(CONFIG_OF) && np) + of_property_read_u32(np, "lines-initial-states", &n_latch); + else if (pdata) + n_latch = pdata->n_latch; + else dev_dbg(&client->dev, "no platform data\n"); - } /* Allocate, initialize, and register this gpio_chip. */ - gpio = kzalloc(sizeof *gpio, GFP_KERNEL); + gpio = devm_kzalloc(&client->dev, sizeof(*gpio), GFP_KERNEL); if (!gpio) return -ENOMEM; mutex_init(&gpio->lock); - - gpio->chip.base = pdata ? pdata->gpio_base : -1; - gpio->chip.can_sleep = 1; - gpio->chip.dev = &client->dev; - gpio->chip.owner = THIS_MODULE; + spin_lock_init(&gpio->slock); + + gpio->chip.base = pdata ? pdata->gpio_base : -1; + gpio->chip.can_sleep = true; + gpio->chip.dev = &client->dev; + gpio->chip.owner = THIS_MODULE; + gpio->chip.get = pcf857x_get; + gpio->chip.set = pcf857x_set; + gpio->chip.direction_input = pcf857x_input; + gpio->chip.direction_output = pcf857x_output; + gpio->chip.ngpio = id->driver_data; + + /* enable gpio_to_irq() if platform has settings */ + if (client->irq) { + status = pcf857x_irq_domain_init(gpio, client); + if (status < 0) { + dev_err(&client->dev, "irq_domain init failed\n"); + goto fail_irq_domain; + } + } /* NOTE: the OnSemi jlc1562b is also largely compatible with * these parts, notably for output. It has a low-resolution @@ -216,12 +334,9 @@ static int pcf857x_probe(struct i2c_client *client, * * NOTE: we don't distinguish here between *4 and *4a parts. */ - gpio->chip.ngpio = id->driver_data; if (gpio->chip.ngpio == 8) { - gpio->chip.direction_input = pcf857x_input8; - gpio->chip.get = pcf857x_get8; - gpio->chip.direction_output = pcf857x_output8; - gpio->chip.set = pcf857x_set8; + gpio->write = i2c_write_le8; + gpio->read = i2c_read_le8; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE)) @@ -238,10 +353,8 @@ static int pcf857x_probe(struct i2c_client *client, * NOTE: we don't distinguish here between '75 and '75c parts. */ } else if (gpio->chip.ngpio == 16) { - gpio->chip.direction_input = pcf857x_input16; - gpio->chip.get = pcf857x_get16; - gpio->chip.direction_output = pcf857x_output16; - gpio->chip.set = pcf857x_set16; + gpio->write = i2c_write_le16; + gpio->read = i2c_read_le16; if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) status = -EIO; @@ -274,25 +387,17 @@ static int pcf857x_probe(struct i2c_client *client, * may cause transient glitching since it can't know the last value * written (some pins may need to be driven low). * - * Using pdata->n_latch avoids that trouble. When left initialized - * to zero, our software copy of the "latch" then matches the chip's - * all-ones reset state. Otherwise it flags pins to be driven low. + * Using n_latch avoids that trouble. When left initialized to zero, + * our software copy of the "latch" then matches the chip's all-ones + * reset state. Otherwise it flags pins to be driven low. */ - gpio->out = pdata ? ~pdata->n_latch : ~0; + gpio->out = ~n_latch; + gpio->status = gpio->out; status = gpiochip_add(&gpio->chip); if (status < 0) goto fail; - /* NOTE: these chips can issue "some pin-changed" IRQs, which we - * don't yet even try to use. Among other issues, the relevant - * genirq state isn't available to modular drivers; and most irq - * methods can't be called from sleeping contexts. - */ - - dev_info(&client->dev, "%s\n", - client->irq ? " (irq ignored)" : ""); - /* Let platform code set up the GPIOs and their users. * Now is the first time anyone could use them. */ @@ -304,18 +409,24 @@ static int pcf857x_probe(struct i2c_client *client, dev_warn(&client->dev, "setup --> %d\n", status); } + dev_info(&client->dev, "probed\n"); + return 0; fail: + if (client->irq) + pcf857x_irq_domain_cleanup(gpio); + +fail_irq_domain: dev_dbg(&client->dev, "probe error %d for '%s'\n", - status, client->name); - kfree(gpio); + status, client->name); + return status; } static int pcf857x_remove(struct i2c_client *client) { - struct pcf857x_platform_data *pdata = client->dev.platform_data; + struct pcf857x_platform_data *pdata = dev_get_platdata(&client->dev); struct pcf857x *gpio = i2c_get_clientdata(client); int status = 0; @@ -330,10 +441,11 @@ static int pcf857x_remove(struct i2c_client *client) } } + if (client->irq) + pcf857x_irq_domain_cleanup(gpio); + status = gpiochip_remove(&gpio->chip); - if (status == 0) - kfree(gpio); - else + if (status) dev_err(&client->dev, "%s --> %d\n", "remove", status); return status; } @@ -342,6 +454,7 @@ static struct i2c_driver pcf857x_driver = { .driver = { .name = "pcf857x", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pcf857x_of_table), }, .probe = pcf857x_probe, .remove = pcf857x_remove, |
