drm: hdlcd: Fix the picture tear when flipping framebuffers.
authorLiviu Dudau <Liviu.Dudau@arm.com>
Fri, 18 Jul 2014 16:31:38 +0000 (17:31 +0100)
committerLiviu Dudau <Liviu.Dudau@arm.com>
Fri, 18 Jul 2014 16:31:38 +0000 (17:31 +0100)
HDLCD contains logic in hardware to wait for VSYNC before updating
the start of framebuffer register, so we don't need to do it in
software. More than that, to remove any tearing we need to wait
for VSYNC after setting up a new framebuffer as releasing the old
fb while the hardware is still using it can cause artifacts.

Signed-off-by: Liviu Dudau <Liviu.Dudau@arm.com>
drivers/gpu/drm/arm/hdlcd_crtc.c
drivers/gpu/drm/arm/hdlcd_drv.c
drivers/gpu/drm/arm/hdlcd_drv.h
drivers/gpu/drm/arm/hdlcd_fb.c

index 39cf6c0..17c006e 100644 (file)
@@ -31,12 +31,13 @@ static void hdlcd_crtc_destroy(struct drm_crtc *crtc)
        drm_crtc_cleanup(crtc);
 }
 
-void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd)
+void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd, bool wait)
 {
        struct drm_framebuffer *fb = hdlcd->crtc.primary->fb;
        struct hdlcd_bo *bo;
        unsigned int depth, bpp;
        dma_addr_t scanout_start;
+       int ret;
 
        drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp);
        bo = hdlcd->bo;
@@ -44,9 +45,16 @@ void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd)
        scanout_start = bo->dma_addr + fb->offsets[0] +
                (hdlcd->crtc.y * fb->pitches[0]) + (hdlcd->crtc.x * bpp/8);
 
-       if (scanout_start != hdlcd->scanout_buf) {
-               hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start);
-               hdlcd->scanout_buf = scanout_start;
+       hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start);
+
+       if (wait) {
+               drm_vblank_get(fb->dev, 0);
+               reinit_completion(&hdlcd->vsync_completion);
+               do {
+                       ret = wait_for_completion_interruptible_timeout(&hdlcd->vsync_completion,
+                                                       msecs_to_jiffies(1000));
+               } while (ret <= 0);
+               drm_vblank_put(fb->dev, 0);
        }
 }
 
@@ -72,11 +80,13 @@ static int hdlcd_crtc_page_flip(struct drm_crtc *crtc,
                unsigned long flags;
 
                /* not active, update registers immediately */
-               hdlcd_set_scanout(hdlcd);
+               hdlcd_set_scanout(hdlcd, false);
                spin_lock_irqsave(&crtc->dev->event_lock, flags);
                if (event)
                        drm_send_vblank_event(crtc->dev, 0, event);
                spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+       } else {
+               hdlcd_set_scanout(hdlcd, true);
        }
 
        return 0;
@@ -149,7 +159,7 @@ static int hdlcd_crtc_mode_set(struct drm_crtc *crtc,
        /* This function gets called when the only change is the start of
           the scanout buffer. Detect that and bail out early */
        if (hdlcd->initialised && hdlcd_fb_mode_equal(oldfb, crtc->primary->fb)) {
-               hdlcd_set_scanout(hdlcd);
+               hdlcd_set_scanout(hdlcd, true);
                return 0;
        }
 
@@ -226,7 +236,7 @@ static int hdlcd_crtc_mode_set(struct drm_crtc *crtc,
        clk_set_rate(hdlcd->clk, mode->crtc_clock * 1000);
        clk_enable(hdlcd->clk);
 
-       hdlcd_set_scanout(hdlcd);
+       hdlcd_set_scanout(hdlcd, false);
        hdlcd->initialised = true;
 
        return 0;
@@ -237,7 +247,7 @@ int hdlcd_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
 {
        struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
 
-       hdlcd_set_scanout(hdlcd);
+       hdlcd_set_scanout(hdlcd, true);
        return 0;
 }
 
index 62f34a0..a831b86 100644 (file)
@@ -214,8 +214,6 @@ static irqreturn_t hdlcd_irq(int irq, void *arg)
                struct drm_pending_vblank_event *event;
                unsigned long flags;
 
-               hdlcd_set_scanout(hdlcd);
-
                drm_handle_vblank(dev, 0);
 
                spin_lock_irqsave(&dev->event_lock, flags);
@@ -226,7 +224,7 @@ static irqreturn_t hdlcd_irq(int irq, void *arg)
                        drm_vblank_put(dev, 0);
                }
                spin_unlock_irqrestore(&dev->event_lock, flags);
-               complete(&hdlcd->vsync_completion);
+               complete_all(&hdlcd->vsync_completion);
        }
 
        /* acknowledge interrupt(s) */
