drm/amd/display: Read sink freesync support via mccs

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>
This commit is contained in:
Wayne Lin
2026-03-03 13:55:42 +08:00
committed by Alex Deucher
parent 72022bad01
commit 6f71d5dd32
6 changed files with 197 additions and 0 deletions

View 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;

View File

@@ -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;
}
}
}

View 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;

View File

@@ -1315,6 +1315,10 @@ struct dc_panel_config {
} rio;
};
struct mccs_caps {
bool freesync_supported;
};
#define MAX_SINKS_PER_LINK 4
/*

View 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,

View 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) {