[PATCH] drm/i915: Synchronize userspace palette LUT (i.e. gamma) changes to vblank

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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



[Index of Archives]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]
  Powered by Linux