]> Pileus Git - ~andy/linux/blobdiff - drivers/video/sh_mobile_lcdcfb.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ryusuke...
[~andy/linux] / drivers / video / sh_mobile_lcdcfb.c
index a69830d26f7fbdd7a050ef5daa39522b1962b60a..bbd1dbf4026a32439bf5ef7b96b037974c5c6416 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/interrupt.h>
 #include <linux/vmalloc.h>
+#include <linux/ioctl.h>
 #include <video/sh_mobile_lcdc.h>
 #include <asm/atomic.h>
 
@@ -106,6 +107,7 @@ static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = {
 #define LDRCNTR_SRC    0x00010000
 #define LDRCNTR_MRS    0x00000002
 #define LDRCNTR_MRC    0x00000001
+#define LDSR_MRS       0x00000100
 
 struct sh_mobile_lcdc_priv;
 struct sh_mobile_lcdc_chan {
@@ -122,8 +124,8 @@ struct sh_mobile_lcdc_chan {
        struct scatterlist *sglist;
        unsigned long frame_end;
        unsigned long pan_offset;
-       unsigned long new_pan_offset;
        wait_queue_head_t frame_end_wait;
+       struct completion vsync_completion;
 };
 
 struct sh_mobile_lcdc_priv {
@@ -366,19 +368,8 @@ static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data)
                }
 
                /* VSYNC End */
-               if (ldintr & LDINTR_VES) {
-                       unsigned long ldrcntr = lcdc_read(priv, _LDRCNTR);
-                       /* Set the source address for the next refresh */
-                       lcdc_write_chan_mirror(ch, LDSA1R, ch->dma_handle +
-                                              ch->new_pan_offset);
-                       if (lcdc_chan_is_sublcd(ch))
-                               lcdc_write(ch->lcdc, _LDRCNTR,
-                                          ldrcntr ^ LDRCNTR_SRS);
-                       else
-                               lcdc_write(ch->lcdc, _LDRCNTR,
-                                          ldrcntr ^ LDRCNTR_MRS);
-                       ch->pan_offset = ch->new_pan_offset;
-               }
+               if (ldintr & LDINTR_VES)
+                       complete(&ch->vsync_completion);
        }
 
        return IRQ_HANDLED;
@@ -767,25 +758,69 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var,
                                     struct fb_info *info)
 {
        struct sh_mobile_lcdc_chan *ch = info->par;
+       struct sh_mobile_lcdc_priv *priv = ch->lcdc;
+       unsigned long ldrcntr;
+       unsigned long new_pan_offset;
+
+       new_pan_offset = (var->yoffset * info->fix.line_length) +
+               (var->xoffset * (info->var.bits_per_pixel / 8));
 
-       if (info->var.xoffset == var->xoffset &&
-           info->var.yoffset == var->yoffset)
+       if (new_pan_offset == ch->pan_offset)
                return 0;       /* No change, do nothing */
 
-       ch->new_pan_offset = (var->yoffset * info->fix.line_length) +
-               (var->xoffset * (info->var.bits_per_pixel / 8));
+       ldrcntr = lcdc_read(priv, _LDRCNTR);
 
-       if (ch->new_pan_offset != ch->pan_offset) {
-               unsigned long ldintr;
-               ldintr = lcdc_read(ch->lcdc, _LDINTR);
-               ldintr |= LDINTR_VEE;
-               lcdc_write(ch->lcdc, _LDINTR, ldintr);
-               sh_mobile_lcdc_deferred_io_touch(info);
-       }
+       /* Set the source address for the next refresh */
+       lcdc_write_chan_mirror(ch, LDSA1R, ch->dma_handle + new_pan_offset);
+       if (lcdc_chan_is_sublcd(ch))
+               lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS);
+       else
+               lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_MRS);
+
+       ch->pan_offset = new_pan_offset;
+
+       sh_mobile_lcdc_deferred_io_touch(info);
+
+       return 0;
+}
+
+static int sh_mobile_wait_for_vsync(struct fb_info *info)
+{
+       struct sh_mobile_lcdc_chan *ch = info->par;
+       unsigned long ldintr;
+       int ret;
+
+       /* Enable VSync End interrupt */
+       ldintr = lcdc_read(ch->lcdc, _LDINTR);
+       ldintr |= LDINTR_VEE;
+       lcdc_write(ch->lcdc, _LDINTR, ldintr);
+
+       ret = wait_for_completion_interruptible_timeout(&ch->vsync_completion,
+                                                       msecs_to_jiffies(100));
+       if (!ret)
+               return -ETIMEDOUT;
 
        return 0;
 }
 
+static int sh_mobile_ioctl(struct fb_info *info, unsigned int cmd,
+                      unsigned long arg)
+{
+       int retval;
+
+       switch (cmd) {
+       case FBIO_WAITFORVSYNC:
+               retval = sh_mobile_wait_for_vsync(info);
+               break;
+
+       default:
+               retval = -ENOIOCTLCMD;
+               break;
+       }
+       return retval;
+}
+
+
 static struct fb_ops sh_mobile_lcdc_ops = {
        .owner          = THIS_MODULE,
        .fb_setcolreg   = sh_mobile_lcdc_setcolreg,
@@ -795,6 +830,7 @@ static struct fb_ops sh_mobile_lcdc_ops = {
        .fb_copyarea    = sh_mobile_lcdc_copyarea,
        .fb_imageblit   = sh_mobile_lcdc_imageblit,
        .fb_pan_display = sh_mobile_fb_pan_display,
+       .fb_ioctl       = sh_mobile_ioctl,
 };
 
 static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp)
@@ -907,7 +943,7 @@ static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = {
 
 static int sh_mobile_lcdc_remove(struct platform_device *pdev);
 
-static int __init sh_mobile_lcdc_probe(struct platform_device *pdev)
+static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev)
 {
        struct fb_info *info;
        struct sh_mobile_lcdc_priv *priv;
@@ -962,8 +998,8 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev)
                        goto err1;
                }
                init_waitqueue_head(&priv->ch[i].frame_end_wait);
+               init_completion(&priv->ch[i].vsync_completion);
                priv->ch[j].pan_offset = 0;
-               priv->ch[j].new_pan_offset = 0;
 
                switch (pdata->ch[i].chan) {
                case LCDC_CHAN_MAINLCD: