Add support for VSC055 Enhanced Two-Wire Serial Backplane controller
PATCH 1/1 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.
drivers/gpio/Kconfig +7 -0
@@ -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
drivers/gpio/Makefile +1 -0
@@ -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
drivers/gpio/vsc055.c new file +326 -0
326 lines not on this page · raw message
include/linux/i2c/vsc055.h new file +22 -0
@@ -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.
Signature · 1 line
101 things you can't have too much of : 2 - Gaffa tape.
Sorry Jonathan and Grant. I accidentally sent this only to you and not to lkml, hence the resend.
Quoted 3 lines · Jonathan McDowell
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
Quoted 78 lines
-----
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]>drivers/gpio/Kconfig +7
-0@@ -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_WM831Xdrivers/gpio/Makefile +1
-0@@ -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.odrivers/gpio/vsc055.c new file +33
-0@@ -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;
drivers/gpio/vsc055.c line 43
Quoted 10 lines
drivers/gpio/vsc055.c new file +10
-0+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.
drivers/gpio/vsc055.c line 47
Quoted 4 lines
drivers/gpio/vsc055.c new file +4
-0+{ +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.
Quoted 11 lines
drivers/gpio/vsc055.c new file +11
-0+ +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) +{
drivers/gpio/vsc055.c line 68
Quoted 10 lines
drivers/gpio/vsc055.c new file +10
-0+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.
drivers/gpio/vsc055.c line 81
Quoted 13 lines
drivers/gpio/vsc055.c new file +13
-0+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!
drivers/gpio/vsc055.c line 91
Quoted 10 lines
drivers/gpio/vsc055.c new file +10
-0+ 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.
Quoted 7 lines
drivers/gpio/vsc055.c new file +7
-0+exit: +mutex_unlock (&chip->i2c_lock); +return ret; +} + +static int vsc055_gpio_direction_output (struct gpio_chip *gc, +unsigned off,int val)
drivers/gpio/vsc055.c line 108
Quoted 10 lines
drivers/gpio/vsc055.c new file +10
-0+{ +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.
Quoted 24 lines
drivers/gpio/vsc055.c new file +24
-0+ +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);
drivers/gpio/vsc055.c line 142
Quoted 10 lines
drivers/gpio/vsc055.c new file +10
-0+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!
drivers/gpio/vsc055.c line 145
Quoted 3 lines
drivers/gpio/vsc055.c new file +3
-0+ +mutex_lock (&chip->i2c_lock); + ret =vsc055_read_reg (chip,VSC055_GPD0 + (off >>3 ), ®_val);
make this:
ret = i2c_smbus_read_byte_data(chip->client, reg);
drivers/gpio/vsc055.c line 151
Quoted 6 lines
drivers/gpio/vsc055.c new file +6
-0+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...
drivers/gpio/vsc055.c line 56
Quoted 3 lines
drivers/gpio/vsc055.c new file +3
-0+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)
Quoted 6 lines
drivers/gpio/vsc055.c new file +6
-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;
drivers/gpio/vsc055.c line 170
Quoted 10 lines
drivers/gpio/vsc055.c new file +10
-0+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.
Quoted 9 lines
drivers/gpio/vsc055.c new file +9
-0+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; +
drivers/gpio/vsc055.c line 189
Quoted 10 lines
drivers/gpio/vsc055.c new file +10
-0+ 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?
Quoted 36 lines
drivers/gpio/vsc055.c new file +36
-0+ + 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;
drivers/gpio/vsc055.c line 235
Quoted 10 lines
drivers/gpio/vsc055.c new file +10
-0+ + 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.
Quoted 7 lines
drivers/gpio/vsc055.c new file +7
-0+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;
drivers/gpio/vsc055.c line 252
Quoted 10 lines
drivers/gpio/vsc055.c new file +10
-0+ 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?
Quoted 106 lines
drivers/gpio/vsc055.c new file +74
-0+ + 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" );include/linux/i2c/vsc055.h new file +22
-1@@ -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.
Quoted 2 lines · Jonathan Cameron
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.
Quoted 1 line
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.
drivers/gpio/Kconfig +7 -0
@@ -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
drivers/gpio/Makefile +1 -0
@@ -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
drivers/gpio/vsc055.c new file +283 -0
283 lines not on this page · raw message
include/linux/i2c/vsc055.h new file +22 -0
@@ -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.
Signature · 3 lines
/-\ | What's the worse that could |@/ Debian GNU/Linux Developer | happen? Smoke. - Anonymous \- | HWHacker
Jonathan Cameron
Acked-byQuoted 19 lines · Jonathan McDowell
Quoted 2 lines · Jonathan Cameron
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.
Quoted 1 line
Couple of suggestions for minor tidying up inline.
Thanks. Things I've taken on board and updated:
* Use u8, not
uint8_tfor 8 bit types.
* Remove read_reg/write_reg wrappers and usei2c_smbusfunctions directly.
* Foldcontainer_ofusage into structvsc055_chipvariable definition.
* Use !! instead of ? 1 : 0.
* Renamei2c_locktoreg_lock.
* Move comment about cached status to correct location.
Quoted 7 lines
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.
Quoted 11 lines
-----
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]>
Quoted 349 lines
drivers/gpio/Kconfig +7
-0@@ -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_WM831Xdrivers/gpio/Makefile +1
-0@@ -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.odrivers/gpio/vsc055.c new file +283
-0@@ -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" );include/linux/i2c/vsc055.h new file +22
-1@@ -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.