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 -- 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/