[PATCH 2.6.24-rc5-mm 2/3] gpiolib: add Generic IRQ support for 16-bit PCA9539 GPIO expander

eric miao
2007-12-15T04:15:33Z

>From b45be77acbf592b9c2085ed03ab5f16d780fa8c7 Mon Sep 17 00:00:00 2001
From: eric miao <eric.miao@marvell.com>
Date: Mon, 10 Dec 2007 17:24:36 +0800
Subject: [PATCH] gpiolib: add Generic IRQ support for 16-bit PCA9539
GPIO expander

This patch adds the generic IRQ support for the PCA9539 on-chip GPIOs.

Note: due to the inaccessibility of the generic IRQ code within modules,
this support is only available if the driver is built-in.

Signed-off-by: eric miao <eric.miao@marvell.com>
---
drivers/gpio/Kconfig | 11 +++-
drivers/gpio/pca9539.c | 183 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 193 insertions(+), 1 deletions(-)

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 4b54f60..a4f89a6 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -17,7 +17,16 @@ config GPIO_PCA9539
parts are made by NXP and TI.

This driver can also be built as a module. If so, the module
- will be called pca9539.
+ will be called pca9539. Note: the Generic IRQ support for the
+ chip will only be available if the driver is built-in
+
+config GPIO_PCA9539_GENERIC_IRQ
+ bool "Generic IRQ support for PCA9539"
+ depends on GPIO_PCA9539=y && GENERIC_HARDIRQS
+ help
+ Say yes here to support the Generic IRQ for the PCA9539 on-chip
+ GPIO lines. Only pin-changed IRQs (IRQ_TYPE_EDGE_BOTH) are
+ supported in hardware.

config GPIO_PCF857X
tristate "PCF857x, PCA857x, and PCA967x I2C GPIO expanders"
diff --git a/drivers/gpio/pca9539.c b/drivers/gpio/pca9539.c
index 955d891..ad3267b 100644
--- a/drivers/gpio/pca9539.c
+++ b/drivers/gpio/pca9539.c
@@ -14,6 +14,9 @@

#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/i2c/pca9539.h>

@@ -33,6 +36,22 @@ struct pca9539_chip {

struct i2c_client *client;
struct gpio_chip gpio_chip;
+#ifdef CONFIG_GPIO_PCA9539_GENERIC_IRQ
+ /*
+ * Note: Generic IRQ is not accessible within module code, the IRQ
+ * support will thus _only_ be available if the driver is built-in
+ */
+ int irq; /* IRQ for the chip itself */
+ int irq_start; /* starting IRQ for the on-chip GPIO lines */
+
+ uint16_t irq_mask;
+ uint16_t irq_falling_edge;
+ uint16_t irq_rising_edge;
+ uint16_t last_input;
+
+ struct irq_chip irq_chip;
+ struct work_struct irq_work;
+#endif
};

static int pca9539_write_reg(struct pca9539_chip *chip, int reg, uint16_t val)
@@ -155,6 +174,156 @@ static int pca9539_init_gpio(struct pca9539_chip *chip)
return gpiochip_add(gc);
}

+#ifdef CONFIG_GPIO_PCA9539_GENERIC_IRQ
+/* 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);
+
+ ret = pca9539_read_reg(chip, PCA9539_INPUT, &input);
+ if (ret < 0)
+ return;
+
+ 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);
+ }
+ }
+
+ 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;
+ }
+
+ gpio_direction_input(irq_to_gpio(irq));
+
+ if (type & __IRQT_RISEDGE)
+ chip->irq_rising_edge |= mask;
+ else
+ chip->irq_rising_edge &= ~mask;
+
+ if (type & __IRQT_FALEDGE)
+ chip->irq_falling_edge |= mask;
+ else
+ chip->irq_falling_edge &= ~mask;
+
+ return 0;
+}
+
+static int pca9539_init_irq(struct pca9539_chip *chip)
+{
+ struct irq_chip *ic = &chip->irq_chip;
+ int irq, irq_start;
+
+ /* initial input register value for IRQ level change detection */
+ pca9539_read_reg(chip, PCA9539_INPUT, &chip->last_input);
+
+ chip->irq = chip->client->irq;
+ chip->irq_start = irq_start = gpio_to_irq(chip->gpio_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_GPIO_PCA9539_GENERIC_IRQ */
+
static int __devinit pca9539_probe(struct i2c_client *client)
{
struct pca9539_platform_data *pdata;
@@ -191,6 +360,12 @@ static int __devinit pca9539_probe(struct
i2c_client *client)
dev_dbg(&client->dev, "setup failed, %d\n", ret);
}

+ ret = pca9539_init_irq(chip);
+ if (ret) {
+ ret = gpiochip_remove(&chip->gpio_chip);
+ goto out_failed;
+ }
+
i2c_set_clientdata(client, chip);
return 0;

@@ -199,6 +374,13 @@ out_failed:
return ret;
}

+#ifdef CONFIG_GPIO_PCA9539_GENERIC_IRQ
+static int pca9539_remove(struct i2c_client *client)
+{
+ dev_err(&client->dev, "failed to unload driver with IRQ support\n");
+ return -EINVAL;
+}
+#else
static int pca9539_remove(struct i2c_client *client)
{
struct pca9539_platform_data *pdata = client->dev.platform_data;
@@ -221,6 +403,7 @@ static int pca9539_remove(struct i2c_client *client)
kfree(chip);
return 0;
}
+#endif /* CONFIG_GPIO_PCA9539_GENERIC_IRQ */

static struct i2c_driver pca9539_driver = {
.driver = {
--
1.5.2.5.GIT

--
Cheers
- eric