- Monitor connected with self-powered hub cannot get connected after resume
- VRR monitor connected with DP cannot display correcctly after resume
- VRR monitor connected with HDMI diplay with vertical shift
- In desktop(X11) mode, external monitors get lost after resume

Signed-off-by: chentien_amdeng <[email protected]>
---
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 733 ++++++++++++++++++
 1 file changed, 733 insertions(+)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c 
b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index 3fa4dbda4517..474d8e55d222 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -158,6 +158,533 @@ MODULE_FIRMWARE(FIRMWARE_DCN_401_DMUB);
 #define FIRMWARE_DCN_42_DMUB "amdgpu/dcn_4_2_dmcub.bin"
 MODULE_FIRMWARE(FIRMWARE_DCN_42_DMUB);
 
+#define VRR_USBHUB     /* define this to fix bugs related to USBHUB VRR 
support */
+#ifdef VRR_USBHUB
+static bool dc_postresume_hard_modeset = true;
+module_param_named(dc_postresume_hard_modeset, dc_postresume_hard_modeset, 
bool, 0644);
+MODULE_PARM_DESC(dc_postresume_hard_modeset, "Perform hard modeset on resume 
from suspend (default: true)");
+static atomic_t dm_postresume_force_modeset = ATOMIC_INIT(0);
+
+/* Delay before restoring VRR after resume on PCON-in-TMDS */
+static int dc_vrr_restore_ms = 2500;
+module_param_named(dc_vrr_restore_ms, dc_vrr_restore_ms, int, 0644);
+MODULE_PARM_DESC(dc_vrr_restore_ms,
+       "Delay (ms) before restoring VRR after resume on PCON-TMDS (default: 
1500)");
+
+/* Gate VRR works during suspend/shutdown to avoid late commits on reboot */
+static atomic_t dc_vrr_work_ok = ATOMIC_INIT(1);
+
+/* =====================
+ *  Resume policy knobs
+ * =====================
+ */
+
+/* 1) Mask VRR briefly after resume on DP->HDMI PCON in TMDS (no FRL). */
+static bool dc_vrr_pcon_resume_mask = true;
+module_param_named(dc_vrr_pcon_resume_mask, dc_vrr_pcon_resume_mask, bool, 
0644);
+MODULE_PARM_DESC(dc_vrr_pcon_resume_mask,
+       "Mask VRR after resume when DP->HDMI PCON reports TMDS (default: N)");
+
+/* Only re-enable VRR if the adapter is NOT PCON-in-TMDS at restore time */
+static bool dc_vrr_restore_only_when_pcon_not_tmds = true;
+module_param_named(dc_vrr_restore_only_when_pcon_not_tmds,
+                                       dc_vrr_restore_only_when_pcon_not_tmds, 
bool, 0644);
+MODULE_PARM_DESC(dc_vrr_restore_only_when_pcon_not_tmds,
+       "If Y, skip VRR restore when PCON reports TMDS; default Y");
+
+/* (Optional) Native-DP retrain post-resume: leave disabled by default */
+static bool dc_retrain_dp_on_resume = true;
+module_param_named(dc_retrain_dp_on_resume, dc_retrain_dp_on_resume, bool, 
0644);
+MODULE_PARM_DESC(dc_retrain_dp_on_resume,
+       "Native DP retrain after resume (deferred); default N");
+
+/* Try to wake a sink that reports SINK_COUNT==0 by writing D0 to DPCD 0x600 */
+static bool dc_dp_wake_sink = true;
+module_param_named(dc_dp_wake_sink, dc_dp_wake_sink, bool, 0644);
+MODULE_PARM_DESC(dc_dp_wake_sink,
+       "If Y, write DP_SET_POWER=0x01 (D0) to DPCD 0x600 when SINK_COUNT==0; 
default Y");
+
+/* Readiness-gated requeue policy for deferred retrain */
+static int dc_dp_retrain_retry_ms = 400;
+module_param_named(dc_dp_retrain_retry_ms, dc_dp_retrain_retry_ms, int, 0644);
+MODULE_PARM_DESC(dc_dp_retrain_retry_ms,
+       "Delay (ms) before re-trying native-DP retrain readiness probe 
(default: 400)");
+
+static int dc_dp_retrain_max_retries = 10;
+module_param_named(dc_dp_retrain_max_retries, dc_dp_retrain_max_retries, int, 
0644);
+MODULE_PARM_DESC(dc_dp_retrain_max_retries,
+       "Maximum readiness re-tries before giving up (default: 10)");
+
+/* When a DP connector is ready but inactive, nudge userspace repeatedly 
(bounded) */
+static int dc_dp_hotplug_retry_ms = 800;
+module_param_named(dc_dp_hotplug_retry_ms, dc_dp_hotplug_retry_ms, int, 0644);
+MODULE_PARM_DESC(dc_dp_hotplug_retry_ms,
+       "Delay (ms) between hotplug nudges for ready-but-inactive DP (default: 
800)");
+
+static int dc_dp_hotplug_max_retries = 5;
+module_param_named(dc_dp_hotplug_max_retries, dc_dp_hotplug_max_retries, int, 
0644);
+MODULE_PARM_DESC(dc_dp_hotplug_max_retries,
+       "Maximum hotplug nudges after the first one for inactive DP (default: 
5)");
+
+/* Defer time for retrain worker (ms) to avoid racing the main resume commit */
+static int dc_dp_retrain_defer_ms = 700;
+module_param_named(dc_dp_retrain_defer_ms, dc_dp_retrain_defer_ms, int, 0644);
+MODULE_PARM_DESC(dc_dp_retrain_defer_ms,
+       "Delay (ms) before running native-DP retrain after resume (default: 
500)");
+
+/* DPCD register holding PCON HDMI link status */
+#ifndef DPCD_PCON_HDMI_LINK_CONFIG_STATUS
+#define DPCD_PCON_HDMI_LINK_CONFIG_STATUS 0x03036
+#endif
+
+/* EXPERIMENTAL: pre-clear VRR inside cached atomic state before resume */
+static bool dc_vrr_preclear_in_cached_state = true;
+module_param_named(dc_vrr_preclear_in_cached_state, 
dc_vrr_preclear_in_cached_state, bool, 0644);
+MODULE_PARM_DESC(dc_vrr_preclear_in_cached_state,
+       "If Y, clear VRR in dm->cached_state for PCON-in-TMDS before 
drm_atomic_helper_resume(); default N");
+
+/*
+ * Late DP re-detect delay (ms) after HPD-high:
+ * Many docks/bridges aren't AUX/EDID-ready immediately after PSU returns.
+ * A second detect + hotplug after a short delay avoids early AUX/EDID flaps.
+ */
+static int dc_dp_late_hpd_delay_ms = 300;
+module_param_named(dc_dp_late_hpd_delay_ms, dc_dp_late_hpd_delay_ms, int, 
0644);
+MODULE_PARM_DESC(dc_dp_late_hpd_delay_ms,
+       "Delay before late DP re-detect/hotplug on HPD-high (ms)");
+
+/* Global (per-file) late reprobe; coalesces HPD-high events. */
+static struct delayed_work dm_late_dp_reprobe_work;
+static struct drm_device *dm_late_reprobe_ddev;
+static bool dm_late_reprobe_initialized;
+
+static void dm_late_dp_reprobe_fn(struct work_struct *work)
+{
+       struct drm_device *ddev = dm_late_reprobe_ddev;
+       struct amdgpu_device *adev;
+       struct drm_connector_list_iter iter;
+       struct drm_connector *connector;
+       int ret;
+
+       if (!ddev)
+               return;
+       adev = drm_to_adev(ddev);
+
+       drm_connector_list_iter_begin(ddev, &iter);
+       drm_for_each_connector_iter(connector, &iter) {
+               struct amdgpu_dm_connector *aconn;
+
+               if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort)
+                       continue;
+               aconn = to_amdgpu_dm_connector(connector);
+               if (!aconn || aconn->mst_root)
+                       continue; /* SST only; MST has its own flow */
+
+               /* Try a real detect; if present, refresh connector and nudge 
userspace */
+               mutex_lock(&aconn->hpd_lock);
+               mutex_lock(&adev->dm.dc_lock);
+               ret = dc_link_detect(aconn->dc_link, DETECT_REASON_HPD);
+               mutex_unlock(&adev->dm.dc_lock);
+
+               if (ret) {
+                       amdgpu_dm_update_connector_after_detect(aconn);
+                       if (connector->status == connector_status_connected) {
+                               drm_modeset_lock_all(ddev);
+                               dm_restore_drm_connector_state(ddev, connector);
+                               drm_modeset_unlock_all(ddev);
+                               
drm_kms_helper_connector_hotplug_event(connector);
+                               drm_dbg_kms(ddev, "dm: late DP reprobe: 
hotplugged %s\n",
+                                       connector->name);
+                       } else {
+                               drm_dbg_kms(ddev, "dm: late DP reprobe: %s 
still disconnected\n",
+                                       connector->name);
+                       }
+               } else {
+                       drm_dbg_kms(ddev, "dm: late DP reprobe: dc_link_detect 
failed for %s\n",
+                               connector->name);
+               }
+               mutex_unlock(&aconn->hpd_lock);
+       }
+       drm_connector_list_iter_end(&iter);
+}
+
+static void dm_schedule_late_dp_reprobe(struct drm_connector *connector)
+{
+       /* Coalesce multiple HPD-high edges into a single delayed run. */
+       mod_delayed_work(system_wq, &dm_late_dp_reprobe_work,
+                       msecs_to_jiffies(dc_dp_late_hpd_delay_ms));
+       drm_dbg_kms(connector->dev,
+                       "dm: scheduled late DP reprobe for %s in %d ms\n",
+                       connector->name, dc_dp_late_hpd_delay_ms);
+}
+
+struct pcon_vrr_restore_ctx {
+       struct drm_crtc *crtc;
+       bool want_vrr;
+       struct delayed_work work;
+};
+
+struct pcon_vrr_mask_ctx {
+       struct drm_crtc *crtc;
+       struct delayed_work work;
+};
+
+/* Deferred native-DP retrain after resume */
+struct dp_retrain_work {
+       struct amdgpu_device *adev;
+       int tries;
+       struct delayed_work work;
+};
+static void dp_retrain_worker(struct work_struct *w);
+
+/* Small helper: quick DPCD read via DC (returns true on DC_OK) */
+static bool dm_read_dpcd(struct dc_link *link, uint32_t addr, uint8_t *buf, 
uint32_t bytes)
+{
+       return link && core_link_read_dpcd(link, addr, buf, bytes) == DC_OK;
+}
+
+/* Small helper: quick DPCD write via DC (returns true on DC_OK) */
+static bool dm_write_dpcd(struct dc_link *link, uint32_t addr, const uint8_t 
*buf, uint32_t bytes)
+{
+       return link && core_link_write_dpcd(link, addr, buf, bytes) == DC_OK;
+}
+
+static bool dm_set_power_d0(struct dc_link *link)
+{
+       uint8_t d0 = 0x01; /* D0 */
+
+       if (!dc_dp_wake_sink)
+               return false;
+       return dm_write_dpcd(link, 0x00600, &d0, 1);
+}
+
+/* DPCD register holding PCON HDMI link status */
+#ifndef DPCD_PCON_HDMI_LINK_CONFIG_STATUS
+#define DPCD_PCON_HDMI_LINK_CONFIG_STATUS 0x03036
+#endif
+
+/* Returns true when link is DP->HDMI PCON and PCON reports TMDS (no FRL) */
+static bool dm_link_is_pcon_tmds(struct dc_link *link, u8 *status_out)
+{
+       u8 val = 0;
+       enum dc_status st;
+
+       if (!link)
+               return false;
+       if (link->dpcd_caps.dongle_type != DISPLAY_DONGLE_DP_HDMI_CONVERTER)
+               return false;
+       st = core_link_read_dpcd(link, DPCD_PCON_HDMI_LINK_CONFIG_STATUS, &val, 
1);
+       if (status_out)
+               *status_out = val;
+       return st == DC_OK && val == 0x00;
+}
+
+/* Toggle VRR via an internal atomic commit (CRTC vrr_enabled flag). */
+static int dm_atomic_set_crtc_vrr_ex(struct drm_crtc *crtc, bool enable, bool 
allow_modeset)
+{
+       struct drm_device *dev = crtc->dev;
+       struct drm_modeset_acquire_ctx ctx;
+       struct drm_atomic_state *state;
+       struct drm_crtc_state *crtc_state;
+       int ret = 0;
+
+       drm_modeset_acquire_init(&ctx, 0);
+retry:
+       state = drm_atomic_state_alloc(dev);
+       if (!state) {
+               ret = -ENOMEM;
+               goto out_fini;
+       }
+       state->acquire_ctx = &ctx;
+
+       crtc_state = drm_atomic_get_crtc_state(state, crtc);
+       if (IS_ERR(crtc_state)) {
+               ret = PTR_ERR(crtc_state);
+               goto out_put;
+       }
+       crtc_state->vrr_enabled = enable;
+       if (allow_modeset)
+               state->allow_modeset = true; /* 6.11.11: per-commit 
allow_modeset */
+       ret = drm_atomic_commit(state);
+       if (ret == -EDEADLK) {
+               drm_atomic_state_put(state);
+               drm_modeset_backoff(&ctx);
+               goto retry;
+       }
+out_put:
+       drm_atomic_state_put(state);
+       return ret;
+out_fini:
+       drm_modeset_acquire_fini(&ctx);
+       return ret;
+}
+
+static inline int dm_atomic_set_crtc_vrr(struct drm_crtc *crtc, bool enable)
+{
+       return dm_atomic_set_crtc_vrr_ex(crtc, enable, true);
+}
+
+/* Delayed restore worker: re-enable VRR if safe to do so */
+static void pcon_vrr_restore_work(struct work_struct *work)
+{
+       struct pcon_vrr_restore_ctx *ctx =
+               container_of(to_delayed_work(work), struct 
pcon_vrr_restore_ctx, work);
+       int ret = 0;
+
+       if (!atomic_read(&dc_vrr_work_ok)) {
+               kfree(ctx);
+               return;
+       }
+       if (!ctx->crtc || !ctx->want_vrr) {
+               kfree(ctx);
+               return;
+       }
+
+       /* Skip restoring VRR if the bound connector is a PCON still in TMDS. */
+       if (dc_vrr_restore_only_when_pcon_not_tmds) {
+               struct drm_device *ddev = ctx->crtc->dev;
+               struct drm_connector *conn;
+               struct drm_connector_list_iter iter;
+               bool pcon_tmds = false;
+
+               drm_connector_list_iter_begin(ddev, &iter);
+               drm_for_each_connector_iter(conn, &iter) {
+                       struct amdgpu_dm_connector *aconn;
+                       u8 st = 0;
+
+                       if (!conn->state || conn->state->crtc != ctx->crtc)
+                               continue;
+                       if (conn->connector_type != 
DRM_MODE_CONNECTOR_DisplayPort)
+                               continue;
+                       aconn = to_amdgpu_dm_connector(conn);
+                       if (!aconn || !aconn->dc_link)
+                               continue;
+                       if (dm_link_is_pcon_tmds(aconn->dc_link, &st)) {
+                               pcon_tmds = true;
+                               break;
+                       }
+               }
+               drm_connector_list_iter_end(&iter);
+
+               if (pcon_tmds) {
+                       DRM_INFO(
+                               "amdgpu_dm: VRR restore skipped (PCON still 
TMDS); staying fixed timing\n");
+                       kfree(ctx);
+                       return;
+               }
+       }
+
+       /* Prefer no-modeset; if it fails, retry once with modeset */
+       ret = dm_atomic_set_crtc_vrr_ex(ctx->crtc, true, false);
+       if (ret) {
+               msleep(60);
+               (void)dm_atomic_set_crtc_vrr_ex(ctx->crtc, true, true);
+       }
+       kfree(ctx);
+}
+
+static void late_dp_redetect_and_hotplug(struct drm_device *ddev)
+{
+       struct amdgpu_device *adev = drm_to_adev(ddev);
+       struct drm_connector *connector;
+       struct drm_connector_list_iter iter;
+
+       drm_connector_list_iter_begin(ddev, &iter);
+       drm_for_each_connector_iter(connector, &iter) {
+               struct amdgpu_dm_connector *aconn = 
to_amdgpu_dm_connector(connector);
+               int ret;
+
+               if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort 
||
+                       aconn->mst_root)
+                       continue;
+
+               /* Optional: skip if we didn't see resume-time AUX/EDID issues 
*/
+               // if (!aconn->resume_needs_redetect) continue;
+
+               /* Optional: short settle delay on the failure path */
+               // msleep(100);
+
+               mutex_lock(&aconn->hpd_lock);
+               mutex_lock(&adev->dm.dc_lock);
+               ret = dc_link_detect(aconn->dc_link, DETECT_REASON_HPD);
+               mutex_unlock(&adev->dm.dc_lock);
+
+               if (!ret) {
+                       mutex_unlock(&aconn->hpd_lock);
+                       continue;
+               }
+
+               amdgpu_dm_update_connector_after_detect(aconn);
+
+               /* If still disconnected, nothing to notify */
+               if (connector->status != connector_status_connected) {
+                       mutex_unlock(&aconn->hpd_lock);
+                       continue;
+               }
+
+               drm_modeset_lock_all(ddev);
+               dm_restore_drm_connector_state(ddev, connector);
+               drm_modeset_unlock_all(ddev);
+
+               drm_kms_helper_connector_hotplug_event(connector);
+               mutex_unlock(&aconn->hpd_lock);
+       }
+       drm_connector_list_iter_end(&iter);
+}
+
+/* Return true if this DP connector is 'ready' for retrain:
+ * - connector has a bound CRTC and is active (we don't touch idle heads)
+ * - DPCD base/ext capability reads succeed (no repeated failures/DEFER storms)
+ * - SINK_COUNT shows sink present/ready (0x41)
+ */
+static bool dm_dp_retrain_ready(struct drm_connector *conn)
+{
+       struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(conn);
+       struct dc_link *link;
+       uint8_t dpcd[16], ext[16], sink = 0;
+
+       if (conn->connector_type != DRM_MODE_CONNECTOR_DisplayPort)
+               return false;
+
+       if (!aconn)
+               return false;
+       link = aconn->dc_link;
+       if (!link)
+               return false;
+
+       /* Base DPCD (0x00000..0x0000F) */
+       if (!dm_read_dpcd(link, 0x00000, dpcd, sizeof(dpcd)))
+               return false;
+
+       /* Optional ext caps (0x02200..0x0220F); ignore failure but try once */
+       (void)dm_read_dpcd(link, 0x02200, ext, sizeof(ext));
+
+       /* SINK_COUNT: lower 6 bits are the count; CP_READY is bit 6 and can be 
0. */
+       if (!dm_read_dpcd(link, 0x00200, &sink, 1))
+               return false;
+       if ((sink & 0x3f) == 0) {
+               /* Try to wake the sink to D0, then re-read once */
+               if (dm_set_power_d0(link)) {
+                       msleep(30); /* small settle */
+                       (void)dm_read_dpcd(link, 0x00200, &sink, 1);
+               }
+               if ((sink & 0x3f) == 0)
+                       return false;
+       }
+
+       return true;
+}
+
+/* Worker: run a one-shot native-DP retrain after resume settles */
+static void dp_retrain_worker(struct work_struct *w)
+{
+       struct dp_retrain_work *rw =
+               container_of(to_delayed_work(w), struct dp_retrain_work, work);
+       struct amdgpu_device *adev = rw->adev;
+       struct drm_device *ddev = adev_to_drm(adev);
+       struct drm_connector *conn;
+       struct drm_connector_list_iter iter;
+       bool need_requeue = false;
+       bool saw_any_dp = false;
+
+       if (!dc_retrain_dp_on_resume)
+               goto out;
+
+       drm_connector_list_iter_begin(ddev, &iter);
+       drm_for_each_connector_iter(conn, &iter) {
+               struct amdgpu_dm_connector *aconn;
+               struct dc_link *link;
+               union hpd_irq_data irq_data;
+               bool is_active = false;
+
+               if (conn->connector_type != DRM_MODE_CONNECTOR_DisplayPort)
+                       continue;
+               saw_any_dp = true;
+
+               aconn = to_amdgpu_dm_connector(conn);
+               if (!aconn)
+                       continue;
+               else {
+                       link = aconn->dc_link;
+                       if (!link)
+                               continue;
+               }
+               /* Skip MST root; MST resume handled elsewhere */
+               if (aconn->mst_root)
+                       continue;
+               /* Only act on active pipelines */
+               is_active =  (conn->state && conn->state->crtc && 
conn->state->crtc->state &&
+                       conn->state->crtc->state->active);
+
+               /* Readiness gate: if not ready yet, mark for requeue */
+               if (!dm_dp_retrain_ready(conn)) {
+                       need_requeue = true;
+                       DRM_DEBUG_DRIVER("%s: connector %d not ready, will 
retry (try #%d)\n",
+                               __func__, drm_connector_index(conn), rw->tries);
+                       continue;
+               }
+
+               /* Ready but INACTIVE: nudge userspace via hotplug; re-try 
bounded times */
+               if (!is_active) {
+                       DRM_DEBUG_DRIVER("%s: DP-%d ready but inactive -> 
hotplug\n",
+                                                       __func__, 
drm_connector_index(conn));
+                       drm_kms_helper_hotplug_event(ddev);
+
+                       if (rw->tries < dc_dp_hotplug_max_retries) {
+                               rw->tries++;
+                               schedule_delayed_work(&rw->work,
+                                       
msecs_to_jiffies(max(dc_dp_hotplug_retry_ms, 0)));
+                               drm_connector_list_iter_end(&iter);
+                               return;
+                       }
+                       DRM_DEBUG_DRIVER("%s: DP-%d inactive after %d nudges; 
giving up\n",
+                               __func__, drm_connector_index(conn), rw->tries);
+                       continue;
+               }
+
+               /* Ready + ACTIVE: retrain only if link-loss is reported */
+               memset(&irq_data, 0, sizeof(irq_data));
+               if (dc_link_dp_read_hpd_rx_irq_data(link, &irq_data) == DC_OK &&
+                       dc_link_check_link_loss_status(link, &irq_data)) {
+
+                       DRM_DEBUG_DRIVER("%s: DP-%d retraining\n",
+                                                       __func__, 
drm_connector_index(conn));
+                       /* Clean re-train; allow a hard modeset in the next 
pass */
+                       dc_link_dp_handle_link_loss(link);
+                       atomic_set(&dm_postresume_force_modeset, 1);
+                       /* Nudge userspace to pick up new mode if needed */
+                       drm_kms_helper_hotplug_event(ddev);
+
+               } else {
+                       DRM_DEBUG_DRIVER("%s: DP-%d no link-loss; nothing to 
do\n",
+                                                       __func__, 
drm_connector_index(conn));
+               }
+       }
+       drm_connector_list_iter_end(&iter);
+
+       /* If the dock/connector hasn't enumerated yet, keep probing. */
+       if (!saw_any_dp) {
+               need_requeue = true;
+               DRM_DEBUG_DRIVER("%s: no DP connectors present (try %d)\n",
+                                               __func__, rw->tries);
+       }
+
+       /* If no head was ready yet, requeue up to the retry cap */
+       if (need_requeue && rw->tries < dc_dp_retrain_max_retries) {
+               rw->tries++;
+               schedule_delayed_work(&rw->work, 
msecs_to_jiffies(max(dc_dp_retrain_retry_ms, 0)));
+               return;
+       }
+
+       if (need_requeue)
+               DRM_DEBUG_DRIVER("%s: giving up after %d tries\n", __func__, 
rw->tries);
+       else
+               DRM_DEBUG_DRIVER("%s: done (no requeue)\n", __func__);
+out:
+       kfree(rw);
+}
+#endif /* VRR_USBHUB */
+
 /**
  * DOC: overview
  *
@@ -2285,6 +2812,16 @@ static int amdgpu_dm_init(struct amdgpu_device *adev)
                adev->dm.secure_display_ctx.support_mul_roi = true;
 
 #endif
+#ifdef VRR_USBHUB
+       /*
+        * Initialize the global late-reprobe worker once the DRM device exists.
+        */
+       if (!dm_late_reprobe_initialized) {
+               INIT_DELAYED_WORK(&dm_late_dp_reprobe_work, 
dm_late_dp_reprobe_fn);
+               dm_late_reprobe_ddev = &adev->ddev;
+               dm_late_reprobe_initialized = true;
+       }
+#endif /* VRR_USBHUB */
 
        drm_dbg_driver(adev_to_drm(adev), "KMS initialized.\n");
 
@@ -3302,7 +3839,9 @@ static int dm_cache_state(struct amdgpu_device *adev)
 static void dm_destroy_cached_state(struct amdgpu_device *adev)
 {
        struct amdgpu_display_manager *dm = &adev->dm;
+#ifndef VRR_USBHUB
        struct drm_device *ddev = adev_to_drm(adev);
+#endif
        struct dm_plane_state *dm_new_plane_state;
        struct drm_plane_state *new_plane_state;
        struct dm_crtc_state *dm_new_crtc_state;
@@ -3345,9 +3884,12 @@ static void dm_destroy_cached_state(struct amdgpu_device 
*adev)
                }
        }
 
+       /* postpone these two steps to dm_resume */
+#ifndef VRR_USBHUB
        drm_atomic_helper_resume(ddev, dm->cached_state);
 
        dm->cached_state = NULL;
+#endif /* VRR_USBHUB */
 }
 
 static int dm_suspend(struct amdgpu_ip_block *ip_block)
@@ -3405,6 +3947,9 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block)
                dc_allow_idle_optimizations(dm->dc, true);
 
        dc_dmub_srv_set_power_state(dm->dc->ctx->dmub_srv, 
DC_ACPI_CM_POWER_STATE_D3);
+#ifdef VRR_USBHUB
+       atomic_set(&dc_vrr_work_ok, 0);
+#endif /* VRR_USBHUB */
 
        return 0;
 }
@@ -3640,6 +4185,9 @@ static int dm_resume(struct amdgpu_ip_block *ip_block)
        enum dc_connection_type new_connection_type = dc_connection_none;
        struct dc_state *dc_state;
        int i, r, j;
+#ifdef VRR_USBHUB
+       int ret;
+#endif
        struct dc_commit_streams_params commit_params = {};
 
        if (dm->dc->caps.ips_support) {
@@ -3839,6 +4387,119 @@ static int dm_resume(struct amdgpu_ip_block *ip_block)
        }
        drm_connector_list_iter_end(&iter);
 
+#ifdef VRR_USBHUB
+       /* Allow post-resume workers */
+       atomic_set(&dc_vrr_work_ok, 1);
+
+       /* ------------------------------------------------------
+        *  PCON-in-TMDS: clear VRR in cached state before resume
+        * ------------------------------------------------------
+        */
+       if (dc_vrr_pcon_resume_mask && dc_vrr_preclear_in_cached_state && 
dm->cached_state) {
+               struct drm_atomic_state *cached = dm->cached_state;
+               struct drm_connector *conn;
+               struct drm_connector_state *conn_state;
+               struct drm_crtc *crtc;
+               struct drm_crtc_state *crtc_state;
+               int i, n_crtcs = ddev->mode_config.num_crtc;
+               bool *crtc_restore = NULL;
+
+               if (n_crtcs > 0)
+                       crtc_restore = kvcalloc(n_crtcs, sizeof(bool), 
GFP_KERNEL);
+
+               /* Walk connector states being restored and identify 
PCON-in-TMDS links */
+               for_each_new_connector_in_state(cached, conn, conn_state, i) {
+                       struct amdgpu_dm_connector *aconn;
+                       u8 hdmi_stat = 0;
+
+                       if (!conn_state || !conn)
+                               continue;
+                       if (conn->connector_type != 
DRM_MODE_CONNECTOR_DisplayPort)
+                               continue;
+
+                       aconn = to_amdgpu_dm_connector(conn);
+                       if (!aconn || !aconn->dc_link)
+                               continue;
+                       if (aconn->mst_root)
+                               continue; /* only root DP connector */
+
+                       /* Only act on PCON-in-TMDS */
+                       if (!dm_link_is_pcon_tmds(aconn->dc_link, &hdmi_stat))
+                               continue;
+
+                       /* Needs a target CRTC in the cached state */
+                       if (!conn_state->crtc)
+                               continue;
+                       crtc = conn_state->crtc;
+
+                       /* Find the matching CRTC state inside the same cached 
state */
+                       crtc_state = drm_atomic_get_new_crtc_state(cached, 
crtc);
+                       if (!crtc_state)
+                               continue;
+
+                       /* If VRR was enabled pre-suspend, clear it for this 
resume commit */
+                       if (crtc_state->vrr_enabled) {
+                               int idx = drm_crtc_index(crtc);
+
+                               crtc_state->vrr_enabled = false;
+                               if (crtc_restore && idx >= 0 && idx < n_crtcs)
+                                       crtc_restore[idx] = true; /* remember 
to restore later */
+                       }
+               }
+
+               /* Single resume commit with VRR-off on the flagged CRTCs */
+               ret = drm_atomic_helper_resume(ddev, cached);
+               dm->cached_state = NULL;
+
+               /* Schedule delayed VRR restore for the CRTCs masked */
+               if (!ret && crtc_restore) {
+                       int idx;
+
+                       for (idx = 0; idx < n_crtcs; idx++) {
+                               struct drm_crtc *iter;
+                               struct drm_crtc_state *iter_state;
+                               struct pcon_vrr_restore_ctx *r;
+
+                               if (!crtc_restore[idx])
+                                       continue;
+                               iter = drm_crtc_from_index(ddev, idx);
+                               if (!iter)
+                                       continue;
+                               iter_state = iter->state;
+                               if (!iter_state || !iter_state->active)
+                                       continue;
+
+                               r = kzalloc(sizeof(*r), GFP_KERNEL);
+                               if (!r)
+                                       continue;
+                               r->crtc = iter;
+                               r->want_vrr = true;
+                               INIT_DELAYED_WORK(&r->work, 
pcon_vrr_restore_work);
+                               schedule_delayed_work(
+                                       &r->work, 
msecs_to_jiffies(dc_vrr_restore_ms)
+                               );
+                       }
+               }
+               kvfree(crtc_restore);
+       } else {
+               /* Single legacy resume */
+               ret = drm_atomic_helper_resume(ddev, dm->cached_state);
+               dm->cached_state = NULL;
+       }
+
+       /* (Optional) Native DP: schedule retrain once after resume settles */
+       if (dc_retrain_dp_on_resume) {
+               struct dp_retrain_work *rw = kzalloc(sizeof(*rw), GFP_KERNEL);
+
+               if (rw) {
+                       rw->adev = adev;
+                       INIT_DELAYED_WORK(&rw->work, dp_retrain_worker);
+                       schedule_delayed_work(&rw->work,
+                               msecs_to_jiffies(max(dc_dp_retrain_defer_ms, 
0)));
+               }
+       }
+#endif /* VRR_USBHUB */
+
        /* Debug dump: list all DC links and their associated sinks after 
detection
         * is complete for all connectors. This provides a comprehensive view 
of the
         * final state without repeating the dump for each connector.
@@ -3851,6 +4512,13 @@ static int dm_resume(struct amdgpu_ip_block *ip_block)
 
        drm_kms_helper_hotplug_event(ddev);
 
+#ifdef VRR_USBHUB
+       if (dc_postresume_hard_modeset) {
+               atomic_set(&dm_postresume_force_modeset, 1);
+               drm_kms_helper_hotplug_event(ddev);
+       }
+       late_dp_redetect_and_hotplug(ddev);
+#endif /* VRR_USBHUB */
        return 0;
 }
 
@@ -4232,6 +4900,70 @@ static void hdmi_hpd_debounce_work(struct work_struct 
*work)
        bool fake_reconnect = false;
        bool reallow_idle = false;
        bool ret = false;
+#ifdef VRR_USBHUB
+       scoped_guard(mutex, &aconnector->hpd_lock) {
+
+               /* Re-detect the display */
+               scoped_guard(mutex, &adev->dm.dc_lock) {
+                       if (dc->caps.ips_support && 
dc->ctx->dmub_srv->idle_allowed) {
+                               dc_allow_idle_optimizations(dc, false);
+                               reallow_idle = true;
+                       }
+                       ret = dc_link_detect(aconnector->dc_link, 
DETECT_REASON_HPD);
+               }
+
+               if (ret) {
+                       /* Apply workaround delay for certain panels */
+                       apply_delay_after_dpcd_poweroff(adev, 
aconnector->dc_sink);
+                       /* Compare sinks to determine if this was a spontaneous 
HPD toggle */
+                       if (are_sinks_equal(
+                               aconnector->dc_link->local_sink, 
aconnector->hdmi_prev_sink)) {
+                               /*
+                                * Sinks match - this was a spontaneous HDMI 
HPD toggle.
+                                */
+                               drm_dbg_kms(
+                                       dev, "Sink unchanged after debounce, 
internal re-enable\n");
+                               fake_reconnect = true;
+                       }
+
+                       /* Update connector state */
+                       amdgpu_dm_update_connector_after_detect(aconnector);
+
+                       drm_modeset_lock_all(dev);
+                       dm_restore_drm_connector_state(dev, connector);
+                       drm_modeset_unlock_all(dev);
+
+                       /* Only notify OS if sink actually changed */
+                       if (!fake_reconnect && aconnector->base.force == 
DRM_FORCE_UNSPECIFIED)
+                               drm_kms_helper_hotplug_event(dev);
+               }
+
+               /* Release the cached sink reference */
+               if (aconnector->hdmi_prev_sink) {
+                       dc_sink_release(aconnector->hdmi_prev_sink);
+                       aconnector->hdmi_prev_sink = NULL;
+               }
+
+               scoped_guard(mutex, &adev->dm.dc_lock) {
+                       if (reallow_idle && dc->caps.ips_support)
+                               dc_allow_idle_optimizations(dc, true);
+               }
+       }
+       /* --- Begin: late re-probe policy (bus-powered VRR PCON) --- */
+       if (aconnector &&
+               aconnector->base.connector_type == 
DRM_MODE_CONNECTOR_DisplayPort &&
+               !aconnector->mst_root) {
+               if (aconnector->base.status == connector_status_connected) {
+                       dm_schedule_late_dp_reprobe(&aconnector->base);
+               } else if (aconnector->base.status == 
connector_status_disconnected) {
+                       cancel_delayed_work_sync(&dm_late_dp_reprobe_work);
+                       drm_dbg_kms(aconnector->base.dev,
+                                       "dm: late DP reprobe cancelled for %s 
(status=disconnected)\n",
+                                       aconnector->base.name);
+               }
+       }
+       /* --- End: late re-probe policy --- */
+       #else
        guard(mutex)(&aconnector->hpd_lock);
 
        /* Re-detect the display */
@@ -4277,6 +5009,7 @@ static void hdmi_hpd_debounce_work(struct work_struct 
*work)
                if (reallow_idle && dc->caps.ips_support)
                        dc_allow_idle_optimizations(dc, true);
        }
+#endif
 }
 
 static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector)
-- 
2.43.0

Reply via email to