Contributors: 20
Author Tokens Token Proportion Commits Commit Proportion
Harry Wentland 5672 94.53% 8 19.05%
Wenjing Liu 148 2.47% 12 28.57%
Leo (Hanghong) Ma 49 0.82% 1 2.38%
Martin Leung 42 0.70% 2 4.76%
Jimmy Kizito 16 0.27% 1 2.38%
Michael Strauss 13 0.22% 2 4.76%
Melissa Wen 11 0.18% 2 4.76%
Nicholas Kazlauskas 7 0.12% 1 2.38%
rodrigosiqueira 6 0.10% 1 2.38%
Dominik Kaszewski 6 0.10% 1 2.38%
Hersen Wu 5 0.08% 1 2.38%
Bhawanpreet Lakha 4 0.07% 1 2.38%
Hamza Mahfooz 3 0.05% 1 2.38%
Jake Wang 3 0.05% 1 2.38%
abdoulaye berthe 3 0.05% 1 2.38%
Eric Yang 3 0.05% 1 2.38%
David Francis 3 0.05% 1 2.38%
Mikita Lipski 2 0.03% 1 2.38%
Chris Park 2 0.03% 1 2.38%
Dmytro Laktyushkin 2 0.03% 2 4.76%
Total 6000 42


/*
 * Copyright 2022 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors: AMD
 *
 */

/* FILE POLICY AND INTENDED USAGE:
 * This file implements FRL link capability and link training related functions.
 * FRL link is established by order of retrieve_link, verify_link, and poll_status.
 * Other helper functions exist to obtain information required to maintain
 * the correct sequence according to HDMI specification. Each sequence and state
 * inside link training functions are timing sensitive and order sensitive.
 * It is mandatory that these functions are debugged with FRL_LTP output message
 * configurable in DSAT.  Any changes in the LT sequence should follow the HDMI
 * specification as much as possible and tested through HDMI electrical and
 * link layer compliance.
 */
#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)
{
	/* check if cable_id is valid */
	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)
{
	// TODO: rework hdmi_frl_get_verified_link_cap to be a const interface
	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);

	/*Check FLT_update flag*/
	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;
		/*FLT_update - bit 5*/
		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;

	/*Test Condition - FLT_no_timeout avoid link training*/
	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;

	/*Check FLT_update flag*/
	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;
		/*FLT_START - bit 4*/
		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;
		/*Clear SCDC Update Flags*/
		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;
		/*Clear SCDC Update Flags*/
		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);

	/*Test Condition - FLT_no_timeout avoid link training*/
	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;

	/*Test Condition - No need to poll FRL_START if FLT_no_timeout*/
	if (flt_no_timeout)
		return;

	offset = HDMI_SCDC_UPDATE_0;
	/*LTS:P: Check FRL_START, poll for 200ms */
	for (; num_polls; num_polls--) {
		udelay(wait_time);
		/*Check FLT_update flag*/
		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;
			/*FLT_update - bit 5*/
			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;
				/*FLT_update - bit 5*/
			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);

	/*Check FRL_Rate for fallback*/
	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) {
		/*LTS:L FRL_Rate = 0*/
		write_buffer[0] = HDMI_SCDC_CONFIG_1;
		/*FRL_RATE*/
		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) {
		/*Source 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;

	/*LTS:2: Check FLT Ready, poll for 200ms */
	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) {
			/* Spec recommends to clear update flag, but QD980 has problem */
			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;
		}
	}

	/*Test Condition - FLT_no_timeout avoid link training*/
	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) {
		/*Specify FRL rate*/
		write_buffer[0] = HDMI_SCDC_CONFIG_1;
		/*FRL_RATE*/
		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");
		/*LTS:3: Start Link Training*/
		/*Start FLT Timer = 200 ms*/
		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;
			/*Check FLT_update flag*/
			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);
			/*Set TxFFE = TxFFE0*/
			/*Program FFE_Levels - scdc_config has this field at 0 */
			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);

				/*Clear FLT_update flag*/
				FRL_INFO("FRL LINK TRAINING:  Clear FLT_UPDATE flag.\n");
				write_buffer[0] = HDMI_SCDC_UPDATE_0;
				/*FLT_update - bit 5*/
				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) {
						/*Link Training is done*/
						FRL_INFO("FRL LINK TRAINING:  PASSED\n");
						return LINK_RESULT_SUCCESS;
					}
					if (ln0_pattern == 0x0F || ln1_pattern == 0x0F || ln2_pattern == 0x0F) {
						/*sink requesting to lower link rate*/
						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) {
						/*sink requesting to next FFE*/
						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) {
						/*Link Training is done*/
						FRL_INFO("FRL LINK TRAINING:  PASSED\n");
						return LINK_RESULT_SUCCESS;
					}
					if (ln0_pattern == 0x0F || ln1_pattern == 0x0F ||
						ln2_pattern == 0x0F || ln3_pattern == 0x0F) {
						/*sink requesting to lower link rate*/
						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) {
						/*sink requesting to next FFE*/
						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);
				/* Workaround for DEDCN3AG-111
				 * HDMI-FRL Incorrect Serialization Order for LTP4
				 */
			}

		}
		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;
			/*FRL_RATE*/
			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;

	/* FRL Link Training */
	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);

	/* FRL Link Training */
	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;
		}
		/* Monitor patch do decrease 10G to 8G*/
		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;
	/* disable PHY first for PNP */
	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]);
	/*Either enable PHY ourselves or use VBIOS*/

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

	/* Stream not found */
	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 DCN3.0, can also have 4:1 combine mode.
		 * TODO: Add function get_odm_combine_mode that has different
		 *       implementation for DCN2/DCN3AG and DCN3.0
		 */
		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));
		/* Modify patched_crtc_timing as required for padding */
		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));

		/* Find proper clock source in HDMI FRL mode for phy used for DCCG */
		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);

		/* Enable FRL packet transmission */
		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);

			/* Set HDMISTREAMCLK source to DTBCLK0 and bypass DTO */
			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;

	/* Verify FRL and fill in borrow_params to verified_link_cap*/
	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;
	}
	/*test equipment requires max rate, identified at FRL_Max = 1*/
	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);
}