index cd2923a..455da17 100644 (file)
@@ -16,7 +16,6 @@ struct hdlcd_drm_private {
        struct clk                      *clk;
        struct drm_fb_helper            *fb_helper;
        struct hdlcd_bo                 *bo;
-       dma_addr_t                      scanout_buf;
        struct drm_pending_vblank_event *event;
        struct drm_crtc                 crtc;
        struct device_node              *slave_node;
@@ -73,7 +72,7 @@ static inline int hdlcd_create_virtual_connector(struct drm_device *dev)
 }
 #endif
 
-void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd);
+void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd, bool wait);
 void hdlcd_drm_mode_config_init(struct drm_device *dev);
 int hdlcd_fbdev_init(struct drm_device *dev);
 
index cae947a..ca67e60 100644 (file)
@@ -252,11 +252,14 @@ static int hdlcd_wait_for_vsync(struct fb_info *info)
        struct hdlcd_drm_private *hdlcd = helper->dev->dev_private;
        int ret;
 
-       drm_crtc_vblank_on(&hdlcd->crtc);
-       ret = wait_for_completion_interruptible_timeout(&hdlcd->vsync_completion,
-                                                       msecs_to_jiffies(100));
-       drm_crtc_vblank_off(&hdlcd->crtc);
-       if (ret)
+       drm_vblank_get(helper->dev, 0);
+       reinit_completion(&hdlcd->vsync_completion);
+       do {
+               ret = wait_for_completion_interruptible_timeout(&hdlcd->vsync_completion,
+                                                       msecs_to_jiffies(1000));
+       } while (ret == -ERESTARTSYS);
+       drm_vblank_put(helper->dev, 0);
+       if (!ret)
                return -ETIMEDOUT;
 #endif
 
@@ -288,7 +291,7 @@ static int hdlcd_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
 
        dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
 
-       vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
+       vma->vm_flags |= VM_IO | VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP;
        vm_size = vma->vm_end - vma->vm_start;
 
        if (vm_size > bo->gem.size)
@@ -474,11 +477,11 @@ static int hdlcd_fb_probe(struct drm_fb_helper *helper,
 
        cmd.width = sizes->surface_width;
        cmd.height = sizes->surface_height;
-       cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
+       cmd.pitches[0] = ALIGN(sizes->surface_width * bytes_per_pixel, 64);
        cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
                                                     sizes->surface_depth);
 
-       size = cmd.pitches[0] * cmd.height * MAX_FRAMES;
+       size = PAGE_ALIGN(cmd.pitches[0] * cmd.height * MAX_FRAMES);
 
        bo = hdlcd_fb_bo_create(helper->dev, size);
        if (IS_ERR(bo))
@@ -566,7 +569,6 @@ static struct drm_framebuffer *hdlcd_fb_alloc(struct drm_device *dev,
        int err;
        struct drm_framebuffer *fb;
 
-       dev_info(dev->dev, "Linux is here %s", __func__);
        fb = kzalloc(sizeof(*fb), GFP_KERNEL);
        if (!fb)
                return ERR_PTR(-ENOMEM);