[s3c24xx] jbt6k74:
[openwrt/svn-archive/archive.git] / target / linux / s3c24xx / files-2.6.30 / drivers / video / display / jbt6k74.c
index eb4e46d..87f0d67 100644 (file)
@@ -4,6 +4,7 @@
  * Author: Harald Welte <laforge@openmoko.org>,
  *        Stefan Schmidt <stefan@openmoko.org>
  * Copyright (C) 2008 by Harald Welte <laforge@openmoko.org>
+ * Copyright (C) 2009 by Lars-Peter Clausen <lars@metafoo.de>
  * All rights reserved.
  *
  * This program is free software; you can redistribute it and/or
 #include <linux/device.h>
 #include <linux/platform_device.h>
 #include <linux/delay.h>
+#include <linux/workqueue.h>
 #include <linux/jbt6k74.h>
 #include <linux/fb.h>
+#include <linux/lcd.h>
 #include <linux/time.h>
 
 enum jbt_register {
@@ -97,51 +100,57 @@ enum jbt_register {
 
 };
 
-enum jbt_state {
-       JBT_STATE_DEEP_STANDBY,
-       JBT_STATE_SLEEP,
-       JBT_STATE_NORMAL,
-       JBT_STATE_QVGA_NORMAL,
+enum jbt_resolution {
+       JBT_RESOLUTION_VGA,
+       JBT_RESOLUTION_QVGA,
 };
 
-static const char *jbt_state_names[] = {
-       [JBT_STATE_DEEP_STANDBY]        = "deep-standby",
-       [JBT_STATE_SLEEP]               = "sleep",
-       [JBT_STATE_NORMAL]              = "normal",
-       [JBT_STATE_QVGA_NORMAL]         = "qvga-normal",
+enum jbt_power_mode {
+       JBT_POWER_MODE_DEEP_STANDBY,
+       JBT_POWER_MODE_SLEEP,
+       JBT_POWER_MODE_NORMAL,
+};
+
+static const char *jbt_power_mode_names[] = {
+       [JBT_POWER_MODE_DEEP_STANDBY]   = "deep-standby",
+       [JBT_POWER_MODE_SLEEP]          = "sleep",
+       [JBT_POWER_MODE_NORMAL]         = "normal",
+};
+
+static const char *jbt_resolution_names[] = {
+       [JBT_RESOLUTION_VGA] = "vga",
+       [JBT_RESOLUTION_QVGA] = "qvga",
 };
 
 struct jbt_info {
-       enum jbt_state state, normal_state;
+       struct mutex lock;              /* protects this structure */
+       enum jbt_resolution resolution;
+       enum jbt_power_mode power_mode;
+       enum jbt_power_mode suspend_mode;
+       int suspended;
        struct spi_device *spi_dev;
-       struct mutex lock;              /* protects tx_buf and reg_cache */
-       struct notifier_block fb_notif;
-       u16 tx_buf[8];
+       struct lcd_device *lcd_dev;
+       unsigned long last_sleep;
+       struct delayed_work blank_work;
+       int blank_mode;
+       u16 tx_buf[4];
        u16 reg_cache[0xEE];
-       struct timespec last_sleep;
 };
 
 #define JBT_COMMAND    0x000
 #define JBT_DATA       0x100
 
-static inline unsigned int timespec_sub_ms(struct timespec lhs,
-                                       struct timespec rhs)
-{
-       struct timespec ts = timespec_sub(lhs, rhs);
-       return (ts.tv_sec * MSEC_PER_SEC) + (ts.tv_nsec / NSEC_PER_MSEC);
-}
-
 static int jbt_reg_write_nodata(struct jbt_info *jbt, u8 reg)
 {
        int rc;
 
        jbt->tx_buf[0] = JBT_COMMAND | reg;
        rc = spi_write(jbt->spi_dev, (u8 *)jbt->tx_buf,
-                      1*sizeof(u16));
+                      1*sizeof(u16));
        if (rc == 0)
                jbt->reg_cache[reg] = 0;
        else
-               printk(KERN_ERR"jbt_reg_write_nodata spi_write ret %d\n",
+               dev_err(&jbt->spi_dev->dev, "jbt_reg_write_nodata spi_write ret %d\n",
                       rc);
 
        return rc;
@@ -155,11 +164,11 @@ static int jbt_reg_write(struct jbt_info *jbt, u8 reg, u8 data)
        jbt->tx_buf[0] = JBT_COMMAND | reg;
        jbt->tx_buf[1] = JBT_DATA | data;
        rc = spi_write(jbt->spi_dev, (u8 *)jbt->tx_buf,
-                      2*sizeof(u16));
+                      2*sizeof(u16));
        if (rc == 0)
                jbt->reg_cache[reg] = data;
        else
-               printk(KERN_ERR"jbt_reg_write spi_write ret %d\n", rc);
+               dev_err(&jbt->spi_dev->dev, "jbt_reg_write spi_write ret %d\n", rc);
 
        return rc;
 }
@@ -173,11 +182,11 @@ static int jbt_reg_write16(struct jbt_info *jbt, u8 reg, u16 data)
        jbt->tx_buf[2] = JBT_DATA | (data & 0xff);
 
        rc = spi_write(jbt->spi_dev, (u8 *)jbt->tx_buf,
-                      3*sizeof(u16));
+                      3*sizeof(u16));
        if (rc == 0)
                jbt->reg_cache[reg] = data;
        else
-               printk(KERN_ERR"jbt_reg_write16 spi_write ret %d\n", rc);
+               dev_err(&jbt->spi_dev->dev, "jbt_reg_write16 spi_write ret %d\n", rc);
 
        return rc;
 }
