From c5892256b69006457111e999fcc589d9fcbc0370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:30 +0100 Subject: [PATCH 01/17] drm/amd/display: Return if DisplayID not found in parse_amd_vsdb() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] The function would continue to try to parse EDID even if DisplayID extension block wasn't found. Sometimes it got lucky and found AMD vsdb in CEA extension block which made debugging harder. [How] Add a return if DisplayID extension block wasn't found Signed-off-by: Tomasz Pakuła --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 3 +++ 1 file changed, 3 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 6252afd1d087f..193975a868cf1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -12878,6 +12878,9 @@ static int parse_amd_vsdb(struct amdgpu_dm_connector *aconnector, break; } + if (i == edid->extensions) + return false; + while (j < EDID_LENGTH - sizeof(struct amd_vsdb_block)) { struct amd_vsdb_block *amd_vsdb = (struct amd_vsdb_block *)&edid_ext[j]; unsigned int ieeeId = (amd_vsdb->ieee_id[2] << 16) | (amd_vsdb->ieee_id[1] << 8) | (amd_vsdb->ieee_id[0]); From 1da1945fc76014172a98c8d4ee8dd6e5484d6c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:31 +0100 Subject: [PATCH 02/17] drm/amd/display: Refactor amdgpu_dm_update_freesync_caps() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] This function started to get very messy and hard to follow. [How] Eject some functionality to separate functions and simplify greatly. Signed-off-by: Tomasz Pakuła --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 119 +++++++++++------- 1 file changed, 72 insertions(+), 47 deletions(-) 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 193975a868cf1..ec41fefe39755 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -12861,8 +12861,8 @@ static void parse_edid_displayid_vrr(struct drm_connector *connector, } } -static int parse_amd_vsdb(struct amdgpu_dm_connector *aconnector, - const struct edid *edid, struct amdgpu_hdmi_vsdb_info *vsdb_info) +static int parse_amd_vsdb_did(struct amdgpu_dm_connector *aconnector, + const struct edid *edid, struct amdgpu_hdmi_vsdb_info *vsdb_info) { u8 *edid_ext = NULL; int i; @@ -12899,9 +12899,9 @@ static int parse_amd_vsdb(struct amdgpu_dm_connector *aconnector, return false; } -static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, - const struct edid *edid, - struct amdgpu_hdmi_vsdb_info *vsdb_info) +static int parse_amd_vsdb_cea(struct amdgpu_dm_connector *aconnector, + const struct edid *edid, + struct amdgpu_hdmi_vsdb_info *vsdb_info) { u8 *edid_ext = NULL; int i; @@ -12931,6 +12931,44 @@ static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, return valid_vsdb_found ? i : -ENODEV; } +static bool is_monitor_range_invalid(struct drm_connector *conn) +{ + return conn->display_info.monitor_range.min_vfreq == 0 || + conn->display_info.monitor_range.max_vfreq == 0; +} + +/** + * Returns true if (max_vfreq - min_vfreq) > 10 + */ +static bool is_freesync_capable(struct drm_monitor_range_info *range) +{ + return (range->max_vfreq - range->min_vfreq) > 10; +} + +static void monitor_range_from_vsdb(struct drm_connector *conn, + struct amdgpu_hdmi_vsdb_info *vsdb) +{ + struct drm_monitor_range_info *range = &conn->display_info.monitor_range; + + range->min_vfreq = vsdb->min_refresh_rate_hz; + range->max_vfreq = vsdb->max_refresh_rate_hz; +} + +/** + * Returns true if connector is capable of freesync + * Optionally, can fetch the range from AMD vsdb + */ +static bool copy_range_to_amdgpu_connector(struct drm_connector *conn) +{ + struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(conn); + struct drm_monitor_range_info *range = &conn->display_info.monitor_range; + + aconn->min_vfreq = range->min_vfreq; + aconn->max_vfreq = range->max_vfreq; + + return is_freesync_capable(range); +} + /** * amdgpu_dm_update_freesync_caps - Update Freesync capabilities * @@ -12945,15 +12983,18 @@ static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, const struct drm_edid *drm_edid) { - int i = 0; struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector); struct dm_connector_state *dm_con_state = NULL; struct dc_sink *sink; struct amdgpu_device *adev = drm_to_adev(connector->dev); struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + struct amdgpu_hdmi_vsdb_info vsdb_did = {0}; + struct dpcd_caps dpcd_caps = {0}; const struct edid *edid; bool freesync_capable = false; + bool valid_vsdb_cea = false; + bool vsdb_freesync = false; enum adaptive_sync_type as_type = ADAPTIVE_SYNC_TYPE_NONE; if (!connector->state) { @@ -12983,62 +13024,46 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, goto update; edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() + valid_vsdb_cea = parse_amd_vsdb_cea(amdgpu_dm_connector, edid, &vsdb_info) >= 0; + vsdb_freesync = valid_vsdb_cea && vsdb_info.freesync_supported; + if (amdgpu_dm_connector->dc_link) + dpcd_caps = amdgpu_dm_connector->dc_link->dpcd_caps; /* Some eDP panels only have the refresh rate range info in DisplayID */ - if ((connector->display_info.monitor_range.min_vfreq == 0 || - connector->display_info.monitor_range.max_vfreq == 0)) + if (is_monitor_range_invalid(connector)) parse_edid_displayid_vrr(connector, edid); - if (edid && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || - sink->sink_signal == SIGNAL_TYPE_EDP)) { - if (amdgpu_dm_connector->dc_link && - amdgpu_dm_connector->dc_link->dpcd_caps.allow_invalid_MSA_timing_param) { - amdgpu_dm_connector->min_vfreq = connector->display_info.monitor_range.min_vfreq; - amdgpu_dm_connector->max_vfreq = connector->display_info.monitor_range.max_vfreq; - if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) - freesync_capable = true; - } + if (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || + sink->sink_signal == SIGNAL_TYPE_EDP) { + + if (dpcd_caps.allow_invalid_MSA_timing_param) + freesync_capable = copy_range_to_amdgpu_connector(connector); - parse_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); + /* eDP */ + if (edid) + parse_amd_vsdb_did(amdgpu_dm_connector, edid, &vsdb_did); - if (vsdb_info.replay_mode) { - amdgpu_dm_connector->vsdb_info.replay_mode = vsdb_info.replay_mode; - amdgpu_dm_connector->vsdb_info.amd_vsdb_version = vsdb_info.amd_vsdb_version; + if (vsdb_did.replay_mode) { + amdgpu_dm_connector->vsdb_info.replay_mode = vsdb_did.replay_mode; + amdgpu_dm_connector->vsdb_info.amd_vsdb_version = vsdb_did.amd_vsdb_version; amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP; } - } else if (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { - i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); - if (i >= 0 && vsdb_info.freesync_supported) { - amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz; - amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz; - if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) - freesync_capable = true; - - connector->display_info.monitor_range.min_vfreq = vsdb_info.min_refresh_rate_hz; - connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; - } + } else if (sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A && vsdb_freesync) { + monitor_range_from_vsdb(connector, &vsdb_info); + freesync_capable = copy_range_to_amdgpu_connector(connector); } if (amdgpu_dm_connector->dc_link) as_type = dm_get_adaptive_sync_support_type(amdgpu_dm_connector->dc_link); - if (as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) { - i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); - if (i >= 0 && vsdb_info.freesync_supported && vsdb_info.amd_vsdb_version > 0) { + if (as_type == FREESYNC_TYPE_PCON_IN_WHITELIST && vsdb_freesync) { + amdgpu_dm_connector->pack_sdp_v1_3 = true; + amdgpu_dm_connector->as_type = as_type; + amdgpu_dm_connector->vsdb_info = vsdb_info; - amdgpu_dm_connector->pack_sdp_v1_3 = true; - amdgpu_dm_connector->as_type = as_type; - amdgpu_dm_connector->vsdb_info = vsdb_info; - - amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz; - amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz; - if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) - freesync_capable = true; - - connector->display_info.monitor_range.min_vfreq = vsdb_info.min_refresh_rate_hz; - connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; - } + monitor_range_from_vsdb(connector, &vsdb_info); + freesync_capable = copy_range_to_amdgpu_connector(connector); } update: From 78b30281288d76cc537b4aa2287afad87ecece12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:32 +0100 Subject: [PATCH 03/17] drm/amd/display: Check for VRR range in CEA AMD vsdb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] Some monitors only expose GTF ranges (or others, without Range Limits Only flag). This breaks VRR even though they have explicit FreeSync support. Currently, if monitor ranges were missing, amdgpu only searched for AMD vsdb in DisplayID but many monitors have it in CEA, just like HDMI. [How] For DP and eDP connections, check for VRR ranges provided in AMD vendor- specific data block if VRR range wasn't detected. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/3894 Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4457 Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4747 Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4856 Signed-off-by: Tomasz Pakuła --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 ++++++ 1 file changed, 6 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 ec41fefe39755..b6f01e51077a3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -13035,6 +13035,12 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, if (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || sink->sink_signal == SIGNAL_TYPE_EDP) { + /* + * Many monitors expose AMD vsdb in CAE even for DP and their + * monitor ranges do not contain Range Limits Only flag + */ + if (valid_vsdb_cea && is_monitor_range_invalid(connector)) + monitor_range_from_vsdb(connector, &vsdb_info); if (dpcd_caps.allow_invalid_MSA_timing_param) freesync_capable = copy_range_to_amdgpu_connector(connector); From e46c5efab4cf17e75bad5929a026e9314d16a7dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:33 +0100 Subject: [PATCH 04/17] drm/amd/display: Use bigger VRR range if found in AMD vsdb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] Some monitors only expose their full VRR range in AMD vsdb for some reason. [How] Compare exposed ranges and use the bigger one. This check could be merged with the previous one but it's better to keep them separate to easily convey their meaning. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4177 Signed-off-by: Tomasz Pakuła --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 16 ++++++++++++++++ 1 file changed, 16 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 b6f01e51077a3..ad6b613732471 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -12969,6 +12969,18 @@ static bool copy_range_to_amdgpu_connector(struct drm_connector *conn) return is_freesync_capable(range); } +/** + * Returns true if range from AMD vsdb is bigger + */ +static bool compare_ranges(struct drm_connector *conn, + struct amdgpu_hdmi_vsdb_info *vsdb) +{ + struct drm_monitor_range_info *range = &conn->display_info.monitor_range; + + return (vsdb->max_refresh_rate_hz - vsdb->min_refresh_rate_hz) > + (range->max_vfreq - range->min_vfreq); +} + /** * amdgpu_dm_update_freesync_caps - Update Freesync capabilities * @@ -13042,6 +13054,10 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, if (valid_vsdb_cea && is_monitor_range_invalid(connector)) monitor_range_from_vsdb(connector, &vsdb_info); + /* Use bigger range if found in AMD vsdb */ + if (valid_vsdb_cea && compare_ranges(connector, &vsdb_info)) + monitor_range_from_vsdb(connector, &vsdb_info); + if (dpcd_caps.allow_invalid_MSA_timing_param) freesync_capable = copy_range_to_amdgpu_connector(connector); From 9430df3291a72af5b1782994033c7698939696b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:34 +0100 Subject: [PATCH 05/17] drm/amd/display: Refactor PCON VRR compatibility check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] DP->HDMI PCONs prevously entered the DP path [How] Restructure amdgpu_dm_update_freesync_caps() and move dm_get_adaptive_sync_support_type() to dm_helpers_is_vrr_pcon_allowed() to better reflect what this function does. It never actually gave us any other info. Signed-off-by: Tomasz Pakuła --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 25 +++++++----- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 39 ++++++------------- drivers/gpu/drm/amd/display/dc/dm_helpers.h | 2 +- 3 files changed, 28 insertions(+), 38 deletions(-) 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 ad6b613732471..fdd2b737153b5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -13007,7 +13007,8 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, bool freesync_capable = false; bool valid_vsdb_cea = false; bool vsdb_freesync = false; - enum adaptive_sync_type as_type = ADAPTIVE_SYNC_TYPE_NONE; + bool pcon_allowed = false; + bool is_pcon = false; if (!connector->state) { drm_err(adev_to_drm(adev), "%s - Connector has no state", __func__); @@ -13035,18 +13036,24 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version)) goto update; + /* Gather all data */ edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() valid_vsdb_cea = parse_amd_vsdb_cea(amdgpu_dm_connector, edid, &vsdb_info) >= 0; vsdb_freesync = valid_vsdb_cea && vsdb_info.freesync_supported; - if (amdgpu_dm_connector->dc_link) + + if (amdgpu_dm_connector->dc_link) { dpcd_caps = amdgpu_dm_connector->dc_link->dpcd_caps; + is_pcon = dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER; + pcon_allowed = dm_helpers_is_vrr_pcon_allowed(amdgpu_dm_connector->dc_link); + } /* Some eDP panels only have the refresh rate range info in DisplayID */ if (is_monitor_range_invalid(connector)) parse_edid_displayid_vrr(connector, edid); - if (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || - sink->sink_signal == SIGNAL_TYPE_EDP) { + /* DP & eDP excluding PCONs */ + if ((sink->sink_signal == SIGNAL_TYPE_EDP || + sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT) && !is_pcon) { /* * Many monitors expose AMD vsdb in CAE even for DP and their * monitor ranges do not contain Range Limits Only flag @@ -13071,17 +13078,15 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP; } + /* HDMI */ } else if (sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A && vsdb_freesync) { monitor_range_from_vsdb(connector, &vsdb_info); freesync_capable = copy_range_to_amdgpu_connector(connector); - } - - if (amdgpu_dm_connector->dc_link) - as_type = dm_get_adaptive_sync_support_type(amdgpu_dm_connector->dc_link); - if (as_type == FREESYNC_TYPE_PCON_IN_WHITELIST && vsdb_freesync) { + /* DP -> HDMI PCON */ + } else if (pcon_allowed && vsdb_freesync) { + amdgpu_dm_connector->as_type = FREESYNC_TYPE_PCON_IN_WHITELIST; amdgpu_dm_connector->pack_sdp_v1_3 = true; - amdgpu_dm_connector->as_type = as_type; amdgpu_dm_connector->vsdb_info = vsdb_info; monitor_range_from_vsdb(connector, &vsdb_info); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index d0f770dd0a956..40e90676c998b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -1366,40 +1366,25 @@ void dm_helpers_dp_mst_update_branch_bandwidth( // TODO } -static bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id) +bool dm_helpers_is_vrr_pcon_allowed(const struct dc_link *link) { - bool ret_val = false; + if (link->dpcd_caps.dongle_type != DISPLAY_DONGLE_DP_HDMI_CONVERTER) + return false; + + if (!link->dpcd_caps.allow_invalid_MSA_timing_param) + return false; + + if (!link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT) + return false; - switch (branch_dev_id) { + switch (link->dpcd_caps.branch_dev_id) { case DP_BRANCH_DEVICE_ID_0060AD: case DP_BRANCH_DEVICE_ID_00E04C: case DP_BRANCH_DEVICE_ID_90CC24: - ret_val = true; - break; - default: - break; - } - - return ret_val; -} - -enum adaptive_sync_type dm_get_adaptive_sync_support_type(struct dc_link *link) -{ - struct dpcd_caps *dpcd_caps = &link->dpcd_caps; - enum adaptive_sync_type as_type = ADAPTIVE_SYNC_TYPE_NONE; - - switch (dpcd_caps->dongle_type) { - case DISPLAY_DONGLE_DP_HDMI_CONVERTER: - if (dpcd_caps->adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT == true && - dpcd_caps->allow_invalid_MSA_timing_param == true && - dm_is_freesync_pcon_whitelist(dpcd_caps->branch_dev_id)) - as_type = FREESYNC_TYPE_PCON_IN_WHITELIST; - break; - default: - break; + return true; } - return as_type; + return false; } bool dm_helpers_is_fullscreen(struct dc_context *ctx, struct dc_stream_state *stream) diff --git a/drivers/gpu/drm/amd/display/dc/dm_helpers.h b/drivers/gpu/drm/amd/display/dc/dm_helpers.h index 9d160b39e8c5a..f8b45a09d6806 100644 --- a/drivers/gpu/drm/amd/display/dc/dm_helpers.h +++ b/drivers/gpu/drm/amd/display/dc/dm_helpers.h @@ -219,10 +219,10 @@ int dm_helpers_dmub_set_config_sync(struct dc_context *ctx, const struct dc_link *link, struct set_config_cmd_payload *payload, enum set_config_status *operation_result); -enum adaptive_sync_type dm_get_adaptive_sync_support_type(struct dc_link *link); enum dc_edid_status dm_helpers_get_sbios_edid(struct dc_link *link, struct dc_edid *edid); +bool dm_helpers_is_vrr_pcon_allowed(const struct dc_link *link); bool dm_helpers_is_fullscreen(struct dc_context *ctx, struct dc_stream_state *stream); bool dm_helpers_is_hdr_on(struct dc_context *ctx, struct dc_stream_state *stream); From efe0231a3cf5d2d161bc7d57b07c77efd6e09d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:35 +0100 Subject: [PATCH 06/17] drm/amd/display: Add PCON VRR ID check override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] It's currently very hard to test if a random PCON supports VRR and report it's ID. [How] Adds override as part of dc debug mask. Allows faster testing and reporting of VRR-compatible DP->HDMI adapters. Signed-off-by: Tomasz Pakuła --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 +++++- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 8 +++++++- drivers/gpu/drm/amd/display/dc/dc.h | 1 + drivers/gpu/drm/amd/display/dc/dm_helpers.h | 2 +- drivers/gpu/drm/amd/include/amd_shared.h | 6 ++++++ 5 files changed, 20 insertions(+), 3 deletions(-) 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 fdd2b737153b5..f8a45af4b70e4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -2069,6 +2069,9 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) if (amdgpu_dc_debug_mask & DC_SKIP_DETECTION_LT) adev->dm.dc->debug.skip_detection_link_training = true; + if (amdgpu_dc_debug_mask & DC_OVERRIDE_PCON_VRR_ID_CHECK) + adev->dm.dc->debug.override_pcon_vrr_id_check = true; + adev->dm.dc->debug.visual_confirm = amdgpu_dc_visual_confirm; /* TODO: Remove after DP2 receiver gets proper support of Cable ID feature */ @@ -13044,7 +13047,8 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, if (amdgpu_dm_connector->dc_link) { dpcd_caps = amdgpu_dm_connector->dc_link->dpcd_caps; is_pcon = dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER; - pcon_allowed = dm_helpers_is_vrr_pcon_allowed(amdgpu_dm_connector->dc_link); + pcon_allowed = dm_helpers_is_vrr_pcon_allowed( + amdgpu_dm_connector->dc_link, connector->dev); } /* Some eDP panels only have the refresh rate range info in DisplayID */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 40e90676c998b..b339a09fb9d82 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -1366,7 +1366,7 @@ void dm_helpers_dp_mst_update_branch_bandwidth( // TODO } -bool dm_helpers_is_vrr_pcon_allowed(const struct dc_link *link) +bool dm_helpers_is_vrr_pcon_allowed(const struct dc_link *link, const struct drm_device *dev) { if (link->dpcd_caps.dongle_type != DISPLAY_DONGLE_DP_HDMI_CONVERTER) return false; @@ -1384,6 +1384,12 @@ bool dm_helpers_is_vrr_pcon_allowed(const struct dc_link *link) return true; } + if (link->dc->debug.override_pcon_vrr_id_check) { + drm_info(dev, "Overriding VRR PCON check for ID: 0x%06x\n", + link->dpcd_caps.branch_dev_id); + return true; + } + return false; } diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 98f0b6b3c2136..86ed1c783158f 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1014,6 +1014,7 @@ struct dc_debug_options { bool scl_reset_length10; bool hdmi20_disable; bool skip_detection_link_training; + bool override_pcon_vrr_id_check; uint32_t edid_read_retry_times; unsigned int force_odm_combine; //bit vector based on otg inst unsigned int seamless_boot_odm_combine; diff --git a/drivers/gpu/drm/amd/display/dc/dm_helpers.h b/drivers/gpu/drm/amd/display/dc/dm_helpers.h index f8b45a09d6806..ea94c52d2b871 100644 --- a/drivers/gpu/drm/amd/display/dc/dm_helpers.h +++ b/drivers/gpu/drm/amd/display/dc/dm_helpers.h @@ -222,7 +222,7 @@ int dm_helpers_dmub_set_config_sync(struct dc_context *ctx, enum dc_edid_status dm_helpers_get_sbios_edid(struct dc_link *link, struct dc_edid *edid); -bool dm_helpers_is_vrr_pcon_allowed(const struct dc_link *link); +bool dm_helpers_is_vrr_pcon_allowed(const struct dc_link *link, const struct drm_device *dev); bool dm_helpers_is_fullscreen(struct dc_context *ctx, struct dc_stream_state *stream); bool dm_helpers_is_hdr_on(struct dc_context *ctx, struct dc_stream_state *stream); diff --git a/drivers/gpu/drm/amd/include/amd_shared.h b/drivers/gpu/drm/amd/include/amd_shared.h index 75efda2969cfb..36de94634aadb 100644 --- a/drivers/gpu/drm/amd/include/amd_shared.h +++ b/drivers/gpu/drm/amd/include/amd_shared.h @@ -410,6 +410,12 @@ enum DC_DEBUG_MASK { * @DC_SKIP_DETECTION_LT: (0x200000) If set, skip detection link training */ DC_SKIP_DETECTION_LT = 0x200000, + + /** + * @DC_OVERRIDE_PCON_VRR_ID_CHECK: (0x400000) If set, always return true if checking for + * PCON VRR compatibility and print it's ID in kernel log. + */ + DC_OVERRIDE_PCON_VRR_ID_CHECK = 0x400000, }; enum amd_dpm_forced_level; From 5ed48a9f5e2e702ced251953ddcb0c769ec819d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:36 +0100 Subject: [PATCH 07/17] drm/amd/display: Add CH7218 PCON ID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] Chrontel CH7218 found in Ugreen DP -> HDMI 2.1 adapter (model 85564) works perfectly with VRR after testing. VRR and FreeSync compatibility is explicitly advertised as a feature so it's addition is a formality. Support FreeSync info packet passthrough and "generic" HDMI VRR. [How] Add CH7218's ID to dm_helpers_is_vrr_pcon_allowed() Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4773 Signed-off-by: Tomasz Pakuła --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 1 + drivers/gpu/drm/amd/display/include/ddc_service_types.h | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index b339a09fb9d82..e03321c22f4c3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -1381,6 +1381,7 @@ bool dm_helpers_is_vrr_pcon_allowed(const struct dc_link *link, const struct drm case DP_BRANCH_DEVICE_ID_0060AD: case DP_BRANCH_DEVICE_ID_00E04C: case DP_BRANCH_DEVICE_ID_90CC24: + case DP_BRANCH_DEVICE_ID_2B02F0: return true; } diff --git a/drivers/gpu/drm/amd/display/include/ddc_service_types.h b/drivers/gpu/drm/amd/display/include/ddc_service_types.h index 1c603b12957f6..e838f7c1269c4 100644 --- a/drivers/gpu/drm/amd/display/include/ddc_service_types.h +++ b/drivers/gpu/drm/amd/display/include/ddc_service_types.h @@ -36,6 +36,7 @@ #define DP_BRANCH_DEVICE_ID_006037 0x006037 #define DP_BRANCH_DEVICE_ID_001CF8 0x001CF8 #define DP_BRANCH_DEVICE_ID_0060AD 0x0060AD +#define DP_BRANCH_DEVICE_ID_2B02F0 0x2B02F0 /* Chrontel CH7218 */ #define DP_BRANCH_HW_REV_10 0x10 #define DP_BRANCH_HW_REV_20 0x20 From 88f87c911249a6946bde08b5a8a30f437919f79d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:37 +0100 Subject: [PATCH 08/17] drm/edid: Parse more info from HDMI Forum vsdb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] Drivers may need info about gaming features exposed by HDMI sinks. Add a central way of storing this information. [How] Adds flags and a struct to hold HDMI VRR information. `supported` here is an additional property which allows easier parsing in consumers and adds a bit of logic used to detect malformed VRRmin/VRRmax values. Signed-off-by: Tomasz Pakuła Tested-by: Bernhard Berger --- drivers/gpu/drm/drm_edid.c | 41 +++++++++++++++++++++++++++++++- include/drm/drm_connector.h | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index e2e85345aa9a4..5bdacd425908b 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -6149,6 +6149,33 @@ static void drm_parse_ycbcr420_deep_color_info(struct drm_connector *connector, hdmi->y420_dc_modes = dc_mask; } +static void drm_parse_hdmi_gaming_info(struct drm_hdmi_info *hdmi, const u8 *db) +{ + struct drm_hdmi_vrr_cap *vrr = &hdmi->vrr_cap; + + if (cea_db_payload_len(db) < 8) + return; + + hdmi->fapa_start_location = db[8] & DRM_EDID_FAPA_START_LOCATION; + hdmi->allm = db[8] & DRM_EDID_ALLM; + vrr->fva = db[8] & DRM_EDID_FVA; + vrr->cnmvrr = db[8] & DRM_EDID_CNMVRR; + vrr->cinema_vrr = db[8] & DRM_EDID_CINEMA_VRR; + vrr->mdelta = db[8] & DRM_EDID_MDELTA; + + if (cea_db_payload_len(db) < 9) + return; + + vrr->vrr_min = db[9] & DRM_EDID_VRR_MIN_MASK; + vrr->supported = (vrr->vrr_min > 0 && vrr->vrr_min <= 48); + + if (cea_db_payload_len(db) < 10) + return; + + vrr->vrr_max = (db[9] & DRM_EDID_VRR_MAX_UPPER_MASK) << 2 | db[10]; + vrr->supported &= (vrr->vrr_max == 0 || vrr->vrr_max >= 100); +} + static void drm_parse_dsc_info(struct drm_hdmi_dsc_cap *hdmi_dsc, const u8 *hf_scds) { @@ -6274,7 +6301,7 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector, } drm_parse_ycbcr420_deep_color_info(connector, hf_scds); - + drm_parse_hdmi_gaming_info(&connector->display_info.hdmi, hf_scds); if (cea_db_payload_len(hf_scds) >= 11 && hf_scds[11]) { drm_parse_dsc_info(hdmi_dsc, hf_scds); dsc_support = true; @@ -6284,6 +6311,18 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector, "[CONNECTOR:%d:%s] HF-VSDB: max TMDS clock: %d KHz, HDMI 2.1 support: %s, DSC 1.2 support: %s\n", connector->base.id, connector->name, max_tmds_clock, str_yes_no(max_frl_rate), str_yes_no(dsc_support)); + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] FAPA in blanking: %s, ALLM support: %s, Fast Vactive support: %s\n", + connector->base.id, connector->name, str_yes_no(hdmi->fapa_start_location), + str_yes_no(hdmi->allm), str_yes_no(hdmi->vrr_cap.fva)); + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Negative M VRR support: %s, CinemaVRR support: %s, Mdelta: %d\n", + connector->base.id, connector->name, str_yes_no(hdmi->vrr_cap.cnmvrr), + str_yes_no(hdmi->vrr_cap.cinema_vrr), hdmi->vrr_cap.mdelta); + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] VRRmin: %u, VRRmax: %u, VRR supported: %s\n", + connector->base.id, connector->name, hdmi->vrr_cap.vrr_min, + hdmi->vrr_cap.vrr_max, str_yes_no(hdmi->vrr_cap.supported)); } static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector, diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 8f34f4b8183d8..dab9d5521f41b 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -254,6 +254,44 @@ struct drm_scdc { struct drm_scrambling scrambling; }; +/** + * struct drm_hdmi_vrr_cap - Information about VRR capabilities of a HDMI sink + * + * Describes the VRR support provided by HDMI 2.1 sink. The information is + * fetched fom additional HFVSDB blocks defined for HDMI 2.1. + */ +struct drm_hdmi_vrr_cap { + /** @fva: flag for Fast VActive (Quick Frame Transport) support */ + bool fva; + + /** @mcnmvrr: flag for Negative M VRR support */ + bool cnmvrr; + + /** @mcinema_vrr: flag for Cinema VRR support */ + bool cinema_vrr; + + /** @mdelta: flag for limited frame-to-frame compensation support */ + bool mdelta; + + /** + * @vrr_min : minimum supported variable refresh rate in Hz. + * Valid values only inide 1 - 48 range + */ + u16 vrr_min; + + /** + * @vrr_max : maximum supported variable refresh rate in Hz (optional). + * Valid values are either 0 (max based on video mode) or >= 100 + */ + u16 vrr_max; + + /** + * @supported: flag for vrr support based on checking for VRRmin and + * VRRmax values having correct values. + */ + bool supported; +}; + /** * struct drm_hdmi_dsc_cap - DSC capabilities of HDMI sink * @@ -330,6 +368,15 @@ struct drm_hdmi_info { /** @max_lanes: supported by sink */ u8 max_lanes; + /** @fapa_start_location: flag for the FAPA in blanking support */ + bool fapa_start_location; + + /** @allm: flag for Auto Low Latency Mode support by sink */ + bool allm; + + /** @vrr_cap: VRR capabilities of the sink */ + struct drm_hdmi_vrr_cap vrr_cap; + /** @dsc_cap: DSC capabilities of the sink */ struct drm_hdmi_dsc_cap dsc_cap; }; From 3e290afa814b26b46297e2004e27a57250909ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:38 +0100 Subject: [PATCH 09/17] drm/amd/display: Rename PCON adaptive sync types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] PCONs support sending out HDMI VRR infopackets on their own and this makes this types not specific to FreeSync [How] Make the name more generic for the upcoming HDMI VRR over PCON implementation Signed-off-by: Tomasz Pakuła Tested-by: Bernhard Berger --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 5 +++-- .../gpu/drm/amd/display/modules/inc/mod_info_packet.h | 10 +++++----- .../drm/amd/display/modules/info_packet/info_packet.c | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) 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 f8a45af4b70e4..69ee1c20ed5a9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -9362,7 +9362,8 @@ static void update_freesync_state_on_stream( aconn = (struct amdgpu_dm_connector *)new_stream->dm_stream_context; - if (aconn && (aconn->as_type == FREESYNC_TYPE_PCON_IN_WHITELIST || aconn->vsdb_info.replay_mode)) { + if (aconn && (aconn->as_type == ADAPTIVE_SYNC_TYPE_PCON_ALLOWED || + aconn->vsdb_info.replay_mode)) { pack_sdp_v1_3 = aconn->pack_sdp_v1_3; if (aconn->vsdb_info.amd_vsdb_version == 1) @@ -13089,7 +13090,7 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, /* DP -> HDMI PCON */ } else if (pcon_allowed && vsdb_freesync) { - amdgpu_dm_connector->as_type = FREESYNC_TYPE_PCON_IN_WHITELIST; + amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_PCON_ALLOWED; amdgpu_dm_connector->pack_sdp_v1_3 = true; amdgpu_dm_connector->vsdb_info = vsdb_info; diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h index 66dc9a19aebe5..040aa14cb1ce3 100644 --- a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h @@ -42,11 +42,11 @@ void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, struct dc_info_packet *info_packet); enum adaptive_sync_type { - ADAPTIVE_SYNC_TYPE_NONE = 0, - ADAPTIVE_SYNC_TYPE_DP = 1, - FREESYNC_TYPE_PCON_IN_WHITELIST = 2, - FREESYNC_TYPE_PCON_NOT_IN_WHITELIST = 3, - ADAPTIVE_SYNC_TYPE_EDP = 4, + ADAPTIVE_SYNC_TYPE_NONE = 0, + ADAPTIVE_SYNC_TYPE_DP = 1, + ADAPTIVE_SYNC_TYPE_PCON_ALLOWED = 2, + ADAPTIVE_SYNC_TYPE_PCON_NOT_ALLOWED = 3, + ADAPTIVE_SYNC_TYPE_EDP = 4, }; enum adaptive_sync_sdp_version { diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c index b3d55cac35694..b50fb75236e93 100644 --- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c @@ -535,12 +535,12 @@ void mod_build_adaptive_sync_infopacket(const struct dc_stream_state *stream, if (stream != NULL) mod_build_adaptive_sync_infopacket_v2(stream, param, info_packet); break; - case FREESYNC_TYPE_PCON_IN_WHITELIST: + case ADAPTIVE_SYNC_TYPE_PCON_ALLOWED: case ADAPTIVE_SYNC_TYPE_EDP: mod_build_adaptive_sync_infopacket_v1(info_packet); break; case ADAPTIVE_SYNC_TYPE_NONE: - case FREESYNC_TYPE_PCON_NOT_IN_WHITELIST: + case ADAPTIVE_SYNC_TYPE_PCON_NOT_ALLOWED: default: break; } From df079cdc99cade2288c413f1064f6ccda7fc108b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:39 +0100 Subject: [PATCH 10/17] drm/amd/display: Enable HDMI VRR over PCON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] Not all TVs support FreeSync and many TVs suffer from VRR flickering while Freesync is activated. [How] This works the same as FreeSync over PCON just without sending FreeSync info packets (we're sending standard DisplayPort info packets) + reading the VRR range from the HDMI Forum vendor specific data block. PCONs take over HDMI VRR triggering. Prefer HDMI VRR over FreeSync to reduce VRR flickering on many TVs. FreeSync over HDMI seems to be a fallback solution and not a first-class citizen. This especially helps VMM7100. Tested with VMM7100 and CH7218 based adapters on multiple HDMI 2.1 and HDMI 2.0 devices. (Samsung S95B, LG C4, Sony Bravia 8, Dell AW3423DWF) Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4805 Signed-off-by: Tomasz Pakuła Tested-by: Bernhard Berger --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) 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 69ee1c20ed5a9..4300433dc69a3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -12958,6 +12958,20 @@ static void monitor_range_from_vsdb(struct drm_connector *conn, range->max_vfreq = vsdb->max_refresh_rate_hz; } +/* + * Get VRR range from HDMI VRR info in EDID. + * + * @conn: drm_connector with HDMI VRR info + */ +static void monitor_range_from_hdmi(struct drm_connector *conn) +{ + struct drm_monitor_range_info *range = &conn->display_info.monitor_range; + struct drm_hdmi_vrr_cap *caps = &conn->display_info.hdmi.vrr_cap; + + range->min_vfreq = caps->vrr_min; + range->max_vfreq = caps->vrr_max; +} + /** * Returns true if connector is capable of freesync * Optionally, can fetch the range from AMD vsdb @@ -13007,6 +13021,7 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; struct amdgpu_hdmi_vsdb_info vsdb_did = {0}; struct dpcd_caps dpcd_caps = {0}; + struct drm_hdmi_vrr_cap *hdmi_vrr; const struct edid *edid; bool freesync_capable = false; bool valid_vsdb_cea = false; @@ -13044,6 +13059,7 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() valid_vsdb_cea = parse_amd_vsdb_cea(amdgpu_dm_connector, edid, &vsdb_info) >= 0; vsdb_freesync = valid_vsdb_cea && vsdb_info.freesync_supported; + hdmi_vrr = &connector->display_info.hdmi.vrr_cap; if (amdgpu_dm_connector->dc_link) { dpcd_caps = amdgpu_dm_connector->dc_link->dpcd_caps; @@ -13089,12 +13105,17 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, freesync_capable = copy_range_to_amdgpu_connector(connector); /* DP -> HDMI PCON */ - } else if (pcon_allowed && vsdb_freesync) { - amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_PCON_ALLOWED; - amdgpu_dm_connector->pack_sdp_v1_3 = true; - amdgpu_dm_connector->vsdb_info = vsdb_info; + } else if (pcon_allowed) { + /* Prefer HDMI VRR */ + if (hdmi_vrr->supported && hdmi_vrr->vrr_max > 0) + monitor_range_from_hdmi(connector); + else if (vsdb_freesync) { + amdgpu_dm_connector->vsdb_info = vsdb_info; + monitor_range_from_vsdb(connector, &vsdb_info); + } - monitor_range_from_vsdb(connector, &vsdb_info); + amdgpu_dm_connector->pack_sdp_v1_3 = true; + amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_PCON_ALLOWED; freesync_capable = copy_range_to_amdgpu_connector(connector); } From 4297801b827204221b9902e368390bb968d41efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:40 +0100 Subject: [PATCH 11/17] drm/amd/display: Support HDMI VRRmax=0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] VRRmax=0 is a valid value and means that the upper bound is guared by the selected video mode. [How] In this context, saved vrr max is the max possible refresh rate ever. Try getting upper VRR bound from AMD vsdbif it exists or rely on the limitations of BRR in VTEM info frames. I found through testing, that TVs seem to reject VTEM when BRR is set to over 1000 Hz. Use this as the last resort VRRmax. Signed-off-by: Tomasz Pakuła --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 22 ++++++++++++++----- .../amd/display/modules/inc/mod_info_packet.h | 2 ++ 2 files changed, 19 insertions(+), 5 deletions(-) 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 4300433dc69a3..de7586776abf2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -12959,17 +12959,29 @@ static void monitor_range_from_vsdb(struct drm_connector *conn, } /* - * Get VRR range from HDMI VRR info in EDID. + * Get VRR range from HDMI VRR info in EDID. If VRRmax == 0, + * try getting upper bound from AMD vsdb (if passed). * * @conn: drm_connector with HDMI VRR info + * @vsdb: AMD vsdb from CAE. Can be NULL if not found. */ -static void monitor_range_from_hdmi(struct drm_connector *conn) +static void monitor_range_from_hdmi(struct drm_connector *conn, + const struct amdgpu_hdmi_vsdb_info *vsdb) { struct drm_monitor_range_info *range = &conn->display_info.monitor_range; struct drm_hdmi_vrr_cap *caps = &conn->display_info.hdmi.vrr_cap; + u16 vrr_max = caps->vrr_max; + + /* Try getting upper vrr bound from AMD vsdb */ + if (vrr_max == 0 && vsdb) + vrr_max = vsdb->max_refresh_rate_hz; + + /* Use max possible BRR value as a last resort */ + if (vrr_max == 0) + vrr_max = VTEM_BRR_MAX; range->min_vfreq = caps->vrr_min; - range->max_vfreq = caps->vrr_max; + range->max_vfreq = vrr_max; } /** @@ -13107,8 +13119,8 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, /* DP -> HDMI PCON */ } else if (pcon_allowed) { /* Prefer HDMI VRR */ - if (hdmi_vrr->supported && hdmi_vrr->vrr_max > 0) - monitor_range_from_hdmi(connector); + if (hdmi_vrr->supported) + monitor_range_from_hdmi(connector, valid_vsdb_cea ? &vsdb_info : NULL); else if (vsdb_freesync) { amdgpu_dm_connector->vsdb_info = vsdb_info; monitor_range_from_vsdb(connector, &vsdb_info); diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h index 040aa14cb1ce3..bdbedd58ac722 100644 --- a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h @@ -33,6 +33,8 @@ struct dc_stream_state; struct dc_info_packet; struct mod_vrr_params; +#define VTEM_BRR_MAX 1000 + void mod_build_vsc_infopacket(const struct dc_stream_state *stream, struct dc_info_packet *info_packet, enum dc_color_space cs, From d658fe2fd7ae9b459e6d4c6e92da644f9c8548b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:41 +0100 Subject: [PATCH 12/17] drm/amd/display: Build HDMI vsif in correct slot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] HDMI vsif was assigned to vsp_infopacket (FreeSync) field [How] Build HDMI vsif in the correct hfvsif_infopacket field Signed-off-by: Tomasz Pakuła --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 de7586776abf2..4b3f2b57a02db 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -7282,7 +7282,7 @@ create_stream_for_sink(struct drm_connector *connector, update_stream_signal(stream, sink); if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A) - mod_build_hf_vsif_infopacket(stream, &stream->vsp_infopacket); + mod_build_hf_vsif_infopacket(stream, &stream->hfvsif_infopacket); if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT || stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST || From c2d1695f0214c831de5a0002fd732d9467a6d92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:42 +0100 Subject: [PATCH 13/17] drm/amd/display: Save HDMI gaming info to edid caps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] We need info about these features in parts of the driver where fishing for drm_connector struct is infeasible. [How] Add three new fields to dc_edid_caps and fill them if connected device is HDMI based on it's EDID Signed-off-by: Tomasz Pakuła --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 7 ++++++- drivers/gpu/drm/amd/display/dc/dc_types.h | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index e03321c22f4c3..c6efca2ad9df5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -136,7 +136,12 @@ enum dc_edid_status dm_helpers_parse_edid_caps( edid_caps->display_name, AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS); - edid_caps->edid_hdmi = connector->display_info.is_hdmi; + if (connector->display_info.is_hdmi) { + edid_caps->edid_hdmi = true; + edid_caps->allm = connector->display_info.hdmi.allm; + edid_caps->fva = connector->display_info.hdmi.vrr_cap.fva; + edid_caps->hdmi_vrr = connector->display_info.hdmi.vrr_cap.supported; + } apply_edid_quirks(dev, edid_buf, edid_caps); diff --git a/drivers/gpu/drm/amd/display/dc/dc_types.h b/drivers/gpu/drm/amd/display/dc/dc_types.h index b5aa03a3e39cf..012691adf72c1 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_types.h @@ -206,9 +206,14 @@ struct dc_edid_caps { uint32_t max_tmds_clk_mhz; - /*HDMI 2.0 caps*/ + /* HDMI 2.0 caps */ bool lte_340mcsc_scramble; + /* HDMI 2.1 caps */ + bool allm; + bool fva; + bool hdmi_vrr; + bool edid_hdmi; bool hdr_supported; bool rr_capable; From 4cdb7add388171ac17d9442cfa11d8f00c9996fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:43 +0100 Subject: [PATCH 14/17] drm/amd/display: Restore ALLM support in HDMI vsif MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] Support for triggering ALLM in modern TVs is missing. When HDMI vsif was added in 2019: commit 3c2381b92cba ("drm/amd/display: add support for VSIP info packet") it was improperly handeled as HDMI actually has two separate vsifs. The implementation was based on H14b-vsif and ALLM bit was messing it up because H14b-vsif doesn't support ALLM. It was later removed in: commit 75f77aafe281 ("drm/amd/display: Send H14b-VSIF specified in HDMI") ALLM is supported by hf-vsif (HDMI Forum) instead. [How] Add proper logic to construct either h14b-vsif or hf-vsif based on required capabilities. Currently, only ALLM from hf-vsif is supported. Turns out, hf-vsif is almost identical to h14b-vsif, BUT has additional two bytes of data after OUI. First byte is static and seems like a version supported by leftover define. Second byte consists of 3D and ALLM bits. Implement logic to offset 3D data if building hf-vsif. Signed-off-by: Tomasz Pakuła --- .../display/modules/info_packet/info_packet.c | 106 ++++++++++++------ 1 file changed, 70 insertions(+), 36 deletions(-) diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c index b50fb75236e93..a6c79fc3ac08e 100644 --- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c @@ -45,7 +45,10 @@ enum vsc_packet_revision { }; #define HDMI_INFOFRAME_TYPE_VENDOR 0x81 -#define HF_VSIF_VERSION 1 +#define HDMI_INFOFRAME_LENGTH_MASK 0x1F +#define HF_VSIF_VERSION 1 +#define HF_VSIF_3D_BIT 0 +#define HF_VSIF_ALLM_BIT 1 // VTEM Byte Offset #define VTEM_PB0 0 @@ -441,9 +444,28 @@ void mod_build_vsc_infopacket(const struct dc_stream_state *stream, } } +static bool is_hdmi_vic_mode(const struct dc_stream_state *stream) +{ + bool allm = stream->link->local_sink->edid_caps.allm; + bool stereo = stream->view_format != VIEW_3D_FORMAT_NONE; + + if (stream->timing.hdmi_vic == 0) + return false; + + if (stream->timing.h_total < 3840 || + stream->timing.v_total < 2160) + return false; + + if (stereo || allm) + return false; + + return true; +} + /** * mod_build_hf_vsif_infopacket - Prepare HDMI Vendor Specific info frame. * Follows HDMI Spec to build up Vendor Specific info frame + * Conforms to h14b-vsif or hf-vsif based on the capabilities * * @stream: contains data we may need to construct VSIF (i.e. timing_3d_format, etc.) * @info_packet: output structure where to store VSIF @@ -451,63 +473,75 @@ void mod_build_vsc_infopacket(const struct dc_stream_state *stream, void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, struct dc_info_packet *info_packet) { - unsigned int length = 5; bool hdmi_vic_mode = false; + bool allm = false; + bool stereo = false; uint8_t checksum = 0; - uint32_t i = 0; + uint8_t offset = 0; + uint8_t i = 0; + uint8_t length = 5; + uint32_t oui = HDMI_IEEE_OUI; enum dc_timing_3d_format format; info_packet->valid = false; + format = stream->timing.timing_3d_format; if (stream->view_format == VIEW_3D_FORMAT_NONE) format = TIMING_3D_FORMAT_NONE; + stereo = format != TIMING_3D_FORMAT_NONE; + hdmi_vic_mode = is_hdmi_vic_mode(stream); - if (stream->timing.hdmi_vic != 0 - && stream->timing.h_total >= 3840 - && stream->timing.v_total >= 2160 - && format == TIMING_3D_FORMAT_NONE) - hdmi_vic_mode = true; - - if ((format == TIMING_3D_FORMAT_NONE) && !hdmi_vic_mode) + if (!stereo && !hdmi_vic_mode && !allm) return; - info_packet->sb[1] = 0x03; - info_packet->sb[2] = 0x0C; - info_packet->sb[3] = 0x00; + if (allm) + oui = HDMI_FORUM_IEEE_OUI; - if (format != TIMING_3D_FORMAT_NONE) - info_packet->sb[4] = (2 << 5); + info_packet->sb[1] = oui & 0xff; + info_packet->sb[2] = (oui >> 8) & 0xff; + info_packet->sb[3] = (oui >> 16) & 0xff; - else if (hdmi_vic_mode) - info_packet->sb[4] = (1 << 5); + if (oui == HDMI_FORUM_IEEE_OUI) { + offset = 2; + length += 2; + info_packet->sb[4] = HF_VSIF_VERSION; + info_packet->sb[5] = stereo << HF_VSIF_3D_BIT; + info_packet->sb[5] = allm << HF_VSIF_ALLM_BIT; + } - switch (format) { - case TIMING_3D_FORMAT_HW_FRAME_PACKING: - case TIMING_3D_FORMAT_SW_FRAME_PACKING: - info_packet->sb[5] = (0x0 << 4); - break; + if (stereo) { + info_packet->sb[4 + offset] = (2 << 5); - case TIMING_3D_FORMAT_SIDE_BY_SIDE: - case TIMING_3D_FORMAT_SBS_SW_PACKED: - info_packet->sb[5] = (0x8 << 4); - length = 6; - break; + switch (format) { + case TIMING_3D_FORMAT_HW_FRAME_PACKING: + case TIMING_3D_FORMAT_SW_FRAME_PACKING: + info_packet->sb[5 + offset] = (0x0 << 4); + break; - case TIMING_3D_FORMAT_TOP_AND_BOTTOM: - case TIMING_3D_FORMAT_TB_SW_PACKED: - info_packet->sb[5] = (0x6 << 4); - break; + case TIMING_3D_FORMAT_SIDE_BY_SIDE: + case TIMING_3D_FORMAT_SBS_SW_PACKED: + info_packet->sb[5 + offset] = (0x8 << 4); + ++length; + break; - default: - break; - } + case TIMING_3D_FORMAT_TOP_AND_BOTTOM: + case TIMING_3D_FORMAT_TB_SW_PACKED: + info_packet->sb[5 + offset] = (0x6 << 4); + break; - if (hdmi_vic_mode) + default: + break; + } + + /* Doesn't need the offset as it can't be used with hf-vsif */ + } else if (hdmi_vic_mode) { + info_packet->sb[4] = (1 << 5); info_packet->sb[5] = stream->timing.hdmi_vic; + } info_packet->hb0 = HDMI_INFOFRAME_TYPE_VENDOR; info_packet->hb1 = 0x01; - info_packet->hb2 = (uint8_t) (length); + info_packet->hb2 = length & HDMI_INFOFRAME_LENGTH_MASK; checksum += info_packet->hb0; checksum += info_packet->hb1; From 5fff6609a63586d87e60d42ba9f50d74edeafc3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:44 +0100 Subject: [PATCH 15/17] drm/amd/display: Trigger ALLM if it's available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] ALLM automatically puts TVs into low latency modes (gaming modes) which we basically always want for PC use, be it gaming, or using precise inputs like mice and keyboards. [How] Read the ALLM info from HDMI caps and use it to determine if ALLM should be indicated in HDMI Forum vsif. Additionally, make sure VIC modes are translated in case of ALLM active as VIC cannot be used in conjunction with hf-vsif. I learned this the hard way... Signed-off-by: Tomasz Pakuła --- drivers/gpu/drm/amd/display/dc/core/dc_resource.c | 4 +++- .../gpu/drm/amd/display/modules/info_packet/info_packet.c | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c index bc5dedf5f60c3..b96e2f86f2ea0 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c @@ -4409,6 +4409,7 @@ static void set_avi_info_frame( unsigned int rid = pipe_ctx->stream->timing.rid; unsigned int fr_ind = pipe_ctx->stream->timing.fr_index; enum dc_timing_3d_format format; + bool allm; if (stream->avi_infopacket.valid) { *info_packet = stream->avi_infopacket; @@ -4563,8 +4564,9 @@ static void set_avi_info_frame( if (pipe_ctx->stream->timing.hdmi_vic != 0) vic = 0; format = stream->timing.timing_3d_format; + allm = stream->link->local_sink->edid_caps.allm; /*todo, add 3DStereo support*/ - if (format != TIMING_3D_FORMAT_NONE) { + if ((format != TIMING_3D_FORMAT_NONE) || allm) { // Based on HDMI specs hdmi vic needs to be converted to cea vic when 3D is enabled switch (pipe_ctx->stream->timing.hdmi_vic) { case 1: diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c index a6c79fc3ac08e..9af918c5b24d8 100644 --- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c @@ -485,9 +485,10 @@ void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, info_packet->valid = false; - format = stream->timing.timing_3d_format; - if (stream->view_format == VIEW_3D_FORMAT_NONE) - format = TIMING_3D_FORMAT_NONE; + allm = stream->link->local_sink->edid_caps.allm; + format = stream->view_format == VIEW_3D_FORMAT_NONE ? + TIMING_3D_FORMAT_NONE : + stream->timing.timing_3d_format; stereo = format != TIMING_3D_FORMAT_NONE; hdmi_vic_mode = is_hdmi_vic_mode(stream); From d4d31e1a1da819aa5a95270a76cdc25f5549ba03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:45 +0100 Subject: [PATCH 16/17] drm/amd/display: Reintroduce VTEM info frame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] VTEM info fram building was removed back in: commit a9f54ce3c603 ("drm/amd/display: Refactoring VTEM"), but it's needed to support HDMI VRR signalling. [How] Build completely new and more robust functions to build out the VTEM infopacket. Many values are defined but could have added logic in the future, that's shy they are not static values but already value + bit position in it's byte. Reduced blanking detection was previously missing. The standards for RB and RBv2 use a fixed 160 or 80 lines for the horizontal blank period. RB v3 can use either 160 or 80. Use this to detect if the current timing mode uses reduced blankinig. It doesn't hurt that the new functions look better and cleaner Signed-off-by: Tomasz Pakuła --- .../amd/display/modules/inc/mod_info_packet.h | 4 + .../display/modules/info_packet/info_packet.c | 184 +++++++++++------- 2 files changed, 122 insertions(+), 66 deletions(-) diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h index bdbedd58ac722..18bfac21e3ca5 100644 --- a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h @@ -43,6 +43,10 @@ void mod_build_vsc_infopacket(const struct dc_stream_state *stream, void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, struct dc_info_packet *info_packet); +void mod_build_vtem_infopacket(const struct dc_stream_state *stream, + const struct mod_vrr_params *vrr, + struct dc_info_packet *infopacket); + enum adaptive_sync_type { ADAPTIVE_SYNC_TYPE_NONE = 0, ADAPTIVE_SYNC_TYPE_DP = 1, diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c index 9af918c5b24d8..15f87afb1fad6 100644 --- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c @@ -44,6 +44,7 @@ enum vsc_packet_revision { vsc_packet_rev5 = 5, }; +#define HDMI_INFOFRAME_TYPE_EMP 0x7F #define HDMI_INFOFRAME_TYPE_VENDOR 0x81 #define HDMI_INFOFRAME_LENGTH_MASK 0x1F #define HF_VSIF_VERSION 1 @@ -51,72 +52,51 @@ enum vsc_packet_revision { #define HF_VSIF_ALLM_BIT 1 // VTEM Byte Offset -#define VTEM_PB0 0 -#define VTEM_PB1 1 -#define VTEM_PB2 2 -#define VTEM_PB3 3 -#define VTEM_PB4 4 -#define VTEM_PB5 5 -#define VTEM_PB6 6 - -#define VTEM_MD0 7 -#define VTEM_MD1 8 -#define VTEM_MD2 9 -#define VTEM_MD3 10 - - -// VTEM Byte Masks -//PB0 -#define MASK_VTEM_PB0__RESERVED0 0x01 -#define MASK_VTEM_PB0__SYNC 0x02 -#define MASK_VTEM_PB0__VFR 0x04 -#define MASK_VTEM_PB0__AFR 0x08 -#define MASK_VTEM_PB0__DS_TYPE 0x30 - //0: Periodic pseudo-static EM Data Set - //1: Periodic dynamic EM Data Set - //2: Unique EM Data Set - //3: Reserved -#define MASK_VTEM_PB0__END 0x40 -#define MASK_VTEM_PB0__NEW 0x80 - -//PB1 -#define MASK_VTEM_PB1__RESERVED1 0xFF - -//PB2 -#define MASK_VTEM_PB2__ORGANIZATION_ID 0xFF - //0: This is a Vendor Specific EM Data Set - //1: This EM Data Set is defined by This Specification (HDMI 2.1 r102.clean) - //2: This EM Data Set is defined by CTA-861-G - //3: This EM Data Set is defined by VESA -//PB3 -#define MASK_VTEM_PB3__DATA_SET_TAG_MSB 0xFF -//PB4 -#define MASK_VTEM_PB4__DATA_SET_TAG_LSB 0xFF -//PB5 -#define MASK_VTEM_PB5__DATA_SET_LENGTH_MSB 0xFF -//PB6 -#define MASK_VTEM_PB6__DATA_SET_LENGTH_LSB 0xFF - - - -//PB7-27 (20 bytes): -//PB7 = MD0 -#define MASK_VTEM_MD0__VRR_EN 0x01 -#define MASK_VTEM_MD0__M_CONST 0x02 -#define MASK_VTEM_MD0__QMS_EN 0x04 -#define MASK_VTEM_MD0__RESERVED2 0x08 -#define MASK_VTEM_MD0__FVA_FACTOR_M1 0xF0 - -//MD1 -#define MASK_VTEM_MD1__BASE_VFRONT 0xFF - -//MD2 -#define MASK_VTEM_MD2__BASE_REFRESH_RATE_98 0x03 -#define MASK_VTEM_MD2__RB 0x04 -#define MASK_VTEM_MD2__NEXT_TFR 0xF8 - -//MD3 -#define MASK_VTEM_MD3__BASE_REFRESH_RATE_07 0xFF +#define VTEM_ORG_ID 1 +#define VTEM_DATA_SET_TAG 1 +#define VTEM_DATA_SET_LENGTH 4 + +#define VTEM_M_CONST 0 +#define VTEM_FVA_FACTOR 0 + +#define VTEM_BRR_MASK_UPPER 0x03 +#define VTEM_BRR_MASK_LOWER 0xFF + +/* VTEM Byte Offset */ +#define VTEM_PB0 0 +#define VTEM_PB1 1 +#define VTEM_PB2 2 +#define VTEM_PB3 3 +#define VTEM_PB4 4 +#define VTEM_PB5 5 +#define VTEM_PB6 6 + +#define VTEM_MD0 7 +#define VTEM_MD1 8 +#define VTEM_MD2 9 +#define VTEM_MD3 10 + +/* Extended Metadata Packet */ +/* Header */ +#define EMP_LAST_BIT 6 +#define EMP_FIRST_BIT 7 +/* PB0 */ +#define EMP_SNC_BIT 1 +#define EMP_VFR_BIT 2 +#define EMP_AFR_BIT 3 +#define EMP_DST_BIT 4 +#define EMP_END_BIT 6 +#define EMP_NEW_BIT 7 +/* PB7 = MD0 */ +#define VTEM_VRR_BIT 0 +#define VTEM_M_CONST_BIT 1 +#define VTEM_FVA_BIT 4 +/* MD1 Base_Vfront */ +/* MD2 */ +#define VTEM_BRR_UPPER_BIT 0 +#define VTEM_RB_BIT 2 +/* MD3 BRR Lower */ + enum ColorimetryRGBDP { ColorimetryRGB_DP_sRGB = 0, @@ -556,6 +536,78 @@ void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, info_packet->valid = true; } +static void build_vtem_infopacket_header(struct dc_info_packet *infopacket) +{ + uint8_t pb0 = 0; + + /* might need logic in the future */ + pb0 |= 0 << EMP_SNC_BIT; + pb0 |= 1 << EMP_VFR_BIT; + pb0 |= 0 << EMP_AFR_BIT; + pb0 |= 0 << EMP_DST_BIT; + pb0 |= 0 << EMP_END_BIT; + pb0 |= 1 << EMP_NEW_BIT; + + infopacket->hb0 = HDMI_INFOFRAME_TYPE_EMP; + infopacket->hb1 = (1 << EMP_FIRST_BIT) | (1 << EMP_LAST_BIT); + infopacket->hb2 = 0; // sequence + + infopacket->sb[VTEM_PB0] = pb0; + infopacket->sb[VTEM_PB2] = VTEM_ORG_ID; + infopacket->sb[VTEM_PB4] = VTEM_DATA_SET_TAG; + infopacket->sb[VTEM_PB6] = VTEM_DATA_SET_LENGTH; +} + +static void build_vtem_infopacket_data(const struct dc_stream_state *stream, + const struct mod_vrr_params *vrr, + struct dc_info_packet *infopacket) +{ + unsigned int brr = 0; + bool hdmi_vic_mode = false; + bool vrr_active = false; + bool rb = false; + + hdmi_vic_mode = is_hdmi_vic_mode(stream); + vrr_active = vrr->state == VRR_STATE_ACTIVE_VARIABLE || + vrr->state == VRR_STATE_ACTIVE_FIXED; + + infopacket->sb[VTEM_MD0] = VTEM_M_CONST << VTEM_M_CONST_BIT; + infopacket->sb[VTEM_MD0] |= VTEM_FVA_FACTOR << VTEM_FVA_BIT; + infopacket->sb[VTEM_MD0] |= vrr_active << VTEM_VRR_BIT; + + infopacket->sb[VTEM_MD1] = 0; + infopacket->sb[VTEM_MD2] = 0; + infopacket->sb[VTEM_MD3] = 0; + + if (hdmi_vic_mode || !vrr_active) + return; + /* + * Reduced Blanking standard defines a fixed value of + * 160 for hblank, further reduced to 80 in RB2 + */ + rb = (stream->timing.h_total - stream->timing.h_addressable) <= 160; + brr = mod_freesync_calc_nominal_field_rate(stream) / 1000000; + + if (brr > VTEM_BRR_MAX) { + infopacket->valid = false; + return; + } + + infopacket->sb[VTEM_MD1] = (uint8_t) stream->timing.v_front_porch; + infopacket->sb[VTEM_MD2] = rb << VTEM_RB_BIT; + infopacket->sb[VTEM_MD2] |= (brr & VTEM_BRR_MASK_UPPER) >> 8; + infopacket->sb[VTEM_MD3] = brr & VTEM_BRR_MASK_LOWER; +} + +void mod_build_vtem_infopacket(const struct dc_stream_state *stream, + const struct mod_vrr_params *vrr, + struct dc_info_packet *infopacket) +{ + infopacket->valid = true; + build_vtem_infopacket_header(infopacket); + build_vtem_infopacket_data(stream, vrr, infopacket); +} + void mod_build_adaptive_sync_infopacket(const struct dc_stream_state *stream, enum adaptive_sync_type asType, const struct AS_Df_params *param, From 5906f92687720fba2494a9d4b8fd78e0a8b3bcf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Mon, 19 Jan 2026 02:11:46 +0100 Subject: [PATCH 17/17] drm/amd/display: Enable HDMI VRR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] We'd like to expose VRR functionality to end user if HDMI sink is advertising it's support. [How] VTEM info frame is used to signal HDMI sink that VRR is active. Use VTEM info packet as vrr_infopacket Signed-off-by: Tomasz Pakuła --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 13 +++++++++++-- .../gpu/drm/amd/display/modules/freesync/freesync.c | 4 ++++ .../drm/amd/display/modules/inc/mod_info_packet.h | 1 + .../amd/display/modules/info_packet/info_packet.c | 1 + 4 files changed, 17 insertions(+), 2 deletions(-) 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 4b3f2b57a02db..3dc4c3afe7bdf 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -9377,6 +9377,9 @@ static void update_freesync_state_on_stream( &new_stream->adaptive_sync_infopacket); } + if (aconn && aconn->as_type == ADAPTIVE_SYNC_TYPE_HDMI) + packet_type = PACKET_TYPE_VTEM; + mod_freesync_build_vrr_infopacket( dm->freesync_module, new_stream, @@ -13112,8 +13115,14 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, } /* HDMI */ - } else if (sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A && vsdb_freesync) { - monitor_range_from_vsdb(connector, &vsdb_info); + } else if (sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { + /* Prefer HDMI VRR */ + if (hdmi_vrr->supported) { + amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_HDMI; + monitor_range_from_hdmi(connector, valid_vsdb_cea ? &vsdb_info : NULL); + } else if (vsdb_freesync) + monitor_range_from_vsdb(connector, &vsdb_info); + freesync_capable = copy_range_to_amdgpu_connector(connector); /* DP -> HDMI PCON */ diff --git a/drivers/gpu/drm/amd/display/modules/freesync/freesync.c b/drivers/gpu/drm/amd/display/modules/freesync/freesync.c index 1aae46d703ba0..db197cf048e1b 100644 --- a/drivers/gpu/drm/amd/display/modules/freesync/freesync.c +++ b/drivers/gpu/drm/amd/display/modules/freesync/freesync.c @@ -27,6 +27,7 @@ #include "dc.h" #include "mod_freesync.h" #include "core_types.h" +#include "mod_info_packet.h" #define MOD_FREESYNC_MAX_CONCURRENT_STREAMS 32 @@ -955,6 +956,9 @@ void mod_freesync_build_vrr_infopacket(struct mod_freesync *mod_freesync, return; switch (packet_type) { + case PACKET_TYPE_VTEM: + mod_build_vtem_infopacket(stream, vrr, infopacket); + break; case PACKET_TYPE_FS_V3: build_vrr_infopacket_v3(stream->signal, vrr, app_tf, infopacket, stream->freesync_on_desktop); break; diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h index 18bfac21e3ca5..2b4abfbd8488c 100644 --- a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h @@ -53,6 +53,7 @@ enum adaptive_sync_type { ADAPTIVE_SYNC_TYPE_PCON_ALLOWED = 2, ADAPTIVE_SYNC_TYPE_PCON_NOT_ALLOWED = 3, ADAPTIVE_SYNC_TYPE_EDP = 4, + ADAPTIVE_SYNC_TYPE_HDMI = 5, }; enum adaptive_sync_sdp_version { diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c index 15f87afb1fad6..c9f639a2565d9 100644 --- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c @@ -628,6 +628,7 @@ void mod_build_adaptive_sync_infopacket(const struct dc_stream_state *stream, break; case ADAPTIVE_SYNC_TYPE_NONE: case ADAPTIVE_SYNC_TYPE_PCON_NOT_ALLOWED: + case ADAPTIVE_SYNC_TYPE_HDMI: default: break; }