Skip to content

Commit

Permalink
drivers/gpio/pca953x.c: add a mutex to fix race condition
Browse files Browse the repository at this point in the history
Add a mutex to register communication and handling.  Without the mutex,
GPIOs didn't switch as expected when toggled in a fast sequence of
status changes of multiple outputs.

Signed-off-by: Roland Stigge <stigge@antcom.de>
Acked-by: Eric Miao <eric.y.miao@gmail.com>
Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Marc Zyngier <maz@misterjones.org>
Cc: Ben Gardner <bgardner@wabtec.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
  • Loading branch information
Roland Stigge authored and Linus Torvalds committed Feb 12, 2011
1 parent 01e05e9 commit 6e20fb1
Showing 1 changed file with 22 additions and 6 deletions.
28 changes: 22 additions & 6 deletions drivers/gpio/pca953x.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ struct pca953x_chip {
unsigned gpio_start;
uint16_t reg_output;
uint16_t reg_direction;
struct mutex i2c_lock;

#ifdef CONFIG_GPIO_PCA953X_IRQ
struct mutex irq_lock;
Expand Down Expand Up @@ -119,13 +120,17 @@ static int pca953x_gpio_direction_input(struct gpio_chip *gc, unsigned off)

chip = container_of(gc, struct pca953x_chip, gpio_chip);

mutex_lock(&chip->i2c_lock);
reg_val = chip->reg_direction | (1u << off);
ret = pca953x_write_reg(chip, PCA953X_DIRECTION, reg_val);
if (ret)
return ret;
goto exit;

chip->reg_direction = reg_val;
return 0;
ret = 0;
exit:
mutex_unlock(&chip->i2c_lock);
return ret;
}

static int pca953x_gpio_direction_output(struct gpio_chip *gc,
Expand All @@ -137,6 +142,7 @@ static int pca953x_gpio_direction_output(struct gpio_chip *gc,

chip = container_of(gc, struct pca953x_chip, gpio_chip);

mutex_lock(&chip->i2c_lock);
/* set output level */
if (val)
reg_val = chip->reg_output | (1u << off);
Expand All @@ -145,18 +151,21 @@ static int pca953x_gpio_direction_output(struct gpio_chip *gc,

ret = pca953x_write_reg(chip, PCA953X_OUTPUT, reg_val);
if (ret)
return ret;
goto exit;

chip->reg_output = reg_val;

/* then direction */
reg_val = chip->reg_direction & ~(1u << off);
ret = pca953x_write_reg(chip, PCA953X_DIRECTION, reg_val);
if (ret)
return ret;
goto exit;

chip->reg_direction = reg_val;
return 0;
ret = 0;
exit:
mutex_unlock(&chip->i2c_lock);
return ret;
}

static int pca953x_gpio_get_value(struct gpio_chip *gc, unsigned off)
Expand All @@ -167,7 +176,9 @@ static int pca953x_gpio_get_value(struct gpio_chip *gc, unsigned off)

chip = container_of(gc, struct pca953x_chip, gpio_chip);

mutex_lock(&chip->i2c_lock);
ret = pca953x_read_reg(chip, PCA953X_INPUT, &reg_val);
mutex_unlock(&chip->i2c_lock);
if (ret < 0) {
/* NOTE: diagnostic already emitted; that's all we should
* do unless gpio_*_value_cansleep() calls become different
Expand All @@ -187,16 +198,19 @@ static void pca953x_gpio_set_value(struct gpio_chip *gc, unsigned off, int val)

chip = container_of(gc, struct pca953x_chip, gpio_chip);

mutex_lock(&chip->i2c_lock);
if (val)
reg_val = chip->reg_output | (1u << off);
else
reg_val = chip->reg_output & ~(1u << off);

ret = pca953x_write_reg(chip, PCA953X_OUTPUT, reg_val);
if (ret)
return;
goto exit;

chip->reg_output = reg_val;
exit:
mutex_unlock(&chip->i2c_lock);
}

static void pca953x_setup_gpio(struct pca953x_chip *chip, int gpios)
Expand Down Expand Up @@ -517,6 +531,8 @@ static int __devinit pca953x_probe(struct i2c_client *client,

chip->names = pdata->names;

mutex_init(&chip->i2c_lock);

/* initialize cached registers from their original values.
* we can't share this chip with another i2c master.
*/
Expand Down

0 comments on commit 6e20fb1

Please sign in to comment.