2011-05-05 04:12:21

by Jonathan McDowell

[permalink] [raw]
Subject: [PATCH] Add support for VSC055 Enhanced Two-Wire Serial Backplane controller

I'd been holding off on sending this until I got some feedback about the
PC8741x driver I sent a couple of weeks ago, but seeing as I have it
sitting around I figured I may as well send it anyway.

-----
Add support for the GPIOs on Maxim VSC055 I2C Enhanced Two-Wire Serial
Backplane controller.

This chip features 64 bits of user-definable, bidirectional GPIOs. It also
has the ability to do PWM fan control output and LED flashing, but support
for that functionality is not included at present.

Signed-off-by: Jonathan McDowell <[email protected]>

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index cc150db..4c5de65 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -255,6 +255,13 @@ config GPIO_TWL4030
Say yes here to access the GPIO signals of various multi-function
power management chips from Texas Instruments.

+config GPIO_VSC055
+ tristate "VSC055 GPIOs"
+ depends on I2C
+ help
+ Say yes here to access the GPIOs found on the Maxim VSC055 Enhanced
+ Two-Wire Serial Backplane controller.
+
config GPIO_WM831X
tristate "WM831x GPIOs"
depends on MFD_WM831X
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index d2752b2..f82491d 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -42,4 +42,5 @@ obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o
obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o
obj-$(CONFIG_GPIO_SX150X) += sx150x.o
obj-$(CONFIG_GPIO_VX855) += vx855_gpio.o
+obj-$(CONFIG_GPIO_VSC055) += vsc055.o
obj-$(CONFIG_GPIO_ML_IOH) += ml_ioh_gpio.o
diff --git a/drivers/gpio/vsc055.c b/drivers/gpio/vsc055.c
new file mode 100644
index 0000000..c218dac
--- /dev/null
+++ b/drivers/gpio/vsc055.c
@@ -0,0 +1,326 @@
+/*
+ * vsc055.c - Enhanced Two-Wire Serial Backplane Control with 64 I/O lines.
+ *
+ * Copyright (C) 2011 Jonathan McDowell <[email protected]>
+ *
+ * Derived from drivers/gpio/pca953x.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/i2c/vsc055.h>
+#include <linux/slab.h>
+
+#define VSC055_GPD0 0x00
+#define VSC055_DDP0 0x10
+#define VSC055_BCIS 0xF8
+#define VSC055_BCT 0xFC
+#define VSC055_VER 0xFF
+
+static const struct i2c_device_id vsc055_id[] = {
+ { "vsc055" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, vsc055_id);
+
+struct vsc055_chip {
+ unsigned gpio_start;
+ uint8_t reg_output[8];
+ uint8_t reg_direction[8];
+ struct mutex i2c_lock;
+
+ struct i2c_client *client;
+ struct gpio_chip gpio_chip;
+ const char *const *names;
+};
+
+static int vsc055_write_reg(struct vsc055_chip *chip, int reg, uint8_t val)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(chip->client, reg, val);
+
+ if (ret < 0) {
+ dev_err(&chip->client->dev, "failed writing register\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int vsc055_read_reg(struct vsc055_chip *chip, int reg, uint8_t *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(chip->client, reg);
+
+ if (ret < 0) {
+ dev_err(&chip->client->dev, "failed reading register\n");
+ return ret;
+ }
+
+ *val = (uint8_t)ret;
+ return 0;
+}
+
+static int vsc055_gpio_direction_input(struct gpio_chip *gc, unsigned off)
+{
+ struct vsc055_chip *chip;
+ uint8_t reg_val;
+ int ret;
+ int block, pin;
+
+ chip = container_of(gc, struct vsc055_chip, gpio_chip);
+
+ block = off >> 3;
+ pin = off & 7;
+
+ mutex_lock(&chip->i2c_lock);
+ reg_val = chip->reg_direction[block] | (1u << pin);
+ ret = vsc055_write_reg(chip, VSC055_DDP0 + block, reg_val);
+ if (ret)
+ goto exit;
+
+ chip->reg_direction[block] = reg_val;
+ ret = 0;
+exit:
+ mutex_unlock(&chip->i2c_lock);
+ return ret;
+}
+
+static int vsc055_gpio_direction_output(struct gpio_chip *gc,
+ unsigned off, int val)
+{
+ struct vsc055_chip *chip;
+ uint8_t reg_val;
+ int ret;
+ int block, pin;
+
+ chip = container_of(gc, struct vsc055_chip, gpio_chip);
+
+ block = off >> 3;
+ pin = off & 7;
+
+ mutex_lock(&chip->i2c_lock);
+ /* set output level */
+ if (val)
+ reg_val = chip->reg_output[block] | (1u << pin);
+ else
+ reg_val = chip->reg_output[block] & ~(1u << pin);
+
+ ret = vsc055_write_reg(chip, VSC055_GPD0 + block, reg_val);
+ if (ret)
+ goto exit;
+
+ chip->reg_output[block] = reg_val;
+
+ /* then direction */
+ reg_val = chip->reg_direction[block] & ~(1u << off);
+ ret = vsc055_write_reg(chip, VSC055_DDP0 + block, reg_val);
+ if (ret)
+ goto exit;
+
+ chip->reg_direction[block] = reg_val;
+ ret = 0;
+exit:
+ mutex_unlock(&chip->i2c_lock);
+ return ret;
+}
+
+static int vsc055_gpio_get_value(struct gpio_chip *gc, unsigned off)
+{
+ struct vsc055_chip *chip;
+ uint8_t reg_val;
+ int ret;
+
+ chip = container_of(gc, struct vsc055_chip, gpio_chip);
+
+ mutex_lock(&chip->i2c_lock);
+ ret = vsc055_read_reg(chip, VSC055_GPD0 + (off >> 3), &reg_val);
+ mutex_unlock(&chip->i2c_lock);
+ if (ret < 0) {
+ /* NOTE: diagnostic already emitted; that's all we should
+ * do unless gpio_*_value_cansleep() calls become different
+ * from their nonsleeping siblings (and report faults).
+ */
+ return 0;
+ }
+
+ return (reg_val & (1u << (off & 7))) ? 1 : 0;
+}
+
+static void vsc055_gpio_set_value(struct gpio_chip *gc, unsigned off, int val)
+{
+ struct vsc055_chip *chip;
+ uint8_t reg_val;
+ int ret;
+ int block, pin;
+
+ chip = container_of(gc, struct vsc055_chip, gpio_chip);
+
+ block = off >> 3;
+ pin = off & 7;
+
+ mutex_lock(&chip->i2c_lock);
+ if (val)
+ reg_val = chip->reg_output[block] | (1u << pin);
+ else
+ reg_val = chip->reg_output[block] & ~(1u << pin);
+
+ ret = vsc055_write_reg(chip, VSC055_GPD0 + block, reg_val);
+ if (ret)
+ goto exit;
+
+ chip->reg_output[block] = reg_val;
+exit:
+ mutex_unlock(&chip->i2c_lock);
+}
+
+static void vsc055_setup_gpio(struct vsc055_chip *chip)
+{
+ struct gpio_chip *gc;
+
+ gc = &chip->gpio_chip;
+
+ gc->direction_input = vsc055_gpio_direction_input;
+ gc->direction_output = vsc055_gpio_direction_output;
+ gc->get = vsc055_gpio_get_value;
+ gc->set = vsc055_gpio_set_value;
+ gc->can_sleep = 1;
+
+ gc->base = chip->gpio_start;
+ gc->ngpio = 64;
+ gc->label = chip->client->name;
+ gc->dev = &chip->client->dev;
+ gc->owner = THIS_MODULE;
+ gc->names = chip->names;
+}
+
+static int __devinit vsc055_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct vsc055_platform_data *pdata;
+ struct vsc055_chip *chip;
+ int ret;
+ int i;
+ uint8_t ver;
+
+ chip = kzalloc(sizeof(struct vsc055_chip), GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+
+ pdata = client->dev.platform_data;
+ if (pdata == NULL) {
+ dev_dbg(&client->dev, "no platform data\n");
+ ret = -EINVAL;
+ goto out_failed;
+ }
+
+ chip->client = client;
+
+ chip->gpio_start = pdata->gpio_base;
+
+ chip->names = pdata->names;
+
+ mutex_init(&chip->i2c_lock);
+
+ /* initialize cached registers from their original values.
+ * we can't share this chip with another i2c master.
+ */
+ vsc055_setup_gpio(chip);
+
+ for (i = 0; i < 8; i++) {
+ ret = vsc055_read_reg(chip, VSC055_GPD0 + i,
+ &chip->reg_output[i]);
+ if (ret)
+ goto out_failed;
+ ret = vsc055_read_reg(chip, VSC055_DDP0 + i,
+ &chip->reg_direction[i]);
+ if (ret)
+ goto out_failed;
+ }
+
+ ret = vsc055_read_reg(chip, VSC055_VER, &ver);
+ if (ret)
+ goto out_failed;
+ dev_info(&client->dev, "Found VSC055 revision %d.%d\n", ver >> 4, ver & 0xF);
+
+ ret = gpiochip_add(&chip->gpio_chip);
+ if (ret)
+ goto out_failed;
+
+ if (pdata->setup) {
+ ret = pdata->setup(client, chip->gpio_chip.base,
+ chip->gpio_chip.ngpio, pdata->context);
+ if (ret < 0)
+ dev_warn(&client->dev, "setup failed, %d\n", ret);
+ }
+
+ i2c_set_clientdata(client, chip);
+ return 0;
+
+out_failed:
+ kfree(chip);
+ return ret;
+}
+
+static int vsc055_remove(struct i2c_client *client)
+{
+ struct vsc055_platform_data *pdata = client->dev.platform_data;
+ struct vsc055_chip *chip = i2c_get_clientdata(client);
+ int ret = 0;
+
+ if (pdata->teardown) {
+ ret = pdata->teardown(client, chip->gpio_chip.base,
+ chip->gpio_chip.ngpio, pdata->context);
+ if (ret < 0) {
+ dev_err(&client->dev, "%s failed, %d\n",
+ "teardown", ret);
+ return ret;
+ }
+ }
+
+ ret = gpiochip_remove(&chip->gpio_chip);
+ if (ret) {
+ dev_err(&client->dev, "%s failed, %d\n",
+ "gpiochip_remove()", ret);
+ return ret;
+ }
+
+ kfree(chip);
+ return 0;
+}
+
+static struct i2c_driver vsc055_driver = {
+ .driver = {
+ .name = "vsc055",
+ },
+ .probe = vsc055_probe,
+ .remove = vsc055_remove,
+ .id_table = vsc055_id,
+};
+
+static int __init vsc055_init(void)
+{
+ return i2c_add_driver(&vsc055_driver);
+}
+/* register after i2c postcore initcall and before
+ * subsys initcalls that may rely on these GPIOs
+ */
+subsys_initcall(vsc055_init);
+
+static void __exit vsc055_exit(void)
+{
+ i2c_del_driver(&vsc055_driver);
+}
+module_exit(vsc055_exit);
+
+MODULE_AUTHOR("Jonathan McDowell <[email protected]>");
+MODULE_DESCRIPTION("GPIO driver for VSC055");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/i2c/vsc055.h b/include/linux/i2c/vsc055.h
new file mode 100644
index 0000000..dc1462b
--- /dev/null
+++ b/include/linux/i2c/vsc055.h
@@ -0,0 +1,22 @@
+#ifndef _LINUX_VSC055_H
+#define _LINUX_VSC055_H
+
+#include <linux/types.h>
+#include <linux/i2c.h>
+
+/* Platform data for the VSC055 64-bit I/O expander driver */
+
+struct vsc055_platform_data {
+ unsigned gpio_base; /* number of the first GPIO */
+ void *context; /* param to setup/teardown */
+
+ int (*setup)(struct i2c_client *client,
+ unsigned gpio, unsigned ngpio,
+ void *context);
+ int (*teardown)(struct i2c_client *client,
+ unsigned gpio, unsigned ngpio,
+ void *context);
+ const char *const *names;
+};
+
+#endif /* _LINUX_VSC055_H */
-----

J.

--
101 things you can't have too much of : 2 - Gaffa tape.


2011-05-05 09:01:10

by Jonathan Cameron

[permalink] [raw]
Subject: Re: [PATCH] Add support for VSC055 Enhanced Two-Wire Serial Backplane controller

Sorry Jonathan and Grant. I accidentally sent this only to you and
not to lkml, hence the resend.

On 05/05/11 05:12, Jonathan McDowell wrote:
> I'd been holding off on sending this until I got some feedback about the
> PC8741x driver I sent a couple of weeks ago, but seeing as I have it
> sitting around I figured I may as well send it anyway.
Looks mostly fine to me.

Couple of suggestions for minor tidying up inline.

Main one is that I'm anti wrappers around bus functions that do
very little.

Jonathan
>
> -----
> Add support for the GPIOs on Maxim VSC055 I2C Enhanced Two-Wire Serial
> Backplane controller.
>
> This chip features 64 bits of user-definable, bidirectional GPIOs. It also
> has the ability to do PWM fan control output and LED flashing, but support
> for that functionality is not included at present.
>
> Signed-off-by: Jonathan McDowell <[email protected]>
>
> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> index cc150db..4c5de65 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -255,6 +255,13 @@ config GPIO_TWL4030
> Say yes here to access the GPIO signals of various multi-function
> power management chips from Texas Instruments.
>
> +config GPIO_VSC055
> + tristate "VSC055 GPIOs"
> + depends on I2C
> + help
> + Say yes here to access the GPIOs found on the Maxim VSC055 Enhanced
> + Two-Wire Serial Backplane controller.
> +
> config GPIO_WM831X
> tristate "WM831x GPIOs"
> depends on MFD_WM831X
> diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
> index d2752b2..f82491d 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -42,4 +42,5 @@ obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o
> obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o
> obj-$(CONFIG_GPIO_SX150X) += sx150x.o
> obj-$(CONFIG_GPIO_VX855) += vx855_gpio.o
> +obj-$(CONFIG_GPIO_VSC055) += vsc055.o
> obj-$(CONFIG_GPIO_ML_IOH) += ml_ioh_gpio.o
> diff --git a/drivers/gpio/vsc055.c b/drivers/gpio/vsc055.c
> new file mode 100644
> index 0000000..c218dac
> --- /dev/null
> +++ b/drivers/gpio/vsc055.c
> @@ -0,0 +1,326 @@
> +/*
> + * vsc055.c - Enhanced Two-Wire Serial Backplane Control with 64 I/O lines.
> + *
> + * Copyright (C) 2011 Jonathan McDowell <[email protected]>
> + *
> + * Derived from drivers/gpio/pca953x.c
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 2 of the License.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/i2c/vsc055.h>
> +#include <linux/slab.h>
> +
> +#define VSC055_GPD0 0x00
> +#define VSC055_DDP0 0x10
> +#define VSC055_BCIS 0xF8
> +#define VSC055_BCT 0xFC
> +#define VSC055_VER 0xFF
> +
> +static const struct i2c_device_id vsc055_id[] = {
> + { "vsc055" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, vsc055_id);
> +
> +struct vsc055_chip {
> + unsigned gpio_start;
> + uint8_t reg_output[8];
> + uint8_t reg_direction[8];
> + struct mutex i2c_lock;
> +
> + struct i2c_client *client;
> + struct gpio_chip gpio_chip;
> + const char *const *names;
> +};
> +
> +static int vsc055_write_reg(struct vsc055_chip *chip, int reg, uint8_t val)
kernel type is u8, uint8_t is for userspace bits and bobs.
> +{
> + int ret;
> +
> + ret = i2c_smbus_write_byte_data(chip->client, reg, val);
Why bother wrapping these functions? To my mind the debug message
is overkill.
> +
> + if (ret < 0) {
> + dev_err(&chip->client->dev, "failed writing register\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int vsc055_read_reg(struct vsc055_chip *chip, int reg, uint8_t *val)
> +{
> + int ret;
> +
> + ret = i2c_smbus_read_byte_data(chip->client, reg);
> +
> + if (ret < 0) {
> + dev_err(&chip->client->dev, "failed reading register\n");
> + return ret;
> + }
> +
> + *val = (uint8_t)ret;
no need for the type cast. Again, why is the wrapper needed. Trivial bit of
reorganization and it goes away for no real cost.
> + return 0;
> +}
> +
> +static int vsc055_gpio_direction_input(struct gpio_chip *gc, unsigned off)
> +{
> + struct vsc055_chip *chip;
> + uint8_t reg_val;
> + int ret;
> + int block, pin;
> +
> + chip = container_of(gc, struct vsc055_chip, gpio_chip);
> +
> + block = off >> 3;

Roll pin into the one place it is used? It's rather confusingly named anyway!
> + pin = off & 7;
> +
> + mutex_lock(&chip->i2c_lock);
> + reg_val = chip->reg_direction[block] | (1u << pin);
> + ret = vsc055_write_reg(chip, VSC055_DDP0 + block, reg_val);
> + if (ret)
> + goto exit;
> +
> + chip->reg_direction[block] = reg_val;
> + ret = 0;
why ret = 0? It's already zero if you got here.
> +exit:
> + mutex_unlock(&chip->i2c_lock);
> + return ret;
> +}
> +
> +static int vsc055_gpio_direction_output(struct gpio_chip *gc,
> + unsigned off, int val)
> +{
> + struct vsc055_chip *chip;
> + uint8_t reg_val;
> + int ret;
> + int block, pin;
> +
> + chip = container_of(gc, struct vsc055_chip, gpio_chip);
> +
> + block = off >> 3;
> + pin = off & 7;
Again, I'd just put this in the places pin is used.
> +
> + mutex_lock(&chip->i2c_lock);
> + /* set output level */
> + if (val)
> + reg_val = chip->reg_output[block] | (1u << pin);
> + else
> + reg_val = chip->reg_output[block] & ~(1u << pin);
> +
> + ret = vsc055_write_reg(chip, VSC055_GPD0 + block, reg_val);
> + if (ret)
> + goto exit;
> +
> + chip->reg_output[block] = reg_val;
> +
> + /* then direction */
> + reg_val = chip->reg_direction[block] & ~(1u << off);
> + ret = vsc055_write_reg(chip, VSC055_DDP0 + block, reg_val);
> + if (ret)
> + goto exit;
> +
> + chip->reg_direction[block] = reg_val;
> + ret = 0;
> +exit:
> + mutex_unlock(&chip->i2c_lock);
> + return ret;
> +}
> +
> +static int vsc055_gpio_get_value(struct gpio_chip *gc, unsigned off)
> +{
> + struct vsc055_chip *chip;
> + uint8_t reg_val;
> + int ret;
> +
> + chip = container_of(gc, struct vsc055_chip, gpio_chip);
Given this is just compile time pointer magic, I'd roll it into one
line as:
struct vsc055_chip *chip = container_of(gc, struct vsc055_chip, gpio_chip);

That's just personal taste though so feel free to ignore!

> +
> + mutex_lock(&chip->i2c_lock);
> + ret = vsc055_read_reg(chip, VSC055_GPD0 + (off >> 3), &reg_val);
make this:
ret = i2c_smbus_read_byte_data(chip->client, reg);
> + mutex_unlock(&chip->i2c_lock);
> + if (ret < 0) {
> + /* NOTE: diagnostic already emitted; that's all we should
> + * do unless gpio_*_value_cansleep() calls become different
> + * from their nonsleeping siblings (and report faults).
> + */
then spurt some message here (as no longer in the function). That's uggly
that the gpio funcs don't return errors...
> + return 0;
> + }
> +
then given you've already checked it was zero you can do the following as
return !!(ret & (1u << (off & 7)));
(!! is really handy for this sort of case)

> + return (reg_val & (1u << (off & 7))) ? 1 : 0;
> +}
> +
> +static void vsc055_gpio_set_value(struct gpio_chip *gc, unsigned off, int val)
> +{
> + struct vsc055_chip *chip;
> + uint8_t reg_val;
> + int ret;
> + int block, pin;
> +
> + chip = container_of(gc, struct vsc055_chip, gpio_chip);
> +
> + block = off >> 3;
> + pin = off & 7;
> +
> + mutex_lock(&chip->i2c_lock);
This lock is a little misleading. It doesn't lock the i2c bus. I'd be
more tempted to call it reg_lock to indicate it is preventing others
reading / writing the chip registers.
> + if (val)
> + reg_val = chip->reg_output[block] | (1u << pin);
> + else
> + reg_val = chip->reg_output[block] & ~(1u << pin);
> +
> + ret = vsc055_write_reg(chip, VSC055_GPD0 + block, reg_val);
> + if (ret)
> + goto exit;
> +
> + chip->reg_output[block] = reg_val;
> +exit:
> + mutex_unlock(&chip->i2c_lock);
> +}
> +
> +static void vsc055_setup_gpio(struct vsc055_chip *chip)
> +{
> + struct gpio_chip *gc;
> +
> + gc = &chip->gpio_chip;
Personally I'd just do this long hand.

More general question for gpio subsystem is whether an ops
type strucutre containing the bits that tend to be fixed
for a given gpio_chip implementation would make for cleaner
code?
> +
> + gc->direction_input = vsc055_gpio_direction_input;
> + gc->direction_output = vsc055_gpio_direction_output;
> + gc->get = vsc055_gpio_get_value;
> + gc->set = vsc055_gpio_set_value;
> + gc->can_sleep = 1;
> +
> + gc->base = chip->gpio_start;
> + gc->ngpio = 64;
> + gc->label = chip->client->name;
> + gc->dev = &chip->client->dev;
> + gc->owner = THIS_MODULE;
> + gc->names = chip->names;
> +}
> +
> +static int __devinit vsc055_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct vsc055_platform_data *pdata;
> + struct vsc055_chip *chip;
> + int ret;
> + int i;
> + uint8_t ver;
> +
> + chip = kzalloc(sizeof(struct vsc055_chip), GFP_KERNEL);
> + if (chip == NULL)
> + return -ENOMEM;
> +
> + pdata = client->dev.platform_data;
> + if (pdata == NULL) {
> + dev_dbg(&client->dev, "no platform data\n");
> + ret = -EINVAL;
> + goto out_failed;
> + }
> +
> + chip->client = client;
> +
> + chip->gpio_start = pdata->gpio_base;
> +
> + chip->names = pdata->names;
> +
> + mutex_init(&chip->i2c_lock);
> +
> + /* initialize cached registers from their original values.
> + * we can't share this chip with another i2c master.
> + */
I think the comment needs to be below the next line.

