[08/12] drm/tilcdc: fix the ping-pong dma tearing issue seen on buffer flipping

Message ID dcc9a3ef42c05c97eacd09f51515306cc7fbbad2.1450202735.git.jsarha@ti.com
State New
Headers show

Commit Message

Jyri Sarha Dec. 15, 2015, 7:03 p.m.
From: Darren Etheridge <detheridge@ti.com>

Update the DMA pointers during the pan display ioctl. Borrowed from
da8xx_fb.c update the scanout buffers immediately so the DMA ping/pong
doesn't end up out of sync with what we really want it to DMA,
otherwise part of the screen ends up tearing during rapid flip
operations.

Ported from commit deb95c6c958f ("video: da8xx-fb: fix flicker due to
1 frame delay in updated frame")

Signed-off-by: Darren Etheridge <detheridge@ti.com>
[Rewrapped description and fixed commit reference]
Signed-off-by: Jyri Sarha <jsarha@ti.com>
---
 drivers/gpu/drm/tilcdc/tilcdc_crtc.c | 36 +++++++++++++++++++++++++++++++-----
 1 file changed, 31 insertions(+), 5 deletions(-)

Patch

diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
index cc45818..50384fa 100644
--- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
+++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
@@ -31,6 +31,8 @@  struct tilcdc_crtc {
 	int dpms;
 	wait_queue_head_t frame_done_wq;
 	bool frame_done;
+	spinlock_t irq_lock;
+	int dma_completed_channel;
 
 	/* fb currently set to scanout 0/1: */
 	struct drm_framebuffer *scanout[2];
@@ -102,10 +104,23 @@  static void update_scanout(struct drm_crtc *crtc)
 			(crtc->mode.vdisplay * fb->pitches[0]);
 
 	if (tilcdc_crtc->dpms == DRM_MODE_DPMS_ON) {
-		/* already enabled, so just mark the frames that need
-		 * updating and they will be updated on vblank:
+		/*
+		 * already enabled, so just mark the frames that need
+		 * updating and they will be updated on vblank
+		 * and update the inactive DMA channel immediately
+		 * to avoid any tearing due to the DMA already starting
+		 * on the pending dma buffer when we hit the vblank IRQ
 		 */
-		tilcdc_crtc->dirty |= LCDC_END_OF_FRAME0 | LCDC_END_OF_FRAME1;
+		if (tilcdc_crtc->dma_completed_channel == 0) {
+			tilcdc_crtc->dirty |= LCDC_END_OF_FRAME1;
+			set_scanout(crtc, 0);
+		}
+
+		if (tilcdc_crtc->dma_completed_channel == 1) {
+			tilcdc_crtc->dirty |= LCDC_END_OF_FRAME0;
+			set_scanout(crtc, 1);
+		}
+
 		drm_vblank_get(dev, 0);
 	} else {
 		/* not enabled yet, so update registers immediately: */
@@ -647,6 +662,7 @@  irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc)
 	struct drm_device *dev = crtc->dev;
 	struct tilcdc_drm_private *priv = dev->dev_private;
 	uint32_t stat = tilcdc_read_irqstatus(dev);
+	unsigned long irq_flags;
 
 	if ((stat & LCDC_SYNC_LOST) && (stat & LCDC_FIFO_UNDERFLOW)) {
 		stop(crtc);
@@ -662,11 +678,19 @@  irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc)
 
 		tilcdc_clear_irqstatus(dev, stat);
 
-		if (dirty & LCDC_END_OF_FRAME0)
+		spin_lock_irqsave(&tilcdc_crtc->irq_lock, irq_flags);
+
+		if (dirty & LCDC_END_OF_FRAME0) {
 			set_scanout(crtc, 0);
+			tilcdc_crtc->dma_completed_channel = 0;
+		}
 
-		if (dirty & LCDC_END_OF_FRAME1)
+		if (dirty & LCDC_END_OF_FRAME1) {
 			set_scanout(crtc, 1);
+			tilcdc_crtc->dma_completed_channel = 1;
+		}
+
+		spin_unlock_irqrestore(&tilcdc_crtc->irq_lock, irq_flags);
 
 		drm_handle_vblank(dev, 0);
 
@@ -732,6 +756,8 @@  struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev)
 	drm_flip_work_init(&tilcdc_crtc->unref_work,
 			"unref", unref_worker);
 
+	spin_lock_init(&tilcdc_crtc->irq_lock);
+
 	ret = drm_crtc_init(dev, crtc, &tilcdc_crtc_funcs);
 	if (ret < 0)
 		goto fail;