#include "link_hdmi_frl.h"
#include "link_ddc.h"
#include "link/link_dpms.h"
#include "link/link_validation.h"
#include "resource.h"
#include "dccg.h"
#include "dml/dml1_frl_cap_chk.h"
#define DC_LOGGER \
dc_logger
#define DC_LOGGER_INIT(logger) \
struct dal_logger *dc_logger = logger
#define FRL_INFO(...) \
DC_LOG_HDMI_FRL_LTP( \
__VA_ARGS__)
static bool hdmi_frl_test_max_rate(struct ddc_service *ddc_service)
{
uint8_t slave_address = HDMI_SCDC_ADDRESS;
uint8_t offset = HDMI_SCDC_SOURCE_TEST_REQ;
union hdmi_scdc_source_test_req test_req = {0};
DC_LOGGER_INIT(ddc_service->link->ctx->logger);
link_query_ddc_data(ddc_service, slave_address,
&offset, sizeof(offset), &test_req.byte,
sizeof(test_req.byte));
if (test_req.fields.FRL_MAX) {
FRL_INFO("FRL TEST REQ: FRL_MAX = 1");
return true;
}
return false;
}
static void hdmi_return_preeshoot_and_deemphasis(struct dc_link *link,
union hdmi_scdc_source_test_req *test_req, bool *de_emphasis_only,
bool *pre_shoot_only, bool *no_ffe)
{
if (link->hdmi_cable_id.raw[0] && link->frl_link_settings.frl_link_rate >=
HDMI_FRL_LINK_RATE_16GBPS) {
*de_emphasis_only = (test_req->fields.TXFFE_DEEMPHASIS && link->hdmi_cable_id.bits.no_DeEmphasis_n) ||
!link->hdmi_cable_id.bits.no_PreShoot_n;
*pre_shoot_only = (test_req->fields.TXFFE_PRESHOOT && link->hdmi_cable_id.bits.no_PreShoot_n) ||
!link->hdmi_cable_id.bits.no_DeEmphasis_n;
*no_ffe = test_req->fields.TXFFE_NOFFE ||
(!link->hdmi_cable_id.bits.no_PreShoot_n &&
!link->hdmi_cable_id.bits.no_DeEmphasis_n);
} else {
*de_emphasis_only = test_req->fields.TXFFE_DEEMPHASIS;
*pre_shoot_only = test_req->fields.TXFFE_PRESHOOT;
*no_ffe = test_req->fields.TXFFE_NOFFE;
}
}
static bool hdmi_frl_test_dsc_max_rate(struct ddc_service *ddc_service)
{
uint8_t slave_address = HDMI_SCDC_ADDRESS;
uint8_t offset = HDMI_SCDC_SOURCE_TEST_REQ;
union hdmi_scdc_source_test_req test_req = {0};
DC_LOGGER_INIT(ddc_service->link->ctx->logger);
link_query_ddc_data(ddc_service, slave_address,
&offset, sizeof(offset), &test_req.byte,
sizeof(test_req.byte));
if (test_req.fields.DSC_FRL_MAX) {
FRL_INFO("FRL TEST REQ: DSC_FRL_MAX = 1");
return true;
}
return false;
}
enum clock_source_id hdmi_frl_find_matching_phypll(
struct dc_link *link)
{
switch (link->link_enc->transmitter) {
case TRANSMITTER_UNIPHY_A:
return CLOCK_SOURCE_COMBO_PHY_PLL0;
case TRANSMITTER_UNIPHY_B:
return CLOCK_SOURCE_COMBO_PHY_PLL1;
case TRANSMITTER_UNIPHY_C:
return CLOCK_SOURCE_COMBO_PHY_PLL2;
case TRANSMITTER_UNIPHY_D:
return CLOCK_SOURCE_COMBO_PHY_PLL3;
case TRANSMITTER_UNIPHY_E:
return CLOCK_SOURCE_COMBO_PHY_PLL4;
case TRANSMITTER_UNIPHY_F:
return CLOCK_SOURCE_COMBO_PHY_PLL5;
default:
return CLOCK_SOURCE_ID_UNDEFINED;
};
}
void hdmi_frl_retrieve_link_cap(struct dc_link *link, struct dc_sink *sink)
{
enum hdmi_frl_link_rate encoder_link_rate = HDMI_FRL_LINK_RATE_6GBPS_4LANE;
if (link->link_enc->features.flags.bits.IS_FRL_8G_CAPABLE)
encoder_link_rate = HDMI_FRL_LINK_RATE_8GBPS;
if (link->link_enc->features.flags.bits.IS_FRL_10G_CAPABLE)
encoder_link_rate = HDMI_FRL_LINK_RATE_10GBPS;
if (link->link_enc->features.flags.bits.IS_FRL_12G_CAPABLE)
encoder_link_rate = HDMI_FRL_LINK_RATE_12GBPS;
if (link->dc->debug.max_frl_rate != 0 && encoder_link_rate > link->dc->debug.max_frl_rate)
encoder_link_rate = link->dc->debug.max_frl_rate;
if (link->frl_flags.force_frl_rate != 0 &&
link->frl_flags.force_frl_rate != 0xF)
encoder_link_rate = link->frl_flags.force_frl_rate;
link->frl_reported_link_cap.frl_link_rate =
(encoder_link_rate < sink->edid_caps.max_frl_rate) ?
encoder_link_rate : sink->edid_caps.max_frl_rate;
if (sink->edid_caps.max_frl_rate < HDMI_FRL_LINK_RATE_6GBPS_4LANE)
link->frl_reported_link_cap.frl_num_lanes = 3;
else
link->frl_reported_link_cap.frl_num_lanes = 4;
}
struct dc_hdmi_frl_link_settings *hdmi_frl_get_verified_link_cap(
struct dc_link *link)
{
return &link->frl_verified_link_cap;
}
void hdmi_frl_LTS_clear_Update_flag(struct ddc_service *ddc_service)
{
uint8_t slave_address = HDMI_SCDC_ADDRESS;
uint8_t offset = HDMI_SCDC_UPDATE_0;
uint8_t write_buffer[2] = { 0 };
union hdmi_scdc_update_read_data scdc_update = {0};
DC_LOGGER_INIT(ddc_service->link->ctx->logger);
link_query_ddc_data(ddc_service, slave_address,
&offset, sizeof(offset), &scdc_update.byte[0],
sizeof(scdc_update.byte[0]));
if (scdc_update.fields.FLT_UPDATE != 0) {
FRL_INFO("FRL LINK TRAINING: LTS:L Clear FLT_UPDATE.\n");
write_buffer[0] = HDMI_SCDC_UPDATE_0;
write_buffer[1] = (scdc_update.fields.FLT_UPDATE << 5);
link_query_ddc_data(ddc_service, slave_address,
write_buffer, sizeof(write_buffer), NULL, 0);
}
}
bool hdmi_frl_poll_status_flag(struct dc_link *link)
{
uint8_t slave_address = HDMI_SCDC_ADDRESS;
uint8_t offset = 0;
uint32_t ln0_pattern = 0;
uint32_t ln1_pattern = 0;
uint32_t ln2_pattern = 0;
uint32_t ln3_pattern = 0;
uint8_t write_buffer[2] = {0};
union hdmi_scdc_update_read_data scdc_update = {0};
union hdmi_scdc_source_test_req test_req = {0};
union hdmi_scdc_LTP_req_data ltp_req = {0};
struct hpo_frl_link_encoder *hpo_frl_link_enc = link->hpo_frl_link_enc;
struct link_encoder *dio_link_enc = link->link_enc;
bool flt_no_timeout = false;
bool link_update = false;
offset = HDMI_SCDC_SOURCE_TEST_REQ;
link_query_ddc_data(link->ddc, slave_address, &offset,
sizeof(offset), &test_req.byte, sizeof(test_req.byte));
if (test_req.fields.FLT_NO_TIMEOUT)
flt_no_timeout = true;
offset = HDMI_SCDC_UPDATE_0;
link_query_ddc_data(link->ddc, slave_address,
&offset, sizeof(offset), &scdc_update.byte[0],
sizeof(scdc_update.byte[0]));
if (scdc_update.fields.FRL_START == 1) {
write_buffer[0] = HDMI_SCDC_UPDATE_0;
write_buffer[1] = (scdc_update.fields.FRL_START << 4);
link_query_ddc_data(link->ddc, slave_address,
write_buffer, sizeof(write_buffer), NULL, 0);
}
if (scdc_update.fields.FLT_UPDATE) {
offset = HDMI_SCDC_LTP_REQ;
link_query_ddc_data(link->ddc, slave_address,
&offset, sizeof(offset), ltp_req.byte,
sizeof(ltp_req.byte));
ln0_pattern = ltp_req.fields.LN0_LTP_REQ;
ln1_pattern = ltp_req.fields.LN1_LTP_REQ;
ln2_pattern = ltp_req.fields.LN2_LTP_REQ;
ln3_pattern = ltp_req.fields.LN3_LTP_REQ;
if (ln0_pattern == 0x03 || ln1_pattern == 0x03 ||
ln2_pattern == 0x03 || ln3_pattern == 0x03) {
if (flt_no_timeout) {
hpo_frl_link_enc->funcs->set_hdmi_training_pattern(
hpo_frl_link_enc,
ln0_pattern - 1,
ln1_pattern - 1,
ln2_pattern - 1,
ln3_pattern - 1);
} else
hpo_frl_link_enc->funcs->set_hdmi_training_pattern(
hpo_frl_link_enc,
(ln0_pattern == 0x03) ? 0xF : ln0_pattern - 1,
(ln1_pattern == 0x03) ? 0xF : ln1_pattern - 1,
(ln2_pattern == 0x03) ? 0xF : ln2_pattern - 1,
(ln3_pattern == 0x03) ? 0xF : ln3_pattern - 1);
} else
hpo_frl_link_enc->funcs->set_hdmi_training_pattern(
hpo_frl_link_enc,
ln0_pattern - 1,
ln1_pattern - 1,
ln2_pattern - 1,
ln3_pattern - 1);
if (ln0_pattern == 0x0E || ln1_pattern == 0x0E ||
ln2_pattern == 0x0E || ln3_pattern == 0x0E) {
if (scdc_update.fields.FLT_UPDATE) {
if (link->dc->debug.limit_ffe == 0)
return false;
bool de_emphasis_only = false;
bool pre_shoot_only = false;
bool no_ffe = false;
hdmi_return_preeshoot_and_deemphasis(link, &test_req,
&de_emphasis_only, &pre_shoot_only, &no_ffe);
dio_link_enc->funcs->prog_eq_setting(dio_link_enc, 0xEE,
de_emphasis_only,
pre_shoot_only,
no_ffe,
&link->frl_link_settings);
}
} else
if (!flt_no_timeout) {
hdmi_frl_LTS_clear_Link_Setting(link->ddc);
hdmi_frl_LTS_clear_Update_flag(link->ddc);
hpo_frl_link_enc->funcs->set_hdmi_training_pattern(hpo_frl_link_enc, 0, 0, 0, 0);
hdmi_frl_perform_link_training_with_retries(link);
link_update = true;
}
write_buffer[0] = HDMI_SCDC_UPDATE_0;
write_buffer[1] = (scdc_update.fields.FLT_UPDATE << 5);
link_query_ddc_data(link->ddc, slave_address,
write_buffer, sizeof(write_buffer), NULL, 0);
}
if (scdc_update.fields.SOURCE_TEST_UPDATE) {
offset = HDMI_SCDC_SOURCE_TEST_REQ;
bool de_emphasis_only = false;
bool pre_shoot_only = false;
bool no_ffe = false;
link_query_ddc_data(link->ddc, slave_address,
&offset, sizeof(offset), &test_req.byte,
sizeof(test_req.byte));
hdmi_return_preeshoot_and_deemphasis(link, &test_req,
&de_emphasis_only, &pre_shoot_only, &no_ffe);
dio_link_enc->funcs->prog_eq_setting(dio_link_enc, 0xFF,
de_emphasis_only,
pre_shoot_only,
no_ffe,
&link->frl_link_settings);
write_buffer[0] = HDMI_SCDC_UPDATE_0;
write_buffer[1] = (scdc_update.fields.SOURCE_TEST_UPDATE << 3);
link_query_ddc_data(link->ddc, slave_address,
write_buffer, sizeof(write_buffer), NULL, 0);
}
return link_update;
}
void hdmi_frl_poll_start(struct ddc_service *ddc_service)
{
uint8_t slave_address = HDMI_SCDC_ADDRESS;
uint8_t offset = HDMI_SCDC_UPDATE_0;
uint8_t write_buffer[2] = {0};
union hdmi_scdc_source_test_req test_req = {0};
union hdmi_scdc_update_read_data scdc_update = {0};
uint16_t num_polls = 100;
uint16_t wait_time = 2000;
bool flt_no_timeout = false;
DC_LOGGER_INIT(ddc_service->link->ctx->logger);
offset = HDMI_SCDC_SOURCE_TEST_REQ;
link_query_ddc_data(ddc_service, slave_address, &offset,
sizeof(offset), &test_req.byte, sizeof(test_req.byte));
if (test_req.fields.FLT_NO_TIMEOUT)
flt_no_timeout = true;
if (flt_no_timeout)
return;
offset = HDMI_SCDC_UPDATE_0;
for (; num_polls; num_polls--) {
udelay(wait_time);
link_query_ddc_data(ddc_service, slave_address,
&offset, sizeof(offset), &scdc_update.byte[0],
sizeof(scdc_update.byte[0]));
FRL_INFO("FRL LINK TRAINING: Read FRL_START = %d, FLT_UPDATE = %d. num_polls = %d\n",
scdc_update.fields.FRL_START, scdc_update.fields.FLT_UPDATE, num_polls);
if (scdc_update.fields.FRL_START == 1) {
write_buffer[0] = HDMI_SCDC_UPDATE_0;
write_buffer[1] = (scdc_update.fields.FRL_START << 4);
link_query_ddc_data(ddc_service, slave_address,
write_buffer, sizeof(write_buffer), NULL, 0);
break;
}
if (scdc_update.fields.FLT_UPDATE == 1) {
write_buffer[0] = HDMI_SCDC_UPDATE_0;
write_buffer[1] = (scdc_update.fields.FLT_UPDATE << 5);
link_query_ddc_data(ddc_service, slave_address,
write_buffer, sizeof(write_buffer), NULL, 0);
break;
}
}
return;
}
void hdmi_frl_LTS_clear_Link_Setting(struct ddc_service *ddc_service)
{
uint8_t slave_address = HDMI_SCDC_ADDRESS;
uint8_t offset = HDMI_SCDC_CONFIG_1;
uint8_t write_buffer[2] = { 0 };
union hdmi_scdc_configuration scdc_config = {0};
DC_LOGGER_INIT(ddc_service->link->ctx->logger);
link_query_ddc_data(ddc_service, slave_address,
&offset, sizeof(offset), &scdc_config.byte[1],
sizeof(scdc_config.byte[1]));
if (scdc_config.fields.FRL_RATE != 0) {
write_buffer[0] = HDMI_SCDC_CONFIG_1;
write_buffer[1] = 0;
link_query_ddc_data(ddc_service, slave_address,
write_buffer, sizeof(write_buffer), NULL, 0);
FRL_INFO("FRL LINK TRAINING: LTS:L - Clear FRL_Rate.\n");
}
}
static enum link_result hdmi_frl_perform_link_training(struct ddc_service *ddc_service,
struct dc_hdmi_frl_link_settings *link_settings)
{
enum link_result result = LINK_RESULT_UNKNOWN;
uint8_t slave_address = HDMI_SCDC_ADDRESS;
uint8_t offset = HDMI_SCDC_STATUS_FLAGS;
uint32_t ln0_pattern = 0, pre_ln0_pattern = 0;
uint32_t ln1_pattern = 0, pre_ln1_pattern = 0;
uint32_t ln2_pattern = 0, pre_ln2_pattern = 0;
uint32_t ln3_pattern = 0, pre_ln3_pattern = 0;
uint8_t write_buffer[2] = {0};
union hdmi_scdc_update_read_data scdc_update = {0};
union hdmi_scdc_status_flags_data status_data = {0};
union hdmi_scdc_source_test_req test_req = {0};
union hdmi_scdc_LTP_req_data ltp_req = {0};
uint16_t num_polls = 0;
uint16_t max_polls = 105;
unsigned long long wait_time_ns = 2000000;
struct hpo_frl_link_encoder *hpo_frl_link_enc = ddc_service->link->hpo_frl_link_enc;
struct link_encoder *dio_link_enc = ddc_service->link->link_enc;
uint8_t sink_version = 0;
uint8_t FFE_Levels = (uint8_t)ddc_service->link->dc->debug.limit_ffe;
uint8_t current_FFE = 0;
bool override_FFE = false;
bool flt_no_timeout = false;
unsigned long long flt_poll_cur_time = 0, flt_poll_last_time = 0, flt_poll_elapsed_time_ns = 0;
DC_LOGGER_INIT(ddc_service->link->ctx->logger);
FRL_INFO("FRL LINK TRAINING: Starting FRL Link Training.\n");
hdmi_frl_LTS_clear_Link_Setting(ddc_service);
offset = HDMI_SCDC_SINK_VERSION;
link_query_ddc_data(ddc_service, slave_address, &offset,
sizeof(offset), &sink_version, sizeof(sink_version));
FRL_INFO("FRL LINK TRAINING: Read Sink Version = %d.\n", sink_version);
if (sink_version == 0) {
FRL_INFO("FRL LINK TRAINING: SKIP - FRL not supported by sink.\n");
return LINK_RESULT_FALLBACK;
}
if (sink_version == 1) {
write_buffer[0] = HDMI_SCDC_SOURCE_VERSION;
write_buffer[1] = 1;
link_query_ddc_data(ddc_service, slave_address,
write_buffer, sizeof(write_buffer), NULL, 0);
FRL_INFO("FRL LINK TRAINING: Set Source Version = 1.\n");
}
FRL_INFO("FRL LINK TRAINING: Poll for FLT_READY.\n");
offset = HDMI_SCDC_STATUS_FLAGS;
while (num_polls < max_polls) {
flt_poll_cur_time = dm_get_timestamp(ddc_service->ctx);
flt_poll_elapsed_time_ns = dm_get_elapse_time_in_ns(ddc_service->ctx, flt_poll_cur_time, flt_poll_last_time);
if (flt_poll_elapsed_time_ns < wait_time_ns)
continue;
flt_poll_last_time = dm_get_timestamp(ddc_service->ctx);
num_polls++;
link_query_ddc_data(ddc_service, slave_address,
&offset, sizeof(offset), &status_data.byte,
sizeof(status_data.byte));
FRL_INFO("FRL LINK TRAINING: Read FLT_READY = %d. num_polls = %d\n",
status_data.fields.FLT_READY, num_polls);
if (status_data.fields.FLT_READY) {
hdmi_frl_LTS_clear_Update_flag(ddc_service);
dio_link_enc->funcs->prog_eq_setting(dio_link_enc, 0,
test_req.fields.TXFFE_DEEMPHASIS,
test_req.fields.TXFFE_PRESHOOT,
test_req.fields.TXFFE_NOFFE,
link_settings);
break;
}
}
offset = HDMI_SCDC_SOURCE_TEST_REQ;
link_query_ddc_data(ddc_service, slave_address, &offset,
sizeof(offset), &test_req.byte, sizeof(test_req.byte));
FRL_INFO("FRL TEST REQ: FLT_no_timeout = %d \n", test_req.fields.FLT_NO_TIMEOUT);
if (test_req.fields.FLT_NO_TIMEOUT)
flt_no_timeout = true;
if (status_data.fields.FLT_READY) {
write_buffer[0] = HDMI_SCDC_CONFIG_1;
write_buffer[1] = (uint8_t)(link_settings->frl_link_rate | (FFE_Levels << 4));
link_query_ddc_data(ddc_service, slave_address,
write_buffer, sizeof(write_buffer), NULL, 0);
FRL_INFO("FRL LINK TRAINING: Write link rate = %d. Max FFE_Levels = %d\n",
link_settings->frl_link_rate, FFE_Levels);
FRL_INFO("FRL LINK TRAINING: Poll for FLT_UPDATE.\n");
num_polls = 0;
if (flt_no_timeout)
max_polls = 500;
while (num_polls < max_polls) {
flt_poll_cur_time = dm_get_timestamp(ddc_service->ctx);
flt_poll_elapsed_time_ns = dm_get_elapse_time_in_ns(ddc_service->ctx, flt_poll_cur_time, flt_poll_last_time);
if (flt_poll_elapsed_time_ns < wait_time_ns)
continue;
flt_poll_last_time = flt_poll_cur_time;
num_polls++;
offset = HDMI_SCDC_UPDATE_0;
link_query_ddc_data(ddc_service, slave_address,
&offset, sizeof(offset), &scdc_update.byte[0],
sizeof(scdc_update.byte[0]));
FRL_INFO("FRL LINK TRAINING: Read FLT_UPDATE = %d. num_polls = %d\n",
scdc_update.fields.FLT_UPDATE, num_polls);
if (override_FFE) {
if (flt_no_timeout)
current_FFE = 0;
if (current_FFE == 0)
dio_link_enc->funcs->prog_eq_setting(dio_link_enc, current_FFE,
test_req.fields.TXFFE_DEEMPHASIS,
test_req.fields.TXFFE_PRESHOOT,
test_req.fields.TXFFE_NOFFE,
link_settings);
else {
if (scdc_update.fields.FLT_UPDATE)
dio_link_enc->funcs->prog_eq_setting(dio_link_enc, current_FFE,
test_req.fields.TXFFE_DEEMPHASIS,
test_req.fields.TXFFE_PRESHOOT,
test_req.fields.TXFFE_NOFFE,
link_settings);
}
FRL_INFO("FRL LINK TRAINING: TxFFE = %d.\n", current_FFE);
override_FFE = false;
}
if (scdc_update.fields.FLT_UPDATE) {
offset = HDMI_SCDC_LTP_REQ;
link_query_ddc_data(ddc_service, slave_address,
&offset, sizeof(offset), ltp_req.byte,
sizeof(ltp_req.byte));
pre_ln0_pattern = ln0_pattern;
pre_ln1_pattern = ln1_pattern;
pre_ln2_pattern = ln2_pattern;
pre_ln3_pattern = ln3_pattern;
ln0_pattern = ltp_req.fields.LN0_LTP_REQ;
ln1_pattern = ltp_req.fields.LN1_LTP_REQ;
ln2_pattern = ltp_req.fields.LN2_LTP_REQ;
ln3_pattern = ltp_req.fields.LN3_LTP_REQ;
FRL_INFO("FRL LINK TRAINING: Read LN0_LTP_REQ = %d. LN1_LTP_REQ = %d\n",
ltp_req.fields.LN0_LTP_REQ,
ltp_req.fields.LN1_LTP_REQ);
FRL_INFO("FRL LINK TRAINING: Read LN2_LTP_REQ = %d. LN3_LTP_REQ = %d\n",
ltp_req.fields.LN2_LTP_REQ,
ltp_req.fields.LN3_LTP_REQ);
FRL_INFO("FRL LINK TRAINING: Clear FLT_UPDATE flag.\n");
write_buffer[0] = HDMI_SCDC_UPDATE_0;
write_buffer[1] = (scdc_update.fields.FLT_UPDATE << 5);
link_query_ddc_data(ddc_service, slave_address,
write_buffer, sizeof(write_buffer), NULL, 0);
if (ln0_pattern == 0x03 || ln1_pattern == 0x03 ||
ln2_pattern == 0x03 || ln3_pattern == 0x03)
if (!flt_no_timeout)
continue;
if (link_settings->frl_num_lanes == 3) {
ln3_pattern = 1;
if (!ln0_pattern && !ln1_pattern && !ln2_pattern) {
FRL_INFO("FRL LINK TRAINING: PASSED\n");
return LINK_RESULT_SUCCESS;
}
if (ln0_pattern == 0x0F || ln1_pattern == 0x0F || ln2_pattern == 0x0F) {
FRL_INFO("FRL LINK TRAINING: Sink requesting lower link rate.\n");
return LINK_RESULT_LOWER_LINKRATE;
}
if (ln0_pattern == 0x0E || ln1_pattern == 0x0E || ln2_pattern == 0x0E) {
FRL_INFO("FRL LINK TRAINING: Sink requesting next FFE.\n");
if (ddc_service->link->dc->debug.limit_ffe == 0) {
return LINK_RESULT_LOWER_LINKRATE;
}
current_FFE++;
override_FFE = true;
if (current_FFE > 3)
current_FFE = 0;
if (flt_no_timeout)
current_FFE = 0;
}
} else {
if (!ln0_pattern && !ln1_pattern && !ln2_pattern && !ln3_pattern) {
FRL_INFO("FRL LINK TRAINING: PASSED\n");
return LINK_RESULT_SUCCESS;
}
if (ln0_pattern == 0x0F || ln1_pattern == 0x0F ||
ln2_pattern == 0x0F || ln3_pattern == 0x0F) {
FRL_INFO("FRL LINK TRAINING: Sink requesting lower link rate.\n");
return LINK_RESULT_LOWER_LINKRATE;
}
if (ln0_pattern == 0x0E || ln1_pattern == 0x0E ||
ln2_pattern == 0x0E || ln3_pattern == 0x0E) {
FRL_INFO("FRL LINK TRAINING: Sink requesting next FFE.\n");
if (ddc_service->link->dc->debug.limit_ffe == 0) {
return LINK_RESULT_LOWER_LINKRATE;
}
current_FFE++;
override_FFE = true;
if (current_FFE > 3)
current_FFE = 0;
if (flt_no_timeout)
current_FFE = 0;
}
}
if (override_FFE) {
ln0_pattern = pre_ln0_pattern;
ln1_pattern = pre_ln1_pattern;
ln2_pattern = pre_ln2_pattern;
ln3_pattern = pre_ln3_pattern;
}
FRL_INFO("FRL LINK TRAINING: Setting Training Pattern [ln0,ln1,ln2,ln3] = [%d,%d,%d,%d].\n",
ln0_pattern, ln1_pattern, ln2_pattern, ln3_pattern);
hpo_frl_link_enc->funcs->set_hdmi_training_pattern(
hpo_frl_link_enc,
ln0_pattern - 1,
ln1_pattern - 1,
ln2_pattern - 1,
ln3_pattern - 1);
}
}
if (flt_no_timeout) {
return LINK_RESULT_SUCCESS;
} else {
FRL_INFO("FRL LINK TRAINING: FAILED - Timeout waiting for FLT_UPDATE to be set by sink.\n");
write_buffer[0] = HDMI_SCDC_CONFIG_1;
write_buffer[1] = HDMI_FRL_LINK_RATE_DISABLE | (0 << 4);
link_query_ddc_data(ddc_service, slave_address,
write_buffer, sizeof(write_buffer), NULL, 0);
hdmi_frl_LTS_clear_Link_Setting(ddc_service);
result = LINK_RESULT_TIMEOUT;
}
} else {
FRL_INFO("FRL LINK TRAINING: FAILED - FLT_READY not set by sink.\n");
result = LINK_RESULT_TIMEOUT;
}
return result;
}
enum link_result hdmi_frl_perform_link_training_with_retries(
struct dc_link *link)
{
enum link_result status = LINK_RESULT_UNKNOWN;
unsigned int retry_count = 0;
unsigned int max_retries = 3;
DC_LOGGER_INIT(link->ctx->logger);
if (link->preferred_hdmi_frl_settings.valid)
max_retries = link->preferred_hdmi_frl_settings.max_retries;
while (status != LINK_RESULT_FALLBACK) {
if (status == LINK_RESULT_SUCCESS) {
break;
} else if (status == LINK_RESULT_LOWER_LINKRATE) {
FRL_INFO("FRL LINK TRAINING: Sink requested lower link rate during link enable. \n");
break;
} else {
if (retry_count > 0)
msleep(200);
status = hdmi_frl_perform_link_training(link->ddc,
&link->frl_link_settings);
};
retry_count++;
FRL_INFO("FRL LINK TRAINING: Retry count = %u out of %u\n", retry_count, max_retries);
if (retry_count > max_retries) {
status = LINK_RESULT_FALLBACK;
break;
}
}
return status;
}
enum link_result hdmi_frl_perform_link_training_with_fallback(
struct dc_link *link, struct link_resource *link_res,
enum clock_source_id frl_phy_clock_source_id)
{
enum link_result status = LINK_RESULT_UNKNOWN;
DC_LOGGER_INIT(link->ctx->logger);
while (status != LINK_RESULT_FALLBACK) {
if (link->frl_link_settings.frl_link_rate ==
HDMI_FRL_LINK_RATE_DISABLE) {
FRL_INFO("FRL LINK TRAINING: Cannot Link Train. Fall back to TMDS \n");
status = LINK_RESULT_FALLBACK;
break;
}
msleep(200);
link->ctx->dc->hwss.setup_hdmi_frl_link(link, 0,
frl_phy_clock_source_id);
status = hdmi_frl_perform_link_training(link->ddc,
&link->frl_link_settings);
link->dc->hwss.disable_link_output(link, link_res, SIGNAL_TYPE_HDMI_FRL);
if (status == LINK_RESULT_SUCCESS)
break;
link->frl_link_settings.frl_link_rate--;
if (link->frl_link_settings.frl_link_rate <
HDMI_FRL_LINK_RATE_6GBPS_4LANE)
link->frl_link_settings.frl_num_lanes = 3;
}
return status;
}
void hdmi_frl_verify_link_cap(struct dc_link *link,
struct dc_hdmi_frl_link_settings *known_limit_link_setting)
{
struct dc_hdmi_frl_link_settings cur_link_setting = {0};
struct dc_hdmi_frl_link_settings *cur = &cur_link_setting;
bool success = false;
enum link_result status = LINK_RESULT_UNKNOWN;
enum clock_source_id frl_phy_clock_source_id;
unsigned int t_id = link->link_enc->transmitter;
struct link_resource link_res = {.hpo_frl_link_enc = link->hpo_frl_link_enc};
struct dc_stream_state *link_stream = NULL;
struct dc_stream_state *stream = NULL;
int i;
DC_LOGGER_INIT(link->ctx->logger);
link->frl_flags.force_frl_rate =
link->ctx->dc->debug.force_frl_rate;
link->frl_flags.force_frl_always =
link->preferred_hdmi_frl_settings.force_frl_always ||
link->ctx->dc->debug.force_frl_always;
link->frl_flags.force_frl_max =
link->preferred_hdmi_frl_settings.force_frl_max ||
link->ctx->dc->debug.force_frl_max ? true :
hdmi_frl_test_max_rate(link->ddc);
link->frl_flags.force_frl_dsc =
link->ctx->dc->debug.force_frl_dsc ? true :
hdmi_frl_test_dsc_max_rate(link->ddc);
link->frl_flags.apply_vsdb_rcc_wa =
link->ctx->dc->debug.apply_vsdb_rcc_wa;
if (link->frl_flags.force_frl_rate == 0xF)
return;
if (link->local_sink &&
link->local_sink->edid_caps.panel_patch.force_frl)
link->frl_flags.force_frl_always = true;
if (!link->frl_flags.force_frl_max &&
!link->frl_flags.force_frl_dsc &&
link->local_sink->edid_caps.panel_patch.hdmi_comp_auto) {
link->frl_flags.force_frl_max = true;
link->frl_flags.force_frl_dsc = true;
}
if (link->frl_flags.force_frl_max &&
!link->frl_flags.force_frl_dsc &&
link->local_sink->edid_caps.panel_patch.hdmi_comp_auto) {
link->frl_flags.force_frl_dsc = true;
}
if (link->local_sink &&
link->local_sink->edid_caps.panel_patch.vsdb_rcc_wa)
link->frl_flags.apply_vsdb_rcc_wa = true;
frl_phy_clock_source_id = hdmi_frl_find_matching_phypll(link);
cur_link_setting = *known_limit_link_setting;
if (link->frl_flags.force_frl_rate != 0) {
cur->frl_link_rate = (cur_link_setting.frl_link_rate <
link->frl_flags.force_frl_rate) ?
cur_link_setting.frl_link_rate :
link->frl_flags.force_frl_rate;
link->frl_verified_link_cap = *cur;
return;
}
if (link->local_sink) {
if (link->local_sink->edid_caps.panel_patch.hdmi_spe_handling) {
link->dc->hwss.disable_link_output(link, &link_res, link->connector_signal);
link->dc->res_pool->clock_sources[t_id]->funcs->cs_power_down(
link->dc->res_pool->clock_sources[t_id]);
link->frl_verified_link_cap = *cur;
return;
}
if (link->local_sink->edid_caps.panel_patch.block_10g) {
if (cur->frl_link_rate == HDMI_FRL_LINK_RATE_10GBPS)
cur->frl_link_rate--;
}
}
link->frl_link_settings = cur_link_setting;
if (link->dc->ctx->dce_version <= DCN_VERSION_3_0)
link->dc->hwss.disable_link_output(link, &link_res, SIGNAL_TYPE_HDMI_FRL);
else
link->dc->hwss.disable_link_output(link, &link_res, link->connector_signal);
link->dc->res_pool->clock_sources[t_id]->funcs->cs_power_down(
link->dc->res_pool->clock_sources[t_id]);
FRL_INFO("FRL LINK TRAINING: Validation\n");
status = hdmi_frl_perform_link_training_with_fallback(link, &link_res, frl_phy_clock_source_id);
if (status == LINK_RESULT_SUCCESS) {
cur->frl_link_rate = link->frl_link_settings.frl_link_rate;
cur->frl_num_lanes = link->frl_link_settings.frl_num_lanes;
success = true;
link->frl_verified_link_cap = *cur;
}
if (!success) {
link->frl_verified_link_cap.frl_link_rate = HDMI_FRL_LINK_RATE_DISABLE;
link->frl_verified_link_cap.frl_num_lanes = 3;
}
for (i = 0; i < MAX_STREAMS; i++) {
stream = link->dc->current_state->streams[i];
if (stream && stream->link == link) {
link_stream = stream;
break;
}
}
if (link_stream) {
link->dc->hwss.disable_link_output(link, &link_res, link_stream->signal);
}
}
void hdmi_frl_set_preferred_link_settings(struct dc *dc,
struct dc_hdmi_frl_link_settings *link_setting,
struct dc_hdmi_frl_link_training_overrides *lt_overrides,
struct dc_link *link)
{
int i;
struct pipe_ctx *pipe;
struct dc_stream_state *link_stream = 0;
struct pipe_ctx *link_pipe = 0;
struct pipe_ctx *odm_pipe;
int opp_cnt = 1;
enum link_result link_stat = LINK_RESULT_UNKNOWN;
enum clock_source_id frl_phy_clock_source_id;
struct dc_stream_state *temp_stream = &dc->scratch.temp_stream;
DC_LOGGER_INIT(link->ctx->logger);
for (i = 0; i < MAX_PIPES; i++) {
pipe = &dc->current_state->res_ctx.pipe_ctx[i];
if (pipe->stream && pipe->stream->link) {
if (pipe->stream->link == link) {
link_stream = pipe->stream;
link_pipe = pipe;
break;
}
}
}
if (i == MAX_PIPES)
return;
FRL_INFO("FRL LINK TRAINING: Preferred link Update = %d.\n", link_setting->frl_link_rate);
frl_validate_mode_timing(link, &link_stream->timing, link_setting);
if (lt_overrides)
link->preferred_hdmi_frl_settings = *lt_overrides;
else
memset(&link->preferred_hdmi_frl_settings, 0, sizeof(link->preferred_hdmi_frl_settings));
link_stream->link->frl_link_settings = *link_setting;
link_stream->link->frl_verified_link_cap = *link_setting;
while (link_stat != LINK_RESULT_SUCCESS) {
link_set_dpms_off(pipe);
for (odm_pipe = pipe->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe)
opp_cnt++;
memcpy(temp_stream, link_stream, sizeof(struct dc_stream_state));
if (link_pipe->dsc_padding_params.dsc_hactive_padding) {
temp_stream->timing.h_addressable = link_stream->timing.h_addressable + link_pipe->dsc_padding_params.dsc_hactive_padding;
temp_stream->timing.h_total = link_stream->timing.h_total + link_pipe->dsc_padding_params.dsc_htotal_padding;
}
pipe->stream_res.hpo_frl_stream_enc->funcs->hdmi_frl_set_stream_attribute(
pipe->stream_res.hpo_frl_stream_enc,
&temp_stream->timing,
&link_stream->link->frl_link_settings.borrow_params,
opp_cnt);
if (pipe->stream_res.tg->funcs->set_out_mux)
pipe->stream_res.tg->funcs->set_out_mux(pipe->stream_res.tg, OUT_MUX_HPO_FRL);
if ((!link_stream->link->link_enc) ||
(!link_stream->link->hpo_frl_link_enc) ||
(!link_stream->ctx->dc->res_pool->dccg->funcs->enable_hdmicharclk) ||
(!(pipe->stream_res.hpo_frl_stream_enc)))
return;
switch (link_stream->link->frl_link_settings.frl_link_rate) {
case HDMI_FRL_LINK_RATE_3GBPS:
pipe->stream_res.pix_clk_params.requested_sym_clk = 166667;
break;
case HDMI_FRL_LINK_RATE_6GBPS:
case HDMI_FRL_LINK_RATE_6GBPS_4LANE:
pipe->stream_res.pix_clk_params.requested_sym_clk = 333333;
break;
case HDMI_FRL_LINK_RATE_8GBPS:
pipe->stream_res.pix_clk_params.requested_sym_clk = 444444;
break;
case HDMI_FRL_LINK_RATE_10GBPS:
pipe->stream_res.pix_clk_params.requested_sym_clk = 555555;
break;
case HDMI_FRL_LINK_RATE_12GBPS:
pipe->stream_res.pix_clk_params.requested_sym_clk = 666667;
break;
case HDMI_FRL_LINK_RATE_16GBPS:
pipe->stream_res.pix_clk_params.requested_sym_clk = 888889;
break;
case HDMI_FRL_LINK_RATE_20GBPS:
pipe->stream_res.pix_clk_params.requested_sym_clk = 1111111;
break;
case HDMI_FRL_LINK_RATE_24GBPS:
pipe->stream_res.pix_clk_params.requested_sym_clk = 1333333;
break;
default:
break;
}
link_stream->phy_pix_clk = pipe->stream_res.pix_clk_params.requested_sym_clk;
memset(&link_stream->link->cur_link_settings, 0,
sizeof(struct dc_link_settings));
frl_phy_clock_source_id = hdmi_frl_find_matching_phypll(link);
dc->hwss.setup_hdmi_frl_link(link,
(pipe->stream_res.hpo_frl_stream_enc->id - ENGINE_ID_HPO_0),
frl_phy_clock_source_id);
FRL_INFO("FRL LINK TRAINING: Start forced link training at %d. \n",
link_stream->link->frl_link_settings.frl_link_rate);
link_stat = hdmi_frl_perform_link_training_with_retries(link_stream->link);
if (link_stat == LINK_RESULT_SUCCESS) {
link_stream->link->hpo_frl_link_enc->funcs->enable_output(
link_stream->link->hpo_frl_link_enc);
if (link_stream->link->frl_flags.apply_vsdb_rcc_wa)
link_stream->link->hpo_frl_link_enc->funcs->apply_vsdb_rcc_wa(link_stream->link->hpo_frl_link_enc);
hdmi_frl_poll_start(link_stream->link->ddc);
if (dc->res_pool->dccg->funcs->set_hdmistreamclk) {
dc->res_pool->dccg->funcs->set_hdmistreamclk(
dc->res_pool->dccg,
DTBCLK0,
pipe->stream_res.tg->inst);
}
pipe->stream_res.hpo_frl_stream_enc->funcs->hdmi_frl_enable(
pipe->stream_res.hpo_frl_stream_enc,
pipe->stream_res.tg->inst);
resource_build_info_frame(pipe);
link_stream->ctx->dc->hwss.update_info_frame(pipe);
if (link_stream->timing.flags.DSC)
link_set_dsc_on_stream(pipe, true);
link_stream->ctx->dc->hwss.enable_audio_stream(pipe);
link_stream->ctx->dc->hwss.enable_stream(pipe);
link_stream->ctx->dc->hwss.unblank_stream(pipe,
&pipe->stream->link->cur_link_settings);
FRL_INFO("FRL LINK TRAINING: Forced link training successful. \n");
}
if (link_stat == LINK_RESULT_LOWER_LINKRATE) {
link_stream->link->frl_link_settings.frl_link_rate--;
if (link_stream->link->frl_link_settings.frl_link_rate >
HDMI_FRL_LINK_RATE_6GBPS)
link_stream->link->frl_link_settings.frl_num_lanes = 4;
else
link_stream->link->frl_link_settings.frl_num_lanes = 3;
FRL_INFO("FRL LINK TRAINING: Lower link rate = %d.\n",
link_stream->link->frl_link_settings.frl_link_rate);
}
if (link_stat == LINK_RESULT_FALLBACK) {
FRL_INFO("FRL LINK TRAINING: Forced Link Training failed. Fallback to TMDS. \n");
break;
}
}
}
static void update_borrow_mode_from_dsc_padding(struct dsc_padding_params *dsc_padding_params,
struct dc_crtc_timing *timing,
struct dc_hdmi_frl_link_settings *frl_link_settings)
{
#ifdef CONFIG_DRM_AMD_DC_FP
uint32_t h_active = timing->h_addressable + timing->h_border_left + timing->h_border_right;
uint32_t h_blank = timing->h_total - h_active;
struct frl_borrow_params *borrow_params = &frl_link_settings->borrow_params;
borrow_params->borrow_mode = frl_modify_borrow_mode_for_dsc_padding(timing->pix_clk_100hz,
h_active,
h_active + dsc_padding_params->dsc_hactive_padding,
h_blank,
h_blank + dsc_padding_params->dsc_htotal_padding,
borrow_params->hc_active_target,
borrow_params->hc_blank_target,
frl_link_settings->frl_num_lanes,
frl_link_settings->frl_link_rate);
#endif
}
void hdmi_frl_decide_link_settings(struct dc_stream_state *stream,
struct dc_hdmi_frl_link_settings *frl_link_settings,
struct dsc_padding_params *dsc_padding_params)
{
bool success = false;
struct dc_hdmi_frl_link_settings temp_settings = {0};
temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_3GBPS;
temp_settings.frl_num_lanes = 3;
frl_validate_mode_timing(
stream->link,
&stream->timing,
&stream->link->frl_verified_link_cap);
if (stream->link->frl_flags.force_frl_rate != 0 &&
stream->link->frl_flags.force_frl_rate < stream->link->frl_verified_link_cap.frl_link_rate) {
switch (stream->link->frl_flags.force_frl_rate) {
case HDMI_FRL_LINK_RATE_3GBPS:
temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_3GBPS;
temp_settings.frl_num_lanes = 3;
break;
case HDMI_FRL_LINK_RATE_6GBPS:
temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_6GBPS;
temp_settings.frl_num_lanes = 3;
break;
case HDMI_FRL_LINK_RATE_6GBPS_4LANE:
temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_6GBPS;
temp_settings.frl_num_lanes = 4;
break;
case HDMI_FRL_LINK_RATE_8GBPS:
temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_8GBPS;
temp_settings.frl_num_lanes = 4;
break;
case HDMI_FRL_LINK_RATE_10GBPS:
temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_10GBPS;
temp_settings.frl_num_lanes = 4;
break;
case HDMI_FRL_LINK_RATE_12GBPS:
temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_12GBPS;
temp_settings.frl_num_lanes = 4;
break;
case HDMI_FRL_LINK_RATE_16GBPS:
temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_16GBPS;
temp_settings.frl_num_lanes = 4;
break;
case HDMI_FRL_LINK_RATE_20GBPS:
temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_20GBPS;
temp_settings.frl_num_lanes = 4;
break;
case HDMI_FRL_LINK_RATE_24GBPS:
temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_24GBPS;
temp_settings.frl_num_lanes = 4;
break;
default:
break;
}
*frl_link_settings = temp_settings;
return;
}
if (stream->link->frl_flags.force_frl_max) {
*frl_link_settings = stream->link->frl_verified_link_cap;
return;
}
if (stream->link->frl_flags.force_frl_dsc) {
*frl_link_settings = stream->link->frl_verified_link_cap;
return;
}
if (stream->link->local_sink)
if (stream->link->local_sink->edid_caps.panel_patch.hdmi_spe_handling) {
*frl_link_settings = stream->link->frl_verified_link_cap;
return;
}
do {
success = frl_validate_mode_timing(
stream->link,
&stream->timing,
&temp_settings);
if (temp_settings.frl_link_rate ==
stream->link->frl_verified_link_cap.frl_link_rate)
break;
if (!success)
temp_settings.frl_link_rate++;
if (temp_settings.frl_link_rate > HDMI_FRL_LINK_RATE_6GBPS)
temp_settings.frl_num_lanes = 4;
} while (!success);
*frl_link_settings = temp_settings;
update_borrow_mode_from_dsc_padding(dsc_padding_params, &stream->timing, frl_link_settings);
}
void hdmi_frl_write_read_request_enable(struct ddc_service *ddc_service)
{
uint8_t slave_address = HDMI_SCDC_ADDRESS;
uint8_t offset = HDMI_SCDC_CONFIG_0;
uint8_t scdc_config = 0;
uint8_t write_buffer[2] = {0};
link_query_ddc_data(ddc_service, slave_address, &offset,
sizeof(offset), &scdc_config, sizeof(scdc_config));
write_buffer[0] = HDMI_SCDC_CONFIG_0;
write_buffer[1] = scdc_config;
write_buffer[1] |= 0x1;
link_query_ddc_data(ddc_service, slave_address, write_buffer,
sizeof(write_buffer), NULL, 0);
}