> + vsc055_setup_gpio(chip);
> +
> + for (i = 0; i < 8; i++) {
> + ret = vsc055_read_reg(chip, VSC055_GPD0 + i,
> + &chip->reg_output[i]);
> + if (ret)
> + goto out_failed;
> + ret = vsc055_read_reg(chip, VSC055_DDP0 + i,
> + &chip->reg_direction[i]);
> + if (ret)
> + goto out_failed;
> + }
> +
> + ret = vsc055_read_reg(chip, VSC055_VER, &ver);
> + if (ret)
> + goto out_failed;
> + dev_info(&client->dev, "Found VSC055 revision %d.%d\n", ver >> 4, ver & 0xF);
Silly question but is there any reason to care about the version number?
> +
> + ret = gpiochip_add(&chip->gpio_chip);
> + if (ret)
> + goto out_failed;
> +
> + if (pdata->setup) {
> + ret = pdata->setup(client, chip->gpio_chip.base,
> + chip->gpio_chip.ngpio, pdata->context);
> + if (ret < 0)
> + dev_warn(&client->dev, "setup failed, %d\n", ret);
> + }
> +
> + i2c_set_clientdata(client, chip);
> + return 0;
> +
> +out_failed:
> + kfree(chip);
> + return ret;
> +}
> +
> +static int vsc055_remove(struct i2c_client *client)
> +{
> + struct vsc055_platform_data *pdata = client->dev.platform_data;
> + struct vsc055_chip *chip = i2c_get_clientdata(client);
> + int ret = 0;
> +
> + if (pdata->teardown) {
> + ret = pdata->teardown(client, chip->gpio_chip.base,
> + chip->gpio_chip.ngpio, pdata->context);
> + if (ret < 0) {
> + dev_err(&client->dev, "%s failed, %d\n",
> + "teardown", ret);
> + return ret;
> + }
> + }
> +
> + ret = gpiochip_remove(&chip->gpio_chip);
> + if (ret) {
> + dev_err(&client->dev, "%s failed, %d\n",
> + "gpiochip_remove()", ret);
> + return ret;
> + }
> +
> + kfree(chip);
> + return 0;
> +}
> +
> +static struct i2c_driver vsc055_driver = {
> + .driver = {
> + .name = "vsc055",
> + },
> + .probe = vsc055_probe,
> + .remove = vsc055_remove,
> + .id_table = vsc055_id,
> +};
> +
> +static int __init vsc055_init(void)
> +{
> + return i2c_add_driver(&vsc055_driver);
> +}
> +/* register after i2c postcore initcall and before
> + * subsys initcalls that may rely on these GPIOs
> + */
> +subsys_initcall(vsc055_init);
> +
> +static void __exit vsc055_exit(void)
> +{
> + i2c_del_driver(&vsc055_driver);
> +}
> +module_exit(vsc055_exit);
> +
> +MODULE_AUTHOR("Jonathan McDowell <[email protected]>");
> +MODULE_DESCRIPTION("GPIO driver for VSC055");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/i2c/vsc055.h b/include/linux/i2c/vsc055.h
> new file mode 100644
> index 0000000..dc1462b
> --- /dev/null
> +++ b/include/linux/i2c/vsc055.h
> @@ -0,0 +1,22 @@
> +#ifndef _LINUX_VSC055_H
> +#define _LINUX_VSC055_H
> +
> +#include <linux/types.h>
> +#include <linux/i2c.h>
> +
> +/* Platform data for the VSC055 64-bit I/O expander driver */
> +
> +struct vsc055_platform_data {
> + unsigned gpio_base; /* number of the first GPIO */
> + void *context; /* param to setup/teardown */
> +
> + int (*setup)(struct i2c_client *client,
> + unsigned gpio, unsigned ngpio,
> + void *context);
> + int (*teardown)(struct i2c_client *client,
> + unsigned gpio, unsigned ngpio,
> + void *context);
> + const char *const *names;
> +};
> +
> +#endif /* _LINUX_VSC055_H */
> -----
>
> J.
>

