All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jonathan McDowell <noodles@earth.li>
To: Grant Likely <grant.likely@secretlab.ca>
Subject: [PATCH] Add support for VSC055 Enhanced Two-Wire Serial Backplane controller
Date: Wed, 4 May 2011 21:12:05 -0700	[thread overview]
Message-ID: <20110505041205.GY4835@earth.li> (raw)

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 <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..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 <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;
+	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 <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.

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

             reply	other threads:[~2011-05-05  4:12 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-05-05  4:12 Jonathan McDowell [this message]
2011-05-05  9:03 ` [PATCH] Add support for VSC055 Enhanced Two-Wire Serial Backplane controller Jonathan Cameron
2011-05-05 22:00   ` Jonathan McDowell
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=20110505041205.GY4835@earth.li \
    --to=noodles@earth.li \
    --cc=grant.likely@secretlab.ca \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.