public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Jonathan McDowell <noodles@earth.li>
To: Jonathan Cameron <jic23@cam.ac.uk>
Cc: Grant Likely <grant.likely@secretlab.ca>,
	LKML <linux-kernel@vger.kernel.org>
Subject: Re: [PATCH] Add support for VSC055 Enhanced Two-Wire Serial Backplane controller
Date: Thu, 5 May 2011 15:00:18 -0700	[thread overview]
Message-ID: <20110505220018.GF28362@earth.li> (raw)
In-Reply-To: <4DC267E3.6080206@cam.ac.uk>

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 <noodles@earth.li>

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 <noodles@earth.li>
+ *
+ *  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 <noodles@earth.li>");
+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

  reply	other threads:[~2011-05-05 22:00 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-05-05  4:12 [PATCH] Add support for VSC055 Enhanced Two-Wire Serial Backplane controller Jonathan McDowell
2011-05-05  9:03 ` Jonathan Cameron
2011-05-05 22:00   ` Jonathan McDowell [this message]
2011-05-06 13:40     ` Jonathan Cameron

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20110505220018.GF28362@earth.li \
    --to=noodles@earth.li \
    --cc=grant.likely@secretlab.ca \
    --cc=jic23@cam.ac.uk \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox