rk31 lcdc: fix compile err
[firefly-linux-kernel-4.4.55.git] / drivers / video / sh_mobile_lcdcfb.c
index d72075a9f01c367ceb9e573f3a475774db6a4b80..019dbd3f12b247d3fec1fb4836dd8f36cf5a3819 100644 (file)
@@ -12,7 +12,6 @@
 #include <linux/init.h>
 #include <linux/delay.h>
 #include <linux/mm.h>
-#include <linux/fb.h>
 #include <linux/clk.h>
 #include <linux/pm_runtime.h>
 #include <linux/platform_device.h>
 #include <linux/vmalloc.h>
 #include <linux/ioctl.h>
 #include <linux/slab.h>
+#include <linux/console.h>
+#include <linux/backlight.h>
+#include <linux/gpio.h>
 #include <video/sh_mobile_lcdc.h>
 #include <asm/atomic.h>
 
-#define PALETTE_NR 16
+#include "sh_mobile_lcdcfb.h"
+#include "sh_mobile_meram.h"
+
 #define SIDE_B_OFFSET 0x1000
 #define MIRROR_OFFSET 0x2000
 
@@ -53,11 +57,8 @@ static int lcdc_shared_regs[] = {
 };
 #define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs)
 
-/* per-channel registers */
-enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R,
-       LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR,
-       LDHAJR,
-       NR_CH_REGS };
+#define MAX_XRES 1920
+#define MAX_YRES 1080
 
 static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = {
        [LDDCKPAT1R] = 0x400,
@@ -69,6 +70,7 @@ static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = {
        [LDSM1R] = 0x428,
        [LDSM2R] = 0x42c,
        [LDSA1R] = 0x430,
+       [LDSA2R] = 0x434,
        [LDMLSR] = 0x438,
        [LDHCNR] = 0x448,
        [LDHSYNR] = 0x44c,
@@ -112,23 +114,22 @@ static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = {
 #define LDRCNTR_MRC    0x00000001
 #define LDSR_MRS       0x00000100
 
-struct sh_mobile_lcdc_priv;
-struct sh_mobile_lcdc_chan {
-       struct sh_mobile_lcdc_priv *lcdc;
-       unsigned long *reg_offs;
-       unsigned long ldmt1r_value;
-       unsigned long enabled; /* ME and SE in LDCNT2R */
-       struct sh_mobile_lcdc_chan_cfg cfg;
-       u32 pseudo_palette[PALETTE_NR];
-       unsigned long saved_ch_regs[NR_CH_REGS];
-       struct fb_info *info;
-       dma_addr_t dma_handle;
-       struct fb_deferred_io defio;
-       struct scatterlist *sglist;
-       unsigned long frame_end;
-       unsigned long pan_offset;
-       wait_queue_head_t frame_end_wait;
-       struct completion vsync_completion;
+static const struct fb_videomode default_720p = {
+       .name = "HDMI 720p",
+       .xres = 1280,
+       .yres = 720,
+
+       .left_margin = 220,
+       .right_margin = 110,
+       .hsync_len = 40,
+
+       .upper_margin = 20,
+       .lower_margin = 5,
+       .vsync_len = 5,
+
+       .pixclock = 13468,
+       .refresh = 60,
+       .sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
 };
 
 struct sh_mobile_lcdc_priv {
@@ -142,6 +143,8 @@ struct sh_mobile_lcdc_priv {
        struct notifier_block notifier;
        unsigned long saved_shared_regs[NR_SHARED_REGS];
        int started;
+       int forced_bpp; /* 2 channel LCDC must share bpp setting */
+       struct sh_mobile_meram_info *meram_dev;
 };
 
 static bool banked(int reg_nr)
@@ -153,6 +156,7 @@ static bool banked(int reg_nr)
        case LDDFR:
        case LDSM1R:
        case LDSA1R:
+       case LDSA2R:
        case LDMLSR:
        case LDHCNR:
        case LDHSYNR:
@@ -409,8 +413,8 @@ static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv,
 
 static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch)
 {
-       struct fb_var_screeninfo *var = &ch->info->var;
-       unsigned long h_total, hsync_pos;
+       struct fb_var_screeninfo *var = &ch->info->var, *display_var = &ch->display_var;
+       unsigned long h_total, hsync_pos, display_h_total;
        u32 tmp;
 
        tmp = ch->ldmt1r_value;
@@ -428,31 +432,33 @@ static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch)
        lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r);
 
        /* horizontal configuration */
-       h_total = var->xres + var->hsync_len +
-               var->left_margin + var->right_margin;
+       h_total = display_var->xres + display_var->hsync_len +
+               display_var->left_margin + display_var->right_margin;
        tmp = h_total / 8; /* HTCN */
-       tmp |= (var->xres / 8) << 16; /* HDCN */
+       tmp |= (min(display_var->xres, var->xres) / 8) << 16; /* HDCN */
        lcdc_write_chan(ch, LDHCNR, tmp);
 
-       hsync_pos = var->xres + var->right_margin;
+       hsync_pos = display_var->xres + display_var->right_margin;
        tmp = hsync_pos / 8; /* HSYNP */
-       tmp |= (var->hsync_len / 8) << 16; /* HSYNW */
+       tmp |= (display_var->hsync_len / 8) << 16; /* HSYNW */
        lcdc_write_chan(ch, LDHSYNR, tmp);
 
        /* vertical configuration */
-       tmp = var->yres + var->vsync_len +
-               var->upper_margin + var->lower_margin; /* VTLN */
-       tmp |= var->yres << 16; /* VDLN */
+       tmp = display_var->yres + display_var->vsync_len +
+               display_var->upper_margin + display_var->lower_margin; /* VTLN */
+       tmp |= min(display_var->yres, var->yres) << 16; /* VDLN */
        lcdc_write_chan(ch, LDVLNR, tmp);
 
-       tmp = var->yres + var->lower_margin; /* VSYNP */
-       tmp |= var->vsync_len << 16; /* VSYNW */
+       tmp = display_var->yres + display_var->lower_margin; /* VSYNP */
+       tmp |= display_var->vsync_len << 16; /* VSYNW */
        lcdc_write_chan(ch, LDVSYNR, tmp);
 
        /* Adjust horizontal synchronisation for HDMI */
-       tmp = ((var->xres & 7) << 24) |
-               ((h_total & 7) << 16) |
-               ((var->hsync_len & 7) << 8) |
+       display_h_total = display_var->xres + display_var->hsync_len +
+               display_var->left_margin + display_var->right_margin;
+       tmp = ((display_var->xres & 7) << 24) |
+               ((display_h_total & 7) << 16) |
+               ((display_var->hsync_len & 7) << 8) |
                hsync_pos;
        lcdc_write_chan(ch, LDHAJR, tmp);
 }
@@ -460,16 +466,20 @@ static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch)
 static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
 {
        struct sh_mobile_lcdc_chan *ch;
-       struct fb_videomode *lcd_cfg;
        struct sh_mobile_lcdc_board_cfg *board_cfg;
        unsigned long tmp;
-       int k, m;
-       int ret = 0;
+       int bpp = 0;
+       unsigned long ldddsr;
+       int k, m, ret;
 
        /* enable clocks before accessing the hardware */
-       for (k = 0; k < ARRAY_SIZE(priv->ch); k++)
-               if (priv->ch[k].enabled)
+       for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
+               if (priv->ch[k].enabled) {
                        sh_mobile_lcdc_clk_on(priv);
+                       if (!bpp)
+                               bpp = priv->ch[k].info->var.bits_per_pixel;
+               }
+       }
 
        /* reset */
        lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) | LCDC_RESET);
@@ -503,7 +513,8 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
                        m = 1 << 6;
                tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0);
 
-               lcdc_write_chan(ch, LDDCKPAT1R, 0x00000000);
+               /* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider denominator */
+               lcdc_write_chan(ch, LDDCKPAT1R, 0);
                lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1);
        }
 
@@ -518,7 +529,6 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
 
        for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
                ch = &priv->ch[k];
-               lcd_cfg = &ch->cfg.lcd_cfg;
 
                if (!ch->enabled)
                        continue;
@@ -529,17 +539,36 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
                lcdc_write_chan(ch, LDPMR, 0);
 
                board_cfg = &ch->cfg.board_cfg;
-               if (board_cfg->setup_sys)
-                       ret = board_cfg->setup_sys(board_cfg->board_data, ch,
-                                                  &sh_mobile_lcdc_sys_bus_ops);
-               if (ret)
-                       return ret;
+               if (board_cfg->setup_sys) {
+                       ret = board_cfg->setup_sys(board_cfg->board_data,
+                                               ch, &sh_mobile_lcdc_sys_bus_ops);
+                       if (ret)
+                               return ret;
+               }
        }
 
        /* word and long word swap */
-       lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 6);
+       ldddsr = lcdc_read(priv, _LDDDSR);
+       if  (priv->ch[0].info->var.nonstd)
+               lcdc_write(priv, _LDDDSR, ldddsr | 7);
+       else {
+               switch (bpp) {
+               case 16:
+                       lcdc_write(priv, _LDDDSR, ldddsr | 6);
+                       break;
+               case 24:
+                       lcdc_write(priv, _LDDDSR, ldddsr | 7);
+                       break;
+               case 32:
+                       lcdc_write(priv, _LDDDSR, ldddsr | 4);
+                       break;
+               }
+       }
 
        for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
+               unsigned long base_addr_y;
+               unsigned long base_addr_c = 0;
+               int pitch;
                ch = &priv->ch[k];
 
                if (!priv->ch[k].enabled)
@@ -547,15 +576,95 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
 
                /* set bpp format in PKF[4:0] */
                tmp = lcdc_read_chan(ch, LDDFR);
-               tmp &= ~(0x0001001f);
-               tmp |= (ch->info->var.bits_per_pixel == 16) ? 3 : 0;
+               tmp &= ~0x0003031f;
+               if (ch->info->var.nonstd) {
+                       tmp |= (ch->info->var.nonstd << 16);
+                       switch (ch->info->var.bits_per_pixel) {
+                       case 12:
+                               break;
+                       case 16:
+                               tmp |= (0x1 << 8);
+                               break;
+                       case 24:
+                               tmp |= (0x2 << 8);
+                               break;
+                       }
+               } else {
+                       switch (ch->info->var.bits_per_pixel) {
+                       case 16:
+                               tmp |= 0x03;
+                               break;
+                       case 24:
+                               tmp |= 0x0b;
+                               break;
+                       case 32:
+                               break;
+                       }
+               }
                lcdc_write_chan(ch, LDDFR, tmp);
 
