diff options
Diffstat (limited to 'drivers/gpio/pca953x.c')
| -rw-r--r-- | drivers/gpio/pca953x.c | 249 | 
1 files changed, 237 insertions, 12 deletions
diff --git a/drivers/gpio/pca953x.c b/drivers/gpio/pca953x.c index 6a2fb3fbb3d9..ab5daab14bc2 100644 --- a/drivers/gpio/pca953x.c +++ b/drivers/gpio/pca953x.c @@ -14,6 +14,8 @@  #include <linux/module.h>  #include <linux/init.h>  #include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/irq.h>  #include <linux/i2c.h>  #include <linux/i2c/pca953x.h>  #ifdef CONFIG_OF_GPIO @@ -26,23 +28,28 @@  #define PCA953X_INVERT         2  #define PCA953X_DIRECTION      3 +#define PCA953X_GPIOS	       0x00FF +#define PCA953X_INT	       0x0100 +  static const struct i2c_device_id pca953x_id[] = { -	{ "pca9534", 8, }, -	{ "pca9535", 16, }, +	{ "pca9534", 8  | PCA953X_INT, }, +	{ "pca9535", 16 | PCA953X_INT, },  	{ "pca9536", 4, }, -	{ "pca9537", 4, }, -	{ "pca9538", 8, }, -	{ "pca9539", 16, }, -	{ "pca9554", 8, }, -	{ "pca9555", 16, }, +	{ "pca9537", 4  | PCA953X_INT, }, +	{ "pca9538", 8  | PCA953X_INT, }, +	{ "pca9539", 16 | PCA953X_INT, }, +	{ "pca9554", 8  | PCA953X_INT, }, +	{ "pca9555", 16 | PCA953X_INT, },  	{ "pca9556", 8, },  	{ "pca9557", 8, },  	{ "max7310", 8, }, -	{ "max7315", 8, }, -	{ "pca6107", 8, }, -	{ "tca6408", 8, }, -	{ "tca6416", 16, }, +	{ "max7312", 16 | PCA953X_INT, }, +	{ "max7313", 16 | PCA953X_INT, }, +	{ "max7315", 8  | PCA953X_INT, }, +	{ "pca6107", 8  | PCA953X_INT, }, +	{ "tca6408", 8  | PCA953X_INT, }, +	{ "tca6416", 16 | PCA953X_INT, },  	/* NYET:  { "tca6424", 24, }, */  	{ }  }; @@ -53,6 +60,15 @@ struct pca953x_chip {  	uint16_t reg_output;  	uint16_t reg_direction; +#ifdef CONFIG_GPIO_PCA953X_IRQ +	struct mutex irq_lock; +	uint16_t irq_mask; +	uint16_t irq_stat; +	uint16_t irq_trig_raise; +	uint16_t irq_trig_fall; +	int	 irq_base; +#endif +  	struct i2c_client *client;  	struct pca953x_platform_data *dyn_pdata;  	struct gpio_chip gpio_chip; @@ -202,6 +218,210 @@ static void pca953x_setup_gpio(struct pca953x_chip *chip, int gpios)  	gc->names = chip->names;  } +#ifdef CONFIG_GPIO_PCA953X_IRQ +static int pca953x_gpio_to_irq(struct gpio_chip *gc, unsigned off) +{ +	struct pca953x_chip *chip; + +	chip = container_of(gc, struct pca953x_chip, gpio_chip); +	return chip->irq_base + off; +} + +static void pca953x_irq_mask(unsigned int irq) +{ +	struct pca953x_chip *chip = get_irq_chip_data(irq); + +	chip->irq_mask &= ~(1 << (irq - chip->irq_base)); +} + +static void pca953x_irq_unmask(unsigned int irq) +{ +	struct pca953x_chip *chip = get_irq_chip_data(irq); + +	chip->irq_mask |= 1 << (irq - chip->irq_base); +} + +static void pca953x_irq_bus_lock(unsigned int irq) +{ +	struct pca953x_chip *chip = get_irq_chip_data(irq); + +	mutex_lock(&chip->irq_lock); +} + +static void pca953x_irq_bus_sync_unlock(unsigned int irq) +{ +	struct pca953x_chip *chip = get_irq_chip_data(irq); + +	mutex_unlock(&chip->irq_lock); +} + +static int pca953x_irq_set_type(unsigned int irq, unsigned int type) +{ +	struct pca953x_chip *chip = get_irq_chip_data(irq); +	uint16_t level = irq - chip->irq_base; +	uint16_t mask = 1 << level; + +	if (!(type & IRQ_TYPE_EDGE_BOTH)) { +		dev_err(&chip->client->dev, "irq %d: unsupported type %d\n", +			irq, type); +		return -EINVAL; +	} + +	if (type & IRQ_TYPE_EDGE_FALLING) +		chip->irq_trig_fall |= mask; +	else +		chip->irq_trig_fall &= ~mask; + +	if (type & IRQ_TYPE_EDGE_RISING) +		chip->irq_trig_raise |= mask; +	else +		chip->irq_trig_raise &= ~mask; + +	return pca953x_gpio_direction_input(&chip->gpio_chip, level); +} + +static struct irq_chip pca953x_irq_chip = { +	.name			= "pca953x", +	.mask			= pca953x_irq_mask, +	.unmask			= pca953x_irq_unmask, +	.bus_lock		= pca953x_irq_bus_lock, +	.bus_sync_unlock	= pca953x_irq_bus_sync_unlock, +	.set_type		= pca953x_irq_set_type, +}; + +static uint16_t pca953x_irq_pending(struct pca953x_chip *chip) +{ +	uint16_t cur_stat; +	uint16_t old_stat; +	uint16_t pending; +	uint16_t trigger; +	int ret; + +	ret = pca953x_read_reg(chip, PCA953X_INPUT, &cur_stat); +	if (ret) +		return 0; + +	/* Remove output pins from the equation */ +	cur_stat &= chip->reg_direction; + +	old_stat = chip->irq_stat; +	trigger = (cur_stat ^ old_stat) & chip->irq_mask; + +	if (!trigger) +		return 0; + +	chip->irq_stat = cur_stat; + +	pending = (old_stat & chip->irq_trig_fall) | +		  (cur_stat & chip->irq_trig_raise); +	pending &= trigger; + +	return pending; +} + +static irqreturn_t pca953x_irq_handler(int irq, void *devid) +{ +	struct pca953x_chip *chip = devid; +	uint16_t pending; +	uint16_t level; + +	pending = pca953x_irq_pending(chip); + +	if (!pending) +		return IRQ_HANDLED; + +	do { +		level = __ffs(pending); +		handle_nested_irq(level + chip->irq_base); + +		pending &= ~(1 << level); +	} while (pending); + +	return IRQ_HANDLED; +} + +static int pca953x_irq_setup(struct pca953x_chip *chip, +			     const struct i2c_device_id *id) +{ +	struct i2c_client *client = chip->client; +	struct pca953x_platform_data *pdata = client->dev.platform_data; +	int ret; + +	if (pdata->irq_base && (id->driver_data & PCA953X_INT)) { +		int lvl; + +		ret = pca953x_read_reg(chip, PCA953X_INPUT, +				       &chip->irq_stat); +		if (ret) +			goto out_failed; + +		/* +		 * There is no way to know which GPIO line generated the +		 * interrupt.  We have to rely on the previous read for +		 * this purpose. +		 */ +		chip->irq_stat &= chip->reg_direction; +		chip->irq_base = pdata->irq_base; +		mutex_init(&chip->irq_lock); + +		for (lvl = 0; lvl < chip->gpio_chip.ngpio; lvl++) { +			int irq = lvl + chip->irq_base; + +			set_irq_chip_data(irq, chip); +			set_irq_chip_and_handler(irq, &pca953x_irq_chip, +						 handle_edge_irq); +			set_irq_nested_thread(irq, 1); +#ifdef CONFIG_ARM +			set_irq_flags(irq, IRQF_VALID); +#else +			set_irq_noprobe(irq); +#endif +		} + +		ret = request_threaded_irq(client->irq, +					   NULL, +					   pca953x_irq_handler, +					   IRQF_TRIGGER_FALLING | IRQF_ONESHOT, +					   dev_name(&client->dev), chip); +		if (ret) { +			dev_err(&client->dev, "failed to request irq %d\n", +				client->irq); +			goto out_failed; +		} + +		chip->gpio_chip.to_irq = pca953x_gpio_to_irq; +	} + +	return 0; + +out_failed: +	chip->irq_base = 0; +	return ret; +} + +static void pca953x_irq_teardown(struct pca953x_chip *chip) +{ +	if (chip->irq_base) +		free_irq(chip->client->irq, chip); +} +#else /* CONFIG_GPIO_PCA953X_IRQ */ +static int pca953x_irq_setup(struct pca953x_chip *chip, +			     const struct i2c_device_id *id) +{ +	struct i2c_client *client = chip->client; +	struct pca953x_platform_data *pdata = client->dev.platform_data; + +	if (pdata->irq_base && (id->driver_data & PCA953X_INT)) +		dev_warn(&client->dev, "interrupt support not compiled in\n"); + +	return 0; +} + +static void pca953x_irq_teardown(struct pca953x_chip *chip) +{ +} +#endif +  /*   * Handlers for alternative sources of platform_data   */ @@ -286,7 +506,7 @@ static int __devinit pca953x_probe(struct i2c_client *client,  	/* initialize cached registers from their original values.  	 * we can't share this chip with another i2c master.  	 */ -	pca953x_setup_gpio(chip, id->driver_data); +	pca953x_setup_gpio(chip, id->driver_data & PCA953X_GPIOS);  	ret = pca953x_read_reg(chip, PCA953X_OUTPUT, &chip->reg_output);  	if (ret) @@ -301,6 +521,9 @@ static int __devinit pca953x_probe(struct i2c_client *client,  	if (ret)  		goto out_failed; +	ret = pca953x_irq_setup(chip, id); +	if (ret) +		goto out_failed;  	ret = gpiochip_add(&chip->gpio_chip);  	if (ret) @@ -317,6 +540,7 @@ static int __devinit pca953x_probe(struct i2c_client *client,  	return 0;  out_failed: +	pca953x_irq_teardown(chip);  	kfree(chip->dyn_pdata);  	kfree(chip);  	return ret; @@ -345,6 +569,7 @@ static int pca953x_remove(struct i2c_client *client)  		return ret;  	} +	pca953x_irq_teardown(chip);  	kfree(chip->dyn_pdata);  	kfree(chip);  	return 0;  |