diff options
Diffstat (limited to 'drivers/gpio/gpio-twl4030.c')
| -rw-r--r-- | drivers/gpio/gpio-twl4030.c | 176 | 
1 files changed, 112 insertions, 64 deletions
diff --git a/drivers/gpio/gpio-twl4030.c b/drivers/gpio/gpio-twl4030.c index 9572aa137e6..4d330e36da1 100644 --- a/drivers/gpio/gpio-twl4030.c +++ b/drivers/gpio/gpio-twl4030.c @@ -37,7 +37,6 @@  #include <linux/i2c/twl.h> -  /*   * The GPIO "subchip" supports 18 GPIOs which can be configured as   * inputs or outputs, with pullups or pulldowns on each pin.  Each @@ -49,11 +48,6 @@   * There are also two LED pins used sometimes as output-only GPIOs.   */ - -static struct gpio_chip twl_gpiochip; -static int twl4030_gpio_base; -static int twl4030_gpio_irq_base; -  /* genirq interfaces are not available to modules */  #ifdef MODULE  #define is_module()	true @@ -69,14 +63,24 @@ static int twl4030_gpio_irq_base;  /* Mask for GPIO registers when aggregated into a 32-bit integer */  #define GPIO_32_MASK			0x0003ffff -/* Data structures */ -static DEFINE_MUTEX(gpio_lock); +struct gpio_twl4030_priv { +	struct gpio_chip gpio_chip; +	struct mutex mutex; +	int irq_base; -/* store usage of each GPIO. - each bit represents one GPIO */ -static unsigned int gpio_usage_count; +	/* Bitfields for state caching */ +	unsigned int usage_count; +	unsigned int direction; +	unsigned int out_state; +};  /*----------------------------------------------------------------------*/ +static inline struct gpio_twl4030_priv *to_gpio_twl4030(struct gpio_chip *chip) +{ +	return container_of(chip, struct gpio_twl4030_priv, gpio_chip); +} +  /*   * To configure TWL4030 GPIO module registers   */ @@ -126,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   * external pullup is needed.  We could also expose the integrated PWM @@ -140,14 +144,12 @@ static void twl4030_led_set_value(int led, int value)  	if (led)  		mask <<= 1; -	mutex_lock(&gpio_lock);  	if (value)  		cached_leden &= ~mask;  	else  		cached_leden |= mask;  	status = twl_i2c_write_u8(TWL4030_MODULE_LED, cached_leden,  				  TWL4030_LED_LEDEN_REG); -	mutex_unlock(&gpio_lock);  }  static int twl4030_set_gpio_direction(int gpio, int is_input) @@ -158,7 +160,6 @@ static int twl4030_set_gpio_direction(int gpio, int is_input)  	u8 base = REG_GPIODATADIR1 + d_bnk;  	int ret = 0; -	mutex_lock(&gpio_lock);  	ret = gpio_twl4030_read(base);  	if (ret >= 0) {  		if (is_input) @@ -168,7 +169,6 @@ static int twl4030_set_gpio_direction(int gpio, int is_input)  		ret = gpio_twl4030_write(base, reg);  	} -	mutex_unlock(&gpio_lock);  	return ret;  } @@ -193,10 +193,6 @@ static int twl4030_get_gpio_datain(int gpio)  	u8 base = 0;  	int ret = 0; -	if (unlikely((gpio >= TWL4030_GPIO_MAX) -		|| !(gpio_usage_count & BIT(gpio)))) -		return -EPERM; -  	base = REG_GPIODATAIN1 + d_bnk;  	ret = gpio_twl4030_read(base);  	if (ret > 0) @@ -209,9 +205,10 @@ static int twl4030_get_gpio_datain(int gpio)  static int twl_request(struct gpio_chip *chip, unsigned offset)  { +	struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip);  	int status = 0; -	mutex_lock(&gpio_lock); +	mutex_lock(&priv->mutex);  	/* Support the two LED outputs as output-only GPIOs. */  	if (offset >= TWL4030_GPIO_MAX) { @@ -252,7 +249,7 @@ static int twl_request(struct gpio_chip *chip, unsigned offset)  	}  	/* on first use, turn GPIO module "on" */ -	if (!gpio_usage_count) { +	if (!priv->usage_count) {  		struct twl4030_gpio_platform_data *pdata;  		u8 value = MASK_GPIO_CTRL_GPIO_ON; @@ -266,79 +263,120 @@ static int twl_request(struct gpio_chip *chip, unsigned offset)  		status = gpio_twl4030_write(REG_GPIO_CTRL, value);  	} +done:  	if (!status) -		gpio_usage_count |= (0x1 << offset); +		priv->usage_count |= BIT(offset); -done: -	mutex_unlock(&gpio_lock); +	mutex_unlock(&priv->mutex);  	return status;  }  static void twl_free(struct gpio_chip *chip, unsigned offset)  { +	struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip); + +	mutex_lock(&priv->mutex);  	if (offset >= TWL4030_GPIO_MAX) {  		twl4030_led_set_value(offset - TWL4030_GPIO_MAX, 1); -		return; +		goto out;  	} -	mutex_lock(&gpio_lock); - -	gpio_usage_count &= ~BIT(offset); +	priv->usage_count &= ~BIT(offset);  	/* on last use, switch off GPIO module */ -	if (!gpio_usage_count) +	if (!priv->usage_count)  		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)  { -	return (offset < TWL4030_GPIO_MAX) -		? twl4030_set_gpio_direction(offset, 1) -		: -EINVAL; +	struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip); +	int ret; + +	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)  { +	struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip); +	int ret;  	int status = 0; -	if (offset < TWL4030_GPIO_MAX) -		status = twl4030_get_gpio_datain(offset); -	else if (offset == TWL4030_GPIO_MAX) -		status = cached_leden & LEDEN_LEDAON; +	mutex_lock(&priv->mutex); +	if (!(priv->usage_count & BIT(offset))) { +		ret = -EPERM; +		goto out; +	} + +	if (priv->direction & BIT(offset)) +		status = priv->out_state & BIT(offset);  	else -		status = cached_leden & LEDEN_LEDBON; -	return (status < 0) ? 0 : status; +		status = twl4030_get_gpio_datain(offset); + +	ret = (status <= 0) ? 0 : 1; +out: +	mutex_unlock(&priv->mutex); +	return ret;  } -static int twl_direction_out(struct gpio_chip *chip, unsigned offset, int value) +static void twl_set(struct gpio_chip *chip, unsigned offset, int value)  { -	if (offset < TWL4030_GPIO_MAX) { +	struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip); + +	mutex_lock(&priv->mutex); +	if (offset < TWL4030_GPIO_MAX)  		twl4030_set_gpio_dataout(offset, value); -		return twl4030_set_gpio_direction(offset, 0); -	} else { +	else  		twl4030_led_set_value(offset - TWL4030_GPIO_MAX, value); -		return 0; -	} + +	if (value) +		priv->out_state |= BIT(offset); +	else +		priv->out_state &= ~BIT(offset); + +	mutex_unlock(&priv->mutex);  } -static void twl_set(struct gpio_chip *chip, unsigned offset, int value) +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); -	else -		twl4030_led_set_value(offset - TWL4030_GPIO_MAX, 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)  { -	return (twl4030_gpio_irq_base && (offset < TWL4030_GPIO_MAX)) -		? (twl4030_gpio_irq_base + offset) +	struct gpio_twl4030_priv *priv = to_gpio_twl4030(chip); + +	return (priv->irq_base && (offset < TWL4030_GPIO_MAX)) +		? (priv->irq_base + offset)  		: -EINVAL;  } -static struct gpio_chip twl_gpiochip = { +static struct gpio_chip template_chip = {  	.label			= "twl4030",  	.owner			= THIS_MODULE,  	.request		= twl_request, @@ -424,8 +462,14 @@ static int gpio_twl4030_probe(struct platform_device *pdev)  {  	struct twl4030_gpio_platform_data *pdata = pdev->dev.platform_data;  	struct device_node *node = pdev->dev.of_node; +	struct gpio_twl4030_priv *priv;  	int ret, irq_base; +	priv = devm_kzalloc(&pdev->dev, sizeof(struct gpio_twl4030_priv), +			    GFP_KERNEL); +	if (!priv) +		return -ENOMEM; +  	/* maybe setup IRQs */  	if (is_module()) {  		dev_err(&pdev->dev, "can't dispatch IRQs from modules\n"); @@ -445,12 +489,15 @@ static int gpio_twl4030_probe(struct platform_device *pdev)  	if (ret < 0)  		return ret; -	twl4030_gpio_irq_base = irq_base; +	priv->irq_base = irq_base;  no_irqs: -	twl_gpiochip.base = -1; -	twl_gpiochip.ngpio = TWL4030_GPIO_MAX; -	twl_gpiochip.dev = &pdev->dev; +	priv->gpio_chip = template_chip; +	priv->gpio_chip.base = -1; +	priv->gpio_chip.ngpio = TWL4030_GPIO_MAX; +	priv->gpio_chip.dev = &pdev->dev; + +	mutex_init(&priv->mutex);  	if (node)  		pdata = of_gpio_twl4030(&pdev->dev); @@ -481,23 +528,23 @@ no_irqs:  	 * is (still) clear if use_leds is set.  	 */  	if (pdata->use_leds) -		twl_gpiochip.ngpio += 2; +		priv->gpio_chip.ngpio += 2; -	ret = gpiochip_add(&twl_gpiochip); +	ret = gpiochip_add(&priv->gpio_chip);  	if (ret < 0) {  		dev_err(&pdev->dev, "could not register gpiochip, %d\n", ret); -		twl_gpiochip.ngpio = 0; +		priv->gpio_chip.ngpio = 0;  		gpio_twl4030_remove(pdev);  		goto out;  	} -	twl4030_gpio_base = twl_gpiochip.base; +	platform_set_drvdata(pdev, priv);  	if (pdata && pdata->setup) {  		int status; -		status = pdata->setup(&pdev->dev, -				twl4030_gpio_base, TWL4030_GPIO_MAX); +		status = pdata->setup(&pdev->dev, priv->gpio_chip.base, +				      TWL4030_GPIO_MAX);  		if (status)  			dev_dbg(&pdev->dev, "setup --> %d\n", status);  	} @@ -510,18 +557,19 @@ out:  static int gpio_twl4030_remove(struct platform_device *pdev)  {  	struct twl4030_gpio_platform_data *pdata = pdev->dev.platform_data; +	struct gpio_twl4030_priv *priv = platform_get_drvdata(pdev);  	int status;  	if (pdata && pdata->teardown) { -		status = pdata->teardown(&pdev->dev, -				twl4030_gpio_base, TWL4030_GPIO_MAX); +		status = pdata->teardown(&pdev->dev, priv->gpio_chip.base, +					 TWL4030_GPIO_MAX);  		if (status) {  			dev_dbg(&pdev->dev, "teardown --> %d\n", status);  			return status;  		}  	} -	status = gpiochip_remove(&twl_gpiochip); +	status = gpiochip_remove(&priv->gpio_chip);  	if (status < 0)  		return status;  |