2011-05-05 22:00:22

by Jonathan McDowell

[permalink] [raw]
Subject: Re: [PATCH] Add support for VSC055 Enhanced Two-Wire Serial Backplane controller

On Thu, May 05, 2011 at 10:03:31AM +0100, Jonathan Cameron wrote:
> Sorry Jonathan and Grant. I accidentally sent this only to you and
> not to lkml, hence the resend.

That's my fault; I sent the initial revision to Grant and missed the
lkml CC, then just bounced it to lkml.

> Couple of suggestions for minor tidying up inline.

Thanks. Things I've taken on board and updated:

* Use u8, not uint8_t for 8 bit types.
* Remove read_reg/write_reg wrappers and use i2c_smbus functions
directly.
* Fold container_of usage into struct vsc055_chip variable definition.
* Use !! instead of ? 1 : 0.
* Rename i2c_lock to reg_lock.
* Move comment about cached status to correct location.

Things I left:

* pin/block - when they're used multiple times I think this is clearer
and reflects how the data sheet talks about the GPIO lines.
* Display version number - I think this is useful to indicate the
device has been correctly found (I'd prefer it if there was a
certain way to ID I2C devices).

-----

Add support for the GPIOs on Maxim VSC055 I2C Enhanced Two-Wire Serial
Backplane controller.