+               base_addr_y = ch->info->fix.smem_start;
+               base_addr_c = base_addr_y +
+                               ch->info->var.xres *
+                               ch->info->var.yres_virtual;
+               pitch = ch->info->fix.line_length;
+
+               /* test if we can enable meram */
+               if (ch->cfg.meram_cfg && priv->meram_dev &&
+                               priv->meram_dev->ops) {
+                       struct sh_mobile_meram_cfg *cfg;
+                       struct sh_mobile_meram_info *mdev;
+                       unsigned long icb_addr_y, icb_addr_c;
+                       int icb_pitch;
+                       int pf;
+
+                       cfg = ch->cfg.meram_cfg;
+                       mdev = priv->meram_dev;
+                       /* we need to de-init configured ICBs before we
+                        * we can re-initialize them.
+                        */
+                       if (ch->meram_enabled)
+                               mdev->ops->meram_unregister(mdev, cfg);
+
+                       ch->meram_enabled = 0;
+
+                       if (ch->info->var.nonstd) {
+                               if (ch->info->var.bits_per_pixel == 24)
+                                       pf = SH_MOBILE_MERAM_PF_NV24;
+                               else
+                                       pf = SH_MOBILE_MERAM_PF_NV;
+                       } else {
+                               pf = SH_MOBILE_MERAM_PF_RGB;
+                       }
+
+                       ret = mdev->ops->meram_register(mdev, cfg, pitch,
+                                               ch->info->var.yres,
+                                               pf,
+                                               base_addr_y,
+                                               base_addr_c,
+                                               &icb_addr_y,
+                                               &icb_addr_c,
+                                               &icb_pitch);
+                       if (!ret)  {
+                               /* set LDSA1R value */
+                               base_addr_y = icb_addr_y;
+                               pitch = icb_pitch;
+
+                               /* set LDSA2R value if required */
+                               if (base_addr_c)
+                                       base_addr_c = icb_addr_c;
+
+                               ch->meram_enabled = 1;
+                       }
+               }
+
                /* point out our frame buffer */
-               lcdc_write_chan(ch, LDSA1R, ch->info->fix.smem_start);
+               lcdc_write_chan(ch, LDSA1R, base_addr_y);
+               if (ch->info->var.nonstd)
+                       lcdc_write_chan(ch, LDSA2R, base_addr_c);
 
                /* set line size */
-               lcdc_write_chan(ch, LDMLSR, ch->info->fix.line_length);
+               lcdc_write_chan(ch, LDMLSR, pitch);
 
                /* setup deferred io if SYS bus */
                tmp = ch->cfg.sys_bus_cfg.deferred_io_msec;
@@ -591,8 +700,15 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
                        continue;
 
                board_cfg = &ch->cfg.board_cfg;
-               if (board_cfg->display_on)
+               if (board_cfg->display_on && try_module_get(board_cfg->owner)) {
                        board_cfg->display_on(board_cfg->board_data, ch->info);
+                       module_put(board_cfg->owner);
+               }
+
+               if (ch->bl) {
+                       ch->bl->props.power = FB_BLANK_UNBLANK;
+                       backlight_update_status(ch->bl);
+               }
        }
 
        return 0;
@@ -614,7 +730,7 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv)
                 * flush frame, and wait for frame end interrupt
                 * clean up deferred io and enable clock
                 */
-               if (ch->info->fbdefio) {
+               if (ch->info && ch->info->fbdefio) {
                        ch->frame_end = 0;
                        schedule_delayed_work(&ch->info->deferred_work, 0);
                        wait_event(ch->frame_end_wait, ch->frame_end);
@@ -623,9 +739,27 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv)
                        sh_mobile_lcdc_clk_on(priv);
                }
 
+               if (ch->bl) {
+                       ch->bl->props.power = FB_BLANK_POWERDOWN;
+                       backlight_update_status(ch->bl);
+               }
+
                board_cfg = &ch->cfg.board_cfg;
-               if (board_cfg->display_off)
+               if (board_cfg->display_off && try_module_get(board_cfg->owner)) {
                        board_cfg->display_off(board_cfg->board_data);
+                       module_put(board_cfg->owner);
+               }
+
+               /* disable the meram */
+               if (ch->meram_enabled) {
+                       struct sh_mobile_meram_cfg *cfg;
+                       struct sh_mobile_meram_info *mdev;
+                       cfg = ch->cfg.meram_cfg;
+                       mdev = priv->meram_dev;
+                       mdev->ops->meram_unregister(mdev, cfg);
+                       ch->meram_enabled = 0;
+               }
+
        }
 
        /* stop the lcdc */
@@ -704,7 +838,6 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev,
                        return PTR_ERR(priv->dot_clk);
                }
        }
