drm: rcar-du: Wait for page flip completion when turning the CRTC off
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / rcar-du / rcar_du_crtc.c
index 25c7a998fc2cf075fe1ecb6c8fe603f439abab7f..7f5ae0269a618638dee585ec424008b25e8e8cea 100644 (file)
@@ -99,6 +99,10 @@ static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc)
        clk_disable_unprepare(rcrtc->clock);
 }
 
+/* -----------------------------------------------------------------------------
+ * Hardware Setup
+ */
+
 static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
 {
        const struct drm_display_mode *mode = &rcrtc->crtc.mode;
@@ -256,6 +260,83 @@ void rcar_du_crtc_update_planes(struct drm_crtc *crtc)
                            dspr);
 }
 
+/* -----------------------------------------------------------------------------
+ * Page Flip
+ */
+
+void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc,
+                                  struct drm_file *file)
+{
+       struct drm_pending_vblank_event *event;
+       struct drm_device *dev = rcrtc->crtc.dev;
+       unsigned long flags;
+
+       /* Destroy the pending vertical blanking event associated with the
+        * pending page flip, if any, and disable vertical blanking interrupts.
+        */
+       spin_lock_irqsave(&dev->event_lock, flags);
+       event = rcrtc->event;
+       if (event && event->base.file_priv == file) {
+               rcrtc->event = NULL;
+               event->base.destroy(&event->base);
+               drm_vblank_put(dev, rcrtc->index);
+       }
+       spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+static void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc)
+{
+       struct drm_pending_vblank_event *event;
+       struct drm_device *dev = rcrtc->crtc.dev;
+       unsigned long flags;
+
+       spin_lock_irqsave(&dev->event_lock, flags);
+       event = rcrtc->event;
+       rcrtc->event = NULL;
+       spin_unlock_irqrestore(&dev->event_lock, flags);
+
+       if (event == NULL)
+               return;
+
+       spin_lock_irqsave(&dev->event_lock, flags);
+       drm_send_vblank_event(dev, rcrtc->index, event);
+       wake_up(&rcrtc->flip_wait);
+       spin_unlock_irqrestore(&dev->event_lock, flags);
+
+       drm_vblank_put(dev, rcrtc->index);
+}
+
+static bool rcar_du_crtc_page_flip_pending(struct rcar_du_crtc *rcrtc)
+{
+       struct drm_device *dev = rcrtc->crtc.dev;
+       unsigned long flags;
+       bool pending;
+
+       spin_lock_irqsave(&dev->event_lock, flags);
+       pending = rcrtc->event != NULL;
+       spin_unlock_irqrestore(&dev->event_lock, flags);
+
+       return pending;
+}
+
+static void rcar_du_crtc_wait_page_flip(struct rcar_du_crtc *rcrtc)
+{
+       struct rcar_du_device *rcdu = rcrtc->group->dev;
+
+       if (wait_event_timeout(rcrtc->flip_wait,
+                              !rcar_du_crtc_page_flip_pending(rcrtc),
+                              msecs_to_jiffies(50)))
+               return;
+
+       dev_warn(rcdu->dev, "page flip timeout\n");
+
+       rcar_du_crtc_finish_page_flip(rcrtc);
+}
+
+/* -----------------------------------------------------------------------------
+ * Start/Stop and Suspend/Resume
+ */
+
 static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc)
 {
        struct drm_crtc *crtc = &rcrtc->crtc;
@@ -312,6 +393,11 @@ static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc)
        if (!rcrtc->started)
                return;
 
+       /* Wait for page flip completion before stopping the CRTC as userspace
+        * excepts page flips to eventually complete.
+        */
+       rcar_du_crtc_wait_page_flip(rcrtc);
+
        mutex_lock(&rcrtc->group->planes.lock);
        rcrtc->plane->enabled = false;
        rcar_du_crtc_update_planes(crtc);
@@ -350,6 +436,10 @@ static void rcar_du_crtc_update_base(struct rcar_du_crtc *rcrtc)
        rcar_du_plane_update_base(rcrtc->plane);
 }
 