This chip features 64 bits of user-definable, bidirectional GPIOs. It also
has the ability to do PWM fan control output and LED flashing, but support
for that functionality is not included at present.

Signed-off-by: Jonathan McDowell <[email protected]>

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index cc150db..4c5de65 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -255,6 +255,13 @@ config GPIO_TWL4030
Say yes here to access the GPIO signals of various multi-function
power management chips from Texas Instruments.

+config GPIO_VSC055
+ tristate "VSC055 GPIOs"
+ depends on I2C
+ help
+ Say yes here to access the GPIOs found on the Maxim VSC055 Enhanced
+ Two-Wire Serial Backplane controller.
+
config GPIO_WM831X
tristate "WM831x GPIOs"
depends on MFD_WM831X
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index d2752b2..f82491d 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -42,4 +42,5 @@ obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o
obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o
obj-$(CONFIG_GPIO_SX150X) += sx150x.o
obj-$(CONFIG_GPIO_VX855) += vx855_gpio.o
+obj-$(CONFIG_GPIO_VSC055) += vsc055.o
obj-$(CONFIG_GPIO_ML_IOH) += ml_ioh_gpio.o
diff --git a/drivers/gpio/vsc055.c b/drivers/gpio/vsc055.c
new file mode 100644
index 0000000..1ca6ae0
--- /dev/null
+++ b/drivers/gpio/vsc055.c
@@ -0,0 +1,283 @@
+/*
+ * vsc055.c - Enhanced Two-Wire Serial Backplane Control with 64 I/O lines.
+ *
+ * Copyright (C) 2011 Jonathan McDowell <[email protected]>
+ *
+ * Derived from drivers/gpio/pca953x.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/i2c/vsc055.h>
+#include <linux/slab.h>
+
+#define VSC055_GPD0 0x00
+#define VSC055_DDP0 0x10
+#define VSC055_BCIS 0xF8
+#define VSC055_BCT 0xFC
+#define VSC055_VER 0xFF
+
+static const struct i2c_device_id vsc055_id[] = {
+ { "vsc055" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, vsc055_id);
+
+struct vsc055_chip {
+ unsigned gpio_start;
+ u8 reg_output[8];
+ u8 reg_direction[8];
+ struct mutex reg_lock;
+
+ struct i2c_client *client;
+ struct gpio_chip gpio_chip;
+ const char *const *names;
+};
+
+static int vsc055_gpio_direction_input(struct gpio_chip *gc, unsigned off)
+{
+ struct vsc055_chip *chip = container_of(gc, struct vsc055_chip, gpio_chip);
+ u8 reg_val;
+ int ret;
+ int block, pin;
+
+ block = off >> 3;
+ pin = off & 7;
+
+ mutex_lock(&chip->reg_lock);
+ reg_val = chip->reg_direction[block] | (1u << pin);
+ ret = i2c_smbus_write_byte_data(chip->client, VSC055_DDP0 + block, reg_val);
+ if (ret)
+ goto exit;
+
+ chip->reg_direction[block] = reg_val;
+exit:
+ mutex_unlock(&chip->reg_lock);
+ return ret;
+}
+
+static int vsc055_gpio_direction_output(struct gpio_chip *gc,
+ unsigned off, int val)
+{
+ struct vsc055_chip *chip = container_of(gc, struct vsc055_chip, gpio_chip);
+ u8 reg_val;
+ int ret;
+ int block, pin;
+
+ block = off >> 3;
+ pin = off & 7;
+
+ mutex_lock(&chip->reg_lock);
+ /* set output level */
+ if (val)
+ reg_val = chip->reg_output[block] | (1u << pin);
+ else
+ reg_val = chip->reg_output[block] & ~(1u << pin);
+
+ ret = i2c_smbus_write_byte_data(chip->client, VSC055_GPD0 + block, reg_val);
+ if (ret)
+ goto exit;
+
+ chip->reg_output[block] = reg_val;
+
+ /* then direction */
+ reg_val = chip->reg_direction[block] & ~(1u << off);
+ ret = i2c_smbus_write_byte_data(chip->client, VSC055_DDP0 + block, reg_val);
+ if (ret)
+ goto exit;
+
+ chip->reg_direction[block] = reg_val;
+ ret = 0;
+exit:
+ mutex_unlock(&chip->reg_lock);
+ return ret;
+}
+
+static int vsc055_gpio_get_value(struct gpio_chip *gc, unsigned off)
+{
+ struct vsc055_chip *chip = container_of(gc, struct vsc055_chip, gpio_chip);
+ u8 reg_val;
+
+ mutex_lock(&chip->reg_lock);
+ reg_val = i2c_smbus_read_byte_data(chip->client, VSC055_GPD0 + (off >> 3));
+ mutex_unlock(&chip->reg_lock);
+ if (reg_val < 0) {
+ dev_err(&chip->client->dev, "failed to read GPIO value.\n");
+ return 0;
+ }
+
+ return !!(reg_val & (1u << (off & 7)));
+}
+
+static void vsc055_gpio_set_value(struct gpio_chip *gc, unsigned off, int val)
+{
+ struct vsc055_chip *chip = container_of(gc, struct vsc055_chip, gpio_chip);
+ u8 reg_val;
+ int ret;
+ int block, pin;
+
+ block = off >> 3;
+ pin = off & 7;
+
+ mutex_lock(&chip->reg_lock);
+ if (val)
+ reg_val = chip->reg_output[block] | (1u << pin);
+ else
+ reg_val = chip->reg_output[block] & ~(1u << pin);
+
+ ret = i2c_smbus_write_byte_data(chip->client, VSC055_GPD0 + block, reg_val);
+ if (ret)
+ goto exit;
+
+ chip->reg_output[block] = reg_val;
+exit:
+ mutex_unlock(&chip->reg_lock);
+}
+
+static void vsc055_setup_gpio(struct vsc055_chip *chip)
+{
+ struct gpio_chip *gc = &chip->gpio_chip;;
+
+ gc->direction_input = vsc055_gpio_direction_input;
+ gc->direction_output = vsc055_gpio_direction_output;
+ gc->get = vsc055_gpio_get_value;
+ gc->set = vsc055_gpio_set_value;
+ gc->can_sleep = 1;
+
+ gc->base = chip->gpio_start;
+ gc->ngpio = 64;
+ gc->label = chip->client->name;
+ gc->dev = &chip->client->dev;
+ gc->owner = THIS_MODULE;
+ gc->names = chip->names;
+}
+
+static int __devinit vsc055_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct vsc055_platform_data *pdata;
+ struct vsc055_chip *chip;
+ int ret;
+ int i;
+
+ chip = kzalloc(sizeof(struct vsc055_chip), GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+
+ pdata = client->dev.platform_data;
+ if (pdata == NULL) {
+ dev_dbg(&client->dev, "no platform data\n");
+ ret = -EINVAL;
+ goto out_failed;
+ }
+
+ chip->client = client;
+
+ chip->gpio_start = pdata->gpio_base;
+
+ chip->names = pdata->names;
+
+ mutex_init(&chip->reg_lock);
+
+ vsc055_setup_gpio(chip);
+
+ /*
+ * Initialize cached registers from their original values.
+ * We can't share this chip with another i2c master.
+ */
+ for (i = 0; i < 8; i++) {
+ ret = i2c_smbus_read_byte_data(chip->client, VSC055_GPD0 + i);
+ if (ret < 0)
+ goto out_failed;
+ chip->reg_output[i] = ret;
+
+ ret = i2c_smbus_read_byte_data(chip->client, VSC055_DDP0 + i);
+ if (ret < 0)
+ goto out_failed;
+ chip->reg_direction[i] = ret;
+ }
+
+ ret = i2c_smbus_read_byte_data(chip->client, VSC055_VER);
+ if (ret < 0)
+ goto out_failed;
+ dev_info(&client->dev, "Found VSC055 revision %d.%d\n", ret >> 4, ret & 0xF);
+
+ ret = gpiochip_add(&chip->gpio_chip);
+ if (ret)
+ goto out_failed;
+
+ if (pdata->setup) {
+ ret = pdata->setup(client, chip->gpio_chip.base,
+ chip->gpio_chip.ngpio, pdata->context);
+ if (ret < 0)
+ dev_warn(&client->dev, "setup failed, %d\n", ret);
+ }
+
+ i2c_set_clientdata(client, chip);
+ return 0;
+
+out_failed:
+ kfree(chip);
+ return ret;
+}
+
+static int vsc055_remove(struct i2c_client *client)
+{
+ struct vsc055_platform_data *pdata = client->dev.platform_data;
+ struct vsc055_chip *chip = i2c_get_clientdata(client);
+ int ret = 0;
+
+ if (pdata->teardown) {
+ ret = pdata->teardown(client, chip->gpio_chip.base,
+ chip->gpio_chip.ngpio, pdata->context);
+ if (ret < 0) {
+ dev_err(&client->dev, "%s failed, %d\n",
+ "teardown", ret);
+ return ret;
+ }
+ }
+
+ ret = gpiochip_remove(&chip->gpio_chip);
+ if (ret) {
+ dev_err(&client->dev, "%s failed, %d\n",
+ "gpiochip_remove()", ret);
+ return ret;
+ }
+
+ kfree(chip);
+ return 0;
+}
+
+static struct i2c_driver vsc055_driver = {
+ .driver = {
+ .name = "vsc055",
+ },
+ .probe = vsc055_probe,
+ .remove = vsc055_remove,
+ .id_table = vsc055_id,
+};
+
+static int __init vsc055_init(void)
+{
+ return i2c_add_driver(&vsc055_driver);
+}
+/* register after i2c postcore initcall and before
+ * subsys initcalls that may rely on these GPIOs
+ */
+subsys_initcall(vsc055_init);
+
+static void __exit vsc055_exit(void)
+{
+ i2c_del_driver(&vsc055_driver);
+}
+module_exit(vsc055_exit);
+
+MODULE_AUTHOR("Jonathan McDowell <[email protected]>");
+MODULE_DESCRIPTION("GPIO driver for VSC055");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/i2c/vsc055.h b/include/linux/i2c/vsc055.h
new file mode 100644
index 0000000..dc1462b
--- /dev/null
+++ b/include/linux/i2c/vsc055.h
@@ -0,0 +1,22 @@
+#ifndef _LINUX_VSC055_H
+#define _LINUX_VSC055_H
+
+#include <linux/types.h>
+#include <linux/i2c.h>
+
+/* Platform data for the VSC055 64-bit I/O expander driver */
+
+struct vsc055_platform_data {
+ unsigned gpio_base; /* number of the first GPIO */
+ void *context; /* param to setup/teardown */
+
+ int (*setup)(struct i2c_client *client,
+ unsigned gpio, unsigned ngpio,
+ void *context);
+ int (*teardown)(struct i2c_client *client,
+ unsigned gpio, unsigned ngpio,
+ void *context);
+ const char *const *names;
+};
+
+#endif /* _LINUX_VSC055_H */
-----
J.