-       atomic_set(&priv->hw_usecnt, -1);
 
        /* Runtime PM support involves two step for this driver:
         * 1) Enable Runtime PM
@@ -778,9 +911,15 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var,
        struct sh_mobile_lcdc_priv *priv = ch->lcdc;
        unsigned long ldrcntr;
        unsigned long new_pan_offset;
+       unsigned long base_addr_y, base_addr_c;
+       unsigned long c_offset;
 
-       new_pan_offset = (var->yoffset * info->fix.line_length) +
-               (var->xoffset * (info->var.bits_per_pixel / 8));
+       if (!var->nonstd)
+               new_pan_offset = (var->yoffset * info->fix.line_length) +
+                       (var->xoffset * (info->var.bits_per_pixel / 8));
+       else
+               new_pan_offset = (var->yoffset * info->fix.line_length) +
+                       (var->xoffset);
 
        if (new_pan_offset == ch->pan_offset)
                return 0;       /* No change, do nothing */
@@ -788,7 +927,46 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var,
        ldrcntr = lcdc_read(priv, _LDRCNTR);
 
        /* Set the source address for the next refresh */
-       lcdc_write_chan_mirror(ch, LDSA1R, ch->dma_handle + new_pan_offset);
+       base_addr_y = ch->dma_handle + new_pan_offset;
+       if (var->nonstd) {
+               /* Set y offset */
+               c_offset = (var->yoffset *
+                       info->fix.line_length *
+                       (info->var.bits_per_pixel - 8)) / 8;
+               base_addr_c = ch->dma_handle + var->xres * var->yres_virtual +
+                       c_offset;
+               /* Set x offset */
+               if (info->var.bits_per_pixel == 24)
+                       base_addr_c += 2 * var->xoffset;
+               else
+                       base_addr_c += var->xoffset;
+       } else
+               base_addr_c = 0;
+
+       if (!ch->meram_enabled) {
+               lcdc_write_chan_mirror(ch, LDSA1R, base_addr_y);
+               if (base_addr_c)
+                       lcdc_write_chan_mirror(ch, LDSA2R, base_addr_c);
+       } else {
+               struct sh_mobile_meram_cfg *cfg;
+               struct sh_mobile_meram_info *mdev;
+               unsigned long icb_addr_y, icb_addr_c;
+               int ret;
+
+               cfg = ch->cfg.meram_cfg;
+               mdev = priv->meram_dev;
+               ret = mdev->ops->meram_update(mdev, cfg,
+                                       base_addr_y, base_addr_c,
+                                       &icb_addr_y, &icb_addr_c);
+               if (ret)
+                       return ret;
+
+               lcdc_write_chan_mirror(ch, LDSA1R, icb_addr_y);
+               if (icb_addr_c)
+                       lcdc_write_chan_mirror(ch, LDSA2R, icb_addr_c);
+
+       }
+
        if (lcdc_chan_is_sublcd(ch))
                lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS);
        else
@@ -837,6 +1015,153 @@ static int sh_mobile_ioctl(struct fb_info *info, unsigned int cmd,
        return retval;
 }
 