+/* -----------------------------------------------------------------------------
+ * CRTC Functions
+ */
+
 static void rcar_du_crtc_dpms(struct drm_crtc *crtc, int mode)
 {
        struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
@@ -485,65 +575,6 @@ static const struct drm_crtc_helper_funcs crtc_helper_funcs = {
        .disable = rcar_du_crtc_disable,
 };
 
-void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc,
-                                  struct drm_file *file)
-{
-       struct drm_pending_vblank_event *event;
-       struct drm_device *dev = rcrtc->crtc.dev;
-       unsigned long flags;
-
-       /* Destroy the pending vertical blanking event associated with the
-        * pending page flip, if any, and disable vertical blanking interrupts.
-        */
-       spin_lock_irqsave(&dev->event_lock, flags);
-       event = rcrtc->event;
-       if (event && event->base.file_priv == file) {
-               rcrtc->event = NULL;
-               event->base.destroy(&event->base);
-               drm_vblank_put(dev, rcrtc->index);
-       }
-       spin_unlock_irqrestore(&dev->event_lock, flags);
-}
-
-static void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc)
-{
-       struct drm_pending_vblank_event *event;
-       struct drm_device *dev = rcrtc->crtc.dev;
-       unsigned long flags;
-
-       spin_lock_irqsave(&dev->event_lock, flags);
-       event = rcrtc->event;
-       rcrtc->event = NULL;
-       spin_unlock_irqrestore(&dev->event_lock, flags);
-
-       if (event == NULL)
-               return;
-
-       spin_lock_irqsave(&dev->event_lock, flags);
-       drm_send_vblank_event(dev, rcrtc->index, event);
-       spin_unlock_irqrestore(&dev->event_lock, flags);
-
-       drm_vblank_put(dev, rcrtc->index);
-}
-
-static irqreturn_t rcar_du_crtc_irq(int irq, void *arg)
-{
-       struct rcar_du_crtc *rcrtc = arg;
-       irqreturn_t ret = IRQ_NONE;
-       u32 status;
-
-       status = rcar_du_crtc_read(rcrtc, DSSR);
-       rcar_du_crtc_write(rcrtc, DSRCR, status & DSRCR_MASK);
-
-       if (status & DSSR_FRM) {
-               drm_handle_vblank(rcrtc->crtc.dev, rcrtc->index);
-               rcar_du_crtc_finish_page_flip(rcrtc);
-               ret = IRQ_HANDLED;
-       }
-
-       return ret;
-}
-
 static int rcar_du_crtc_page_flip(struct drm_crtc *crtc,
                                  struct drm_framebuffer *fb,
                                  struct drm_pending_vblank_event *event,
@@ -580,6 +611,32 @@ static const struct drm_crtc_funcs crtc_funcs = {
        .page_flip = rcar_du_crtc_page_flip,
 };
 
+/* -----------------------------------------------------------------------------
+ * Interrupt Handling
+ */
+
+static irqreturn_t rcar_du_crtc_irq(int irq, void *arg)
+{
+       struct rcar_du_crtc *rcrtc = arg;
+       irqreturn_t ret = IRQ_NONE;
+       u32 status;
+
+       status = rcar_du_crtc_read(rcrtc, DSSR);
+       rcar_du_crtc_write(rcrtc, DSRCR, status & DSRCR_MASK);
+
+       if (status & DSSR_FRM) {
+               drm_handle_vblank(rcrtc->crtc.dev, rcrtc->index);
+               rcar_du_crtc_finish_page_flip(rcrtc);
+               ret = IRQ_HANDLED;
+       }
+
+       return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Initialization
+ */
+
 int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index)
 {
        static const unsigned int mmio_offsets[] = {
@@ -620,6 +677,8 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index)
                return -EPROBE_DEFER;
        }
 
+       init_waitqueue_head(&rcrtc->flip_wait);
+
        rcrtc->group = rgrp;
        rcrtc->mmio_offset = mmio_offsets[index];
        rcrtc->index = index;