[i2c] [PATCH] basic gpio_chip support for 16-bit PCA9539 GPIO expander
eric miao
eric.y.miao at gmail.com
Mon Dec 3 12:01:26 CET 2007
On Dec 3, 2007 5:12 PM, David Brownell <david-b at pacbell.net> wrote:
> Kconfig needs to say that it depends on GPIO_LIB...
>
> And none of the IRQ logic should be included in modular builds;
> see below.
>
> - Dave
>
Indeed, sorry for this. Updated, please review, thx.
>From 4e79cf08d4f75fbc713cf8840aac05e31e65993f Mon Sep 17 00:00:00 2001
From: eric miao <eric.miao at marvell.com>
Date: Thu, 29 Nov 2007 09:44:57 +0800
Subject: [PATCH] pxa: basic gpio_chip support for 16-bit PCA9539 GPIO expander
1. make the original pca9539 driver to use the new style I2C driver
model
2. use "struct gpio_chip" so that PCA9539 can be used by Generic GPIO
API
3. add "struct irq_chip" support for on-chip IRQs, due to the interrupt
based I2C transfer, the IRQ handlers are actually performed within
workqueue
4. remove sysfs entries to avoid register access conflict with Generic
GPIO/IRQ support (assuming that no one is ever using these entries)
5. use 2 x 8-bit register access to simplify the logic, cache OUTPUT
and DIRECTION registers for fast access
6. platform code is required to setup
a) the numbering of GPIO/IRQ for PCA9539
b) the mapping between these GPIOs and IRQs
i.e. keep correct gpio_to_irq()/irq_to_gpio()
c) pass "pca9539_platform_data" within "i2c_board_info"
Signed-off-by: eric miao <eric.miao at marvell.com>
---
Documentation/i2c/chips/pca9539 | 36 ++--
drivers/i2c/chips/Kconfig | 2 +-
drivers/i2c/chips/pca9539.c | 456 ++++++++++++++++++++++++++++-----------
include/linux/pca9539.h | 18 ++
4 files changed, 361 insertions(+), 151 deletions(-)
create mode 100644 include/linux/pca9539.h
diff --git a/Documentation/i2c/chips/pca9539 b/Documentation/i2c/chips/pca9539
index c4fce6a..8426696 100644
--- a/Documentation/i2c/chips/pca9539
+++ b/Documentation/i2c/chips/pca9539
@@ -19,29 +19,23 @@ All 16 lines can be individually configured as an
input or output.
The input sense can also be inverted.
The 16 lines are split between two bytes.
+Generic GPIO support
+--------------------
-Sysfs entries
--------------
-
-Each is a byte that maps to the 8 I/O bits.
-A '0' suffix is for bits 0-7, while '1' is for bits 8-15.
-
-input[01] - read the current value
-output[01] - sets the output value
-direction[01] - direction of each bit: 1=input, 0=output
-invert[01] - toggle the input bit sense
+The extended I/O ports are now fully covered by Generic GPIO API with
+GPIO lib support. The "struct pca9539_platform_data" has to be filled
+by platform specific code to properly initialize the PCA9539.
-input reads the actual state of the line and is always available.
-The direction defaults to input for all channels.
+Generic IRQ support
+-------------------
+Each of the 16 I/O ports can also be used as cascaded IRQ line. Unlike
+normal IRQs, the IRQ handler code for PCA9539 is actually executed by
+work queue to avoid I2C transactions to be performed within interrupt
+context.
-General Remarks
----------------
-
-Note that each output, direction, and invert entry controls 8 lines.
-You should use the read, modify, write sequence.
-For example. to set output bit 0 of 1.
- val=$(cat output0)
- val=$(( $val | 1 ))
- echo $val > output0
+Sysfs entries
+-------------
+The original sysfs entries are removed to avoid register access conflict
+with the Generic GPIO/IRQ support.
diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index 2e1c24f..90f1064 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -67,7 +67,7 @@ config SENSORS_PCF8574
config SENSORS_PCA9539
tristate "Philips PCA9539 16-bit I/O port"
- depends on EXPERIMENTAL
+ depends on EXPERIMENTAL && GPIO_LIB
help
If you say yes here you get support for the Philips PCA9539
16-bit I/O port.
diff --git a/drivers/i2c/chips/pca9539.c b/drivers/i2c/chips/pca9539.c
index f43c4e7..de64e27 100644
--- a/drivers/i2c/chips/pca9539.c
+++ b/drivers/i2c/chips/pca9539.c
@@ -2,6 +2,7 @@
pca9539.c - 16-bit I/O port with interrupt and reset
Copyright (C) 2005 Ben Gardner <bgardner at wabtec.com>
+ Copyright (C) 2007 Marvell Internation Ltd.
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
@@ -10,173 +11,371 @@
#include <linux/module.h>
#include <linux/init.h>
-#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
#include <linux/i2c.h>
-#include <linux/hwmon-sysfs.h>
+#include <linux/pca9539.h>
-/* Addresses to scan */
-static unsigned short normal_i2c[] = {0x74, 0x75, 0x76, 0x77, I2C_CLIENT_END};
+#include <asm/gpio.h>
-/* Insmod parameters */
-I2C_CLIENT_INSMOD_1(pca9539);
+#define NR_PCA9539_GPIOS 16
-enum pca9539_cmd
-{
- PCA9539_INPUT_0 = 0,
- PCA9539_INPUT_1 = 1,
- PCA9539_OUTPUT_0 = 2,
- PCA9539_OUTPUT_1 = 3,
- PCA9539_INVERT_0 = 4,
- PCA9539_INVERT_1 = 5,
- PCA9539_DIRECTION_0 = 6,
- PCA9539_DIRECTION_1 = 7,
-};
+#define PCA9539_INPUT 0
+#define PCA9539_OUTPUT 2
+#define PCA9539_INVERT 4
+#define PCA9539_DIRECTION 6
-static int pca9539_attach_adapter(struct i2c_adapter *adapter);
-static int pca9539_detect(struct i2c_adapter *adapter, int address, int kind);
-static int pca9539_detach_client(struct i2c_client *client);
+struct pca9539_chip {
+ int irq;
-/* This is the driver that will be inserted */
-static struct i2c_driver pca9539_driver = {
- .driver = {
- .name = "pca9539",
- },
- .attach_adapter = pca9539_attach_adapter,
- .detach_client = pca9539_detach_client,
-};
+ unsigned gpio_start;
+ int irq_start;
-struct pca9539_data {
- struct i2c_client client;
+ uint16_t reg_output;
+ uint16_t reg_direction;
+
+ uint16_t last_input;
+
+ struct i2c_client *client;
+ struct gpio_chip gpio_chip;
+
+#ifndef MODULE
+ /*
+ * Note: Generic IRQ is not accessible within module code, the IRQ
+ * support will thus _only_ be present if the driver is built-in
+ */
+ uint16_t irq_mask;
+ uint16_t irq_falling_edge;
+ uint16_t irq_rising_edge;
+
+ struct irq_chip irq_chip;
+ struct work_struct irq_work;
+#endif
};
-/* following are the sysfs callback functions */
-static ssize_t pca9539_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+static int pca9539_write_reg(struct pca9539_chip *chip, int reg, uint16_t val)
{
- struct sensor_device_attribute *psa = to_sensor_dev_attr(attr);
- struct i2c_client *client = to_i2c_client(dev);
- return sprintf(buf, "%d\n", i2c_smbus_read_byte_data(client,
- psa->index));
+ return i2c_smbus_write_word_data(chip->client, reg, val);
}
-static ssize_t pca9539_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
+static int pca9539_read_reg(struct pca9539_chip *chip, int reg, uint16_t *val)
{
- struct sensor_device_attribute *psa = to_sensor_dev_attr(attr);
- struct i2c_client *client = to_i2c_client(dev);
- unsigned long val = simple_strtoul(buf, NULL, 0);
- if (val > 0xff)
- return -EINVAL;
- i2c_smbus_write_byte_data(client, psa->index, val);
- return count;
-}
-
-/* Define the device attributes */
-
-#define PCA9539_ENTRY_RO(name, cmd_idx) \
- static SENSOR_DEVICE_ATTR(name, S_IRUGO, pca9539_show, NULL, cmd_idx)
-
-#define PCA9539_ENTRY_RW(name, cmd_idx) \
- static SENSOR_DEVICE_ATTR(name, S_IRUGO | S_IWUSR, pca9539_show, \
- pca9539_store, cmd_idx)
-
-PCA9539_ENTRY_RO(input0, PCA9539_INPUT_0);
-PCA9539_ENTRY_RO(input1, PCA9539_INPUT_1);
-PCA9539_ENTRY_RW(output0, PCA9539_OUTPUT_0);
-PCA9539_ENTRY_RW(output1, PCA9539_OUTPUT_1);
-PCA9539_ENTRY_RW(invert0, PCA9539_INVERT_0);
-PCA9539_ENTRY_RW(invert1, PCA9539_INVERT_1);
-PCA9539_ENTRY_RW(direction0, PCA9539_DIRECTION_0);
-PCA9539_ENTRY_RW(direction1, PCA9539_DIRECTION_1);
-
-static struct attribute *pca9539_attributes[] = {
- &sensor_dev_attr_input0.dev_attr.attr,
- &sensor_dev_attr_input1.dev_attr.attr,
- &sensor_dev_attr_output0.dev_attr.attr,
- &sensor_dev_attr_output1.dev_attr.attr,
- &sensor_dev_attr_invert0.dev_attr.attr,
- &sensor_dev_attr_invert1.dev_attr.attr,
- &sensor_dev_attr_direction0.dev_attr.attr,
- &sensor_dev_attr_direction1.dev_attr.attr,
- NULL
-};
+ int ret;
-static struct attribute_group pca9539_defattr_group = {
- .attrs = pca9539_attributes,
-};
+ ret = i2c_smbus_read_word_data(chip->client, reg);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: failed to read\n", __FUNCTION__);
+ return ret;
+ }
+
+ *val = (uint16_t)ret;
+ return 0;
+}
+
+static int pca9539_gpio_direction_input(struct gpio_chip *gc, unsigned off)
+{
+ struct pca9539_chip *chip;
+ uint16_t reg_val;
+ int ret;
+
+ chip = container_of(gc, struct pca9539_chip, gpio_chip);
+
+ reg_val = chip->reg_direction | (1u << off);
+ ret = pca9539_write_reg(chip, PCA9539_DIRECTION, reg_val);
+ if (ret)
+ return ret;
+
+ chip->reg_direction = reg_val;
+ return 0;
+}
+
+static int pca9539_gpio_direction_output(struct gpio_chip *gc,
+ unsigned off, int val)
+{
+ struct pca9539_chip *chip;
+ uint16_t reg_val;
+ int ret;
-static int pca9539_attach_adapter(struct i2c_adapter *adapter)
+ chip = container_of(gc, struct pca9539_chip, gpio_chip);
+
+ /* set output level */
+ if (val)
+ reg_val = chip->reg_output | (1u << off);
+ else
+ reg_val = chip->reg_output & ~(1u << off);
+
+ ret = pca9539_write_reg(chip, PCA9539_OUTPUT, reg_val);
+ if (ret)
+ return ret;
+
+ chip->reg_output = reg_val;
+
+ /* then direction */
+ reg_val = chip->reg_direction & ~(1u << off);
+ ret = pca9539_write_reg(chip, PCA9539_DIRECTION, reg_val);
+ if (ret)
+ return ret;
+
+ chip->reg_direction = reg_val;
+ return 0;
+}
+
+static int pca9539_gpio_get_value(struct gpio_chip *gc, unsigned off)
{
- return i2c_probe(adapter, &addr_data, pca9539_detect);
+ struct pca9539_chip *chip;
+ uint16_t reg_val;
+ int ret;
+
+ chip = container_of(gc, struct pca9539_chip, gpio_chip);
+
+ ret = pca9539_read_reg(chip, PCA9539_INPUT, ®_val);
+ if (ret < 0)
+ return ret;
+
+ return (reg_val & (1u << off)) ? 1 : 0;
}
-/* This function is called by i2c_probe */
-static int pca9539_detect(struct i2c_adapter *adapter, int address, int kind)
+static void pca9539_gpio_set_value(struct gpio_chip *gc, unsigned off, int val)
{
- struct i2c_client *new_client;
- struct pca9539_data *data;
- int err = 0;
+ struct pca9539_chip *chip;
+ uint16_t reg_val;
+ int ret;
+
+ chip = container_of(gc, struct pca9539_chip, gpio_chip);
+
+ if (val)
+ reg_val = chip->reg_output | (1u << off);
+ else
+ reg_val = chip->reg_output & ~(1u << off);
+
+ ret = pca9539_write_reg(chip, PCA9539_OUTPUT, reg_val);
+ if (ret)
+ return;
+
+ chip->reg_output = reg_val;
+}
+
+static int pca9539_init_gpio(struct pca9539_chip *chip)
+{
+ struct gpio_chip *gc;
+
+ gc = &chip->gpio_chip;
+
+ gc->direction_input = pca9539_gpio_direction_input;
+ gc->direction_output = pca9539_gpio_direction_output;
+ gc->get = pca9539_gpio_get_value;
+ gc->set = pca9539_gpio_set_value;
+
+ gc->base = chip->gpio_start;
+ gc->ngpio = NR_PCA9539_GPIOS;
+
+ return gpiochip_add(gc);
+}
+
+#ifndef MODULE
+/* FIXME: change to schedule_delayed_work() here if reading out of
+ * registers does not reflect the actual pin levels
+ */
+
+static void pca9539_irq_work(struct work_struct *work)
+{
+ struct pca9539_chip *chip;
+ uint16_t input, mask, rising, falling;
+ int ret, i;
+
+ chip = container_of(work, struct pca9539_chip, irq_work);
- if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
- goto exit;
+ ret = pca9539_read_reg(chip, PCA9539_INPUT, &input);
+ if (ret < 0)
+ return;
- /* OK. For now, we presume we have a valid client. We now create the
- client structure, even though we cannot fill it completely yet. */
- if (!(data = kzalloc(sizeof(struct pca9539_data), GFP_KERNEL))) {
- err = -ENOMEM;
- goto exit;
+ mask = (input ^ chip->last_input) & chip->irq_mask;
+ rising = (input & mask) & chip->irq_rising_edge;
+ falling = (~input & mask) & chip->irq_falling_edge;
+
+ irq_enter();
+
+ for (i = 0; i < NR_PCA9539_GPIOS; i++) {
+ if ((rising | falling) & (1u << i)) {
+ int irq = chip->irq_start + i;
+ struct irq_desc *desc;
+
+ desc = irq_desc + irq;
+ desc_handle_irq(irq, desc);
+ }
}
- new_client = &data->client;
- i2c_set_clientdata(new_client, data);
- new_client->addr = address;
- new_client->adapter = adapter;
- new_client->driver = &pca9539_driver;
- new_client->flags = 0;
-
- if (kind < 0) {
- /* Detection: the pca9539 only has 8 registers (0-7).
- A read of 7 should succeed, but a read of 8 should fail. */
- if ((i2c_smbus_read_byte_data(new_client, 7) < 0) ||
- (i2c_smbus_read_byte_data(new_client, 8) >= 0))
- goto exit_kfree;
+ irq_exit();
+
+ chip->last_input = input;
+}
+
+static void fastcall
+pca9539_irq_demux(unsigned int irq, struct irq_desc *desc)
+{
+ struct pca9539_chip *chip = desc->handler_data;
+
+ desc->chip->mask(chip->irq);
+ desc->chip->ack(chip->irq);
+ schedule_work(&chip->irq_work);
+ desc->chip->unmask(chip->irq);
+}
+
+static void pca9539_irq_mask(unsigned int irq)
+{
+ struct irq_desc *desc = irq_desc + irq;
+ struct pca9539_chip *chip = desc->chip_data;
+
+ chip->irq_mask &= ~(1u << (irq - chip->irq_start));
+}
+
+static void pca9539_irq_unmask(unsigned int irq)
+{
+ struct irq_desc *desc = irq_desc + irq;
+ struct pca9539_chip *chip = desc->chip_data;
+
+ chip->irq_mask |= 1u << (irq - chip->irq_start);
+}
+
+static void pca9539_irq_ack(unsigned int irq)
+{
+ /* unfortunately, we have to provide an empty irq_chip.ack even
+ * if we do nothing here, Generic IRQ will complain otherwise
+ */
+}
+
+static int pca9539_irq_set_type(unsigned int irq, unsigned int type)
+{
+ struct irq_desc *desc = irq_desc + irq;
+ struct pca9539_chip *chip = desc->chip_data;
+ uint16_t mask = 1u << (irq - chip->irq_start);
+
+ if (type == IRQT_PROBE) {
+ if ((mask & chip->irq_rising_edge) ||
+ (mask & chip->irq_falling_edge) ||
+ (mask & ~chip->reg_direction))
+ return 0;
+
+ type = __IRQT_RISEDGE | __IRQT_FALEDGE;
}
- strlcpy(new_client->name, "pca9539", I2C_NAME_SIZE);
+ gpio_direction_input(irq_to_gpio(irq));
- /* Tell the I2C layer a new client has arrived */
- if ((err = i2c_attach_client(new_client)))
- goto exit_kfree;
+ if (type & __IRQT_RISEDGE)
+ chip->irq_rising_edge |= mask;
+ else
+ chip->irq_rising_edge &= ~mask;
- /* Register sysfs hooks */
- err = sysfs_create_group(&new_client->dev.kobj,
- &pca9539_defattr_group);
- if (err)
- goto exit_detach;
+ if (type & __IRQT_FALEDGE)
+ chip->irq_falling_edge |= mask;
+ else
+ chip->irq_falling_edge &= ~mask;
return 0;
+}
-exit_detach:
- i2c_detach_client(new_client);
-exit_kfree:
- kfree(data);
-exit:
- return err;
+static int pca9539_init_irq(struct pca9539_chip *chip)
+{
+ struct irq_chip *ic = &chip->irq_chip;
+ int irq, irq_start = chip->irq_start;
+
+ /* do not install GPIO interrupts for the chip if
+ * 1. the PCA9539 interrupt line is not used
+ * 2. or the GPIO interrupt number exceeds NR_IRQS
+ */
+ if (chip->irq <= 0 || irq_start + NR_PCA9539_GPIOS >= NR_IRQS)
+ return -EINVAL;
+
+ chip->irq_mask = 0;
+ chip->irq_rising_edge = 0;
+ chip->irq_falling_edge = 0;
+
+ ic->ack = pca9539_irq_ack;
+ ic->mask = pca9539_irq_mask;
+ ic->unmask = pca9539_irq_unmask;
+ ic->set_type = pca9539_irq_set_type;
+
+ for (irq = irq_start; irq < irq_start + NR_PCA9539_GPIOS; irq++) {
+ set_irq_chip(irq, ic);
+ set_irq_chip_data(irq, chip);
+ set_irq_handler(irq, handle_edge_irq);
+ set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+ }
+
+ set_irq_type(chip->irq, IRQT_FALLING);
+ set_irq_data(chip->irq, chip);
+ set_irq_chained_handler(chip->irq, pca9539_irq_demux);
+
+ INIT_WORK(&chip->irq_work, pca9539_irq_work);
+ return 0;
}
+#else
+static inline int pca9539_init_irq(struct pca9539_chip *chip)
+{
+ return 0;
+}
+#endif /* !CONFIG_MODULE */
-static int pca9539_detach_client(struct i2c_client *client)
+static int __devinit pca9539_probe(struct i2c_client *client)
{
- int err;
+ struct pca9539_platform_data *pdata;
+ struct pca9539_chip *chip;
+ int ret;
+
+ pdata = client->dev.platform_data;
+ if (pdata == NULL)
+ return -ENODEV;
+
+ chip = kzalloc(sizeof(struct pca9539_chip), GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->irq = client->irq;
- sysfs_remove_group(&client->dev.kobj, &pca9539_defattr_group);
+ chip->gpio_start = pdata->gpio_base;
+ chip->irq_start = gpio_to_irq(chip->gpio_start);
- if ((err = i2c_detach_client(client)))
- return err;
+ /* read init register values */
+ ret = pca9539_read_reg(chip, PCA9539_INPUT, &chip->last_input);
+ if (ret)
+ goto out_failed;
- kfree(i2c_get_clientdata(client));
+ /* initialize registers */
+ chip->reg_output = 0xffff;
+ chip->reg_direction = 0xffff;
+
+ pca9539_write_reg(chip, PCA9539_OUTPUT, chip->reg_output);
+ pca9539_write_reg(chip, PCA9539_DIRECTION, chip->reg_direction);
+
+ /* set platform specific polarity inversion */
+ pca9539_write_reg(chip, PCA9539_INVERT, pdata->invert);
+
+ ret = pca9539_init_gpio(chip);
+ if (ret)
+ goto out_failed;
+
+ ret = pca9539_init_irq(chip);
+ if (ret)
+ goto out_failed;
+
+ i2c_set_clientdata(client, chip);
return 0;
+
+out_failed:
+ kfree(chip);
+ return ret;
}
+/* FIXME: PCA9539 is not supposed to be removed for now */
+static struct i2c_driver pca9539_driver = {
+ .driver = {
+ .name = "pca9539",
+ },
+ .probe = pca9539_probe,
+};
+
static int __init pca9539_init(void)
{
return i2c_add_driver(&pca9539_driver);
@@ -193,4 +392,3 @@ MODULE_LICENSE("GPL");
module_init(pca9539_init);
module_exit(pca9539_exit);
-
diff --git a/include/linux/pca9539.h b/include/linux/pca9539.h
new file mode 100644
index 0000000..611d84a
--- /dev/null
+++ b/include/linux/pca9539.h
@@ -0,0 +1,18 @@
+/* platform data for the PCA9539 16-bit I/O expander driver */
+
+struct pca9539_platform_data {
+ /* number of the first GPIO */
+ unsigned gpio_base;
+
+ /* initial polarity inversion setting */
+ uint16_t invert;
+
+ 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);
+};
--
1.5.2.5.GIT
--
Cheers
- eric
More information about the i2c
mailing list