Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/i915/lspcon: Add workaround for resuming in PCON mode

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

+40 -2
+1 -1
drivers/gpu/drm/i915/intel_dp.c
··· 1451 1451 DRM_DEBUG_KMS("common rates: %s\n", str); 1452 1452 } 1453 1453 1454 - static bool 1454 + bool 1455 1455 __intel_dp_read_desc(struct intel_dp *intel_dp, struct intel_dp_desc *desc) 1456 1456 { 1457 1457 u32 base = drm_dp_is_branch(intel_dp->dpcd) ? DP_BRANCH_OUI :
+3
drivers/gpu/drm/i915/intel_drv.h
··· 974 974 struct intel_lspcon { 975 975 bool active; 976 976 enum drm_lspcon_mode mode; 977 + bool desc_valid; 977 978 }; 978 979 979 980 struct intel_digital_port { ··· 1468 1467 } 1469 1468 1470 1469 bool intel_dp_read_dpcd(struct intel_dp *intel_dp); 1470 + bool __intel_dp_read_desc(struct intel_dp *intel_dp, 1471 + struct intel_dp_desc *desc); 1471 1472 bool intel_dp_read_desc(struct intel_dp *intel_dp); 1472 1473 1473 1474 /* intel_dp_aux_backlight.c */
+36 -1
drivers/gpu/drm/i915/intel_lspcon.c
··· 97 97 return true; 98 98 } 99 99 100 + static void lspcon_resume_in_pcon_wa(struct intel_lspcon *lspcon) 101 + { 102 + struct intel_dp *intel_dp = lspcon_to_intel_dp(lspcon); 103 + unsigned long start = jiffies; 104 + 105 + if (!lspcon->desc_valid) 106 + return; 107 + 108 + while (1) { 109 + struct intel_dp_desc desc; 110 + 111 + /* 112 + * The w/a only applies in PCON mode and we don't expect any 113 + * AUX errors. 114 + */ 115 + if (!__intel_dp_read_desc(intel_dp, &desc)) 116 + return; 117 + 118 + if (!memcmp(&intel_dp->desc, &desc, sizeof(desc))) { 119 + DRM_DEBUG_KMS("LSPCON recovering in PCON mode after %u ms\n", 120 + jiffies_to_msecs(jiffies - start)); 121 + return; 122 + } 123 + 124 + if (time_after(jiffies, start + msecs_to_jiffies(1000))) 125 + break; 126 + 127 + usleep_range(10000, 15000); 128 + } 129 + 130 + DRM_DEBUG_KMS("LSPCON DP descriptor mismatch after resume\n"); 131 + } 132 + 100 133 void lspcon_resume(struct intel_lspcon *lspcon) 101 134 { 135 + lspcon_resume_in_pcon_wa(lspcon); 136 + 102 137 if (lspcon_change_mode(lspcon, DRM_LSPCON_MODE_PCON, true)) 103 138 DRM_ERROR("LSPCON resume failed\n"); 104 139 else ··· 178 143 return false; 179 144 } 180 145 181 - intel_dp_read_desc(dp); 146 + lspcon->desc_valid = intel_dp_read_desc(dp); 182 147 183 148 DRM_DEBUG_KMS("Success: LSPCON init\n"); 184 149 return true;