[PATCH 15/24] drm/i915: Add vblank based delayed watermark update mechanism

ville.syrjala
2014-03-07T16:33:43Z

From: Ville Syrjälä <ville.syrjala@linux.intel.com>

Add a mechanism by which you can queue up watermark update to happen
after the vblank counter has reached a certain value. The vblank
interrupt handler will schedule a work which will do the actual
watermark programming in process context.

Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
---
drivers/gpu/drm/i915/i915_drv.h | 2 +
drivers/gpu/drm/i915/i915_irq.c | 12 ++-
drivers/gpu/drm/i915/intel_display.c | 1 +
drivers/gpu/drm/i915/intel_drv.h | 27 +++++++
drivers/gpu/drm/i915/intel_pm.c | 144 +++++++++++++++++++++++++++++++++++
5 files changed, 183 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index 0b19723..f79f1b4 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -1606,6 +1606,8 @@ typedef struct drm_i915_private {
* state as well as the actual hardware registers
*/
struct mutex mutex;
+
+ struct work_struct work;
} wm;

struct i915_package_c8 pc8;
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c
index bd79224..052da8c 100644
--- a/drivers/gpu/drm/i915/i915_irq.c
+++ b/drivers/gpu/drm/i915/i915_irq.c
@@ -1909,8 +1909,10 @@ static void ilk_display_irq_handler(struct drm_device *dev, u32 de_iir)
DRM_ERROR("Poison interrupt\n");

