]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/gpio/twl4030-gpio.c
twl4030 MMC card detction
[linux-2.6-omap-h63xx.git] / drivers / gpio / twl4030-gpio.c
index 23150274c50593dd9165841f6e0d76f58dc85ca4..37d3eec8730afe3f1a6f48b920cdb4ac3678d2aa 100644 (file)
@@ -46,8 +46,6 @@
  * intended to support multiple hosts.
  *
  * There are also two LED pins used sometimes as output-only GPIOs.
- *
- * FIXME code currently only handles the first IRQ line.
  */
 
 
@@ -85,6 +83,32 @@ static inline int gpio_twl4030_write(u8 address, u8 data)
        return twl4030_i2c_write_u8(TWL4030_MODULE_GPIO, data, address);
 }
 
+/*----------------------------------------------------------------------*/
+
+/*
+ * LED register offsets (use TWL4030_MODULE_{LED,PWMA,PWMB}))
+ * PWMs A and B are dedicated to LEDs A and B, respectively.
+ */
+
+#define TWL4030_LED_LEDEN      0x0
+
+/* LEDEN bits */
+#define LEDEN_LEDAON           BIT(0)
+#define LEDEN_LEDBON           BIT(1)
+#define LEDEN_LEDAEXT          BIT(2)
+#define LEDEN_LEDBEXT          BIT(3)
+#define LEDEN_LEDAPWM          BIT(4)
+#define LEDEN_LEDBPWM          BIT(5)
+#define LEDEN_PWM_LENGTHA      BIT(6)
+#define LEDEN_PWM_LENGTHB      BIT(7)
+
+#define TWL4030_PWMx_PWMxON    0x0
+#define TWL4030_PWMx_PWMxOFF   0x1
+
+#define PWMxON_LENGTH          BIT(7)
+
+/*----------------------------------------------------------------------*/
+
 /*
  * To read a TWL4030 GPIO module register
  */
@@ -97,67 +121,31 @@ static inline int gpio_twl4030_read(u8 address)
        return (ret < 0) ? ret : data;
 }
 
-/*
- * twl4030 GPIO request function
- */
-int twl4030_request_gpio(int gpio)
-{
-       int ret = 0;
-
-       if (unlikely(gpio >= TWL4030_GPIO_MAX))
-               return -EPERM;
-
-       ret = gpio_request(twl_gpiochip.base + gpio, NULL);
-       if (ret < 0)
-               return ret;
-
-       mutex_lock(&gpio_lock);
-       if (gpio_usage_count & BIT(gpio)) {
-               ret = -EBUSY;
-       } else {
-               /* First time usage? - switch on GPIO module */
-               if (!gpio_usage_count) {
-                       ret = gpio_twl4030_write(REG_GPIO_CTRL,
-                                       MASK_GPIO_CTRL_GPIO_ON);
+/*----------------------------------------------------------------------*/
 
-               }
-               if (!ret)
-                       gpio_usage_count |= BIT(gpio);
-               else
-                       gpio_free(twl_gpiochip.base + gpio);
-       }
-       mutex_unlock(&gpio_lock);
-       return ret;
-}
-EXPORT_SYMBOL(twl4030_request_gpio);
+static u8 cached_leden;                /* protected by gpio_lock */
 
-/*
- * TWL4030 GPIO free module
+/* 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
+ * as a LED brightness control; we initialize it as "always on".
  */
-int twl4030_free_gpio(int gpio)
+static void twl4030_led_set_value(int led, int value)
 {
-       int ret = 0;
+       u8 mask = LEDEN_LEDAON | LEDEN_LEDAPWM;
+       int status;
 
-       if (unlikely(gpio >= TWL4030_GPIO_MAX))
-               return -EPERM;
+       if (led)
+               mask <<= 1;
 
        mutex_lock(&gpio_lock);
-
-       if ((gpio_usage_count & BIT(gpio)) == 0) {
-               ret = -EPERM;
-       } else {
-               gpio_usage_count &= ~BIT(gpio);
-               gpio_free(twl_gpiochip.base + gpio);
-       }
-
-       /* Last time usage? - switch off GPIO module */
-       if (ret == 0 && !gpio_usage_count)
-               ret = gpio_twl4030_write(REG_GPIO_CTRL, 0x0);
-
+       if (value)
+               cached_leden &= ~mask;
+       else
+               cached_leden |= mask;
+       status = twl4030_i2c_write_u8(TWL4030_MODULE_LED, cached_leden,
+                       TWL4030_LED_LEDEN);
        mutex_unlock(&gpio_lock);
-       return ret;
 }
