drm: omapdrm: Turn vblank on/off when enabling/disabling CRTC
authorLaurent Pinchart <laurent.pinchart@ideasonboard.com>
Sun, 25 Jan 2015 20:42:30 +0000 (22:42 +0200)
committerLaurent Pinchart <laurent.pinchart@ideasonboard.com>
Fri, 12 Jun 2015 19:52:46 +0000 (22:52 +0300)
The DRM core vblank handling mechanism requires drivers to forcefully
turn vblank reporting off when disabling the CRTC, and to restore the
vblank reporting status when enabling the CRTC.

Implement this using the drm_crtc_vblank_on/off helpers. When disabling
vblank we must first wait for page flips to complete, so implement page
flip completion wait as well.

Finally, drm_crtc_vblank_off() must be called at startup to synchronize
the state of the vblank core code with the hardware, which is initially
disabled. An interesting side effect is that the .disable_vblank()
operation will now be called for the first time with the CRTC disabled
and the DISPC runtime suspended. The dispc_runtime_get() call in
.disable_vblank() is supposed to take care of that, but the operation is
called with a spinlock held, which prevents it from sleeping.

To fix that move DISPC runtime PM handling out of the vblank operations
to the CRTC code, ensuring that the display controller will always be
powered when enabling or disabling vblank interrupts.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
drivers/gpu/drm/omapdrm/omap_crtc.c
drivers/gpu/drm/omapdrm/omap_drv.c
drivers/gpu/drm/omapdrm/omap_irq.c

index c086f72..1076bc0 100644 (file)
@@ -76,6 +76,7 @@ struct omap_crtc {
         */
        enum omap_page_flip_state flip_state;
        struct drm_pending_vblank_event *flip_event;
+       wait_queue_head_t flip_wait;
        struct work_struct flip_work;
 
        struct completion completion;
@@ -309,6 +310,61 @@ static void omap_crtc_complete_page_flip(struct drm_crtc *crtc,
        }
 
        omap_crtc->flip_state = state;
+
+       if (state == OMAP_PAGE_FLIP_IDLE)
+               wake_up(&omap_crtc->flip_wait);
+}
+
+static bool omap_crtc_page_flip_pending(struct drm_crtc *crtc)
+{
+       struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
+       struct drm_device *dev = crtc->dev;
+       unsigned long flags;
+       bool pending;
+
+       spin_lock_irqsave(&dev->event_lock, flags);
+       pending = omap_crtc->flip_state != OMAP_PAGE_FLIP_IDLE;
+       spin_unlock_irqrestore(&dev->event_lock, flags);
+
+       return pending;
+}
+
+static void omap_crtc_wait_page_flip(struct drm_crtc *crtc)
+{
+       struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
+       struct drm_device *dev = crtc->dev;
+       bool cancelled = false;
+       unsigned long flags;
+
+       /*
+        * If we're still waiting for the GEM async operation to complete just
+        * cancel the page flip, as we're holding the CRTC mutex preventing the
+        * page flip work handler from queueing the page flip.
+        *
+        * We can't release the reference to the frame buffer here as the async
+        * operation doesn't keep its own reference to the buffer. We'll just
+        * let the page flip work queue handle that.
+        */
+       spin_lock_irqsave(&dev->event_lock, flags);
+       if (omap_crtc->flip_state == OMAP_PAGE_FLIP_WAIT) {
+               omap_crtc_complete_page_flip(crtc, OMAP_PAGE_FLIP_CANCELLED);
+               cancelled = true;
+       }
+       spin_unlock_irqrestore(&dev->event_lock, flags);
+
+       if (cancelled)
+               return;
+
+       if (wait_event_timeout(omap_crtc->flip_wait,
+                              !omap_crtc_page_flip_pending(crtc),
+                              msecs_to_jiffies(50)))
+               return;
+
+       dev_warn(crtc->dev->dev, "page flip timeout!\n");
+
+       spin_lock_irqsave(&dev->event_lock, flags);
+       omap_crtc_complete_page_flip(crtc, OMAP_PAGE_FLIP_IDLE);
+       spin_unlock_irqrestore(&dev->event_lock, flags);
 }
 
 static void omap_crtc_error_irq(struct omap_drm_irq *irq, uint32_t irqstatus)
