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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



[updated according to David's suggestion to handle the error
of I2C transfer]

>From c9b78718488dadc702f40789bd532d1f1765d76e Mon Sep 17 00:00:00 2001
From: eric miao <[email protected]>
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 <[email protected]>
---
 drivers/gpio/Kconfig   |   11 +++-
 drivers/gpio/pca9539.c |  185 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 195 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 fc8bee4..10f9549 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,158 @@ 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 ret, irq, irq_start;
+
+	/* initial input register value for IRQ level change detection */
+	ret = pca9539_read_reg(chip, PCA9539_INPUT, &chip->last_input);
+	if (ret)
+		return -EIO;
+
+	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;
@@ -204,6 +375,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;

@@ -212,6 +389,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;
@@ -234,6 +418,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
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux