Commit 16d44b60 authored by Sungbo Eo's avatar Sungbo Eo Committed by Linus Walleij

gpio: pca9570: add GPO driver for PCA9570

NXP PCA9570 is a 4-bit I2C GPO expander without interrupt functionality.
Its ports are controlled only by a data byte without register address.
Signed-off-by: default avatarSungbo Eo <mans0n@gorani.run>
Reviewed-by: default avatarAndy Shevchenko <andy.shevchenko@gmail.com>
Datasheet: https://www.nxp.com/docs/en/data-sheet/PCA9570.pdf
Link: https://lore.kernel.org/r/20200709134829.216393-1-mans0n@gorani.runSigned-off-by: default avatarLinus Walleij <linus.walleij@linaro.org>
parent e6827bc3
...@@ -962,6 +962,14 @@ config GPIO_PCA953X_IRQ ...@@ -962,6 +962,14 @@ config GPIO_PCA953X_IRQ
Say yes here to enable the pca953x to be used as an interrupt Say yes here to enable the pca953x to be used as an interrupt
controller. It requires the driver to be built in the kernel. controller. It requires the driver to be built in the kernel.
config GPIO_PCA9570
tristate "PCA9570 4-Bit I2C GPO expander"
help
Say yes here to enable the GPO driver for the NXP PCA9570 chip.
To compile this driver as a module, choose M here: the module will
be called gpio-pca9570.
config GPIO_PCF857X config GPIO_PCF857X
tristate "PCF857x, PCA{85,96}7x, and MAX732[89] I2C GPIO expanders" tristate "PCF857x, PCA{85,96}7x, and MAX732[89] I2C GPIO expanders"
select GPIOLIB_IRQCHIP select GPIOLIB_IRQCHIP
......
...@@ -111,6 +111,7 @@ obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o ...@@ -111,6 +111,7 @@ obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o
obj-$(CONFIG_GPIO_OMAP) += gpio-omap.o obj-$(CONFIG_GPIO_OMAP) += gpio-omap.o
obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.o obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.o
obj-$(CONFIG_GPIO_PCA953X) += gpio-pca953x.o obj-$(CONFIG_GPIO_PCA953X) += gpio-pca953x.o
obj-$(CONFIG_GPIO_PCA9570) += gpio-pca9570.o
obj-$(CONFIG_GPIO_PCF857X) += gpio-pcf857x.o obj-$(CONFIG_GPIO_PCF857X) += gpio-pcf857x.o
obj-$(CONFIG_GPIO_PCH) += gpio-pch.o obj-$(CONFIG_GPIO_PCH) += gpio-pch.o
obj-$(CONFIG_GPIO_PCIE_IDIO_24) += gpio-pcie-idio-24.o obj-$(CONFIG_GPIO_PCIE_IDIO_24) += gpio-pcie-idio-24.o
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for PCA9570 I2C GPO expander
*
* Copyright (C) 2020 Sungbo Eo <mans0n@gorani.run>
*
* Based on gpio-tpic2810.c
* Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/
* Andrew F. Davis <afd@ti.com>
*/
#include <linux/gpio/driver.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/property.h>
/**
* struct pca9570 - GPIO driver data
* @chip: GPIO controller chip
* @lock: Protects write sequences
* @out: Buffer for device register
*/
struct pca9570 {
struct gpio_chip chip;
struct mutex lock;
u8 out;
};
static int pca9570_read(struct pca9570 *gpio, u8 *value)
{
struct i2c_client *client = to_i2c_client(gpio->chip.parent);
int ret;
ret = i2c_smbus_read_byte(client);
if (ret < 0)
return ret;
*value = ret;
return 0;
}
static int pca9570_write(struct pca9570 *gpio, u8 value)
{
struct i2c_client *client = to_i2c_client(gpio->chip.parent);
return i2c_smbus_write_byte(client, value);
}
static int pca9570_get_direction(struct gpio_chip *chip,
unsigned offset)
{
/* This device always output */
return GPIO_LINE_DIRECTION_OUT;
}
static int pca9570_get(struct gpio_chip *chip, unsigned offset)
{
struct pca9570 *gpio = gpiochip_get_data(chip);
u8 buffer;
int ret;
ret = pca9570_read(gpio, &buffer);
if (ret)
return ret;
return !!(buffer & BIT(offset));
}
static void pca9570_set(struct gpio_chip *chip, unsigned offset, int value)
{
struct pca9570 *gpio = gpiochip_get_data(chip);
u8 buffer;
int ret;
mutex_lock(&gpio->lock);
buffer = gpio->out;
if (value)
buffer |= BIT(offset);
else
buffer &= ~BIT(offset);
ret = pca9570_write(gpio, buffer);
if (ret)
goto out;
gpio->out = buffer;
out:
mutex_unlock(&gpio->lock);
}
static int pca9570_probe(struct i2c_client *client)
{
struct pca9570 *gpio;
gpio = devm_kzalloc(&client->dev, sizeof(*gpio), GFP_KERNEL);
if (!gpio)
return -ENOMEM;
gpio->chip.label = client->name;
gpio->chip.parent = &client->dev;
gpio->chip.owner = THIS_MODULE;
gpio->chip.get_direction = pca9570_get_direction;
gpio->chip.get = pca9570_get;
gpio->chip.set = pca9570_set;
gpio->chip.base = -1;
gpio->chip.ngpio = (uintptr_t)device_get_match_data(&client->dev);
gpio->chip.can_sleep = true;
mutex_init(&gpio->lock);
/* Read the current output level */
pca9570_read(gpio, &gpio->out);
i2c_set_clientdata(client, gpio);
return devm_gpiochip_add_data(&client->dev, &gpio->chip, gpio);
}
static const struct i2c_device_id pca9570_id_table[] = {
{ "pca9570", 4 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, pca9570_id_table);
static const struct of_device_id pca9570_of_match_table[] = {
{ .compatible = "nxp,pca9570", .data = (void *)4 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, pca9570_of_match_table);
static struct i2c_driver pca9570_driver = {
.driver = {
.name = "pca9570",
.of_match_table = pca9570_of_match_table,
},
.probe_new = pca9570_probe,
.id_table = pca9570_id_table,
};
module_i2c_driver(pca9570_driver);
MODULE_AUTHOR("Sungbo Eo <mans0n@gorani.run>");
MODULE_DESCRIPTION("GPIO expander driver for PCA9570");
MODULE_LICENSE("GPL v2");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment