When disabling pattern training after successful link training the
CH7511 bridge incorrectly clears the DPCD link status bits: not only
INTERLANE_ALIGN_DONE, but also the per-lane CR_DONE, CHANNEL_EQ_DONE
and SYMBOL_LOCKED bits. This is interpreted as a link failure when
rechecking the link status and triggers an endless loop of retraining
sequences.

Add a quirk to skip the link status check for sinks with unreliable
link status reporting. The quirk is matched on the Chrontel IEEE OUI
(F0:02:2B) and the device ID "CH7511" on any platform.

While at it, clear the DPCD quirks mask before scanning the quirk
table, so that quirks of a previously connected sink do not stick to
a new sink plugged into the same connector on re-detection.

Tested on Ivy Bridge (HD Graphics 2500, device 0152) with a CH7511
DP-to-HDMI adapter on kernels 7.1-rc1, drm-tip and 7.3.

Fixes: 3b3be899fc81 ("drm/i915/dp: Recheck link state after modeset")
Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/15744
Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/15994
Co-developed-by: Patrik Jakobsson <[email protected]>
Signed-off-by: Patrik Jakobsson <[email protected]>
Signed-off-by: bitey <[email protected]>
---
Changes in v5:
- Rename the quirk to QUIRK_DP_LINK_STATUS_UNRELIABLE and drop the
  commit subject mention of the interlane align bit, since the quirk
  now covers the whole link status (Sashiko)
- Skip the link status check entirely for quirked sinks instead of
  synthesizing link status bits (Sashiko)
- Clear intel_dp->quirks.mask before scanning the quirk table to
  prevent stale quirks from a previously connected sink and repeated
  quirk messages on connector re-detection (Sashiko)
- Use drm_info_once() for the quirk message (Sashiko)

Changes in v4:
- Also force the per-lane status bits (CR_DONE / CHANNEL_EQ_DONE /
  SYMBOL_LOCKED). On Ivy Bridge the CH7511 zeroes the entire lane
  status registers after training (ln0_1 = ln2_3 = 0x00), so forcing
  only the INTERLANE_ALIGN_DONE bit is not enough to stop the retrain
  loop.
- Rebased on top of v7.3

Changes in v3:
- Match on all devices with a CH7511, instead of only VLV
- Rebased on top of drm-tip
- Additional "Closes" tag
---
 .../drm/i915/display/intel_dp_link_training.c | 12 ++++++++++
 drivers/gpu/drm/i915/display/intel_quirks.c   | 23 +++++++++++++++++++
 drivers/gpu/drm/i915/display/intel_quirks.h   |  1 +
 3 files changed, 36 insertions(+)

diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c 
b/drivers/gpu/drm/i915/display/intel_dp_link_training.c
index 9a692f4..0fdc5a4 100644
--- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c
+++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c
@@ -43,6 +43,7 @@
 #include "intel_hotplug.h"
 #include "intel_modeset_lock.h"
 #include "intel_panel.h"
+#include "intel_quirks.h"
 #include "intel_psr.h"
 
 /**
@@ -2515,6 +2516,17 @@ intel_dp_needs_link_retrain(struct intel_dp *intel_dp)
        if (link_recovery_autoretrain_pending(link_training))
                return true;
 
+       /*
+        * The Chrontel CH7511 DP-to-LVDS bridge does not maintain the DPCD
+        * link status fields after the training pattern is disabled: the
+        * interlane alignment and per-lane status bits all read back as zero
+        * even though the link is up. Skip the link status check for such
+        * sinks, otherwise the bogus status triggers an endless retrain
+        * loop.
+        */
+       if (intel_has_dpcd_quirk(intel_dp, QUIRK_DP_LINK_STATUS_UNRELIABLE))
+               return false;
+
        /* Retrain if link not ok */
        return !intel_dp_link_ok(intel_dp, link_status) &&
                !intel_psr_link_ok(intel_dp);
diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c 
b/drivers/gpu/drm/i915/display/intel_quirks.c
index 7d7db77..27141ad 100644
--- a/drivers/gpu/drm/i915/display/intel_quirks.c
+++ b/drivers/gpu/drm/i915/display/intel_quirks.c
@@ -94,6 +94,14 @@ static void quirk_disable_edp_panel_replay(struct intel_dp 
*intel_dp)
        drm_info(display->drm, "Applying disable Panel Replay quirk\n");
 }
 
+static void quirk_dp_link_status_unreliable(struct intel_dp *intel_dp)
+{
+       struct intel_display *display = to_intel_display(intel_dp);
+
+       intel_set_dpcd_quirk(intel_dp, QUIRK_DP_LINK_STATUS_UNRELIABLE);
+       drm_info_once(display->drm, "Applying DP link status unreliable quirk 
(Chrontel CH7511)\n");
+}
+
 static void quirk_disable_psr2(struct intel_display *display)
 {
        intel_set_quirk(display, QUIRK_DISABLE_PSR2);
@@ -289,6 +297,15 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = 
{
                .sink_oui = SINK_OUI(0x00, 0x22, 0xb9),
                .hook = quirk_disable_edp_panel_replay,
        },
+       /* Chrontel CH7511 DP-to-LVDS bridge */
+       {
+               .device = DEVICE_ID_ANY,
+               .subsystem_vendor = PCI_ANY_ID,
+               .subsystem_device = PCI_ANY_ID,
+               .sink_oui = SINK_OUI(0x2b, 0x02, 0xf0),
+               .sink_device_id = SINK_DEVICE_ID('C', 'H', '7', '5', '1', '1'),
+               .hook = quirk_dp_link_status_unreliable,
+       },
 };
 
 void intel_init_quirks(struct intel_display *display)
@@ -320,6 +337,12 @@ void intel_init_dpcd_quirks(struct intel_dp *intel_dp,
        struct pci_dev *d = to_pci_dev(display->drm->dev);
        int i;
 
+       /*
+        * Reset the mask so that quirks from a previously connected sink
+        * do not stick to a new sink plugged into the same connector.
+        */
+       intel_dp->quirks.mask = 0;
+
        for (i = 0; i < ARRAY_SIZE(intel_dpcd_quirks); i++) {
                const struct intel_dpcd_quirk *q = &intel_dpcd_quirks[i];
 
diff --git a/drivers/gpu/drm/i915/display/intel_quirks.h 
b/drivers/gpu/drm/i915/display/intel_quirks.h
index 970a4fe..483aa1f 100644
--- a/drivers/gpu/drm/i915/display/intel_quirks.h
+++ b/drivers/gpu/drm/i915/display/intel_quirks.h
@@ -23,6 +23,7 @@ enum intel_quirk_id {
        QUIRK_EDP_LIMIT_RATE_HBR2,
        QUIRK_DISABLE_EDP_PANEL_REPLAY,
        QUIRK_DISABLE_PSR2,
+       QUIRK_DP_LINK_STATUS_UNRELIABLE,
 };
 
 void intel_init_quirks(struct intel_display *display);
-- 
2.56.0

Reply via email to