]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
drm/amd/display: Read sink freesync support via mccs
authorWayne Lin <Wayne.Lin@amd.com>
Tue, 3 Mar 2026 05:55:42 +0000 (13:55 +0800)
committerAlex Deucher <alexander.deucher@amd.com>
Fri, 17 Apr 2026 19:20:56 +0000 (15:20 -0400)
If EDID AMD VSDB declares that sink supports MCCS method for freesync
usage, send mccs request to understand sink freesync current supporting
state.

If sink supports freesync but user toggles OSD to turn off it, disable
freesync.

If HDMI sink doesn't support MCCS method for freesync usage, disable
freesync as well.

Reviewed-by: Harry Wentland <harry.wentland@amd.com>
Signed-off-by: Wayne Lin <Wayne.Lin@amd.com>
Signed-off-by: Roman Li <roman.li@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
drivers/gpu/drm/amd/display/dc/dc.h
drivers/gpu/drm/amd/display/dc/dc_types.h
drivers/gpu/drm/amd/display/dc/dm_helpers.h
drivers/gpu/drm/amd/display/dc/link/link_detection.c

index 1e5953d8a90df001b8732ba931c2a984a2ec1b75..c7dd75c5d9218abf520a86f06c6727fcc27b4d31 100644 (file)
@@ -13376,6 +13376,14 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector,
                }
        }
 
+       /* Handle MCCS */
+       dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink);
+       if ((sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A ||
+               as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) &&
+               (!sink->edid_caps.freesync_vcp_code ||
+               (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported)))
+               freesync_capable = false;
+
 update:
        if (dm_con_state)
                dm_con_state->freesync_capable = freesync_capable;
index d5ea4fe84b7336c5826faaa8a0ae0e6a55a0695f..1332969c749197ded3eae4e816437b1811184a64 100644 (file)
 #include "ddc_service_types.h"
 #include "clk_mgr.h"
 