@@ -187,7 +196,7 @@ static int jbt_init_regs(struct jbt_info *jbt)
        int rc;
 
        dev_dbg(&jbt->spi_dev->dev, "entering %cVGA mode\n",
-                       jbt->normal_state == JBT_STATE_QVGA_NORMAL ? 'Q' : ' ');
+                       jbt->resolution == JBT_RESOLUTION_QVGA ? 'Q' : ' ');
 
        rc = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE1, 0x01);
        rc |= jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE2, 0x00);
@@ -205,7 +214,7 @@ static int jbt_init_regs(struct jbt_info *jbt)
         * default of 0x02 in JBT_REG_ASW_SLEW responsible for 72Hz requirement
         * to avoid red / blue flicker
         */
-       rc |= jbt_reg_write(jbt, JBT_REG_ASW_SLEW, 0x04);
+       rc |= jbt_reg_write(jbt, JBT_REG_ASW_SLEW, 0x04 | (1 << 5));
        rc |= jbt_reg_write(jbt, JBT_REG_DUMMY_DISPLAY, 0x00);
 
        rc |= jbt_reg_write(jbt, JBT_REG_SLEEP_OUT_FR_A, 0x11);
@@ -221,7 +230,7 @@ static int jbt_init_regs(struct jbt_info *jbt)
        rc |= jbt_reg_write(jbt, JBT_REG_GAMMA1_INCLINATION, 0x00);
        rc |= jbt_reg_write(jbt, JBT_REG_GAMMA1_BLUE_OFFSET, 0x00);
 
