[PATCH] gpio: add interrupt support on pca953x
Haojian Zhuang
haojian.zhuang at marvell.com
Tue Dec 22 15:08:41 EST 2009
Support interrupt on pca953x gpio expander.
Signed-off-by: Haojian Zhuang <haojian.zhuang at marvell.com>
---
drivers/gpio/pca953x.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 119 insertions(+), 2 deletions(-)
diff --git a/drivers/gpio/pca953x.c b/drivers/gpio/pca953x.c
index 6a2fb3f..2d6b9e8 100644
--- a/drivers/gpio/pca953x.c
+++ b/drivers/gpio/pca953x.c
@@ -16,6 +16,7 @@
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/i2c/pca953x.h>
+#include <linux/irq.h>
#ifdef CONFIG_OF_GPIO
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
@@ -50,12 +51,19 @@ MODULE_DEVICE_TABLE(i2c, pca953x_id);
struct pca953x_chip {
unsigned gpio_start;
+ unsigned irq_start;
uint16_t reg_output;
uint16_t reg_direction;
+ uint16_t irq_mask;
+ uint16_t irq_falling_edge;
+ uint16_t irq_rising_edge;
+ uint16_t last_input;
- struct i2c_client *client;
struct pca953x_platform_data *dyn_pdata;
- struct gpio_chip gpio_chip;
+ struct i2c_client *client;
+ struct gpio_chip gpio_chip;
+ struct mutex irq_lock;
+ struct work_struct irq_work;
char **names;
};
@@ -250,11 +258,113 @@ pca953x_get_alt_pdata(struct i2c_client *client)
}
#endif
+#define PCA953X_NUM_IRQ 16
+
+static void pca953x_irq_work(struct work_struct *work)
+{
+ DECLARE_BITMAP(rising, PCA953X_NUM_IRQ);
+ DECLARE_BITMAP(falling, PCA953X_NUM_IRQ);
+ struct pca953x_chip *chip;
+ unsigned short input, mask;
+ int ret, irq;
+
+ chip = container_of(work, struct pca953x_chip, irq_work);
+ ret = pca953x_read_reg(chip, PCA953X_INPUT, &input);
+ if (ret < 0)
+ return;
+
+ mutex_lock(&chip->irq_lock);
+ mask = (input ^ chip->last_input) & chip->irq_mask;
+ rising[0] = (input & mask) & chip->irq_rising_edge;
+ falling[0] = (~input & mask) & chip->irq_falling_edge;
+
+ while (!bitmap_empty(rising, PCA953X_NUM_IRQ)) {
+ irq = find_first_bit(rising, PCA953X_NUM_IRQ);
+ clear_bit(irq, rising);
+ generic_handle_irq(chip->irq_start + irq);
+ }
+ while (!bitmap_empty(falling, PCA953X_NUM_IRQ)) {
+ irq = find_first_bit(falling, PCA953X_NUM_IRQ);
+ clear_bit(irq, falling);
+ generic_handle_irq(chip->irq_start + irq);
+ }
+ chip->last_input = input;
+ mutex_unlock(&chip->irq_lock);
+}
+
+static void pca953x_irq_handler(unsigned int irq, struct irq_desc *desc)
+{
+ struct pca953x_chip *chip = desc->handler_data;
+
+ desc->chip->ack(irq);
+ schedule_work(&chip->irq_work);
+}
+
+static void pca953x_irq_enable(unsigned int irq)
+{
+ struct pca953x_chip *chip = get_irq_chip_data(irq);
+
+ chip->irq_mask |= 1u << (irq - chip->irq_start);
+}
+
+static void pca953x_irq_disable(unsigned int irq)
+{
+ struct pca953x_chip *chip = get_irq_chip_data(irq);
+
+ chip->irq_mask &= ~(1u << (irq - chip->irq_start));
+}
+
+static int pca953x_irq_type(unsigned int irq, unsigned int trigger)
+{
+ struct pca953x_chip *chip = get_irq_chip_data(irq);
+ unsigned short mask = 1u << (irq - chip->irq_start);
+
+ if (trigger & IRQ_TYPE_EDGE_RISING)
+ chip->irq_rising_edge |= mask;
+ else
+ chip->irq_rising_edge &= ~mask;
+ if (trigger & IRQ_TYPE_EDGE_FALLING)
+ chip->irq_falling_edge |= mask;
+ else
+ chip->irq_falling_edge &= ~mask;
+ return 0;
+}
+
+static struct irq_chip pca953x_irqchip = {
+ .name = "GPIO",
+ .enable = pca953x_irq_enable,
+ .disable = pca953x_irq_disable,
+ .set_type = pca953x_irq_type,
+};
+
+static void pca953x_setup_irq(struct i2c_client *client)
+{
+ struct pca953x_chip *chip = i2c_get_clientdata(client);
+ int i;
+
+ if (!client->irq)
+ return;
+
+ mutex_init(&chip->irq_lock);
+ chip->irq_start = gpio_to_irq(chip->gpio_start);
+ for (i = 0; i < chip->gpio_chip.ngpio; i++) {
+ set_irq_chip(chip->irq_start + i, &pca953x_irqchip);
+ set_irq_chip_data(chip->irq_start + i, chip);
+ set_irq_handler(chip->irq_start + i, handle_edge_irq);
+ set_irq_flags(chip->irq_start + i, IRQF_VALID);
+ }
+ set_irq_type(client->irq, IRQ_TYPE_EDGE_FALLING);
+ set_irq_data(client->irq, chip);
+ set_irq_chained_handler(client->irq, pca953x_irq_handler);
+ INIT_WORK(&chip->irq_work, pca953x_irq_work);
+}
+
static int __devinit pca953x_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct pca953x_platform_data *pdata;
struct pca953x_chip *chip;
+ unsigned short input;
int ret;
chip = kzalloc(sizeof(struct pca953x_chip), GFP_KERNEL);
@@ -282,12 +392,18 @@ static int __devinit pca953x_probe(struct
i2c_client *client,
chip->gpio_start = pdata->gpio_base;
chip->names = pdata->names;
+ chip->irq_mask = -1UL;
/* initialize cached registers from their original values.
* we can't share this chip with another i2c master.
*/
pca953x_setup_gpio(chip, id->driver_data);
+ /* clear interrupt status */
+ ret = pca953x_read_reg(chip, PCA953X_INPUT, &input);
+ if (ret)
+ goto out_failed;
+
ret = pca953x_read_reg(chip, PCA953X_OUTPUT, &chip->reg_output);
if (ret)
goto out_failed;
@@ -314,6 +430,7 @@ static int __devinit pca953x_probe(struct
i2c_client *client,
}
i2c_set_clientdata(client, chip);
+ pca953x_setup_irq(client);
return 0;
out_failed:
--
1.5.6.5
More information about the linux-arm-kernel
mailing list