+static void sh_mobile_fb_reconfig(struct fb_info *info)
+{
+       struct sh_mobile_lcdc_chan *ch = info->par;
+       struct fb_videomode mode1, mode2;
+       struct fb_event event;
+       int evnt = FB_EVENT_MODE_CHANGE_ALL;
+
+       if (ch->use_count > 1 || (ch->use_count == 1 && !info->fbcon_par))
+               /* More framebuffer users are active */
+               return;
+
+       fb_var_to_videomode(&mode1, &ch->display_var);
+       fb_var_to_videomode(&mode2, &info->var);
+
+       if (fb_mode_is_equal(&mode1, &mode2))
+               return;
+
+       /* Display has been re-plugged, framebuffer is free now, reconfigure */
+       if (fb_set_var(info, &ch->display_var) < 0)
+               /* Couldn't reconfigure, hopefully, can continue as before */
+               return;
+
+       if (info->var.nonstd)
+               info->fix.line_length = mode1.xres;
+       else
+               info->fix.line_length = mode1.xres * (ch->cfg.bpp / 8);
+
+       /*
+        * fb_set_var() calls the notifier change internally, only if
+        * FBINFO_MISC_USEREVENT flag is set. Since we do not want to fake a
+        * user event, we have to call the chain ourselves.
+        */
+       event.info = info;
+       event.data = &mode1;
+       fb_notifier_call_chain(evnt, &event);
+}
+
+/*
+ * Locking: both .fb_release() and .fb_open() are called with info->lock held if
+ * user == 1, or with console sem held, if user == 0.
+ */
+static int sh_mobile_release(struct fb_info *info, int user)
+{
+       struct sh_mobile_lcdc_chan *ch = info->par;
+
+       mutex_lock(&ch->open_lock);
+       dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count);
+
+       ch->use_count--;
+
+       /* Nothing to reconfigure, when called from fbcon */
+       if (user) {
+               console_lock();
+               sh_mobile_fb_reconfig(info);
+               console_unlock();
+       }
+
+       mutex_unlock(&ch->open_lock);
+
+       return 0;
+}
+
+static int sh_mobile_open(struct fb_info *info, int user)
+{
+       struct sh_mobile_lcdc_chan *ch = info->par;
+
+       mutex_lock(&ch->open_lock);
+       ch->use_count++;
+
+       dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count);
+       mutex_unlock(&ch->open_lock);
+
+       return 0;
+}
+
+static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+       struct sh_mobile_lcdc_chan *ch = info->par;
+       struct sh_mobile_lcdc_priv *p = ch->lcdc;
+
+       if (var->xres > MAX_XRES || var->yres > MAX_YRES ||
+           var->xres * var->yres * (ch->cfg.bpp / 8) * 2 > info->fix.smem_len) {
+               dev_warn(info->dev, "Invalid info: %u-%u-%u-%u x %u-%u-%u-%u @ %lukHz!\n",
+                        var->left_margin, var->xres, var->right_margin, var->hsync_len,
+                        var->upper_margin, var->yres, var->lower_margin, var->vsync_len,
+                        PICOS2KHZ(var->pixclock));
+               return -EINVAL;
+       }
+
+       /* only accept the forced_bpp for dual channel configurations */
+       if (p->forced_bpp && p->forced_bpp != var->bits_per_pixel)
+               return -EINVAL;
+
+       switch (var->bits_per_pixel) {
+       case 16: /* PKF[4:0] = 00011 - RGB 565 */
+       case 24: /* PKF[4:0] = 01011 - RGB 888 */
+       case 32: /* PKF[4:0] = 00000 - RGBA 888 */
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/*
+ * Screen blanking. Behavior is as follows:
+ * FB_BLANK_UNBLANK: screen unblanked, clocks enabled
+ * FB_BLANK_NORMAL: screen blanked, clocks enabled
+ * FB_BLANK_VSYNC,
+ * FB_BLANK_HSYNC,
+ * FB_BLANK_POWEROFF: screen blanked, clocks disabled
+ */
+static int sh_mobile_lcdc_blank(int blank, struct fb_info *info)
+{
+       struct sh_mobile_lcdc_chan *ch = info->par;
+       struct sh_mobile_lcdc_priv *p = ch->lcdc;
+
+       /* blank the screen? */
+       if (blank > FB_BLANK_UNBLANK && ch->blank_status == FB_BLANK_UNBLANK) {
+               struct fb_fillrect rect = {
+                       .width = info->var.xres,
+                       .height = info->var.yres,
+               };
+               sh_mobile_lcdc_fillrect(info, &rect);
+       }
+       /* turn clocks on? */
+       if (blank <= FB_BLANK_NORMAL && ch->blank_status > FB_BLANK_NORMAL) {
+               sh_mobile_lcdc_clk_on(p);
+       }
+       /* turn clocks off? */
+       if (blank > FB_BLANK_NORMAL && ch->blank_status <= FB_BLANK_NORMAL) {
+               /* make sure the screen is updated with the black fill before
+                * switching the clocks off. one vsync is not enough since
+                * blanking may occur in the middle of a refresh. deferred io
+                * mode will reenable the clocks and update the screen in time,
+                * so it does not need this. */
+               if (!info->fbdefio) {
+                       sh_mobile_wait_for_vsync(info);
+                       sh_mobile_wait_for_vsync(info);
+               }
+               sh_mobile_lcdc_clk_off(p);
+       }
+
+       ch->blank_status = blank;
+       return 0;
+}
 
 static struct fb_ops sh_mobile_lcdc_ops = {
        .owner          = THIS_MODULE,
@@ -846,12 +1171,89 @@ static struct fb_ops sh_mobile_lcdc_ops = {
        .fb_fillrect    = sh_mobile_lcdc_fillrect,
        .fb_copyarea    = sh_mobile_lcdc_copyarea,
        .fb_imageblit   = sh_mobile_lcdc_imageblit,
+       .fb_blank       = sh_mobile_lcdc_blank,
        .fb_pan_display = sh_mobile_fb_pan_display,
        .fb_ioctl       = sh_mobile_ioctl,
+       .fb_open        = sh_mobile_open,
+       .fb_release     = sh_mobile_release,
+       .fb_check_var   = sh_mobile_check_var,
 };
 
-static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp)
+static int sh_mobile_lcdc_update_bl(struct backlight_device *bdev)
+{
+       struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev);
+       struct sh_mobile_lcdc_board_cfg *cfg = &ch->cfg.board_cfg;
+       int brightness = bdev->props.brightness;
+
+       if (bdev->props.power != FB_BLANK_UNBLANK ||
+           bdev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
+               brightness = 0;
+
+       return cfg->set_brightness(cfg->board_data, brightness);
+}
+
+static int sh_mobile_lcdc_get_brightness(struct backlight_device *bdev)
+{
+       struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev);
+       struct sh_mobile_lcdc_board_cfg *cfg = &ch->cfg.board_cfg;
+
+       return cfg->get_brightness(cfg->board_data);
+}
+
+static int sh_mobile_lcdc_check_fb(struct backlight_device *bdev,
+                                  struct fb_info *info)
 {
+       return (info->bl_dev == bdev);
+}
+
+static struct backlight_ops sh_mobile_lcdc_bl_ops = {
+       .options        = BL_CORE_SUSPENDRESUME,
+       .update_status  = sh_mobile_lcdc_update_bl,
+       .get_brightness = sh_mobile_lcdc_get_brightness,
+       .check_fb       = sh_mobile_lcdc_check_fb,
+};
+
+static struct backlight_device *sh_mobile_lcdc_bl_probe(struct device *parent,
+                                              struct sh_mobile_lcdc_chan *ch)
+{
+       struct backlight_device *bl;
+
+       bl = backlight_device_register(ch->cfg.bl_info.name, parent, ch,
+                                      &sh_mobile_lcdc_bl_ops, NULL);
+       if (IS_ERR(bl)) {
+               dev_err(parent, "unable to register backlight device: %ld\n",
+                       PTR_ERR(bl));
+               return NULL;
+       }
+
+       bl->props.max_brightness = ch->cfg.bl_info.max_brightness;
+       bl->props.brightness = bl->props.max_brightness;
+       backlight_update_status(bl);
+
+       return bl;
+}
+
+static void sh_mobile_lcdc_bl_remove(struct backlight_device *bdev)
+{
+       backlight_device_unregister(bdev);
+}
+
+static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp,
+                                  int nonstd)
+{
+       if (nonstd) {
+               switch (bpp) {
+               case 12:
+               case 16:
+               case 24:
+                       var->bits_per_pixel = bpp;
+                       var->nonstd = nonstd;
+                       return 0;
+               default:
+                       return -EINVAL;
+               }
+       }
+
        switch (bpp) {
        case 16: /* PKF[4:0] = 00011 - RGB 565 */
                var->red.offset = 11;
@@ -864,19 +1266,27 @@ static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp)
                var->transp.length = 0;
                break;
 
-       case 32: /* PKF[4:0] = 00000 - RGB 888
-                 * sh7722 pdf says 00RRGGBB but reality is GGBB00RR
-                 * this may be because LDDDSR has word swap enabled..
-                 */
-               var->red.offset = 0;
+       case 24: /* PKF[4:0] = 01011 - RGB 888 */
+               var->red.offset = 16;
                var->red.length = 8;
-               var->green.offset = 24;
+               var->green.offset = 8;
                var->green.length = 8;
-               var->blue.offset = 16;
+               var->blue.offset = 0;
                var->blue.length = 8;
                var->transp.offset = 0;
                var->transp.length = 0;
                break;
+
+       case 32: /* PKF[4:0] = 00000 - RGBA 888 */
+               var->red.offset = 16;
+               var->red.length = 8;
+               var->green.offset = 8;
+               var->green.length = 8;
+               var->blue.offset = 0;
+               var->blue.length = 8;
+               var->transp.offset = 24;
+               var->transp.length = 8;
+               break;
        default:
                return -EINVAL;
        }
