diff mbox

[RFC/PATCH,2/2] gpio: pcf857x: Add PCA9621 support

Message ID 1444329152-11946-3-git-send-email-laurent.pinchart@ideasonboard.com
State New
Headers show

Commit Message

Laurent Pinchart Oct. 8, 2015, 6:32 p.m. UTC
The PCA9621 is an I2C 8-bit output open-drain expander. The driver has
to be adapted to support open-drain outputs as the register bit values
are inverted compared to currently supported chips.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpio/gpio-pcf857x.c | 41 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 36 insertions(+), 5 deletions(-)
diff mbox

Patch

diff --git a/drivers/gpio/gpio-pcf857x.c b/drivers/gpio/gpio-pcf857x.c
index 404f3c61ef9b..24788f2da93b 100644
--- a/drivers/gpio/gpio-pcf857x.c
+++ b/drivers/gpio/gpio-pcf857x.c
@@ -31,6 +31,9 @@ 
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 
+#define PCF857X_FLAG_OPEN_DRAIN		(1 << 8)
+#define PCF857X_FLAGS_MASK		(0xff << 8)
+#define PCF857X_INPUTS_MASK		0xff
 
 static const struct i2c_device_id pcf857x_id[] = {
 	{ "pcf8574", 8 },
@@ -41,6 +44,7 @@  static const struct i2c_device_id pcf857x_id[] = {
 	{ "pca9674", 8 },
 	{ "pcf8575", 16 },
 	{ "pca8575", 16 },
+	{ "pca9621", 8 | OPEN_DRAIN },
 	{ "pca9671", 16 },
 	{ "pca9673", 16 },
 	{ "pca9675", 16 },
@@ -56,6 +60,7 @@  static const struct of_device_id pcf857x_of_table[] = {
 	{ .compatible = "nxp,pcf8574" },
 	{ .compatible = "nxp,pcf8574a" },
 	{ .compatible = "nxp,pca8574" },
+	{ .compatible = "nxp,pca9621" },
 	{ .compatible = "nxp,pca9670" },
 	{ .compatible = "nxp,pca9672" },
 	{ .compatible = "nxp,pca9674" },
@@ -93,6 +98,7 @@  struct pcf857x {
 	unsigned		status;		/* current status */
 	unsigned int		irq_parent;
 	unsigned		irq_enabled;	/* enabled irqs */
+	unsigned		flags;
 
 	int (*write)(struct i2c_client *client, unsigned data);
 	int (*read)(struct i2c_client *client);
@@ -142,7 +148,10 @@  static int pcf857x_input(struct gpio_chip *chip, unsigned offset)
 	int		status;
 
 	mutex_lock(&gpio->lock);
-	gpio->out |= (1 << offset);
+	if (gpio->flags & PCF857X_FLAG_OPEN_DRAIN)
+		gpio->out &= ~(1 << offset);
+	else
+		gpio->out |= (1 << offset);
 	status = gpio->write(gpio->client, gpio->out);
 	mutex_unlock(&gpio->lock);
 
@@ -155,7 +164,13 @@  static int pcf857x_get(struct gpio_chip *chip, unsigned offset)
 	int		value;
 
 	value = gpio->read(gpio->client);
-	return (value < 0) ? 0 : (value & (1 << offset));
+	if (value < 0)
+		return 0;
+
+	if (gpio->flags & PCF857X_FLAG_OPEN_DRAIN)
+		return !(value & (1 << offset));
+	else
+		return value & (1 << offset);
 }
 
 static int pcf857x_output(struct gpio_chip *chip, unsigned offset, int value)
@@ -164,6 +179,17 @@  static int pcf857x_output(struct gpio_chip *chip, unsigned offset, int value)
 	unsigned	bit = 1 << offset;
 	int		status;
 
+	if (gpio->flags & PCF857X_FLAG_OPEN_DRAIN) {
+		/* The output is open-drain and can't be driven high. */
+		if (value)
+			return -EINVAL;
+
+		/* To set the direction to output the register value has to be
+		 * set to 1.
+		 */
+		value = 1;
+	}
+
 	mutex_lock(&gpio->lock);
 	if (value)
 		gpio->out |= bit;
@@ -295,6 +321,8 @@  static int pcf857x_probe(struct i2c_client *client,
 	mutex_init(&gpio->lock);
 	spin_lock_init(&gpio->slock);
 
+	gpio->flags = id->driver_data & PCF857X_FLAGS_MASK;
+
 	gpio->chip.base			= pdata ? pdata->gpio_base : -1;
 	gpio->chip.can_sleep		= true;
 	gpio->chip.dev			= &client->dev;
@@ -303,7 +331,7 @@  static int pcf857x_probe(struct i2c_client *client,
 	gpio->chip.set			= pcf857x_set;
 	gpio->chip.direction_input	= pcf857x_input;
 	gpio->chip.direction_output	= pcf857x_output;
-	gpio->chip.ngpio		= id->driver_data;
+	gpio->chip.ngpio		= id->driver_data & PCF857X_INPUTS_MASK;
 
 	/* NOTE:  the OnSemi jlc1562b is also largely compatible with
 	 * these parts, notably for output.  It has a low-resolution
@@ -321,12 +349,15 @@  static int pcf857x_probe(struct i2c_client *client,
 		gpio->read	= i2c_read_le8;
 
 		if (!i2c_check_functionality(client->adapter,
-				I2C_FUNC_SMBUS_BYTE))
+				I2C_FUNC_SMBUS_BYTE)) {
 			status = -EIO;
 
 		/* fail if there's no chip present */
-		else
+		} else {
 			status = i2c_smbus_read_byte(client);
+			if (gpio->flags & PCF857X_FLAG_OPEN_DRAIN)
+				n_latch = status;
+		}
 
 	/* '75/'75c addresses are 0x20..0x27, just like the '74;
 	 * the '75c doesn't have a current source pulling high.