lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:	Fri, 18 Mar 2011 12:04:58 +0800
From:	Haojian Zhuang <haojian.zhuang@...vell.com>
To:	grant.likely@...retlab.ca, linux@....linux.org.uk,
	linux-kernel@...r.kernel.org
Cc:	Haojian Zhuang <haojian.zhuang@...vell.com>
Subject: [PATCH] gpio: enable expander pca957x

Support both PCA9574(8-bit) and PCA9575(16-bit) gpio expander.

Signed-off-by: Haojian Zhuang <haojian.zhuang@...vell.com>
---
 drivers/gpio/Kconfig        |   14 ++
 drivers/gpio/Makefile       |    1 +
 drivers/gpio/pca957x.c      |  491 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/i2c/pca957x.h |   28 +++
 4 files changed, 534 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpio/pca957x.c
 create mode 100644 include/linux/i2c/pca957x.h

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 7ef9108..28be09a 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -191,6 +191,20 @@ config GPIO_PCA953X_IRQ
 	  Say yes here to enable the pca953x to be used as an interrupt
 	  controller. It requires the driver to be built in the kernel.
 
+config GPIO_PCA957X
+	tristate "PCA957x I/O ports"
+	depends on I2C
+	help
+	  Say yes here to provide access several register-oriented
+	  SMBus I/O expanders. Compatible models include:
+
+	  8 bits:	pca9574
+
+	  16 bits:	pca9575
+
+	  This driver can also be built as a module. If so, the module
+	  will be called pca957x
+
 config GPIO_PCF857X
 	tristate "PCF857x, PCA{85,96}7x, and MAX732[89] I2C GPIO expanders"
 	depends on I2C
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 3351cf8..6d941bd 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_GPIO_MC33880)	+= mc33880.o
 obj-$(CONFIG_GPIO_MCP23S08)	+= mcp23s08.o
 obj-$(CONFIG_GPIO_74X164)	+= 74x164.o
 obj-$(CONFIG_GPIO_PCA953X)	+= pca953x.o
+obj-$(CONFIG_GPIO_PCA957X)	+= pca957x.o
 obj-$(CONFIG_GPIO_PCF857X)	+= pcf857x.o
 obj-$(CONFIG_GPIO_PCH)		+= pch_gpio.o
 obj-$(CONFIG_GPIO_PL061)	+= pl061.o
