intelfb: support 945GME (as used in ASUS Eee 901)
[safe/jmp/linux-2.6] / drivers / video / backlight / corgi_lcd.c
index bf69e50..2afd47e 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/delay.h>
+#include <linux/gpio.h>
 #include <linux/fb.h>
 #include <linux/lcd.h>
 #include <linux/spi/spi.h>
@@ -86,17 +87,26 @@ struct corgi_lcd {
        struct lcd_device       *lcd_dev;
        struct backlight_device *bl_dev;
 
+       int     limit_mask;
        int     intensity;
        int     power;
        int     mode;
        char    buf[2];
 
-       void (*notify)(int intensity);
+       int     gpio_backlight_on;
+       int     gpio_backlight_cont;
+       int     gpio_backlight_cont_inverted;
+
        void (*kick_battery)(void);
 };
 
 static int corgi_ssp_lcdtg_send(struct corgi_lcd *lcd, int reg, uint8_t val);
 
+static struct corgi_lcd *the_corgi_lcd;
+static unsigned long corgibl_flags;
+#define CORGIBL_SUSPENDED     0x01
+#define CORGIBL_BATTLOW       0x02
+
 /*
  * This is only a psuedo I2C interface. We can't use the standard kernel
  * routines as the interface is write only. We just assume the data is acked...
@@ -387,18 +397,26 @@ static int corgi_bl_get_intensity(struct backlight_device *bd)
 
 static int corgi_bl_set_intensity(struct corgi_lcd *lcd, int intensity)
 {
+       int cont;
+
        if (intensity > 0x10)
                intensity += 0x10;
 
        corgi_ssp_lcdtg_send(lcd, DUTYCTRL_ADRS, intensity);
-       lcd->intensity = intensity;
 
-       if (lcd->notify)
-               lcd->notify(intensity);
+       /* Bit 5 via GPIO_BACKLIGHT_CONT */
+       cont = !!(intensity & 0x20) ^ lcd->gpio_backlight_cont_inverted;
+
+       if (gpio_is_valid(lcd->gpio_backlight_cont))
+               gpio_set_value(lcd->gpio_backlight_cont, cont);
+
+       if (gpio_is_valid(lcd->gpio_backlight_on))
+               gpio_set_value(lcd->gpio_backlight_on, intensity);
 
        if (lcd->kick_battery)
                lcd->kick_battery();
 
+       lcd->intensity = intensity;
        return 0;
 }
 
@@ -413,9 +431,25 @@ static int corgi_bl_update_status(struct backlight_device *bd)
        if (bd->props.fb_blank != FB_BLANK_UNBLANK)
                intensity = 0;
 
+       if (corgibl_flags & CORGIBL_SUSPENDED)
+               intensity = 0;
+       if (corgibl_flags & CORGIBL_BATTLOW)
+               intensity &= lcd->limit_mask;
+
        return corgi_bl_set_intensity(lcd, intensity);
 }
 
