2007-12-10 09:43:34

by Eric Miao

[permalink] [raw]
Subject: [PATCH 2.6.24-rc4-mm 2/2] gpiolib: add Generic IRQ support for 16-bit PCA9539 GPIO expander

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 <[email protected]>
Acked-by: Ben Gardner <[email protected]>
---
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


2007-12-10 10:15:04

by David Brownell

[permalink] [raw]
Subject: Re: [PATCH 2.6.24-rc4-mm 2/2] gpiolib: add Generic IRQ support for 16-bit PCA9539 GPIO expander

On Monday 10 December 2007, eric miao wrote:
> +config GPIO_PCA9539_GENERIC_IRQ
> +???????bool " ? ?Generic IRQ support for PCA9539"
> +???????depends on GPIO_PCA9539=y

Also depends on GENERIC_HARDIRQS, right? (You should let
the Kconfig UI handle indentation, too...)

Seems like doing this for an I2C chip ought to shake loose
some interesting review comments. :)


> +???????help
> +??????? ?Say yes here to support the Generic IRQ for the PCA9539 on-chip
> +??????? ?GPIO lines.

This somewhat resembles the pcf857x chips in that it only support
pin-changed IRQs (IRQ_TYPE_EDGE_BOTH) in hardware. Some other I/O
expanders are a bit more flexible.

- Dave

2007-12-11 02:28:53

by Eric Miao

[permalink] [raw]
Subject: Re: [PATCH 2.6.24-rc4-mm 2/2] gpiolib: add Generic IRQ support for 16-bit PCA9539 GPIO expander

On Dec 10, 2007 6:14 PM, David Brownell <[email protected]> wrote:
> On Monday 10 December 2007, eric miao wrote:
> > +config GPIO_PCA9539_GENERIC_IRQ
> > +bool " Generic IRQ support for PCA9539"
> > +depends on GPIO_PCA9539=y
>
> Also depends on GENERIC_HARDIRQS, right? (You should let
> the Kconfig UI handle indentation, too...)
>
> Seems like doing this for an I2C chip ought to shake loose
> some interesting review comments. :)
>
>
> > +help
> > + Say yes here to support the Generic IRQ for the PCA9539 on-chip
> > + GPIO lines.
>
> This somewhat resembles the pcf857x chips in that it only support
> pin-changed IRQs (IRQ_TYPE_EDGE_BOTH) in hardware. Some other I/O
> expanders are a bit more flexible.
>
> - Dave
>

Updated as follows:

>From 486724d8b2b7a668600e38807680cc3a089ad533 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]>
Acked-by: Ben Gardner <[email protected]>
---
drivers/gpio/Kconfig | 11 +++-
drivers/gpio/pca9539.c | 184 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 194 insertions(+), 1 deletions(-)

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 6528fce..f897df8 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -40,7 +40,16 @@ 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 && 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.

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



--
Cheers
- eric