Adam Jackson was watching the screensaver fade out and expressed a
desire for the gamma updates to be synchronized to vblank to avoid the
unsightly tears.

Reported-by: Adam Jackson <[email protected]>
Cc: Adam Jackson <[email protected]>
Signed-off-by: Chris Wilson <[email protected]>
---
 drivers/gpu/drm/i915/intel_display.c |  108 +++++++++++++++++++++++++++++-----
 drivers/gpu/drm/i915/intel_drv.h     |    9 +++
 2 files changed, 102 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/i915/intel_display.c 
b/drivers/gpu/drm/i915/intel_display.c
index 66b19d3..6c2101e 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -47,6 +47,7 @@ bool intel_pipe_has_type(struct drm_crtc *crtc, int type);
 static void intel_update_watermarks(struct drm_device *dev);
 static void intel_increase_pllclock(struct drm_crtc *crtc);
 static void intel_crtc_update_cursor(struct drm_crtc *crtc, bool on);
+static void __intel_crtc_load_lut(struct intel_crtc *crtc);
 
 typedef struct {
        /* given values */
@@ -3094,7 +3095,7 @@ static void ironlake_crtc_enable(struct drm_crtc *crtc)
         * On ILK+ LUT must be loaded before the pipe is running but with
         * clocks enabled
         */
-       intel_crtc_load_lut(crtc);
+       __intel_crtc_load_lut(to_intel_crtc(crtc));
 
        intel_enable_pipe(dev_priv, pipe, is_pch_port);
        intel_enable_plane(dev_priv, plane, pipe);
@@ -6258,29 +6259,100 @@ void intel_write_eld(struct drm_encoder *encoder,
                dev_priv->display.write_eld(connector, crtc);
 }
 
-/** Loads the palette/gamma unit for the CRTC with the prepared values */
-void intel_crtc_load_lut(struct drm_crtc *crtc)
+static void __intel_crtc_load_lut(struct intel_crtc *crtc)
 {
-       struct drm_device *dev = crtc->dev;
-       struct drm_i915_private *dev_priv = dev->dev_private;
-       struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
-       int palreg = PALETTE(intel_crtc->pipe);
+       struct drm_i915_private *dev_priv = crtc->base.dev->dev_private;
+       int reg;
        int i;
 
-       /* The clocks have to be on to load the palette. */
-       if (!crtc->enabled || !intel_crtc->active)
+       if (!crtc->base.enabled || !crtc->active)
                return;
 
        /* use legacy palette for Ironlake */
-       if (HAS_PCH_SPLIT(dev))
-               palreg = LGC_PALETTE(intel_crtc->pipe);
+       reg = PALETTE(crtc->pipe);
+       if (HAS_PCH_SPLIT(crtc->base.dev))
+               reg = LGC_PALETTE(crtc->pipe);
 
        for (i = 0; i < 256; i++) {
-               I915_WRITE(palreg + 4 * i,
-                          (intel_crtc->lut_r[i] << 16) |
-                          (intel_crtc->lut_g[i] << 8) |
-                          intel_crtc->lut_b[i]);
+               I915_WRITE(reg + 4 * i,
+                          crtc->lut_r[i] << 16 |
+                          crtc->lut_g[i] << 8  |
+                          crtc->lut_b[i]);
+       }
+}
+
+struct intel_crtc_vblank_task {
+       struct list_head list;
+       void (*func)(struct intel_crtc *);
+};
+
+static void intel_crtc_vblank_work_fn(struct work_struct *_work)
+{
+       struct intel_crtc_vblank_work *work =
+               container_of(_work, struct intel_crtc_vblank_work, work);
+       struct intel_crtc *crtc = work->crtc;
+
+       intel_wait_for_vblank(crtc->base.dev, crtc->pipe);
+
+       mutex_lock(&crtc->vblank_mutex);
+       while (!list_empty(&work->tasks)) {
+               struct intel_crtc_vblank_task *task
+                       = list_first_entry(&work->tasks, struct 
intel_crtc_vblank_task, list);
+
+               task->func(crtc);
+               list_del(&task->list);
+               kfree(task);
        }
+       crtc->vblank_work = NULL;
+       mutex_unlock(&crtc->vblank_mutex);
+
+       kfree(work);
+}
+
+static int intel_crtc_add_vblank_task(struct intel_crtc *crtc,
+                                     void (*func)(struct intel_crtc *))
+{
+       struct intel_crtc_vblank_task *task;
+       struct intel_crtc_vblank_work *work;
+
+       task = kzalloc(sizeof *task, GFP_KERNEL);
+       if (task == NULL)
+               return -ENOMEM;
+       task->func = func;
+
+       mutex_lock(&crtc->vblank_mutex);
+       work = crtc->vblank_work;
+       if (work == NULL) {
+               work = kzalloc(sizeof *work, GFP_KERNEL);
+               if (work == NULL) {
+                       mutex_unlock(&crtc->vblank_mutex);
+                       kfree(task);
+                       return -ENOMEM;
+               }
+
+               work->crtc = crtc;
+               INIT_LIST_HEAD(&work->tasks);
+               INIT_WORK(&work->work, intel_crtc_vblank_work_fn);
+               schedule_work(&work->work);
+               crtc->vblank_work = work;
+       }
+       list_add(&task->list, &work->tasks);
+       mutex_unlock(&crtc->vblank_mutex);
+
+       return 0;
+}
+
+/** Loads the palette/gamma unit for the CRTC with the prepared values */
+void intel_crtc_load_lut(struct drm_crtc *crtc)
+{
+       struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
+
+       /* The clocks have to be on to load the palette. */
+       if (!crtc->enabled || !intel_crtc->active)
+               return;
+
+       if (intel_crtc_add_vblank_task(intel_crtc, __intel_crtc_load_lut))
+               __intel_crtc_load_lut(intel_crtc);
 }
 
 static void i845_update_cursor(struct drm_crtc *crtc, u32 base)
@@ -6570,6 +6642,7 @@ static void intel_crtc_gamma_set(struct drm_crtc *crtc, 
u16 *red, u16 *green,
        int end = (start + size > 256) ? 256 : start + size, i;
        struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
 
+       /* We race here with setting the lut and reading it during vblank. */
        for (i = start; i < end; i++) {
                intel_crtc->lut_r[i] = red[i] >> 8;
                intel_crtc->lut_g[i] = green[i] >> 8;
@@ -7647,6 +7720,8 @@ static void intel_crtc_init(struct drm_device *dev, int 
pipe)
        if (intel_crtc == NULL)
                return;
 
+       mutex_init(&intel_crtc->vblank_mutex);
+
        drm_crtc_init(dev, &intel_crtc->base, &intel_crtc_funcs);
 
        drm_mode_crtc_set_gamma_size(&intel_crtc->base, 256);
@@ -9161,6 +9236,9 @@ void intel_modeset_cleanup(struct drm_device *dev)
        struct drm_crtc *crtc;
        struct intel_crtc *intel_crtc;
 
+       /* Clear the vblank worker prior to taking any locks */
+       flush_scheduled_work();
+
        drm_kms_helper_poll_fini(dev);
        mutex_lock(&dev->struct_mutex);
 
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 9cec6c3..1e3d8a9 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -160,6 +160,9 @@ struct intel_crtc {
        struct intel_unpin_work *unpin_work;
        int fdi_lanes;
 
+       struct mutex vblank_mutex; /* protects *vblank_work */
+       struct intel_crtc_vblank_work *vblank_work;
+
        struct drm_i915_gem_object *cursor_bo;
        uint32_t cursor_addr;
        int16_t cursor_x, cursor_y;
@@ -267,6 +270,12 @@ intel_get_crtc_for_plane(struct drm_device *dev, int plane)
        return dev_priv->plane_to_crtc_mapping[plane];
 }
 
+struct intel_crtc_vblank_work {
+       struct work_struct work;
+       struct intel_crtc *crtc;
+       struct list_head tasks;
+};
+
 struct intel_unpin_work {
        struct work_struct work;
        struct drm_device *dev;
-- 
1.7.9.1

_______________________________________________
Intel-gfx mailing list
[email protected]
http://lists.freedesktop.org/mailman/listinfo/intel-gfx

Reply via email to