diff --git a/drivers/gpio/pca957x.c b/drivers/gpio/pca957x.c
new file mode 100644
index 0000000..9d8ceb1
--- /dev/null
+++ b/drivers/gpio/pca957x.c
@@ -0,0 +1,491 @@
+/*
+ *  pca957x.c - 8/16 bit I/O ports
+ *
+ *  Copyright (C) 2005 Ben Gardner <bgardner@...tec.com>
+ *  Copyright (C) 2007 Marvell International Ltd.
+ *
+ *  Derived from drivers/i2c/chips/pca9539.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/irq.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/i2c/pca957x.h>
+
+#define PCA957X_IN	0
+#define PCA957X_INVRT	1
+#define PCA957X_BKEN	2
+#define PCA957X_PUPD	3
+#define PCA957X_CFG	4
+#define PCA957X_OUT	5
+#define PCA957X_MSK	6
+#define PCA957X_INTS	7
+
+static const struct i2c_device_id pca957x_id[] = {
+	{ "pca9574", 8, },
+	{ "pca9575", 16, },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, pca957x_id);
+
+struct pca957x_chip {
+	unsigned	gpio_start;
+	uint16_t	reg_output;
+	uint16_t	reg_direction;
+
+	struct		i2c_client *client;
+	struct		gpio_chip gpio_chip;
+	struct mutex	irq_lock;
+	uint16_t	irq_mask;
+	uint16_t	irq_stat;
+	uint16_t	irq_trig_raise;
+	uint16_t	irq_trig_fall;
+	int		irq_base;
+};
+
+static int pca957x_read_reg(struct pca957x_chip *chip, int reg, uint16_t *val)
+{
+	int ret;
+
+	if (chip->gpio_chip.ngpio <= 8)
+		ret = i2c_smbus_read_byte_data(chip->client, reg);
+	else
+		ret = i2c_smbus_read_word_data(chip->client, reg << 1);
+
+	if (ret < 0) {
+		dev_err(&chip->client->dev, "failed reading register\n");
+		return ret;
+	}
+
+	*val = (uint16_t)ret;
+	return 0;
+}
+
+static int pca957x_write_reg(struct pca957x_chip *chip, int reg, uint16_t val)
+{
+	int ret;
+
+	if (chip->gpio_chip.ngpio <= 8)
+		ret = i2c_smbus_write_byte_data(chip->client, reg, val);
+	else {
+		ret = i2c_smbus_write_byte_data(chip->client, reg << 1,
+						val & 0xff);
+		if (!ret) {
+			ret = i2c_smbus_write_byte_data(chip->client,
+							(reg << 1) + 1,
+							(val & 0xff00) >> 8);
+		}
+	}
+
+	if (ret < 0) {
+		dev_err(&chip->client->dev, "failed writing register\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int pca957x_gpio_direction_input(struct gpio_chip *gc, unsigned off)
+{
+	struct pca957x_chip *chip;
+	uint16_t reg_val;
+	int ret;
+
+	chip = container_of(gc, struct pca957x_chip, gpio_chip);
+
+	reg_val = chip->reg_direction | (1u << off);
+	ret = pca957x_write_reg(chip, PCA957X_CFG, reg_val);
+	if (ret)
+		return ret;
+
+	chip->reg_direction = reg_val;
+	return 0;
+}
+
+static int pca957x_gpio_direction_output(struct gpio_chip *gc,
+					 unsigned off, int val)
+{
+	struct pca957x_chip *chip;
+	uint16_t reg_val;
+	int ret;
+
+	chip = container_of(gc, struct pca957x_chip, gpio_chip);
+
+	/* set output level */
+	if (val)
+		reg_val = chip->reg_output | (1u << off);
+	else
+		reg_val = chip->reg_output & ~(1u << off);
+
+	ret = pca957x_write_reg(chip, PCA957X_OUT, reg_val);
+	if (ret)
+		return ret;
+
+	chip->reg_output = reg_val;
+
+	/* then direction */
+	reg_val = chip->reg_direction & ~(1u << off);
+	ret = pca957x_write_reg(chip, PCA957X_CFG, reg_val);
+	if (ret)
+		return ret;
+
+	chip->reg_direction = reg_val;
+	return 0;
+}
+
+static int pca957x_gpio_get_value(struct gpio_chip *gc, unsigned off)
+{
+	struct pca957x_chip *chip;
+	uint16_t reg_val;
+	int ret;
+
+	chip = container_of(gc, struct pca957x_chip, gpio_chip);
+
+	ret = pca957x_read_reg(chip, PCA957X_IN, &reg_val);
+	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)) ? 1 : 0;
+}
+
+static void pca957x_gpio_set_value(struct gpio_chip *gc, unsigned off, int val)
+{
+	struct pca957x_chip *chip;
+	uint16_t reg_val;
+	int ret;
+
+	chip = container_of(gc, struct pca957x_chip, gpio_chip);
+
+	if (val)
+		reg_val = chip->reg_output | (1u << off);
+	else
+		reg_val = chip->reg_output & ~(1u << off);
+
+	ret = pca957x_write_reg(chip, PCA957X_OUT, reg_val);
+	if (ret)
+		return;
+
+	chip->reg_output = reg_val;
+}
+
+static void pca957x_setup_gpio(struct pca957x_chip *chip, int gpios)
+{
+	struct gpio_chip *gc;
+
+	gc = &chip->gpio_chip;
+
+	gc->direction_input  = pca957x_gpio_direction_input;
+	gc->direction_output = pca957x_gpio_direction_output;
+	gc->get = pca957x_gpio_get_value;
+	gc->set = pca957x_gpio_set_value;
+	gc->can_sleep = 1;
+
+	gc->base = chip->gpio_start;
+	gc->ngpio = gpios;
+	gc->label = chip->client->name;
+	gc->dev = &chip->client->dev;
+	gc->owner = THIS_MODULE;
+}
+
+static int pca957x_gpio_to_irq(struct gpio_chip *gc, unsigned off)
+{
+	struct pca957x_chip *chip;
+
+	chip = container_of(gc, struct pca957x_chip, gpio_chip);
+	return chip->irq_base + off;
+}
+
+static void pca957x_irq_mask(struct irq_data *d)
+{
+	struct pca957x_chip *chip = irq_data_get_irq_chip_data(d);
+
+	chip->irq_mask &= ~(1 << (d->irq - chip->irq_base));
+}
+
+static void pca957x_irq_unmask(struct irq_data *d)
+{
+	struct pca957x_chip *chip = irq_data_get_irq_chip_data(d);
+
+	chip->irq_mask |= 1 << (d->irq - chip->irq_base);
+}
+
+static void pca957x_irq_bus_lock(struct irq_data *d)
+{
+	struct pca957x_chip *chip = irq_data_get_irq_chip_data(d);
+
+	mutex_lock(&chip->irq_lock);
+}
+
+static void pca957x_irq_bus_sync_unlock(struct irq_data *d)
+{
+	struct pca957x_chip *chip = irq_data_get_irq_chip_data(d);
+
+	mutex_unlock(&chip->irq_lock);
+}
+
+static int pca957x_irq_set_type(struct irq_data *d, unsigned int type)
+{
+	struct pca957x_chip *chip = irq_data_get_irq_chip_data(d);
+	uint16_t mask = 1u << (d->irq - chip->irq_base);
+
+	if (!(type & IRQ_TYPE_EDGE_BOTH)) {
+		dev_err(&chip->client->dev, "irq %d: unsupported type %d\n",
+			d->irq, type);
+		return -EINVAL;
+	}
+
+	if (type & IRQ_TYPE_EDGE_RISING)
+		chip->irq_trig_raise |= mask;
+	else
+		chip->irq_trig_raise &= ~mask;
+
+	if (type & IRQ_TYPE_EDGE_FALLING)
+		chip->irq_trig_fall |= mask;
+	else
+		chip->irq_trig_fall &= ~mask;
+
+	return 0;
+}
+
+static struct irq_chip pca957x_irq_chip = {
+	.name			= "pca957x",
+	.irq_mask		= pca957x_irq_mask,
+	.irq_unmask		= pca957x_irq_unmask,
+	.irq_bus_lock		= pca957x_irq_bus_lock,
+	.irq_bus_sync_unlock	= pca957x_irq_bus_sync_unlock,
+	.irq_set_type		= pca957x_irq_set_type,
+};
+
+static irqreturn_t pca957x_irq_handler(int irq, void *devid)
+{
+	struct pca957x_chip *chip = devid;
+	uint16_t old_in, new_in, pending, level, trigger;
+	int ret;
+
+	ret = pca957x_read_reg(chip, PCA957X_IN, &new_in);
+	if (ret)
+		goto out;
+
+	new_in &= chip->reg_direction;
+	old_in = chip->irq_stat;
+	trigger = (new_in ^ old_in) & chip->irq_mask;
+	if (!trigger)
+		goto out;
+
+	chip->irq_stat = new_in;
+	pending = (old_in & chip->irq_trig_fall)
+		| (new_in & chip->irq_trig_raise);
+	pending &= trigger;
+
+	do {
+		level = __ffs(pending);
+		generic_handle_irq(level + chip->irq_base);
+
+		pending &= ~(1 << level);
+	} while (pending);
+out:
+	return IRQ_HANDLED;
+}
+
+static int pca957x_init_irq(struct pca957x_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	struct pca957x_platform_data *pdata = client->dev.platform_data;
+	unsigned long flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
+				| IRQF_ONESHOT;
+	int ret = -EINVAL, i, __irq;
+
+	if (pdata->irq_base != -1) {
+		ret = pca957x_read_reg(chip, PCA957X_IN, &chip->irq_stat);
+		if (ret)
+			goto out;
+
+		chip->irq_stat &= chip->reg_direction;
+		chip->irq_base = pdata->irq_base;
+		mutex_init(&chip->irq_lock);
+
+		for (i = 0; i < chip->gpio_chip.ngpio; i++) {
+			__irq = i + chip->irq_base;
+			set_irq_chip_data(__irq, chip);
+			set_irq_chip_and_handler(__irq, &pca957x_irq_chip,
+						 handle_edge_irq);
+			set_irq_nested_thread(__irq, 1);
+#ifdef CONFIG_ARM
+			set_irq_flags(__irq, IRQF_VALID);
+#else
+			set_irq_noprobe(irq);
+#endif
+		}
+		ret = request_threaded_irq(client->irq, NULL,
+					   pca957x_irq_handler, flags,
+					   dev_name(&client->dev), chip);
+		if (ret) {
+			dev_err(&client->dev, "failed to request irq %d\n",
+				client->irq);
+			goto out;
+		}
+		chip->gpio_chip.to_irq = pca957x_gpio_to_irq;
+	} else
+		goto out;
+
+	return 0;
+out:
+	chip->irq_base = -1;
+	return ret;
+}
+
+static void pca957x_irq_teardown(struct pca957x_chip *chip)
+{
+	if (chip->irq_base != -1)
+		free_irq(chip->client->irq, chip);
+}
+
+static int __devinit pca957x_probe(struct i2c_client *client,
+				   const struct i2c_device_id *id)
+{
+	struct pca957x_platform_data *pdata;
+	struct pca957x_chip *chip;
+	int ret, retval;
+	uint16_t val;
+
+	pdata = client->dev.platform_data;
+	if (pdata == NULL) {
+		dev_dbg(&client->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	chip = kzalloc(sizeof(struct pca957x_chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+
+	chip->client = client;
+
+	chip->gpio_start = pdata->gpio_base;
+
+	/*
+	 * Initialize cached registers from their original values.
+	 * We can't share this chip with another i2c master.
+	 */
+	pca957x_setup_gpio(chip, id->driver_data);
+
+	/* Let every port in proper state , that could save power */
+	pca957x_write_reg(chip, PCA957X_PUPD, 0x0);
+	pca957x_write_reg(chip, PCA957X_CFG, 0xffff);
+	pca957x_write_reg(chip, PCA957X_OUT, 0x0);
+
+	ret = pca957x_read_reg(chip, PCA957X_IN, &val);
+	if (ret)
+		goto out;
+
+	ret = pca957x_read_reg(chip, PCA957X_OUT, &chip->reg_output);
+	if (ret)
+		goto out;
+
+	ret = pca957x_read_reg(chip, PCA957X_CFG, &chip->reg_direction);
+	if (ret)
+		goto out;
+
+	/* set platform specific polarity inversion */
+	ret = pca957x_write_reg(chip, PCA957X_INVRT, pdata->invert);
+	if (ret)
+		goto out;
+
+	/* To enable register 6, 7 to controll pull up and pull down */
+	pca957x_write_reg(chip, PCA957X_BKEN, 0x202);
+
+	ret = gpiochip_add(&chip->gpio_chip);
+	if (ret)
+		goto out;
+
+	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);
+	}
+
+	ret = pca957x_init_irq(chip);
+	if (ret)
+		goto out_irq;
+
+	i2c_set_clientdata(client, chip);
+	return 0;
+
+out_irq:
+	retval = gpiochip_remove(&chip->gpio_chip);
+out:
+	kfree(chip);
+	return ret;
+}
+
+static int __devexit pca957x_remove(struct i2c_client *client)
+{
+	struct pca957x_platform_data *pdata = client->dev.platform_data;
+	struct pca957x_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, ret:%d\n",
+				"teardown", ret);
+			return ret;
+		}
+	}
+
+	ret = gpiochip_remove(&chip->gpio_chip);
+	if (ret) {
+		dev_err(&client->dev, "%s failed, ret:%d\n",
+			"gpiochip_remove()", ret);
+		return ret;
+	}
+
+	pca957x_irq_teardown(chip);
+	kfree(chip);
+	return 0;
+}
+
+static struct i2c_driver pca957x_driver = {
+	.driver = {
+		.name	= "pca9575",
+	},
+	.probe		= pca957x_probe,
+	.remove		= __devexit_p(pca957x_remove),
+	.id_table	= pca957x_id,
+};
+
+static int __init pca957x_init(void)
+{
+	return i2c_add_driver(&pca957x_driver);
+}
+/*
+ * register after i2c postcore initcall and before
+ * subsys initcalls that may rely on these GPIOs
+ */
+subsys_initcall_sync(pca957x_init);
+
+static void __exit pca957x_exit(void)
+{
+	i2c_del_driver(&pca957x_driver);
+}
+module_exit(pca957x_exit);
+
+MODULE_AUTHOR("Jack Ren <jack.ren@...vell.com>");
+MODULE_DESCRIPTION("GPIO expander driver for PCA957x");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/i2c/pca957x.h b/include/linux/i2c/pca957x.h
new file mode 100644
index 0000000..dd78b63
--- /dev/null
+++ b/include/linux/i2c/pca957x.h
@@ -0,0 +1,28 @@
+#ifndef _LINUX_PCA957X_H
+#define _LINUX_PCA957X_H
+
+#include <linux/types.h>
+#include <linux/i2c.h>
+
+struct pca957x_platform_data {
+	/* number of the first GPIO */
+	unsigned	gpio_base;
+
+	/* initial polarity inversion setting */
+	uint16_t	invert;
+
+	/* interrupt base */
+	int		irq_base;
+
+	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_PCA957X_H */
-- 
1.5.6.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists