[PATCH 3.16 016/192] drm/i915: Workaround VLV/CHV DSI scanline counter hardware fail

Ben Hutchings
2017-10-09T13:16:36Z

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

commit ec1b4ee2834e66884e5b0d3d465f347ff212e372 upstream.

The scanline counter is bonkers on VLV/CHV DSI. The scanline counter
increment is not lined up with the start of vblank like it is on
every other platform and output type. This causes problems for
both the vblank timestamping and atomic update vblank evasion.

On my FFRD8 machine at least, the scanline counter increment
happens about 1/3 of a scanline ahead of the start of vblank (which
is where all register latching happens still). That means we can't
trust the scanline counter to tell us whether we're in vblank or not
while we're on that particular line. In order to keep vblank
timestamping in working condition when called from the vblank irq,
we'll leave scanline_offset at one, which means that the entire
line containing the start of vblank is considered to be inside
the vblank.

For the vblank evasion we'll need to consider that entire line
to be bad, since we can't tell whether the registers already
got latched or not. And we can't actually use the start of vblank
interrupt to get us past that line as the interrupt would fire
too soon, and then we'd up waiting for the next start of vblank
instead. One way around that would using the frame start
interrupt instead since that wouldn't fire until the next
scanline, but that would require some bigger changes in the
interrupt code. So for simplicity we'll just poll until we get
past the bad line.

v2: Adjust the comments a bit

Cc: Jonas Aaberg <cja@gmx.net>
Tested-by: Jonas Aaberg <cja@gmx.net>
Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=99086
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/20161215174734.28779-1-ville.syrjala@linux.intel.com
Tested-by: Mika Kahola <mika.kahola@intel.com>
Reviewed-by: Mika Kahola <mika.kahola@intel.com>
[bwh: Backported to 3.16:
- Pass dev instead of dev_priv to hardware type predicates
- Use intel_pipe_has_type() to check output type]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
---
drivers/gpu/drm/i915/intel_display.c | 9 +++++++++
drivers/gpu/drm/i915/intel_sprite.c | 21 +++++++++++++++++++++
2 files changed, 30 insertions(+)

--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -369,7 +369,7 @@ static void vlv_clock(int refclk, intel_
/**
* Returns whether any output on the specified pipe is of the specified type
*/
-static bool intel_pipe_has_type(struct drm_crtc *crtc, int type)
+bool intel_pipe_has_type(struct drm_crtc *crtc, int type)
{
struct drm_device *dev = crtc->dev;
struct intel_encoder *encoder;
@@ -10313,6 +10313,15 @@ static void update_scanline_offset(struc
* type. For DP ports it behaves like most other platforms, but on HDMI
* there's an extra 1 line difference. So we need to add two instead of
* one to the value.
+ *
+ * On VLV/CHV DSI the scanline counter would appear to increment
+ * approx. 1/3 of a scanline before start of vblank. Unfortunately
+ * that means we can't tell whether we're in vblank or not while
+ * we're on that particular line. We must still set scanline_offset
+ * to 1 so that the vblank timestamps come out correct when we query
+ * the scanline counter from within the vblank interrupt handler.
+ * However if queried just before the start of vblank we'll get an
+ * answer that's slightly in the future.
*/
if (IS_GEN2(dev)) {
const struct drm_display_mode *mode = &crtc->config.adjusted_mode;
--- a/drivers/gpu/drm/i915/intel_sprite.c
+++ b/drivers/gpu/drm/i915/intel_sprite.c
@@ -53,6 +53,8 @@ static bool intel_pipe_update_start(stru
enum pipe pipe = crtc->pipe;
long timeout = msecs_to_jiffies_timeout(1);
int scanline, min, max, vblank_start;
+ bool need_vlv_dsi_wa = (IS_VALLEYVIEW(dev) || IS_CHERRYVIEW(dev)) &&
+ intel_pipe_has_type(&crtc->base, INTEL_OUTPUT_DSI);
DEFINE_WAIT(wait);

WARN_ON(!drm_modeset_is_locked(&crtc->base.mutex));
@@ -104,6 +106,24 @@ static bool intel_pipe_update_start(stru

drm_vblank_put(dev, pipe);

+ /*
+ * On VLV/CHV DSI the scanline counter would appear to
+ * increment approx. 1/3 of a scanline before start of vblank.
+ * The registers still get latched at start of vblank however.
+ * This means we must not write any registers on the first
+ * line of vblank (since not the whole line is actually in
+ * vblank). And unfortunately we can't use the interrupt to
+ * wait here since it will fire too soon. We could use the
+ * frame start interrupt instead since it will fire after the
+ * critical scanline, but that would require more changes
+ * in the interrupt code. So for now we'll just do the nasty
+ * thing and poll for the bad scanline to pass us by.
+ *
+ * FIXME figure out if BXT+ DSI suffers from this as well
+ */
+ while (need_vlv_dsi_wa && scanline == vblank_start)
+ scanline = intel_get_crtc_scanline(crtc);
+
*start_vbl_count = dev->driver->get_vblank_counter(dev, pipe);

trace_i915_pipe_update_vblank_evaded(crtc, min, max, *start_vbl_count);
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -721,6 +721,7 @@ void intel_ddi_get_config(struct intel_e
const char *intel_output_name(int output);
bool intel_has_pending_fb_unpin(struct drm_device *dev);
int intel_pch_rawclk(struct drm_device *dev);
+bool intel_pipe_has_type(struct drm_crtc *crtc, int type);
int valleyview_cur_cdclk(struct drm_i915_private *dev_priv);
void intel_mark_busy(struct drm_device *dev);
void intel_mark_fb_busy(struct drm_i915_gem_object *obj,