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 <[email protected]>
---
 drivers/gpio/gpio-pcf857x.c | 41 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 36 insertions(+), 5 deletions(-)

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.
-- 
2.4.9

--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to