@@ -958,6 +1368,7 @@ static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = {
        .runtime_resume = sh_mobile_lcdc_runtime_resume,
 };
 
+/* locking: called with info->lock held */
 static int sh_mobile_lcdc_notify(struct notifier_block *nb,
                                 unsigned long action, void *data)
 {
@@ -965,53 +1376,36 @@ static int sh_mobile_lcdc_notify(struct notifier_block *nb,
        struct fb_info *info = event->info;
        struct sh_mobile_lcdc_chan *ch = info->par;
        struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg;
-       struct fb_var_screeninfo *var;
 
        if (&ch->lcdc->notifier != nb)
-               return 0;
+               return NOTIFY_DONE;
 
        dev_dbg(info->dev, "%s(): action = %lu, data = %p\n",
                __func__, action, event->data);
 
        switch(action) {
        case FB_EVENT_SUSPEND:
-               if (board_cfg->display_off)
+               if (board_cfg->display_off && try_module_get(board_cfg->owner)) {
                        board_cfg->display_off(board_cfg->board_data);
-               pm_runtime_put(info->device);
+                       module_put(board_cfg->owner);
+               }
+               sh_mobile_lcdc_stop(ch->lcdc);
                break;
        case FB_EVENT_RESUME:
-               var = &info->var;
+               mutex_lock(&ch->open_lock);
+               sh_mobile_fb_reconfig(info);
+               mutex_unlock(&ch->open_lock);
 
                /* HDMI must be enabled before LCDC configuration */
-               if (board_cfg->display_on)
-                       board_cfg->display_on(board_cfg->board_data, ch->info);
-
-               /* Check if the new display is not in our modelist */
-               if (ch->info->modelist.next &&
-                   !fb_match_mode(var, &ch->info->modelist)) {
-                       struct fb_videomode mode;
-                       int ret;
-
-                       /* Can we handle this display? */
-                       if (var->xres > ch->cfg.lcd_cfg.xres ||
-                           var->yres > ch->cfg.lcd_cfg.yres)
-                               return -ENOMEM;
-
-                       /* Add to the modelist */
-                       fb_var_to_videomode(&mode, var);
-                       ret = fb_add_videomode(&mode, &ch->info->modelist);
-                       if (ret < 0)
-                               return ret;
+               if (board_cfg->display_on && try_module_get(board_cfg->owner)) {
+                       board_cfg->display_on(board_cfg->board_data, info);
+                       module_put(board_cfg->owner);
                }
 
-               pm_runtime_get_sync(info->device);
-
-               sh_mobile_lcdc_geometry(ch);
-
-               break;
+               sh_mobile_lcdc_start(ch->lcdc);
        }
 
-       return 0;
+       return NOTIFY_OK;
 }
 
 static int sh_mobile_lcdc_remove(struct platform_device *pdev);
@@ -1020,14 +1414,13 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev)
 {
        struct fb_info *info;
        struct sh_mobile_lcdc_priv *priv;
-       struct sh_mobile_lcdc_info *pdata;
-       struct sh_mobile_lcdc_chan_cfg *cfg;
+       struct sh_mobile_lcdc_info *pdata = pdev->dev.platform_data;
        struct resource *res;
        int error;
        void *buf;
        int i, j;
 
-       if (!pdev->dev.platform_data) {
+       if (!pdata) {
                dev_err(&pdev->dev, "no platform data defined\n");
                return -EINVAL;
        }
@@ -1055,31 +1448,37 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev)
        }
 
        priv->irq = i;
-       pdata = pdev->dev.platform_data;
+       atomic_set(&priv->hw_usecnt, -1);
 
        j = 0;
        for (i = 0; i < ARRAY_SIZE(pdata->ch); i++) {
-               priv->ch[j].lcdc = priv;
-               memcpy(&priv->ch[j].cfg, &pdata->ch[i], sizeof(pdata->ch[i]));
+               struct sh_mobile_lcdc_chan *ch = priv->ch + j;
+
+               ch->lcdc = priv;
+               memcpy(&ch->cfg, &pdata->ch[i], sizeof(pdata->ch[i]));
 
-               error = sh_mobile_lcdc_check_interface(&priv->ch[j]);
+               error = sh_mobile_lcdc_check_interface(ch);
                if (error) {
                        dev_err(&pdev->dev, "unsupported interface type\n");
                        goto err1;
                }
-               init_waitqueue_head(&priv->ch[j].frame_end_wait);
-               init_completion(&priv->ch[j].vsync_completion);
-               priv->ch[j].pan_offset = 0;
+               init_waitqueue_head(&ch->frame_end_wait);
+               init_completion(&ch->vsync_completion);
+               ch->pan_offset = 0;
+
+               /* probe the backlight is there is one defined */
+               if (ch->cfg.bl_info.max_brightness)
+                       ch->bl = sh_mobile_lcdc_bl_probe(&pdev->dev, ch);
 
                switch (pdata->ch[i].chan) {
                case LCDC_CHAN_MAINLCD:
-                       priv->ch[j].enabled = 1 << 1;
-                       priv->ch[j].reg_offs = lcdc_offs_mainlcd;
+                       ch->enabled = 1 << 1;
+                       ch->reg_offs = lcdc_offs_mainlcd;
                        j++;
                        break;
                case LCDC_CHAN_SUBLCD:
-                       priv->ch[j].enabled = 1 << 2;
-                       priv->ch[j].reg_offs = lcdc_offs_sublcd;
+                       ch->enabled = 1 << 2;
+                       ch->reg_offs = lcdc_offs_sublcd;
                        j++;
                        break;
                }
@@ -1091,6 +1490,10 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev)
                goto err1;
        }
 