--
/-\ | What's the worse that could
|@/ Debian GNU/Linux Developer | happen? Smoke. - Anonymous
\- | HWHacker

2011-05-06 13:37:47

by Jonathan Cameron

[permalink] [raw]
Subject: Re: [PATCH] Add support for VSC055 Enhanced Two-Wire Serial Backplane controller

On 05/05/11 23:00, Jonathan McDowell wrote:
> On Thu, May 05, 2011 at 10:03:31AM +0100, Jonathan Cameron wrote:
>> Sorry Jonathan and Grant. I accidentally sent this only to you and
>> not to lkml, hence the resend.
>
> That's my fault; I sent the initial revision to Grant and missed the
> lkml CC, then just bounced it to lkml.
>
>> Couple of suggestions for minor tidying up inline.
>
> Thanks. Things I've taken on board and updated:
>
> * Use u8, not uint8_t for 8 bit types.
> * Remove read_reg/write_reg wrappers and use i2c_smbus functions
> directly.
> * Fold container_of usage into struct vsc055_chip variable definition.
> * Use !! instead of ? 1 : 0.
> * Rename i2c_lock to reg_lock.
> * Move comment about cached status to correct location.
>
> Things I left:
>
> * pin/block - when they're used multiple times I think this is clearer
> and reflects how the data sheet talks about the GPIO lines.
> * Display version number - I think this is useful to indicate the
> device has been correctly found (I'd prefer it if there was a
> certain way to ID I2C devices).
Fair enough to both of those.
>
> -----
>
> Add support for the GPIOs on Maxim VSC055 I2C Enhanced Two-Wire Serial
> Backplane controller.
>
> This chip features 64 bits of user-definable, bidirectional GPIOs. It also
> has the ability to do PWM fan control output and LED flashing, but support
> for that functionality is not included at present.
>
> Signed-off-by: Jonathan McDowell <[email protected]>
Acked-by: Jonathan Cameron <[email protected]>
>
> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> index cc150db..4c5de65 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -255,6 +255,13 @@ config GPIO_TWL4030
> Say yes here to access the GPIO signals of various multi-function
> power management chips from Texas Instruments.
>
> +config GPIO_VSC055
> + tristate "VSC055 GPIOs"
> + depends on I2C
> + help
> + Say yes here to access the GPIOs found on the Maxim VSC055 Enhanced
> + Two-Wire Serial Backplane controller.
> +
> config GPIO_WM831X
> tristate "WM831x GPIOs"
> depends on MFD_WM831X
> diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
> index d2752b2..f82491d 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -42,4 +42,5 @@ obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o
> obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o
> obj-$(CONFIG_GPIO_SX150X) += sx150x.o
> obj-$(CONFIG_GPIO_VX855) += vx855_gpio.o
> +obj-$(CONFIG_GPIO_VSC055) += vsc055.o
> obj-$(CONFIG_GPIO_ML_IOH) += ml_ioh_gpio.o
> diff --git a/drivers/gpio/vsc055.c b/drivers/gpio/vsc055.c
> new file mode 100644
> index 0000000..1ca6ae0
> --- /dev/null
> +++ b/drivers/gpio/vsc055.c
> @@ -0,0 +1,283 @@
> +/*
> + * vsc055.c - Enhanced Two-Wire Serial Backplane Control with 64 I/O lines.
> + *
> + * Copyright (C) 2011 Jonathan McDowell <[email protected]>
> + *
> + * Derived from drivers/gpio/pca953x.c
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 2 of the License.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/i2c/vsc055.h>
> +#include <linux/slab.h>
> +
> +#define VSC055_GPD0 0x00
> +#define VSC055_DDP0 0x10
> +#define VSC055_BCIS 0xF8
> +#define VSC055_BCT 0xFC
> +#define VSC055_VER 0xFF
> +
> +static const struct i2c_device_id vsc055_id[] = {
> + { "vsc055" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, vsc055_id);
> +
> +struct vsc055_chip {
> + unsigned gpio_start;
> + u8 reg_output[8];
> + u8 reg_direction[8];
> + struct mutex reg_lock;
> +
> + struct i2c_client *client;
> + struct gpio_chip gpio_chip;
> + const char *const *names;
> +};
> +
> +static int vsc055_gpio_direction_input(struct gpio_chip *gc, unsigned off)
> +{
> + struct vsc055_chip *chip = container_of(gc, struct vsc055_chip, gpio_chip);
> + u8 reg_val;
> + int ret;
> + int block, pin;
> +
> + block = off >> 3;
> + pin = off & 7;
> +
> + mutex_lock(&chip->reg_lock);
> + reg_val = chip->reg_direction[block] | (1u << pin);
> + ret = i2c_smbus_write_byte_data(chip->client, VSC055_DDP0 + block, reg_val);
> + if (ret)
> + goto exit;
> +
> + chip->reg_direction[block] = reg_val;
> +exit:
> + mutex_unlock(&chip->reg_lock);
> + return ret;
> +}
> +
> +static int vsc055_gpio_direction_output(struct gpio_chip *gc,
> + unsigned off, int val)
> +{
> + struct vsc055_chip *chip = container_of(gc, struct vsc055_chip, gpio_chip);
> + u8 reg_val;
> + int ret;
> + int block, pin;
> +
> + block = off >> 3;
> + pin = off & 7;
> +
> + mutex_lock(&chip->reg_lock);
> + /* set output level */
> + if (val)
> + reg_val = chip->reg_output[block] | (1u << pin);
> + else
> + reg_val = chip->reg_output[block] & ~(1u << pin);
> +
> + ret = i2c_smbus_write_byte_data(chip->client, VSC055_GPD0 + block, reg_val);
> + if (ret)
> + goto exit;
> +
> + chip->reg_output[block] = reg_val;
> +
> + /* then direction */
> + reg_val = chip->reg_direction[block] & ~(1u << off);
> + ret = i2c_smbus_write_byte_data(chip->client, VSC055_DDP0 + block, reg_val);
> + if (ret)
> + goto exit;
> +
> + chip->reg_direction[block] = reg_val;
> + ret = 0;
> +exit:
> + mutex_unlock(&chip->reg_lock);
> + return ret;
> +}
> +
> +static int vsc055_gpio_get_value(struct gpio_chip *gc, unsigned off)
> +{
> + struct vsc055_chip *chip = container_of(gc, struct vsc055_chip, gpio_chip);
> + u8 reg_val;
> +
> + mutex_lock(&chip->reg_lock);
> + reg_val = i2c_smbus_read_byte_data(chip->client, VSC055_GPD0 + (off >> 3));
> + mutex_unlock(&chip->reg_lock);
> + if (reg_val < 0) {
> + dev_err(&chip->client->dev, "failed to read GPIO value.\n");
> + return 0;
> + }
> +
> + return !!(reg_val & (1u << (off & 7)));
> +}
> +
> +static void vsc055_gpio_set_value(struct gpio_chip *gc, unsigned off, int val)
> +{
> + struct vsc055_chip *chip = container_of(gc, struct vsc055_chip, gpio_chip);
> + u8 reg_val;
> + int ret;
> + int block, pin;
> +
> + block = off >> 3;
> + pin = off & 7;
> +
> + mutex_lock(&chip->reg_lock);
> + if (val)
> + reg_val = chip->reg_output[block] | (1u << pin);
> + else
> + reg_val = chip->reg_output[block] & ~(1u << pin);
> +
> + ret = i2c_smbus_write_byte_data(chip->client, VSC055_GPD0 + block, reg_val);
> + if (ret)
> + goto exit;
> +
> + chip->reg_output[block] = reg_val;
> +exit:
> + mutex_unlock(&chip->reg_lock);
> +}
> +
> +static void vsc055_setup_gpio(struct vsc055_chip *chip)
> +{
> + struct gpio_chip *gc = &chip->gpio_chip;;
> +
> + gc->direction_input = vsc055_gpio_direction_input;
> + gc->direction_output = vsc055_gpio_direction_output;
> + gc->get = vsc055_gpio_get_value;
> + gc->set = vsc055_gpio_set_value;
> + gc->can_sleep = 1;
> +
> + gc->base = chip->gpio_start;
> + gc->ngpio = 64;
> + gc->label = chip->client->name;
> + gc->dev = &chip->client->dev;
> + gc->owner = THIS_MODULE;
> + gc->names = chip->names;
> +}
> +
> +static int __devinit vsc055_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct vsc055_platform_data *pdata;
> + struct vsc055_chip *chip;
> + int ret;
> + int i;
> +
> + chip = kzalloc(sizeof(struct vsc055_chip), GFP_KERNEL);
> + if (chip == NULL)
> + return -ENOMEM;
> +
> + pdata = client->dev.platform_data;
> + if (pdata == NULL) {
> + dev_dbg(&client->dev, "no platform data\n");
> + ret = -EINVAL;
> + goto out_failed;
> + }
> +
> + chip->client = client;
> +
> + chip->gpio_start = pdata->gpio_base;
> +
> + chip->names = pdata->names;
> +
> + mutex_init(&chip->reg_lock);
> +
> + vsc055_setup_gpio(chip);
> +
> + /*
> + * Initialize cached registers from their original values.
> + * We can't share this chip with another i2c master.
> + */
> + for (i = 0; i < 8; i++) {
> + ret = i2c_smbus_read_byte_data(chip->client, VSC055_GPD0 + i);
> + if (ret < 0)
> + goto out_failed;
> + chip->reg_output[i] = ret;
> +
> + ret = i2c_smbus_read_byte_data(chip->client, VSC055_DDP0 + i);
> + if (ret < 0)
> + goto out_failed;
> + chip->reg_direction[i] = ret;
> + }
> +
> + ret = i2c_smbus_read_byte_data(chip->client, VSC055_VER);
> + if (ret < 0)
> + goto out_failed;
> + dev_info(&client->dev, "Found VSC055 revision %d.%d\n", ret >> 4, ret & 0xF);
> +
> + ret = gpiochip_add(&chip->gpio_chip);
> + if (ret)
> + goto out_failed;
> +
> + if (pdata->setup) {
> + ret = pdata->setup(client, chip->gpio_chip.base,
> + chip->gpio_chip.ngpio, pdata->context);
> + if (ret < 0)
> + dev_warn(&client->dev, "setup failed, %d\n", ret);
> + }
> +
> + i2c_set_clientdata(client, chip);
> + return 0;
> +
> +out_failed:
> + kfree(chip);
> + return ret;
> +}
> +
> +static int vsc055_remove(struct i2c_client *client)
> +{
> + struct vsc055_platform_data *pdata = client->dev.platform_data;
> + struct vsc055_chip *chip = i2c_get_clientdata(client);
> + int ret = 0;
> +
> + if (pdata->teardown) {
> + ret = pdata->teardown(client, chip->gpio_chip.base,
> + chip->gpio_chip.ngpio, pdata->context);
> + if (ret < 0) {
> + dev_err(&client->dev, "%s failed, %d\n",
> + "teardown", ret);
> + return ret;
> + }
> + }
> +
> + ret = gpiochip_remove(&chip->gpio_chip);
> + if (ret) {
> + dev_err(&client->dev, "%s failed, %d\n",
> + "gpiochip_remove()", ret);
> + return ret;
> + }
> +
> + kfree(chip);
> + return 0;
> +}
> +
> +static struct i2c_driver vsc055_driver = {
> + .driver = {
> + .name = "vsc055",
> + },
> + .probe = vsc055_probe,
> + .remove = vsc055_remove,
> + .id_table = vsc055_id,
> +};
> +
> +static int __init vsc055_init(void)
> +{
> + return i2c_add_driver(&vsc055_driver);
> +}
> +/* register after i2c postcore initcall and before
> + * subsys initcalls that may rely on these GPIOs
> + */
> +subsys_initcall(vsc055_init);
> +
> +static void __exit vsc055_exit(void)
> +{
> + i2c_del_driver(&vsc055_driver);
> +}
> +module_exit(vsc055_exit);
> +
> +MODULE_AUTHOR("Jonathan McDowell <[email protected]>");
> +MODULE_DESCRIPTION("GPIO driver for VSC055");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/i2c/vsc055.h b/include/linux/i2c/vsc055.h
> new file mode 100644
> index 0000000..dc1462b
> --- /dev/null
> +++ b/include/linux/i2c/vsc055.h
> @@ -0,0 +1,22 @@
> +#ifndef _LINUX_VSC055_H
> +#define _LINUX_VSC055_H
> +
> +#include <linux/types.h>
> +#include <linux/i2c.h>
> +
> +/* Platform data for the VSC055 64-bit I/O expander driver */
> +
> +struct vsc055_platform_data {
> + unsigned gpio_base; /* number of the first GPIO */
> + void *context; /* param to setup/teardown */
> +
> + int (*setup)(struct i2c_client *client,
> + unsigned gpio, unsigned ngpio,
> + void *context);
> + int (*teardown)(struct i2c_client *client,
> + unsigned gpio, unsigned ngpio,
> + void *context);
> + const char *const *names;
> +};
> +
> +#endif /* _LINUX_VSC055_H */
> -----
> J.
>