for_each_pipe(pipe) {
- if (de_iir & DE_PIPE_VBLANK(pipe))
+ if (de_iir & DE_PIPE_VBLANK(pipe)) {
intel_pipe_handle_vblank(dev, pipe);
+ ilk_update_pipe_wm(dev, pipe);
+ }

if (de_iir & DE_PIPE_FIFO_UNDERRUN(pipe))
if (intel_set_cpu_fifo_underrun_reporting(dev, pipe, false))
@@ -1959,8 +1961,10 @@ static void ivb_display_irq_handler(struct drm_device *dev, u32 de_iir)
intel_opregion_asle_intr(dev);

for_each_pipe(pipe) {
- if (de_iir & (DE_PIPE_VBLANK_IVB(pipe)))
+ if (de_iir & (DE_PIPE_VBLANK_IVB(pipe))) {
intel_pipe_handle_vblank(dev, pipe);
+ ilk_update_pipe_wm(dev, pipe);
+ }

/* plane/pipes map 1:1 on ilk+ */
if (de_iir & DE_PLANE_FLIP_DONE_IVB(pipe)) {
@@ -2102,8 +2106,10 @@ static irqreturn_t gen8_irq_handler(int irq, void *arg)
continue;

pipe_iir = I915_READ(GEN8_DE_PIPE_IIR(pipe));
- if (pipe_iir & GEN8_PIPE_VBLANK)
+ if (pipe_iir & GEN8_PIPE_VBLANK) {
intel_pipe_handle_vblank(dev, pipe);
+ ilk_update_pipe_wm(dev, pipe);
+ }

if (pipe_iir & GEN8_PIPE_FLIP_DONE) {
intel_prepare_page_flip(dev, pipe);
diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index c028b5c..aa9acc5 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -10283,6 +10283,7 @@ static void intel_crtc_init(struct drm_device *dev, int pipe)
intel_crtc->plane = !pipe;
}

+ spin_lock_init(&intel_crtc->wm.lock);
init_waitqueue_head(&intel_crtc->vbl_wait);

BUG_ON(pipe >= ARRAY_SIZE(dev_priv->plane_to_crtc_mapping) ||
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 8e32d69..c9d2603 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -389,6 +389,32 @@ struct intel_crtc {
* protected by dev_priv->wm.mutex
*/
struct intel_pipe_wm active;
+ /*
+ * watermarks queued for next vblank
+ * protected by dev_priv->wm.mutex
+ */
+ struct intel_pipe_wm pending;
+
+ /*
+ * the vblank count after which we can switch over to 'pending'
+ * protected by intel_crtc->wm.lock
+ */
+ u32 pending_vbl_count;
+ /*
+ * indicates that 'pending' contains changed watermarks
+ * protected by intel_crtc->wm.lock
+ */
+ bool dirty;
+ /*
+ * watermark update has a vblank reference?
+ * protected by intel_crtc->wm.lock
+ */
+ bool vblank;
+
+ /*
+ * protects some intel_crtc->wm state
+ */
+ spinlock_t lock;
} wm;

wait_queue_head_t vbl_wait;
@@ -904,6 +930,7 @@ void intel_runtime_pm_put(struct drm_i915_private *dev_priv);
void intel_init_runtime_pm(struct drm_i915_private *dev_priv);
void intel_fini_runtime_pm(struct drm_i915_private *dev_priv);
void ilk_wm_get_hw_state(struct drm_device *dev);
+void ilk_update_pipe_wm(struct drm_device *dev, enum pipe pipe);

/* intel_sdvo.c */
diff --git a/drivers/gpu/drm/i915/intel_pm.c b/drivers/gpu/drm/i915/intel_pm.c
index d8adcb3..de8fb65 100644
--- a/drivers/gpu/drm/i915/intel_pm.c
+++ b/drivers/gpu/drm/i915/intel_pm.c
@@ -2701,6 +2701,65 @@ static bool ilk_disable_lp_wm(struct drm_device *dev)
return changed;
}

+static bool vbl_count_after_eq(struct drm_device *dev, u32 a, u32 b)
+{
+ u32 mask = dev->max_vblank_count;
+
+ /* just the msb please */
+ mask &= ~(mask >> 1);
+
+ return !((a - b) & mask);
+}
+
+static bool ilk_pending_watermarks_ready(struct intel_crtc *intel_crtc)
+{
+ struct drm_device *dev = intel_crtc->base.dev;
+ u32 vbl_count;
+
+ assert_spin_locked(&intel_crtc->wm.lock);
+
+ if (!intel_crtc->wm.dirty)
+ return false;
+
+ vbl_count = dev->driver->get_vblank_counter(dev, intel_crtc->pipe);
+
+ if (!vbl_count_after_eq(dev, vbl_count, intel_crtc->wm.pending_vbl_count))
+ return false;
+
+ if (intel_crtc->wm.vblank) {
+ drm_vblank_put(dev, intel_crtc->pipe);
+ intel_crtc->wm.vblank = false;
+ }
+
+ return true;
+}
+
+static bool ilk_refresh_pending_watermarks(struct drm_device *dev)
+{
+ struct intel_crtc *intel_crtc;
+ bool changed = false;
+
+ list_for_each_entry(intel_crtc, &dev->mode_config.crtc_list, base.head) {
+ bool ready;
+
+ spin_lock_irq(&intel_crtc->wm.lock);
+
+ ready = ilk_pending_watermarks_ready(intel_crtc);
+ if (ready)
+ intel_crtc->wm.dirty = false;
+
+ spin_unlock_irq(&intel_crtc->wm.lock);
+
+ if (!ready)
+ continue;
+
+ intel_crtc->wm.active = intel_crtc->wm.pending;
+ changed = true;
+ }
+
+ return changed;
+}
+
static void ilk_program_watermarks(struct drm_device *dev)
{
struct drm_i915_private *dev_priv = dev->dev_private;
@@ -2759,6 +2818,87 @@ static void ilk_update_wm(struct drm_crtc *crtc)
mutex_unlock(&dev_priv->wm.mutex);
}

+static void ilk_update_watermarks(struct drm_device *dev)
+{
+ bool changed;
+
+ changed = ilk_refresh_pending_watermarks(dev);
+
+ if (changed)
+ ilk_program_watermarks(dev);
+}
+
+static void ilk_setup_pending_watermarks(struct intel_crtc *intel_crtc,
+ const struct intel_pipe_wm *pipe_wm,
+ u32 vbl_count)
+{
+ struct drm_device *dev = intel_crtc->base.dev;
+ enum pipe pipe = intel_crtc->pipe;
+
+ WARN(!intel_crtc->active, "pipe %c should be enabled\n",
+ pipe_name(pipe));
+
+ /* do the watermarks actually need changing? */
+ if (!memcmp(&intel_crtc->wm.pending, pipe_wm, sizeof(*pipe_wm)))
+ return;
+
+ intel_crtc->wm.pending = *pipe_wm;
+
+ spin_lock_irq(&intel_crtc->wm.lock);
+ intel_crtc->wm.pending_vbl_count = (vbl_count + 1) & dev->max_vblank_count;
+ intel_crtc->wm.dirty = true;
+ spin_unlock_irq(&intel_crtc->wm.lock);
+
+ /* try to update immediately */
+ ilk_update_watermarks(dev);
+
+ spin_lock_irq(&intel_crtc->wm.lock);
+
+ /* did the immediate update succeed? */
+ if (!intel_crtc->wm.dirty)
+ goto unlock;
+
+ /*
+ * We might already have a pending watermark update, in
+ * which case we shouldn't grab another vblank reference.
+ */
+ if (!intel_crtc->wm.vblank && drm_vblank_get(dev, pipe) == 0)
+ intel_crtc->wm.vblank = true;
+
+ WARN(!intel_crtc->wm.vblank,
+ "unable to set up watermarks for pipe %c\n", pipe_name(pipe));
+
+ unlock:
+ spin_unlock_irq(&intel_crtc->wm.lock);
+}
+
+static void ilk_watermark_work(struct work_struct *work)
+{
+ struct drm_i915_private *dev_priv =
+ container_of(work, struct drm_i915_private, wm.work);
+
+ mutex_lock(&dev_priv->wm.mutex);
+
+ ilk_update_watermarks(dev_priv->dev);
+
+ mutex_unlock(&dev_priv->wm.mutex);
+}
+
+/* Called from vblank irq */
+void ilk_update_pipe_wm(struct drm_device *dev, enum pipe pipe)
+{
+ struct drm_i915_private *dev_priv = dev->dev_private;
+ struct intel_crtc *intel_crtc =
+ to_intel_crtc(dev_priv->pipe_to_crtc_mapping[pipe]);
+
+ spin_lock(&intel_crtc->wm.lock);
+
+ if (ilk_pending_watermarks_ready(intel_crtc))
+ schedule_work(&dev_priv->wm.work);
+
+ spin_unlock(&intel_crtc->wm.lock);
+}
+
static void ilk_update_sprite_wm(struct drm_plane *plane,
struct drm_crtc *crtc,
uint32_t sprite_width, int pixel_size,
@@ -2821,6 +2961,9 @@ static void _ilk_pipe_wm_hw_to_sw(struct drm_crtc *crtc)
for (level = 0; level <= max_level; level++)
active->wm[level].enable = true;
}
+
+ /* no update pending */
+ intel_crtc->wm.pending = intel_crtc->wm.active;
}

void ilk_wm_get_hw_state(struct drm_device *dev)
@@ -5776,6 +5919,7 @@ void intel_init_pm(struct drm_device *dev)
struct drm_i915_private *dev_priv = dev->dev_private;

mutex_init(&dev_priv->wm.mutex);
+ INIT_WORK(&dev_priv->wm.work, ilk_watermark_work);

if (HAS_FBC(dev)) {
if (INTEL_INFO(dev)->gen >= 7) {
--
1.8.3.2