-       if (jbt->normal_state != JBT_STATE_QVGA_NORMAL) {
+       if (jbt->resolution != JBT_RESOLUTION_QVGA) {
                rc |= jbt_reg_write16(jbt, JBT_REG_HCLOCK_VGA, 0x1f0);
                rc |= jbt_reg_write(jbt, JBT_REG_BLANK_CONTROL, 0x02);
                rc |= jbt_reg_write16(jbt, JBT_REG_BLANK_TH_TV, 0x0804);
@@ -276,12 +285,10 @@ static int sleep_to_normal(struct jbt_info *jbt)
        int rc;
 
        /* Make sure we are 120 ms after SLEEP_OUT */
-       unsigned int sleep_time = timespec_sub_ms(current_kernel_time(),
-                                                       jbt->last_sleep);
-       if (sleep_time < 120)
-               mdelay(120 - sleep_time);
+       if (time_before(jiffies, jbt->last_sleep))
+               mdelay(jiffies_to_msecs(jbt->last_sleep - jiffies));
 
-       if (jbt->normal_state == JBT_STATE_NORMAL) {
+       if (jbt->resolution == JBT_RESOLUTION_VGA) {
                /* RGB I/F on, RAM wirte off, QVGA through, SIGCON enable */
                rc = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE, 0x80);
 
@@ -306,7 +313,7 @@ static int sleep_to_normal(struct jbt_info *jbt)
 
        /* Sleep mode off */
        rc |= jbt_reg_write_nodata(jbt, JBT_REG_SLEEP_OUT);
-       jbt->last_sleep = current_kernel_time();
+       jbt->last_sleep = jiffies + msecs_to_jiffies(120);
 
        /* Allow the booster and display controller to restart stably */
        mdelay(5);
@@ -319,15 +326,13 @@ static int normal_to_sleep(struct jbt_info *jbt)
        int rc;
 
        /* Make sure we are 120 ms after SLEEP_OUT */
-       unsigned int sleep_time = timespec_sub_ms(current_kernel_time(),
-                                                       jbt->last_sleep);
-       if (sleep_time < 120)
-               mdelay(120 - sleep_time);
+       while (time_before(jiffies, jbt->last_sleep))
+               cpu_relax();
 
        rc = jbt_reg_write_nodata(jbt, JBT_REG_DISPLAY_OFF);
-       rc |= jbt_reg_write16(jbt, JBT_REG_OUTPUT_CONTROL, 0x8002);
+       rc |= jbt_reg_write16(jbt, JBT_REG_OUTPUT_CONTROL, 0x8000 | 1 << 3);
        rc |= jbt_reg_write_nodata(jbt, JBT_REG_SLEEP_IN);
-       jbt->last_sleep = current_kernel_time();
+       jbt->last_sleep = jiffies + msecs_to_jiffies(120);
 
        /* Allow the internal circuits to stop automatically */
        mdelay(5);
@@ -340,41 +345,43 @@ static int sleep_to_standby(struct jbt_info *jbt)
        return jbt_reg_write(jbt, JBT_REG_POWER_ON_OFF, 0x00);
 }
 
-/* frontend function */
-int jbt6k74_enter_state(struct jbt_info *jbt, enum jbt_state new_state)
+int jbt6k74_enter_power_mode(struct jbt_info *jbt, enum jbt_power_mode new_mode)
 {
+       struct jbt6k74_platform_data *pdata = jbt->spi_dev->dev.platform_data;
        int rc = -EINVAL;
 
-/*     dev_dbg(&jbt->spi_dev->dev, "entering (old_state=%s, new_state=%s)\n",
-                       jbt_state_names[jbt->state],
-                       jbt_state_names[new_state]);*/
-
-/*    printk("entering (old_state=%s, new_state=%s)\n",
-                       jbt_state_names[jbt->state],
-                       jbt_state_names[new_state]);*/
+       dev_dbg(&jbt->spi_dev->dev, "entering (old_state=%s, new_state=%s)\n",
+                       jbt_power_mode_names[jbt->power_mode],
+                       jbt_power_mode_names[new_mode]);
 
        mutex_lock(&jbt->lock);
 
-       if (new_state == JBT_STATE_NORMAL ||
-                       new_state == JBT_STATE_QVGA_NORMAL)
-               jbt->normal_state = new_state;
-
-       switch (jbt->state) {
-       case JBT_STATE_DEEP_STANDBY:
-               switch (new_state) {
-               case JBT_STATE_DEEP_STANDBY:
+       if (jbt->suspended) {
+               switch (new_mode) {
+               case JBT_POWER_MODE_DEEP_STANDBY:
+               case JBT_POWER_MODE_SLEEP:
+               case JBT_POWER_MODE_NORMAL:
                        rc = 0;
+                       jbt->suspend_mode = new_mode;
                        break;
-               case JBT_STATE_SLEEP:
-                       rc = standby_to_sleep(jbt);
+               default:
                        break;
-               case JBT_STATE_NORMAL:
-                       /* first transition into sleep */
+               }
+       } else if (new_mode == JBT_POWER_MODE_NORMAL &&
+                  pdata->enable_pixel_clock) {
+               pdata->enable_pixel_clock(&jbt->spi_dev->dev, 1);
+       }
+
+       switch (jbt->power_mode) {
+       case JBT_POWER_MODE_DEEP_STANDBY:
+               switch (new_mode) {
+               case JBT_POWER_MODE_DEEP_STANDBY:
+                       rc = 0;
+                       break;
+               case JBT_POWER_MODE_SLEEP:
                        rc = standby_to_sleep(jbt);
-                       /* then transition into normal */
-                       rc |= sleep_to_normal(jbt);
                        break;
-               case JBT_STATE_QVGA_NORMAL:
+               case JBT_POWER_MODE_NORMAL:
                        /* first transition into sleep */
                        rc = standby_to_sleep(jbt);
                        /* then transition into normal */
@@ -382,107 +389,114 @@ int jbt6k74_enter_state(struct jbt_info *jbt, enum jbt_state new_state)
                        break;
                }
                break;
-       case JBT_STATE_SLEEP:
-               switch (new_state) {
-               case JBT_STATE_SLEEP:
+       case JBT_POWER_MODE_SLEEP:
+               switch (new_mode) {
+               case JBT_POWER_MODE_SLEEP:
                        rc = 0;
                        break;
-               case JBT_STATE_DEEP_STANDBY:
+               case JBT_POWER_MODE_DEEP_STANDBY:
                        rc = sleep_to_standby(jbt);
                        break;
-               case JBT_STATE_NORMAL:
-               case JBT_STATE_QVGA_NORMAL:
+               case JBT_POWER_MODE_NORMAL:
                        rc = sleep_to_normal(jbt);
                        break;
                }
                break;
-       case JBT_STATE_NORMAL:
-               switch (new_state) {
-               case JBT_STATE_NORMAL:
-                       rc = 0;
-                       break;
-               case JBT_STATE_DEEP_STANDBY:
-                       /* first transition into sleep */
-                       rc = normal_to_sleep(jbt);
-                       /* then transition into deep standby */
-                       rc |= sleep_to_standby(jbt);
-                       break;
-               case JBT_STATE_SLEEP:
-                       rc = normal_to_sleep(jbt);
-                       break;
-               case JBT_STATE_QVGA_NORMAL:
-                       /* first transition into sleep */
-                       rc = normal_to_sleep(jbt);
-                       /* second transition into deep standby */
-                       rc |= sleep_to_standby(jbt);
-                       /* third transition into sleep */
-                       rc |= standby_to_sleep(jbt);
-                       /* fourth transition into normal */
-                       rc |= sleep_to_normal(jbt);
-                       break;
-               }
-               break;
-       case JBT_STATE_QVGA_NORMAL:
-               switch (new_state) {
-               case JBT_STATE_QVGA_NORMAL:
+       case JBT_POWER_MODE_NORMAL:
+               switch (new_mode) {
+               case JBT_POWER_MODE_NORMAL:
                        rc = 0;
                        break;
-               case JBT_STATE_DEEP_STANDBY:
+               case JBT_POWER_MODE_DEEP_STANDBY:
                        /* first transition into sleep */
                        rc = normal_to_sleep(jbt);
                        /* then transition into deep standby */
                        rc |= sleep_to_standby(jbt);
                        break;
-               case JBT_STATE_SLEEP:
+               case JBT_POWER_MODE_SLEEP:
                        rc = normal_to_sleep(jbt);
                        break;
-               case JBT_STATE_NORMAL:
-                       /* first transition into sleep */
-                       rc = normal_to_sleep(jbt);
-                       /* second transition into deep standby */
-                       rc |= sleep_to_standby(jbt);
-                       /* third transition into sleep */
-                       rc |= standby_to_sleep(jbt);
-                       /* fourth transition into normal */
-                       rc |= sleep_to_normal(jbt);
-                       break;
                }
-               break;
        }
 
-       if (rc == 0)
-               jbt->state = new_state;
-       else
+       if (rc == 0) {
+               jbt->power_mode = new_mode;
+               if (new_mode != JBT_POWER_MODE_NORMAL &&
+                   pdata->enable_pixel_clock)
+                       pdata->enable_pixel_clock(&jbt->spi_dev->dev, 0);
+       } else {
                dev_err(&jbt->spi_dev->dev, "Failed enter state '%s')\n",
-                               jbt_state_names[new_state]);
+                               jbt_power_mode_names[new_mode]);
+       }
+
+       mutex_unlock(&jbt->lock);
+
+       return rc;
+}
+EXPORT_SYMBOL_GPL(jbt6k74_enter_power_mode);
+
+int jbt6k74_set_resolution(struct jbt_info *jbt, enum jbt_resolution new_resolution) {
+       int rc = 0;
+       enum jbt_resolution old_resolution;
+
+       if (new_resolution != JBT_RESOLUTION_VGA &&
+           new_resolution != JBT_RESOLUTION_QVGA)
+               return -EINVAL;
+
+       mutex_lock(&jbt->lock);
 
+       if (jbt->resolution == new_resolution)
+               goto out_unlock;
+
+       old_resolution = jbt->resolution;
+       jbt->resolution = new_resolution;
+
+       if (jbt->power_mode == JBT_POWER_MODE_NORMAL) {
+
+               /* first transition into sleep */
+               rc = normal_to_sleep(jbt);
+               /* second transition into deep standby */
+/*             rc |= sleep_to_standby(jbt);*/
+               /* third transition into sleep */
+/*             rc |= standby_to_sleep(jbt);*/
+               /* fourth transition into normal */
+               rc |= sleep_to_normal(jbt);
+
+               if (rc) {
+                       jbt->resolution = old_resolution;
+                       dev_err(&jbt->spi_dev->dev, "Failed to set resolution '%s')\n",
+                               jbt_resolution_names[new_resolution]);
+               }
+       }
+
+out_unlock:
        mutex_unlock(&jbt->lock);
 
        return rc;
 }
-EXPORT_SYMBOL_GPL(jbt6k74_enter_state);
+EXPORT_SYMBOL_GPL(jbt6k74_set_resolution);
 
-static ssize_t state_read(struct device *dev, struct device_attribute *attr,
+static ssize_t resolution_read(struct device *dev, struct device_attribute *attr,
                          char *buf)
 {
        struct jbt_info *jbt = dev_get_drvdata(dev);
 
-       if (jbt->state >= ARRAY_SIZE(jbt_state_names))
+       if (jbt->resolution >= ARRAY_SIZE(jbt_resolution_names))
                return -EIO;
 
-       return sprintf(buf, "%s\n", jbt_state_names[jbt->state]);
+       return sprintf(buf, "%s\n", jbt_resolution_names[jbt->resolution]);
 }
 
-static ssize_t state_write(struct device *dev, struct device_attribute *attr,
+static ssize_t resolution_write(struct device *dev, struct device_attribute *attr,
                           const char *buf, size_t count)
 {
        struct jbt_info *jbt = dev_get_drvdata(dev);
        int i, rc;
 
-       for (i = 0; i < ARRAY_SIZE(jbt_state_names); i++) {
-               if (!strncmp(buf, jbt_state_names[i],
-                            strlen(jbt_state_names[i]))) {
-                       rc = jbt6k74_enter_state(jbt, i);
+       for (i = 0; i < ARRAY_SIZE(jbt_resolution_names); i++) {
+               if (!strncmp(buf, jbt_resolution_names[i],
+                      strlen(jbt_resolution_names[i]))) {
+                       rc = jbt6k74_set_resolution(jbt, i);
                        if (rc)
                                return rc;
                        return count;
@@ -492,7 +506,7 @@ static ssize_t state_write(struct device *dev, struct device_attribute *attr,
        return -EINVAL;
 }
 
-static DEVICE_ATTR(state, 0644, state_read, state_write);
+static DEVICE_ATTR(resolution, 0644, resolution_read, resolution_write);
 
 static int reg_by_string(const char *name)
 {
@@ -515,7 +529,7 @@ static ssize_t gamma_read(struct device *dev, struct device_attribute *attr,
 
        mutex_lock(&jbt->lock);
        val = jbt->reg_cache[reg];
-       mutex_unlock(&jbt->lock);
+               mutex_unlock(&jbt->lock);
 
        return sprintf(buf, "0x%04x\n", val);
 }
@@ -547,8 +561,6 @@ static ssize_t reset_write(struct device *dev, struct device_attribute *attr,
 
        mutex_lock(&jbt->lock);
 
-       jbt->state = JBT_STATE_DEEP_STANDBY;
-
        /* hard reset the jbt6k74 */
        (pdata->reset)(0, 0);
        mdelay(1);
@@ -562,7 +574,7 @@ static ssize_t reset_write(struct device *dev, struct device_attribute *attr,
 
        mutex_unlock(&jbt->lock);
 
-       jbt6k74_enter_state(jbt, jbt->normal_state);
+       jbt6k74_enter_power_mode(jbt, jbt->power_mode);
 
        return count;
 }
@@ -574,7 +586,7 @@ static DEVICE_ATTR(gamma_blue_offset, 0644, gamma_read, gamma_write);
 static DEVICE_ATTR(reset, 0600, NULL, reset_write);
 
 static struct attribute *jbt_sysfs_entries[] = {
-       &dev_attr_state.attr,
+       &dev_attr_resolution.attr,
        &dev_attr_gamma_fine1.attr,
        &dev_attr_gamma_fine2.attr,
        &dev_attr_gamma_inclination.attr,
@@ -588,44 +600,89 @@ static struct attribute_group jbt_attr_group = {
        .attrs  = jbt_sysfs_entries,
 };
 
-static int fb_notifier_callback(struct notifier_block *self,
-                               unsigned long event, void *data)
-{
-       struct jbt_info *jbt;
-       struct fb_event *evdata = data;
-       int fb_blank;
+/* FIXME: This in an ugly hack to delay display blanking.
+  When the jbt is in sleep mode it displays an all white screen and thus one
+  will a see a short flash.
+  By delaying the blanking we will give the backlight a chance to turn off and
+  thus avoid getting the flash */
+static void jbt_blank_worker(struct work_struct *work) {
+       struct jbt_info *jbt  = container_of(work, struct jbt_info,
+                                               blank_work.work);
 
-       jbt = container_of(self, struct jbt_info, fb_notif);
+       switch (jbt->blank_mode) {
+       case FB_BLANK_NORMAL:
+               jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_SLEEP);
+               break;
+       case FB_BLANK_POWERDOWN:
+               jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_DEEP_STANDBY);
+               break;
+       default:
+               break;
+       }
+}
 
-       dev_dbg(&jbt->spi_dev->dev, "event=%lu\n", event);
+static int jbt6k74_set_mode(struct lcd_device *ld, struct fb_videomode *m) {
+       int rc = -EINVAL;
+       struct jbt_info *jbt = dev_get_drvdata(&ld->dev);
+
+       if (m->xres == 240 && m->yres == 320) {
+               rc = jbt6k74_set_resolution(jbt, JBT_RESOLUTION_QVGA);
+       } else if (m->xres == 480 && m->yres == 640) {
+               rc = jbt6k74_set_resolution(jbt, JBT_RESOLUTION_VGA);
+       } else {
+               dev_err(&jbt->spi_dev->dev, "Unknown resolution. Entering sleep mode.\n");
+               jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_SLEEP);
+       }
+
+       return rc;
+}
+
+static int jbt6k74_set_power(struct lcd_device *ld, int power) {
+       int rc = -EINVAL;
+       struct jbt_info *jbt = dev_get_drvdata(&ld->dev);
 
-       if (event != FB_EVENT_BLANK && event != FB_EVENT_CONBLANK)
-               return 0;
+       jbt->blank_mode = power;
+       cancel_rearming_delayed_work(&jbt->blank_work);
 
-       fb_blank = *(int *)evdata->data;
-       switch (fb_blank) {
+       switch (power) {
        case FB_BLANK_UNBLANK:
                dev_dbg(&jbt->spi_dev->dev, "unblank\n");
-               jbt6k74_enter_state(jbt, jbt->normal_state);
+               rc = jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_NORMAL);
                break;
        case FB_BLANK_NORMAL:
                dev_dbg(&jbt->spi_dev->dev, "blank\n");
-               break;
-       case FB_BLANK_VSYNC_SUSPEND:
-               dev_dbg(&jbt->spi_dev->dev, "vsync suspend\n");
-               break;
-       case FB_BLANK_HSYNC_SUSPEND:
-               dev_dbg(&jbt->spi_dev->dev, "hsync suspend\n");
+               rc = schedule_delayed_work(&jbt->blank_work, HZ);
                break;
        case FB_BLANK_POWERDOWN:
                dev_dbg(&jbt->spi_dev->dev, "powerdown\n");
-               jbt6k74_enter_state(jbt, JBT_STATE_SLEEP);
+               rc = schedule_delayed_work(&jbt->blank_work, HZ);
+               break;
+       default:
                break;
        }
 
-       return 0;
+       return rc;
+}
+
+static int jbt6k74_get_power(struct lcd_device *ld) {
+       struct jbt_info *jbt = dev_get_drvdata(&ld->dev);
+
+       switch (jbt->power_mode) {
+        case JBT_POWER_MODE_NORMAL:
+               return FB_BLANK_UNBLANK;
+        case JBT_POWER_MODE_SLEEP:
+               return FB_BLANK_NORMAL;
+        default:
+               return JBT_POWER_MODE_DEEP_STANDBY;
+       }
 }
 
+struct lcd_ops jbt6k74_lcd_ops = {
+       .set_power = jbt6k74_set_power,
+       .get_power = jbt6k74_get_power,
+       .set_mode  = jbt6k74_set_mode,
+};
+
 /* linux device model infrastructure */
 
 static int __devinit jbt_probe(struct spi_device *spi)
@@ -651,17 +708,28 @@ static int __devinit jbt_probe(struct spi_device *spi)
                return -ENOMEM;
 
        jbt->spi_dev = spi;
-       jbt->normal_state = JBT_STATE_NORMAL;
-       jbt->state = JBT_STATE_DEEP_STANDBY;
-       jbt->last_sleep = current_kernel_time();
+
+       jbt->lcd_dev = lcd_device_register("jbt6k74-lcd", &spi->dev,
+                          jbt, &jbt6k74_lcd_ops);
+
+       if (IS_ERR(jbt->lcd_dev)) {
+               rc = PTR_ERR(jbt->lcd_dev);
+               goto err_free_drvdata;
+       }
+
+       INIT_DELAYED_WORK(&jbt->blank_work, jbt_blank_worker);
+
+       jbt->resolution = JBT_RESOLUTION_VGA;
+       jbt->power_mode = JBT_POWER_MODE_DEEP_STANDBY;
+       jbt->last_sleep = jiffies + msecs_to_jiffies(120);
        mutex_init(&jbt->lock);
 
        dev_set_drvdata(&spi->dev, jbt);
 
-       rc = jbt6k74_enter_state(jbt, JBT_STATE_NORMAL);
+       rc = jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_NORMAL);
        if (rc < 0) {
                dev_err(&spi->dev, "cannot enter NORMAL state\n");
-               goto err_free_drvdata;
+               goto err_unregister_lcd;
        }
 
        rc = sysfs_create_group(&spi->dev.kobj, &jbt_attr_group);
@@ -670,22 +738,15 @@ static int __devinit jbt_probe(struct spi_device *spi)
                goto err_standby;
        }
 
-       jbt->fb_notif.notifier_call = fb_notifier_callback;
-       rc = fb_register_client(&jbt->fb_notif);
-       if (rc < 0) {
-               dev_err(&spi->dev, "cannot register notifier\n");
-               goto err_sysfs;
-       }
-
        if (pdata->probe_completed)
                (pdata->probe_completed)(&spi->dev);
 
        return 0;
 
-err_sysfs:
-       sysfs_remove_group(&spi->dev.kobj, &jbt_attr_group);
 err_standby:
-       jbt6k74_enter_state(jbt, JBT_STATE_DEEP_STANDBY);
+       jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_DEEP_STANDBY);
+err_unregister_lcd:
+       lcd_device_unregister(jbt->lcd_dev);
 err_free_drvdata:
        dev_set_drvdata(&spi->dev, NULL);
        kfree(jbt);
@@ -698,12 +759,14 @@ static int __devexit jbt_remove(struct spi_device *spi)
        struct jbt_info *jbt = dev_get_drvdata(&spi->dev);
 
        /* We don't want to switch off the display in case the user
-        * accidentially onloads the module (whose use count normally is 0) */
-       jbt6k74_enter_state(jbt, jbt->normal_state);
+        * accidentially unloads the module (whose use count normally is 0) */
+       jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_NORMAL);
 
-       fb_unregister_client(&jbt->fb_notif);
        sysfs_remove_group(&spi->dev.kobj, &jbt_attr_group);
        dev_set_drvdata(&spi->dev, NULL);
+
+       lcd_device_unregister(jbt->lcd_dev);
+
        kfree(jbt);
 
        return 0;
@@ -714,7 +777,10 @@ static int jbt_suspend(struct spi_device *spi, pm_message_t state)
 {
        struct jbt_info *jbt = dev_get_drvdata(&spi->dev);
 
-       jbt6k74_enter_state(jbt, JBT_STATE_DEEP_STANDBY);
+       jbt->suspend_mode = jbt->power_mode;
+
+       jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_DEEP_STANDBY);
+       jbt->suspended = 1;
 
        dev_info(&spi->dev, "suspended\n");
 
@@ -724,12 +790,9 @@ static int jbt_suspend(struct spi_device *spi, pm_message_t state)
 int jbt6k74_resume(struct spi_device *spi)
 {
        struct jbt_info *jbt = dev_get_drvdata(&spi->dev);
-       struct jbt6k74_platform_data *pdata = spi->dev.platform_data;
-
-       jbt6k74_enter_state(jbt, jbt->normal_state);
 
-       if (pdata->resuming)
-               (pdata->resuming)(0);
+       jbt->suspended = 0;
+       jbt6k74_enter_power_mode(jbt, jbt->suspend_mode);
 
        dev_info(&spi->dev, "resumed\n");