mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-08 14:23:19 +00:00
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: Sungbo Eo <mans0n@gorani.run> Reviewed-by: Andy 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.run Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
This commit is contained in:
parent
e6827bc3fa
commit
16d44b6085
@ -962,6 +962,14 @@ config GPIO_PCA953X_IRQ
|
||||
Say yes here to enable the pca953x to be used as an interrupt
|
||||
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
|
||||
tristate "PCF857x, PCA{85,96}7x, and MAX732[89] I2C GPIO expanders"
|
||||
select GPIOLIB_IRQCHIP
|
||||
|
@ -111,6 +111,7 @@ obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o
|
||||
obj-$(CONFIG_GPIO_OMAP) += gpio-omap.o
|
||||
obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.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_PCH) += gpio-pch.o
|
||||
obj-$(CONFIG_GPIO_PCIE_IDIO_24) += gpio-pcie-idio-24.o
|
||||
|
146
drivers/gpio/gpio-pca9570.c
Normal file
146
drivers/gpio/gpio-pca9570.c
Normal file
@ -0,0 +1,146 @@
|
||||
// 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");
|
Loading…
Reference in New Issue
Block a user