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 <ajax at redhat.com> Cc: Adam Jackson <ajax at redhat.com> Signed-off-by: Chris Wilson <chris at chris-wilson.co.uk> --- 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