+       /* for dual channel LCDC (MAIN + SUB) force shared bpp setting */
+       if (j == 2)
+               priv->forced_bpp = pdata->ch[0].bpp;
+
        priv->base = ioremap_nocache(res->start, resource_size(res));
        if (!priv->base)
                goto err1;
@@ -1101,71 +1504,112 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev)
                goto err1;
        }
 
+       priv->meram_dev = pdata->meram_dev;
+
        for (i = 0; i < j; i++) {
                struct fb_var_screeninfo *var;
-               struct fb_videomode *lcd_cfg;
-               cfg = &priv->ch[i].cfg;
-
-               priv->ch[i].info = framebuffer_alloc(0, &pdev->dev);
-               if (!priv->ch[i].info) {
+               const struct fb_videomode *lcd_cfg, *max_cfg = NULL;
+               struct sh_mobile_lcdc_chan *ch = priv->ch + i;
+               struct sh_mobile_lcdc_chan_cfg *cfg = &ch->cfg;
+               const struct fb_videomode *mode = cfg->lcd_cfg;
+               unsigned long max_size = 0;
+               int k;
+               int num_cfg;
+
+               ch->info = framebuffer_alloc(0, &pdev->dev);
+               if (!ch->info) {
                        dev_err(&pdev->dev, "unable to allocate fb_info\n");
                        error = -ENOMEM;
                        break;
                }
 
-               info = priv->ch[i].info;
+               info = ch->info;
                var = &info->var;
-               lcd_cfg = &cfg->lcd_cfg;
                info->fbops = &sh_mobile_lcdc_ops;
-               var->xres = var->xres_virtual = lcd_cfg->xres;
-               var->yres = lcd_cfg->yres;
-               /* Default Y virtual resolution is 2x panel size */
-               var->yres_virtual = var->yres * 2;
+               info->par = ch;
+
+               mutex_init(&ch->open_lock);
+
+               for (k = 0, lcd_cfg = mode;
+                    k < cfg->num_cfg && lcd_cfg;
+                    k++, lcd_cfg++) {
+                       unsigned long size = lcd_cfg->yres * lcd_cfg->xres;
+                       /* NV12 buffers must have even number of lines */
+                       if ((cfg->nonstd) && cfg->bpp == 12 &&
+                                       (lcd_cfg->yres & 0x1)) {
+                               dev_err(&pdev->dev, "yres must be multiple of 2"
+                                               " for YCbCr420 mode.\n");
+                               error = -EINVAL;
+                               goto err1;
+                       }
+
+                       if (size > max_size) {
+                               max_cfg = lcd_cfg;
+                               max_size = size;
+                       }
+               }
+
+               if (!mode)
+                       max_size = MAX_XRES * MAX_YRES;
+               else if (max_cfg)
+                       dev_dbg(&pdev->dev, "Found largest videomode %ux%u\n",
+                               max_cfg->xres, max_cfg->yres);
+
+               info->fix = sh_mobile_lcdc_fix;
+               info->fix.smem_len = max_size * 2 * cfg->bpp / 8;
+
+                /* Only pan in 2 line steps for NV12 */
+               if (cfg->nonstd && cfg->bpp == 12)
+                       info->fix.ypanstep = 2;
+
+               if (!mode) {
+                       mode = &default_720p;
+                       num_cfg = 1;
+               } else {
+                       num_cfg = cfg->num_cfg;
+               }
+
+               fb_videomode_to_modelist(mode, num_cfg, &info->modelist);
+
+               fb_videomode_to_var(var, mode);
                var->width = cfg->lcd_size_cfg.width;
                var->height = cfg->lcd_size_cfg.height;
+               /* Default Y virtual resolution is 2x panel size */
+               var->yres_virtual = var->yres * 2;
                var->activate = FB_ACTIVATE_NOW;
-               var->left_margin = lcd_cfg->left_margin;
-               var->right_margin = lcd_cfg->right_margin;
-               var->upper_margin = lcd_cfg->upper_margin;
-               var->lower_margin = lcd_cfg->lower_margin;
-               var->hsync_len = lcd_cfg->hsync_len;
-               var->vsync_len = lcd_cfg->vsync_len;
-               var->sync = lcd_cfg->sync;
-               var->pixclock = lcd_cfg->pixclock;
-
-               error = sh_mobile_lcdc_set_bpp(var, cfg->bpp);
+
+               error = sh_mobile_lcdc_set_bpp(var, cfg->bpp, cfg->nonstd);
                if (error)
                        break;
 
-               info->fix = sh_mobile_lcdc_fix;
-               info->fix.line_length = lcd_cfg->xres * (cfg->bpp / 8);
-               info->fix.smem_len = info->fix.line_length *
-                       var->yres_virtual;
-
                buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len,
-                                        &priv->ch[i].dma_handle, GFP_KERNEL);
+                                        &ch->dma_handle, GFP_KERNEL);
                if (!buf) {
                        dev_err(&pdev->dev, "unable to allocate buffer\n");
                        error = -ENOMEM;
                        break;
                }
 
