[PATCH 2.6.24-rc4-mm 2/2] gpiolib: add Generic IRQ support for 16-bit PCA9539 GPIO expander
From: eric miao
Date: Mon Dec 10 2007 - 04:43:37 EST
This patch adds the generic IRQ support for the PCA9539 on-chip GPIOs,
platform specific code is required to keep a correct gpio_to_irq() and
irq_to_gpio() mapping.
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@xxxxxxxxxxx>
Acked-by: Ben Gardner <bgardner@xxxxxxxxxx>
---
drivers/gpio/Kconfig | 10 +++-
drivers/gpio/pca9539.c | 184 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 193 insertions(+), 1 deletions(-)
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 6528fce..ee5b684 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -40,7 +40,15 @@ config GPIO_PCA9539
16-bit I/O port.
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
+ help
+ Say yes here to support the Generic IRQ for the PCA9539 on-chip
+ GPIO lines.
comment "SPI GPIO expanders:"
diff --git a/drivers/gpio/pca9539.c b/drivers/gpio/pca9539.c
index 0a3ae6a..e736dd9 100644
--- a/drivers/gpio/pca9539.c
+++ b/drivers/gpio/pca9539.c
@@ -11,6 +11,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>
@@ -27,9 +30,25 @@ struct pca9539_chip {
unsigned gpio_start;
uint16_t reg_output;
uint16_t reg_direction;
+ uint16_t last_input;
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;
+
+ 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)
@@ -152,6 +171,150 @@ 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 = 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_GPIO_PCA9539_GENERIC_IRQ */
+
static int __devinit pca9539_probe(struct i2c_client *client)
{
struct pca9539_platform_data *pdata;
@@ -167,8 +330,15 @@ static int __devinit pca9539_probe(struct
i2c_client *client)
return -ENOMEM;
chip->client = client;
+ chip->irq = client->irq;
chip->gpio_start = pdata->gpio_base;
+ chip->irq_start = gpio_to_irq(chip->gpio_start);
+
+ /* read init register values */
+ ret = pca9539_read_reg(chip, PCA9539_INPUT, &chip->last_input);
+ if (ret)
+ goto out_failed;
/* initialize registers */
chip->reg_output = 0xffff;
@@ -191,6 +361,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 +375,13 @@ out_failed:
return ret;
}
+#ifdef CONFIG_GPIO_PCA9539_GENERIC_IRQ
+static int pca9539_remove(struct i2c_client *client)
+{
+ printk(KERN_ERR "failed to unload the 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 +404,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
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/