+void corgibl_limit_intensity(int limit)
+{
+       if (limit)
+               corgibl_flags |= CORGIBL_BATTLOW;
+       else
+               corgibl_flags &= ~CORGIBL_BATTLOW;
+
+       backlight_update_status(the_corgi_lcd->bl_dev);
+}
+EXPORT_SYMBOL(corgibl_limit_intensity);
+
 static struct backlight_ops corgi_bl_ops = {
        .get_brightness = corgi_bl_get_intensity,
        .update_status  = corgi_bl_update_status,
@@ -426,6 +460,7 @@ static int corgi_lcd_suspend(struct spi_device *spi, pm_message_t state)
 {
        struct corgi_lcd *lcd = dev_get_drvdata(&spi->dev);
 
+       corgibl_flags |= CORGIBL_SUSPENDED;
        corgi_bl_set_intensity(lcd, 0);
        corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_POWERDOWN);
        return 0;
@@ -435,6 +470,7 @@ static int corgi_lcd_resume(struct spi_device *spi)
 {
        struct corgi_lcd *lcd = dev_get_drvdata(&spi->dev);
 
+       corgibl_flags &= ~CORGIBL_SUSPENDED;
        corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_UNBLANK);
        backlight_update_status(lcd->bl_dev);
        return 0;
@@ -444,6 +480,56 @@ static int corgi_lcd_resume(struct spi_device *spi)
 #define corgi_lcd_resume       NULL
 #endif
 
+static int setup_gpio_backlight(struct corgi_lcd *lcd,
+                               struct corgi_lcd_platform_data *pdata)
+{
+       struct spi_device *spi = lcd->spi_dev;
+       int err;
+
+       lcd->gpio_backlight_on = -1;
+       lcd->gpio_backlight_cont = -1;
+
+       if (gpio_is_valid(pdata->gpio_backlight_on)) {
+               err = gpio_request(pdata->gpio_backlight_on, "BL_ON");
+               if (err) {
+                       dev_err(&spi->dev, "failed to request GPIO%d for "
+                               "backlight_on\n", pdata->gpio_backlight_on);
+                       return err;
+               }
+
+               lcd->gpio_backlight_on = pdata->gpio_backlight_on;
+               gpio_direction_output(lcd->gpio_backlight_on, 0);
+       }
+
+       if (gpio_is_valid(pdata->gpio_backlight_cont)) {
+               err = gpio_request(pdata->gpio_backlight_cont, "BL_CONT");
+               if (err) {
+                       dev_err(&spi->dev, "failed to request GPIO%d for "
+                               "backlight_cont\n", pdata->gpio_backlight_cont);
+                       goto err_free_backlight_on;
+               }
+
+               lcd->gpio_backlight_cont = pdata->gpio_backlight_cont;
+
+               /* spitz and akita use both GPIOs for backlight, and
+                * have inverted polarity of GPIO_BACKLIGHT_CONT
+                */
+               if (gpio_is_valid(lcd->gpio_backlight_on)) {
+                       lcd->gpio_backlight_cont_inverted = 1;
+                       gpio_direction_output(lcd->gpio_backlight_cont, 1);
+               } else {
+                       lcd->gpio_backlight_cont_inverted = 0;
+                       gpio_direction_output(lcd->gpio_backlight_cont, 0);
+               }
+       }
+       return 0;
+
+err_free_backlight_on:
+       if (gpio_is_valid(lcd->gpio_backlight_on))
+               gpio_free(lcd->gpio_backlight_on);
+       return err;
+}
+
 static int __devinit corgi_lcd_probe(struct spi_device *spi)
 {
        struct corgi_lcd_platform_data *pdata = spi->dev.platform_data;
@@ -482,14 +568,22 @@ static int __devinit corgi_lcd_probe(struct spi_device *spi)
        lcd->bl_dev->props.brightness = pdata->default_intensity;
        lcd->bl_dev->props.power = FB_BLANK_UNBLANK;
 
-       lcd->notify = pdata->notify;
+       ret = setup_gpio_backlight(lcd, pdata);
+       if (ret)
+               goto err_unregister_bl;
+
        lcd->kick_battery = pdata->kick_battery;
 
        dev_set_drvdata(&spi->dev, lcd);
        corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_UNBLANK);
        backlight_update_status(lcd->bl_dev);
+
+       lcd->limit_mask = pdata->limit_mask;
+       the_corgi_lcd = lcd;
        return 0;
 
+err_unregister_bl:
+       backlight_device_unregister(lcd->bl_dev);
 err_unregister_lcd:
        lcd_device_unregister(lcd->lcd_dev);
 err_free_lcd:
@@ -506,6 +600,12 @@ static int __devexit corgi_lcd_remove(struct spi_device *spi)
        backlight_update_status(lcd->bl_dev);
        backlight_device_unregister(lcd->bl_dev);
 
+       if (gpio_is_valid(lcd->gpio_backlight_on))
+               gpio_free(lcd->gpio_backlight_on);
+
+       if (gpio_is_valid(lcd->gpio_backlight_cont))
+               gpio_free(lcd->gpio_backlight_cont);
+
        corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_POWERDOWN);
        lcd_device_unregister(lcd->lcd_dev);
        kfree(lcd);