-               info->pseudo_palette = &priv->ch[i].pseudo_palette;
+               info->pseudo_palette = &ch->pseudo_palette;
                info->flags = FBINFO_FLAG_DEFAULT;
 
                error = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0);
                if (error < 0) {
                        dev_err(&pdev->dev, "unable to allocate cmap\n");
                        dma_free_coherent(&pdev->dev, info->fix.smem_len,
-                                         buf, priv->ch[i].dma_handle);
+                                         buf, ch->dma_handle);
                        break;
                }
 
-               memset(buf, 0, info->fix.smem_len);
-               info->fix.smem_start = priv->ch[i].dma_handle;
+               info->fix.smem_start = ch->dma_handle;
+               if (var->nonstd)
+                       info->fix.line_length = var->xres;
+               else
+                       info->fix.line_length = var->xres * (cfg->bpp / 8);
+
                info->screen_base = buf;
                info->device = &pdev->dev;
-               info->par = &priv->ch[i];
+               ch->display_var = *var;
        }
 
        if (error)
@@ -1191,6 +1635,8 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev)
                        }
                }
 
+               info->bl_dev = ch->bl;
+
                error = register_framebuffer(info);
                if (error < 0)
                        goto err1;
@@ -1200,8 +1646,7 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev)
                         pdev->name,
                         (ch->cfg.chan == LCDC_CHAN_MAINLCD) ?
                         "mainlcd" : "sublcd",
-                        (int) ch->cfg.lcd_cfg.xres,
-                        (int) ch->cfg.lcd_cfg.yres,
+                        info->var.xres, info->var.yres,
                         ch->cfg.bpp);
 
                /* deferred io mode: disable clock to save power */
@@ -1243,12 +1688,19 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev)
                if (priv->ch[i].sglist)
                        vfree(priv->ch[i].sglist);
 
-               dma_free_coherent(&pdev->dev, info->fix.smem_len,
-                                 info->screen_base, priv->ch[i].dma_handle);
+               if (info->screen_base)
+                       dma_free_coherent(&pdev->dev, info->fix.smem_len,
+                                         info->screen_base,
+                                         priv->ch[i].dma_handle);
                fb_dealloc_cmap(&info->cmap);
                framebuffer_release(info);
        }
 
+       for (i = 0; i < ARRAY_SIZE(priv->ch); i++) {
+               if (priv->ch[i].bl)
+                       sh_mobile_lcdc_bl_remove(priv->ch[i].bl);
+       }
+
        if (priv->dot_clk)
                clk_put(priv->dot_clk);