drm/i915/lspcon: Add workaround for resuming in PCON mode
authorImre Deak <imre.deak@intel.com>
Mon, 24 Oct 2016 16:33:31 +0000 (19:33 +0300)
committerImre Deak <imre.deak@intel.com>
Wed, 26 Oct 2016 09:41:01 +0000 (12:41 +0300)
On my APL the LSPCON firmware resumes in PCON mode as opposed to the
expected LS mode. It also appears to be in a state where AUX DPCD reads
will succeed but return garbage recovering only after a few hundreds of
milliseconds. After the recovery time DPCD reads will result in the
correct values and things will continue to work. If I2C over AUX is
attempted during this recovery time (implying an AUX write transaction)
the firmware won't recover and will stay in this broken state.

As a workaround check if the firmware is in PCON state after resume and
if so wait until the correct DPCD values are returned. For this we
compare the branch descriptor with the one we cached during init time.
If the firmware was in the LS state, we skip the w/a and continue as
before.

v2:
- Use the DP descriptor value cached in intel_dp. (Jani)
- Get to intel_dp using container_of(), instead of a cached ptr.
  (Shashank)
- Use usleep_range() instead of msleep().

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=98353
Cc: Shashank Sharma <shashank.sharma@intel.com>
Cc: Ville Syrjälä <ville.syrjala@linux.intel.com>
Cc: Jani Nikula <jani.nikula@intel.com>
Signed-off-by: Imre Deak <imre.deak@intel.com>
Reviewed-by: Jani Nikula <jani.nikula@intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/1477326811-30431-9-git-send-email-imre.deak@intel.com
drivers/gpu/drm/i915/intel_dp.c
drivers/gpu/drm/i915/intel_drv.h
drivers/gpu/drm/i915/intel_lspcon.c

index 6eb43647163cf052fc35c7e35e91c892d06ed24e..8f313c1d374d7f185aa4344dc870645eafa75239 100644 (file)
@@ -1451,7 +1451,7 @@ static void intel_dp_print_rates(struct intel_dp *intel_dp)
        DRM_DEBUG_KMS("common rates: %s\n", str);
 }
 
-static bool
+bool
 __intel_dp_read_desc(struct intel_dp *intel_dp, struct intel_dp_desc *desc)
 {
        u32 base = drm_dp_is_branch(intel_dp->dpcd) ? DP_BRANCH_OUI :
index 5989c48d79644dc76181acd6334014386b960060..c2f38634e86ea01fc7c1edf5d8d5cf8caa06e709 100644 (file)
@@ -974,6 +974,7 @@ struct intel_dp {
 struct intel_lspcon {
        bool active;
        enum drm_lspcon_mode mode;
+       bool desc_valid;
 };
 
 struct intel_digital_port {
@@ -1467,6 +1468,8 @@ static inline unsigned int intel_dp_unused_lane_mask(int lane_count)
 }
 
 bool intel_dp_read_dpcd(struct intel_dp *intel_dp);
+bool __intel_dp_read_desc(struct intel_dp *intel_dp,
+                         struct intel_dp_desc *desc);
 bool intel_dp_read_desc(struct intel_dp *intel_dp);
 
 /* intel_dp_aux_backlight.c */
index 3dc5a0be7857c5c96a78c01cd34b11d2a77f0331..daa5234109533319dc285cfbc1fb5325a7070567 100644 (file)
@@ -97,8 +97,43 @@ static bool lspcon_probe(struct intel_lspcon *lspcon)
        return true;
 }
 
+static void lspcon_resume_in_pcon_wa(struct intel_lspcon *lspcon)
+{
+       struct intel_dp *intel_dp = lspcon_to_intel_dp(lspcon);
+       unsigned long start = jiffies;
+
+       if (!lspcon->desc_valid)
+               return;
+
+       while (1) {
+               struct intel_dp_desc desc;
+
+               /*
+                * The w/a only applies in PCON mode and we don't expect any
+                * AUX errors.
+                */
+               if (!__intel_dp_read_desc(intel_dp, &desc))
+                       return;
+
+               if (!memcmp(&intel_dp->desc, &desc, sizeof(desc))) {
+                       DRM_DEBUG_KMS("LSPCON recovering in PCON mode after %u ms\n",
+                                     jiffies_to_msecs(jiffies - start));
+                       return;
+               }
+
+               if (time_after(jiffies, start + msecs_to_jiffies(1000)))
+                       break;
+
+               usleep_range(10000, 15000);
+       }
+
+       DRM_DEBUG_KMS("LSPCON DP descriptor mismatch after resume\n");
+}
+
 void lspcon_resume(struct intel_lspcon *lspcon)
 {
+       lspcon_resume_in_pcon_wa(lspcon);
+
        if (lspcon_change_mode(lspcon, DRM_LSPCON_MODE_PCON, true))
                DRM_ERROR("LSPCON resume failed\n");
        else
@@ -143,7 +178,7 @@ bool lspcon_init(struct intel_digital_port *intel_dig_port)
                return false;
        }
 
-       intel_dp_read_desc(dp);
+       lspcon->desc_valid = intel_dp_read_desc(dp);
 
        DRM_DEBUG_KMS("Success: LSPCON init\n");
        return true;