From: Wayne Lin <[email protected]>

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 <[email protected]>
Signed-off-by: Wayne Lin <[email protected]>
Signed-off-by: Roman Li <[email protected]>
---
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c |   8 +
 .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 165 ++++++++++++++++++
 drivers/gpu/drm/amd/display/dc/dc.h           |   1 +
 drivers/gpu/drm/amd/display/dc/dc_types.h     |   4 +
 drivers/gpu/drm/amd/display/dc/dm_helpers.h   |   5 +
 .../drm/amd/display/dc/link/link_detection.c  |  14 ++
 6 files changed, 197 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 c613deb7c88a..ceef9b6e1599 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -13372,6 +13372,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;
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 6de2eb34f862..0146e894a15a 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
@@ -49,6 +49,45 @@
 #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;
+               }
+       }
+}
+
diff --git a/drivers/gpu/drm/amd/display/dc/dc.h 
b/drivers/gpu/drm/amd/display/dc/dc.h
index 5ceadcdca524..7d170eaeb163 100644
--- a/drivers/gpu/drm/amd/display/dc/dc.h
+++ b/drivers/gpu/drm/amd/display/dc/dc.h
@@ -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;
diff --git a/drivers/gpu/drm/amd/display/dc/dc_types.h 
b/drivers/gpu/drm/amd/display/dc/dc_types.h
index 5b7490a7dc7a..7672ee88be82 100644
--- a/drivers/gpu/drm/amd/display/dc/dc_types.h
+++ b/drivers/gpu/drm/amd/display/dc/dc_types.h
@@ -1315,6 +1315,10 @@ struct dc_panel_config {
        } rio;
 };
 
+struct mccs_caps {
+       bool freesync_supported;
+};
+
 #define MAX_SINKS_PER_LINK 4
 
 /*
diff --git a/drivers/gpu/drm/amd/display/dc/dm_helpers.h 
b/drivers/gpu/drm/amd/display/dc/dm_helpers.h
index 2818df555e62..3aa2b11f559b 100644
--- a/drivers/gpu/drm/amd/display/dc/dm_helpers.h
+++ b/drivers/gpu/drm/amd/display/dc/dm_helpers.h
@@ -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,
diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c 
b/drivers/gpu/drm/amd/display/dc/link/link_detection.c
index 714370e773c1..794dd6a95918 100644
--- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c
+++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c
@@ -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) {
-- 
2.34.1

Reply via email to