gpio: twl4030: Cache the direction and output states in private data

Use more coherent locking in the driver. Use bitfield to store the GPIO
direction and if the pin is configured as output store the status also in a
bitfiled.
In this way we can just look at these bitfields when we need information
about the pin status and only reach out to the chip when it is needed.

Signed-off-by: Peter Ujfalusi <peter.ujfalusi@ti.com>
Acked-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
This commit is contained in:
Peter Ujfalusi 2012-12-20 10:44:11 +01:00 committed by Linus Walleij
parent 72c7901ef0
commit c111feabe2

View File

@ -37,7 +37,6 @@
#include <linux/i2c/twl.h> #include <linux/i2c/twl.h>
/* /*
* The GPIO "subchip" supports 18 GPIOs which can be configured as * The GPIO "subchip" supports 18 GPIOs which can be configured as
* inputs or outputs, with pullups or pulldowns on each pin. Each * inputs or outputs, with pullups or pulldowns on each pin. Each
@ -64,14 +63,15 @@
/* Mask for GPIO registers when aggregated into a 32-bit integer */ /* Mask for GPIO registers when aggregated into a 32-bit integer */
#define GPIO_32_MASK 0x0003ffff #define GPIO_32_MASK 0x0003ffff
/* Data structures */
static DEFINE_MUTEX(gpio_lock);
struct gpio_twl4030_priv { struct gpio_twl4030_priv {
struct gpio_chip gpio_chip; struct gpio_chip gpio_chip;
struct mutex mutex;
int irq_base; int irq_base;
/* Bitfields for state caching */
unsigned int usage_count; unsigned int usage_count;
unsigned int direction;
unsigned int out_state;
}; };
/*----------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/
@ -130,7 +130,7 @@ static inline int gpio_twl4030_read(u8 address)
/*----------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/
static u8 cached_leden; /* protected by gpio_lock */ static u8 cached_leden;
/* The LED lines are open drain outputs ... a FET pulls to GND, so an /* The LED lines are open drain outputs ... a FET pulls to GND, so an
* external pullup is needed. We could also expose the integrated PWM * external pullup is needed. We could also expose the integrated PWM
@ -144,14 +144,12 @@ static void twl4030_led_set_value(int led, int value)
if (led) if (led)
mask <<= 1; mask <<= 1;
mutex_lock(&gpio_lock);
if (value) if (value)
cached_leden &= ~mask; cached_leden &= ~mask;
else else
cached_leden |= mask; cached_leden |= mask;
status = twl_i2c_write_u8(TWL4030_MODULE_LED, cached_leden, status = twl_i2c_write_u8(TWL4030_MODULE_LED, cached_leden,
TWL4030_LED_LEDEN_REG); TWL4030_LED_LEDEN_REG);
mutex_unlock(&gpio_lock);
} }
static int twl4030_set_gpio_direction(int gpio, int is_input) static int twl4030_set_gpio_direction(int gpio, int is_input)
@ -162,7 +160,6 @@ static int twl4030_set_gpio_direction(int gpio, int is_input)
u8 base = REG_GPIODATADIR1 + d_bnk; u8 base = REG_GPIODATADIR1 + d_bnk;
int ret = 0; int ret = 0;
mutex_lock(&gpio_lock);
ret = gpio_twl4030_read(base); ret = gpio_twl4030_read(base);
if (ret >= 0) { if (ret >= 0) {
if (is_input) if (is_input)
@ -172,7 +169,6 @@ static int twl4030_set_gpio_direction(int gpio, int is_input)
ret = gpio_twl4030_write(base, reg); ret = gpio_twl4030_write(base, reg);
} }
mutex_unlock(&gpio_lock);
return ret; return ret;
} }
@ -212,7 +208,7 @@ static int twl_request(struct gpio_chip *chip, unsigned offset)
struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip); struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip);
int status = 0; int status = 0;
mutex_lock(&gpio_lock); mutex_lock(&priv->mutex);
/* Support the two LED outputs as output-only GPIOs. */ /* Support the two LED outputs as output-only GPIOs. */
if (offset >= TWL4030_GPIO_MAX) { if (offset >= TWL4030_GPIO_MAX) {
@ -271,7 +267,7 @@ done:
if (!status) if (!status)
priv->usage_count |= BIT(offset); priv->usage_count |= BIT(offset);
mutex_unlock(&gpio_lock); mutex_unlock(&priv->mutex);
return status; return status;
} }
@ -279,64 +275,96 @@ static void twl_free(struct gpio_chip *chip, unsigned offset)
{ {
struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip); struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip);
mutex_lock(&priv->mutex);
if (offset >= TWL4030_GPIO_MAX) { if (offset >= TWL4030_GPIO_MAX) {
twl4030_led_set_value(offset - TWL4030_GPIO_MAX, 1); twl4030_led_set_value(offset - TWL4030_GPIO_MAX, 1);
return; goto out;
} }
mutex_lock(&gpio_lock);
priv->usage_count &= ~BIT(offset); priv->usage_count &= ~BIT(offset);
/* on last use, switch off GPIO module */ /* on last use, switch off GPIO module */
if (!priv->usage_count) if (!priv->usage_count)
gpio_twl4030_write(REG_GPIO_CTRL, 0x0); gpio_twl4030_write(REG_GPIO_CTRL, 0x0);
mutex_unlock(&gpio_lock); out:
mutex_unlock(&priv->mutex);
} }
static int twl_direction_in(struct gpio_chip *chip, unsigned offset) static int twl_direction_in(struct gpio_chip *chip, unsigned offset)
{ {
return (offset < TWL4030_GPIO_MAX) struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip);
? twl4030_set_gpio_direction(offset, 1) int ret;
: -EINVAL;
mutex_lock(&priv->mutex);
if (offset < TWL4030_GPIO_MAX)
ret = twl4030_set_gpio_direction(offset, 1);
else
ret = -EINVAL;
if (!ret)
priv->direction &= ~BIT(offset);
mutex_unlock(&priv->mutex);
return ret;
} }
static int twl_get(struct gpio_chip *chip, unsigned offset) static int twl_get(struct gpio_chip *chip, unsigned offset)
{ {
struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip); struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip);
int ret;
int status = 0; int status = 0;
if (!(priv->usage_count & BIT(offset))) mutex_lock(&priv->mutex);
return -EPERM; if (!(priv->usage_count & BIT(offset))) {
ret = -EPERM;
if (offset < TWL4030_GPIO_MAX) goto out;
status = twl4030_get_gpio_datain(offset);
else if (offset == TWL4030_GPIO_MAX)
status = cached_leden & LEDEN_LEDAON;
else
status = cached_leden & LEDEN_LEDBON;
return (status < 0) ? 0 : status;
}
static int twl_direction_out(struct gpio_chip *chip, unsigned offset, int value)
{
if (offset < TWL4030_GPIO_MAX) {
twl4030_set_gpio_dataout(offset, value);
return twl4030_set_gpio_direction(offset, 0);
} else {
twl4030_led_set_value(offset - TWL4030_GPIO_MAX, value);
return 0;
} }
if (priv->direction & BIT(offset))
status = priv->out_state & BIT(offset);
else
status = twl4030_get_gpio_datain(offset);
ret = (status <= 0) ? 0 : 1;
out:
mutex_unlock(&priv->mutex);
return ret;
} }
static void twl_set(struct gpio_chip *chip, unsigned offset, int value) static void twl_set(struct gpio_chip *chip, unsigned offset, int value)
{ {
struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip);
mutex_lock(&priv->mutex);
if (offset < TWL4030_GPIO_MAX) if (offset < TWL4030_GPIO_MAX)
twl4030_set_gpio_dataout(offset, value); twl4030_set_gpio_dataout(offset, value);
else else
twl4030_led_set_value(offset - TWL4030_GPIO_MAX, value); twl4030_led_set_value(offset - TWL4030_GPIO_MAX, value);
if (value)
priv->out_state |= BIT(offset);
else
priv->out_state &= ~BIT(offset);
mutex_unlock(&priv->mutex);
}
static int twl_direction_out(struct gpio_chip *chip, unsigned offset, int value)
{
struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip);
mutex_lock(&priv->mutex);
if (offset < TWL4030_GPIO_MAX)
twl4030_set_gpio_dataout(offset, value);
priv->direction |= BIT(offset);
mutex_unlock(&priv->mutex);
twl_set(chip, offset, value);
return 0;
} }
static int twl_to_irq(struct gpio_chip *chip, unsigned offset) static int twl_to_irq(struct gpio_chip *chip, unsigned offset)
@ -469,6 +497,8 @@ no_irqs:
priv->gpio_chip.ngpio = TWL4030_GPIO_MAX; priv->gpio_chip.ngpio = TWL4030_GPIO_MAX;
priv->gpio_chip.dev = &pdev->dev; priv->gpio_chip.dev = &pdev->dev;
mutex_init(&priv->mutex);
if (node) if (node)
pdata = of_gpio_twl4030(&pdev->dev); pdata = of_gpio_twl4030(&pdev->dev);