+#define MCCS_DEST_ADDR (0x6E >> 1)
+#define MCCS_SRC_ADDR  0x51
+#define MCCS_LENGTH_OFFSET 0x80
+#define MCCS_MAX_DATA_SIZE 0x20
+
+enum mccs_op_code {
+       MCCS_OP_CODE_VCP_REQUEST = 0x01,
+       MCCS_OP_CODE_VCP_REPLY = 0x02,
+       MCCS_OP_CODE_VCP_SET = 0x03,
+       MCCS_OP_CODE_VCP_RESET = 0x09,
+       MCCS_OP_CODE_CAP_REQUEST = 0xF3,
+       MCCS_OP_CODE_CAP_REPLY = 0xE3
+};
+
+enum mccs_op_buff_size {
+       MCCS_OP_BUFF_SIZE__WR_VCP_REQUEST = 5,
+       MCCS_OP_BUFF_SIZE_RD_VCP_REQUEST = 11,
+       MCCS_OP_BUFF_SIZE_WR_VCP_SET = 7,
+};
+
+enum vcp_reply_mask {
+       FREESYNC_SUPPORTED = 0x1
+};
+
+union vcp_reply {
+       struct {
+               unsigned char src_addr;
+               unsigned char length;                   /* Length is offset by MccsLengthOffs = 0x80 */
+               unsigned char reply_op_code;    /* Should return MCCS_OP_CODE_VCP_REPLY = 0x02 */
+               unsigned char result_code;              /* 00h No Error, 01h Unsupported VCP Code */
+               unsigned char request_code;             /* Should return mccs vcp code sent in the vcp request */
+               unsigned char type_code;                /* VCP type code: 00h Set parameter, 01h Momentary */
+               unsigned char max_value[2];             /* 2 bytes returning max value current value */
+               unsigned char present_value[2]; /* NOTE: Byte0 is MSB, Byte1 is LSB */
+               unsigned char check_sum;
+       } bytes;
+       unsigned char raw[11];
+};
+
 static u32 edid_extract_panel_id(struct edid *edid)
 {
        return (u32)edid->mfg_id[0] << 24   |
@@ -1441,3 +1480,129 @@ bool dm_helpers_is_hdr_on(struct dc_context *ctx, struct dc_stream_state *stream
        // TODO
        return false;
 }
+
+static int mccs_operation_vcp_request(unsigned int vcp_code, struct dc_link *link,
+                               union vcp_reply *reply)
+{
+       const unsigned char retry_interval_ms = 40;
+       unsigned char retry = 5;
+       struct amdgpu_dm_connector *aconnector = link->priv;
+       struct i2c_adapter *ddc;
+       struct i2c_msg msg = {0};
+       int ret = 0;
+       int idx;
+
+       unsigned char wr_data[MCCS_OP_BUFF_SIZE__WR_VCP_REQUEST] = {
+               MCCS_SRC_ADDR,                          /* Byte0 - Src Addr */
+               MCCS_LENGTH_OFFSET + 2,         /* Byte1 - Length */
+               MCCS_OP_CODE_VCP_REQUEST,       /* Byte2 - MCCS Command */
+               (unsigned char) vcp_code,       /* Byte3 - VCP Code */
+               MCCS_DEST_ADDR << 1                     /* Byte4 - CheckSum */
+       };
+
+       /* calculate checksum */
+       for (idx = 0; idx < (MCCS_OP_BUFF_SIZE__WR_VCP_REQUEST - 1); idx++)
+               wr_data[(MCCS_OP_BUFF_SIZE__WR_VCP_REQUEST-1)] ^= wr_data[idx];
+
+       if (link->aux_mode)
+               ddc = &aconnector->dm_dp_aux.aux.ddc;
+       else
+               ddc = &aconnector->i2c->base;
+
+       do {
+               msg.addr = MCCS_DEST_ADDR;
+               msg.flags = 0;
+               msg.len = MCCS_OP_BUFF_SIZE__WR_VCP_REQUEST;
+               msg.buf = wr_data;
+
+               ret = i2c_transfer(ddc, &msg, 1);
+               if (ret != 1)
+                       goto mccs_retry;
+
+               msleep(retry_interval_ms);
+
+               msg.addr = MCCS_DEST_ADDR;
+               msg.flags = I2C_M_RD;
+               msg.len = MCCS_OP_BUFF_SIZE_RD_VCP_REQUEST;
+               msg.buf = reply->raw;
+
+               ret = i2c_transfer(ddc, &msg, 1);
+
+               /* sink might reply with null msg if it can't reply in time */
+               if (ret == 1 && reply->bytes.length > MCCS_LENGTH_OFFSET)
+                       break;
+mccs_retry:
+               retry--;
+               msleep(retry_interval_ms);
+       } while (retry);
+
+       if (!retry) {
+               drm_dbg_driver(aconnector->base.dev,
+                       "%s: MCCS VCP request failed after retries", __func__);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+void dm_helpers_read_mccs_caps(struct dc_context *ctx, struct dc_link *link,
+               struct dc_sink *sink)
+{
+       bool mccs_op = false;
+       struct dpcd_caps *dpcd_caps;
+       struct drm_device *dev;
+       uint16_t freesync_vcp_value = 0;
+       union vcp_reply vcp_reply_value = {0};
+
+       if (!ctx)
+               return;
+       dev = adev_to_drm(ctx->driver_context);
+
+       if (!link || !sink) {
+               drm_dbg_driver(dev, "%s: link or sink is NULL", __func__);
+               return;
+       }
+
+       sink->mccs_caps.freesync_supported = false;
+       dpcd_caps = &link->dpcd_caps;
+
+       if (sink->edid_caps.freesync_vcp_code != 0) {
+               if (dc_is_dp_signal(link->connector_signal)) {
+                       if ((dpcd_caps->dpcd_rev.raw >= DPCD_REV_14) &&
+                               (dpcd_caps->dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) &&
+                               dm_is_freesync_pcon_whitelist(dpcd_caps->branch_dev_id) &&
+                               (dpcd_caps->adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT == true))
+                               mccs_op = true;
+
+                       if ((dpcd_caps->dongle_type != DISPLAY_DONGLE_NONE &&
+                               dpcd_caps->dongle_type != DISPLAY_DONGLE_DP_HDMI_CONVERTER)) {
+                               if (mccs_op == false)
+                                       drm_dbg_driver(dev, "%s: Legacy Pcon support", __func__);
+                               mccs_op = true;
+                       }
+
+                       if (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT_MST) {
+                               // Todo: Freesync over MST
+                               mccs_op = false;
+                       }
+               }
+
+               if (dc_is_hdmi_signal(link->connector_signal)) {
+                       drm_dbg_driver(dev, "%s: Local HDMI sink", __func__);
+                       mccs_op = true;
+               }
+
+               if (mccs_op == true) {
+                       // MCCS VCP request to get VCP value
+                       if (!mccs_operation_vcp_request(sink->edid_caps.freesync_vcp_code, link,
+                                       &vcp_reply_value)) {
+                               freesync_vcp_value = vcp_reply_value.bytes.present_value[1];
+                               freesync_vcp_value |= (uint16_t) vcp_reply_value.bytes.present_value[0] << 8;
+                       }
+                       // If VCP Value bit 0 is 1, freesyncSupport = true
+                       sink->mccs_caps.freesync_supported =
+                               (freesync_vcp_value & FREESYNC_SUPPORTED) ? true : false;
+               }
+       }
+}
+
index 5ceadcdca5248ac4089d40f9b6f6cf96e695a8a3..7d170eaeb163ba289aefc68334337a124abf4cbb 100644 (file)
@@ -2725,6 +2725,7 @@ struct dc_sink {
        struct stereo_3d_features features_3d[TIMING_3D_FORMAT_MAX];
        bool converter_disable_audio;
 
+       struct mccs_caps mccs_caps;
        struct scdc_caps scdc_caps;
        struct dc_sink_dsc_caps dsc_caps;
        struct dc_sink_fec_caps fec_caps;
index 5b7490a7dc7af2183f9d55c066c4e3afce201475..7672ee88be82c5cdae167297461c8b9a1cc7c518 100644 (file)
@@ -1315,6 +1315,10 @@ struct dc_panel_config {
        } rio;
 };
 
+struct mccs_caps {
+       bool freesync_supported;
+};
+
 #define MAX_SINKS_PER_LINK 4
 
 /*
index 2818df555e627f3b260484a1174a1bb6c7be52c7..3aa2b11f559b263940ef956f01e5e28d9ec1c80f 100644 (file)
@@ -181,6 +181,11 @@ enum dc_edid_status dm_helpers_read_local_edid(
                struct dc_link *link,
                struct dc_sink *sink);
 
+void dm_helpers_read_mccs_caps(
+               struct dc_context *ctx,
+               struct dc_link *link,
+               struct dc_sink *sink);
+
 bool dm_helpers_dp_handle_test_pattern_request(
                struct dc_context *ctx,
                const struct dc_link *link,
index 714370e773c1e75f3da3916176cdd56c847c98f2..794dd6a9591830f848ce27906d0cb7a3e2c3d484 100644 (file)
@@ -1234,6 +1234,20 @@ static bool detect_link_and_local_sink(struct dc_link *link,
                if (dc_is_hdmi_signal(link->connector_signal))
                        read_scdc_caps(link->ddc, link->local_sink);
 
+               /* When FreeSync is toggled through OSD,
+                * we see same EDID no matter what. Check MCCS caps
+                * to see if we should update FreeSync caps now.
+                */
+               dm_helpers_read_mccs_caps(
+                               link->ctx,
+                               link,
+                               sink);
+
+               if (prev_sink != NULL) {
+                       if (memcmp(&sink->mccs_caps, &prev_sink->mccs_caps, sizeof(struct mccs_caps)))
+                               same_edid = false;
+               }
+
                if (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT &&
                    sink_caps.transaction_type ==
                    DDC_TRANSACTION_TYPE_I2C_OVER_AUX) {