-EXPORT_SYMBOL(twl4030_free_gpio);
 
 static int twl4030_set_gpio_direction(int gpio, int is_input)
 {
@@ -195,7 +183,7 @@ static int twl4030_set_gpio_dataout(int gpio, int enable)
        return gpio_twl4030_write(base, d_msk);
 }
 
-int twl4030_get_gpio_datain(int gpio)
+static int twl4030_get_gpio_datain(int gpio)
 {
        u8 d_bnk = gpio >> 3;
        u8 d_off = gpio & 0x7;
@@ -213,7 +201,6 @@ int twl4030_get_gpio_datain(int gpio)
 
        return ret;
 }
-EXPORT_SYMBOL(twl4030_get_gpio_datain);
 
 /*
  * Configure debounce timing value for a GPIO pin on TWL4030
@@ -246,69 +233,134 @@ int twl4030_set_gpio_debounce(int gpio, int enable)
 }
 EXPORT_SYMBOL(twl4030_set_gpio_debounce);
 
-#if 0
-/*
- * Configure Card detect for GPIO pin on TWL4030
- *
- * This means:  VMMC1 or VMMC2 is enabled or disabled based
- * on the status of GPIO-0 or GPIO-1 pins (respectively).
- */
-int twl4030_set_gpio_card_detect(int gpio, int enable)
+/*----------------------------------------------------------------------*/
+
+static int twl_request(struct gpio_chip *chip, unsigned offset)
 {
-       u8 reg = 0;
-       u8 msk = (1 << gpio);
-       int ret = 0;
+       int status = 0;
 
-       /* Only GPIO 0 or 1 can be used for CD feature.. */
-       if (unlikely((gpio >= TWL4030_GPIO_MAX)
-               || !(gpio_usage_count & BIT(gpio))
-               || (gpio >= TWL4030_GPIO_MAX_CD))) {
-               return -EPERM;
+       mutex_lock(&gpio_lock);
+
+       /* Support the two LED outputs as output-only GPIOs. */
+       if (offset >= TWL4030_GPIO_MAX) {
+               u8      ledclr_mask = LEDEN_LEDAON | LEDEN_LEDAEXT
+                               | LEDEN_LEDAPWM | LEDEN_PWM_LENGTHA;
+               u8      module = TWL4030_MODULE_PWMA;
+
+               offset -= TWL4030_GPIO_MAX;
+               if (offset) {
+                       ledclr_mask <<= 1;
+                       module = TWL4030_MODULE_PWMB;
+               }
+
+               /* initialize PWM to always-drive */
+               status = twl4030_i2c_write_u8(module, 0x7f,
+                               TWL4030_PWMx_PWMxOFF);
+               if (status < 0)
+                       goto done;
+               status = twl4030_i2c_write_u8(module, 0x7f,
+                               TWL4030_PWMx_PWMxON);
+               if (status < 0)
+                       goto done;
+
+               /* init LED to not-driven (high) */
+               module = TWL4030_MODULE_LED;
+               status = twl4030_i2c_read_u8(module, &cached_leden,
+                               TWL4030_LED_LEDEN);
+               if (status < 0)
+                       goto done;
+               cached_leden &= ~ledclr_mask;
+               status = twl4030_i2c_write_u8(module, cached_leden,
+                               TWL4030_LED_LEDEN);
+               if (status < 0)
+                       goto done;
+
+               status = 0;
+               goto done;
        }
 
-       mutex_lock(&gpio_lock);
-       ret = gpio_twl4030_read(REG_GPIO_CTRL);
-       if (ret >= 0) {
-               if (enable)
-                       reg = (u8) (ret | msk);
-               else
-                       reg = (u8) (ret & ~msk);
+       /* on first use, turn GPIO module "on" */
+       if (!gpio_usage_count) {
+               struct twl4030_gpio_platform_data *pdata;
+               u8 value = MASK_GPIO_CTRL_GPIO_ON;
+
+               /* optionally have the first two GPIOs switch vMMC1
+                * and vMMC2 power supplies based on card presence.
+                */
+               pdata = chip->dev->platform_data;
+               value |= pdata->mmc_cd & 0x03;
 
-               ret = gpio_twl4030_write(REG_GPIO_CTRL, reg);
+               status = gpio_twl4030_write(REG_GPIO_CTRL, value);
        }
+
+       if (!status)
+               gpio_usage_count |= (0x1 << offset);
+
+done:
        mutex_unlock(&gpio_lock);
-       return ret;
+       return status;
 }
-#endif
 
-/*----------------------------------------------------------------------*/
+static void twl_free(struct gpio_chip *chip, unsigned offset)
+{
+       if (offset >= TWL4030_GPIO_MAX) {
+               twl4030_led_set_value(offset - TWL4030_GPIO_MAX, 1);
+               return;
+       }
+
+       mutex_lock(&gpio_lock);
+
+       gpio_usage_count &= ~BIT(offset);
+
+       /* on last use, switch off GPIO module */
+       if (!gpio_usage_count)
+               gpio_twl4030_write(REG_GPIO_CTRL, 0x0);
+
+       mutex_unlock(&gpio_lock);
+}
 
 static int twl_direction_in(struct gpio_chip *chip, unsigned offset)
 {
-       return twl4030_set_gpio_direction(offset, 1);
+       return (offset < TWL4030_GPIO_MAX)
+               ? twl4030_set_gpio_direction(offset, 1)
+               : -EINVAL;
 }
 
 static int twl_get(struct gpio_chip *chip, unsigned offset)
 {
-       int status = twl4030_get_gpio_datain(offset);
+       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;
+       else
+               status = cached_leden & LEDEN_LEDBON;
        return (status < 0) ? 0 : status;
 }
 
 static int twl_direction_out(struct gpio_chip *chip, unsigned offset, int value)
 {
-       twl4030_set_gpio_dataout(offset, value);
-       return twl4030_set_gpio_direction(offset, 0);
+       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;
+       }
 }
 
 static void twl_set(struct gpio_chip *chip, unsigned offset, int value)
 {
-       twl4030_set_gpio_dataout(offset, value);
+       if (offset < TWL4030_GPIO_MAX)
+               twl4030_set_gpio_dataout(offset, value);
+       else
+               twl4030_led_set_value(offset - TWL4030_GPIO_MAX, value);
 }
 
 static int twl_to_irq(struct gpio_chip *chip, unsigned offset)
 {
-       return twl4030_gpio_irq_base
+       return (twl4030_gpio_irq_base && (offset < TWL4030_GPIO_MAX))
                ? (twl4030_gpio_irq_base + offset)
                : -EINVAL;
 }
@@ -316,6 +368,8 @@ static int twl_to_irq(struct gpio_chip *chip, unsigned offset)
 static struct gpio_chip twl_gpiochip = {
        .label                  = "twl4030",
        .owner                  = THIS_MODULE,
+       .request                = twl_request,
+       .free                   = twl_free,
        .direction_input        = twl_direction_in,
        .get                    = twl_get,
        .direction_output       = twl_direction_out,
@@ -389,6 +443,12 @@ no_irqs:
        twl_gpiochip.ngpio = TWL4030_GPIO_MAX;
        twl_gpiochip.dev = &pdev->dev;
 
+       /* NOTE: we assume VIBRA_CTL.VIBRA_EN, in MODULE_AUDIO_VOICE,
+        * is (still) clear if use_leds is set.
+        */
+       if (pdata->use_leds)
+               twl_gpiochip.ngpio += 2;
+
        ret = gpiochip_add(&twl_gpiochip);
        if (ret < 0) {
                dev_err(&pdev->dev,