@@ -455,26 +511,39 @@ static void omap_crtc_dpms(struct drm_crtc *crtc, int mode)
 {
        struct omap_drm_private *priv = crtc->dev->dev_private;
        struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
-       bool enabled = (mode == DRM_MODE_DPMS_ON);
+       bool enable = (mode == DRM_MODE_DPMS_ON);
        int i;
 
        DBG("%s: %d", omap_crtc->name, mode);
 
-       if (enabled == omap_crtc->enabled)
+       if (enable == omap_crtc->enabled)
                return;
 
+       if (!enable) {
+               omap_crtc_wait_page_flip(crtc);
+               dispc_runtime_get();
+               drm_crtc_vblank_off(crtc);
+               dispc_runtime_put();
+       }
+
        /* Enable/disable all planes associated with the CRTC. */
        for (i = 0; i < priv->num_planes; i++) {
                struct drm_plane *plane = priv->planes[i];
 
                if (plane->crtc == crtc)
-                       WARN_ON(omap_plane_set_enable(plane, enabled));
+                       WARN_ON(omap_plane_set_enable(plane, enable));
        }
 
-       omap_crtc->enabled = enabled;
+       omap_crtc->enabled = enable;
 
        omap_crtc_setup(crtc);
        omap_crtc_flush(crtc);
+
+       if (enable) {
+               dispc_runtime_get();
+               drm_crtc_vblank_on(crtc);
+               dispc_runtime_put();
+       }
 }
 
 static bool omap_crtc_mode_fixup(struct drm_crtc *crtc,
@@ -709,6 +778,7 @@ struct drm_crtc *omap_crtc_init(struct drm_device *dev,
        crtc = &omap_crtc->base;
 
        INIT_WORK(&omap_crtc->flip_work, page_flip_worker);
+       init_waitqueue_head(&omap_crtc->flip_wait);
 
        INIT_LIST_HEAD(&omap_crtc->pending_unpins);
 
index cf1b37e..e81fbc0 100644 (file)
@@ -502,6 +502,7 @@ static int dev_load(struct drm_device *dev, unsigned long flags)
 {
        struct omap_drm_platform_data *pdata = dev->dev->platform_data;
        struct omap_drm_private *priv;
+       unsigned int i;
        int ret;
 
        DBG("load: dev=%p", dev);
@@ -529,10 +530,14 @@ static int dev_load(struct drm_device *dev, unsigned long flags)
                return ret;
        }
 
+       /* Initialize vblank handling, start with all CRTCs disabled. */
        ret = drm_vblank_init(dev, priv->num_crtcs);
        if (ret)
                dev_warn(dev->dev, "could not init vblank\n");
 
+       for (i = 0; i < priv->num_crtcs; i++)
+               drm_crtc_vblank_off(priv->crtcs[i]);
+
        priv->fbdev = omap_fbdev_init(dev);
        if (!priv->fbdev) {
                dev_warn(dev->dev, "omap_fbdev_init failed\n");
index 803aff0..249c033 100644 (file)
@@ -152,12 +152,10 @@ int omap_irq_enable_vblank(struct drm_device *dev, int crtc_id)
 
        DBG("dev=%p, crtc=%d", dev, crtc_id);
 
-       dispc_runtime_get();
        spin_lock_irqsave(&list_lock, flags);
        priv->vblank_mask |= pipe2vbl(crtc);
        omap_irq_update(dev);
        spin_unlock_irqrestore(&list_lock, flags);
-       dispc_runtime_put();
 
        return 0;
 }
@@ -179,12 +177,10 @@ void omap_irq_disable_vblank(struct drm_device *dev, int crtc_id)
 
        DBG("dev=%p, crtc=%d", dev, crtc_id);
 
-       dispc_runtime_get();
        spin_lock_irqsave(&list_lock, flags);
        priv->vblank_mask &= ~pipe2vbl(crtc);
        omap_irq_update(dev);
        spin_unlock_irqrestore(&list_lock, flags);
-       dispc_runtime_put();
 }
 
 static irqreturn_t omap_irq_handler(int irq, void *arg)