Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,702 @@
/*
* Copyright 2012-15 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 generic display communication protocols such as i2c, aux
* and scdc. The file should not contain any specific applications of these
* protocols such as display capability query, detection, or handshaking such as
* link training.
*/
#include "link_ddc.h"
#include "link_hdmi_frl.h"
#include "vector.h"
#include "dce/dce_aux.h"
#include "dal_asic_id.h"
#include "link_dpcd.h"
#include "dm_helpers.h"
#include "atomfirmware.h"
#define DC_LOGGER \
ddc_service->ctx->logger
#define DC_LOGGER_INIT(logger)
static const uint8_t DP_VGA_DONGLE_BRANCH_DEV_NAME[] = "DpVga";
/* DP to Dual link DVI converter */
static const uint8_t DP_DVI_CONVERTER_ID_4[] = "m2DVIa";
static const uint8_t DP_DVI_CONVERTER_ID_5[] = "3393N2";
struct i2c_payloads {
struct vector payloads;
};
static bool i2c_payloads_create(
struct dc_context *ctx,
struct i2c_payloads *payloads,
uint32_t count)
{
if (dal_vector_construct(
&payloads->payloads, ctx, count, sizeof(struct i2c_payload)))
return true;
return false;
}
static struct i2c_payload *i2c_payloads_get(struct i2c_payloads *p)
{
return (struct i2c_payload *)p->payloads.container;
}
static uint32_t i2c_payloads_get_count(struct i2c_payloads *p)
{
return p->payloads.count;
}
static void i2c_payloads_destroy(struct i2c_payloads *p)
{
if (!p)
return;
dal_vector_destruct(&p->payloads);
}
#define DDC_MIN(a, b) (((a) < (b)) ? (a) : (b))
static void i2c_payloads_add(
struct i2c_payloads *payloads,
uint32_t address,
uint32_t len,
uint8_t *data,
bool write)
{
uint32_t payload_size = EDID_SEGMENT_SIZE;
uint32_t pos;
for (pos = 0; pos < len; pos += payload_size) {
struct i2c_payload payload = {
.write = write,
.address = (uint8_t)address,
.length = DDC_MIN(payload_size, len - pos),
.data = data + pos };
dal_vector_append(&payloads->payloads, &payload);
}
}
static void ddc_service_construct(
struct ddc_service *ddc_service,
struct ddc_service_init_data *init_data)
{
enum connector_id connector_id =
dal_graphics_object_id_get_connector_id(init_data->id);
struct gpio_service *gpio_service = init_data->ctx->gpio_service;
struct graphics_object_i2c_info i2c_info;
struct gpio_ddc_hw_info hw_info;
struct dc_bios *dcb = init_data->ctx->dc_bios;
ddc_service->link = init_data->link;
ddc_service->ctx = init_data->ctx;
if (ddc_service->link && ddc_service->ctx->dc->config.dp_connector_no_native_i2c &&
ddc_service->link->no_ddc_pin) {
// Obtain aux instance info from i2c_info without GPIO DDC pin info
if (dcb->funcs->get_connector_aux_info(dcb, init_data->id, &i2c_info) == BP_RESULT_OK)
ddc_service->link->aux_hw_inst = (uint8_t)i2c_info.i2c_line;
} else {
if (init_data->is_dpia_link ||
dcb->funcs->get_i2c_info(dcb, init_data->id, &i2c_info) != BP_RESULT_OK) {
ddc_service->ddc_pin = NULL;
} else {
DC_LOGGER_INIT(ddc_service->ctx->logger);
DC_LOG_DC("BIOS object table - i2c_line: %d", i2c_info.i2c_line);
DC_LOG_DC("BIOS object table - i2c_engine_id: %d", i2c_info.i2c_engine_id);
hw_info.ddc_channel = i2c_info.i2c_line;
if (ddc_service->link != NULL)
hw_info.hw_supported = i2c_info.i2c_hw_assist;
else
hw_info.hw_supported = false;
ddc_service->ddc_pin = dal_gpio_create_ddc(
gpio_service,
i2c_info.gpio_info.clk_a_register_index,
1 << i2c_info.gpio_info.clk_a_shift,
&hw_info);
}
}
ddc_service->flags.EDID_QUERY_DONE_ONCE = false;
ddc_service->flags.FORCE_READ_REPEATED_START = false;
ddc_service->flags.EDID_STRESS_READ = false;
ddc_service->flags.IS_INTERNAL_DISPLAY =
connector_id == CONNECTOR_ID_EDP ||
connector_id == CONNECTOR_ID_LVDS;
ddc_service->wa.raw = 0;
}
struct ddc_service *link_create_ddc_service(
struct ddc_service_init_data *init_data)
{
struct ddc_service *ddc_service;
ddc_service = kzalloc_obj(struct ddc_service);
if (!ddc_service)
return NULL;
ddc_service_construct(ddc_service, init_data);
return ddc_service;
}
static void ddc_service_destruct(struct ddc_service *ddc)
{
if (ddc->ddc_pin)
dal_gpio_destroy_ddc(&ddc->ddc_pin);
}
void link_destroy_ddc_service(struct ddc_service **ddc)
{
if (!ddc || !*ddc) {
BREAK_TO_DEBUGGER();
return;
}
ddc_service_destruct(*ddc);
kfree(*ddc);
*ddc = NULL;
}
void set_ddc_transaction_type(
struct ddc_service *ddc,
enum ddc_transaction_type type)
{
ddc->transaction_type = type;
}
bool link_is_in_aux_transaction_mode(struct ddc_service *ddc)
{
switch (ddc->transaction_type) {
case DDC_TRANSACTION_TYPE_I2C_OVER_AUX:
case DDC_TRANSACTION_TYPE_I2C_OVER_AUX_WITH_DEFER:
case DDC_TRANSACTION_TYPE_I2C_OVER_AUX_RETRY_DEFER:
return true;
default:
break;
}
return false;
}
void set_dongle_type(struct ddc_service *ddc,
enum display_dongle_type dongle_type)
{
ddc->dongle_type = dongle_type;
}
static uint32_t defer_delay_converter_wa(
struct ddc_service *ddc,
uint32_t defer_delay)
{
struct dc_link *link = ddc->link;
if (link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_VGA_CONVERTER &&
link->dpcd_caps.branch_dev_id == DP_BRANCH_DEVICE_ID_0080E1 &&
(link->dpcd_caps.branch_fw_revision[0] < 0x01 ||
(link->dpcd_caps.branch_fw_revision[0] == 0x01 &&
link->dpcd_caps.branch_fw_revision[1] < 0x40)) &&
!memcmp(link->dpcd_caps.branch_dev_name,
DP_VGA_DONGLE_BRANCH_DEV_NAME,
sizeof(link->dpcd_caps.branch_dev_name)))
return defer_delay > DPVGA_DONGLE_AUX_DEFER_WA_DELAY ?
defer_delay : DPVGA_DONGLE_AUX_DEFER_WA_DELAY;
if (link->dpcd_caps.branch_dev_id == DP_BRANCH_DEVICE_ID_0080E1 &&
!memcmp(link->dpcd_caps.branch_dev_name,
DP_DVI_CONVERTER_ID_4,
sizeof(link->dpcd_caps.branch_dev_name)))
return defer_delay > I2C_OVER_AUX_DEFER_WA_DELAY ?
defer_delay : I2C_OVER_AUX_DEFER_WA_DELAY;
if (link->dpcd_caps.branch_dev_id == DP_BRANCH_DEVICE_ID_006037 &&
!memcmp(link->dpcd_caps.branch_dev_name,
DP_DVI_CONVERTER_ID_5,
sizeof(link->dpcd_caps.branch_dev_name)))
return defer_delay > I2C_OVER_AUX_DEFER_WA_DELAY_1MS ?
I2C_OVER_AUX_DEFER_WA_DELAY_1MS : defer_delay;
return defer_delay;
}
#define DP_TRANSLATOR_DELAY 5
uint32_t link_get_aux_defer_delay(struct ddc_service *ddc)
{
uint32_t defer_delay = 0;
switch (ddc->transaction_type) {
case DDC_TRANSACTION_TYPE_I2C_OVER_AUX:
if ((DISPLAY_DONGLE_DP_VGA_CONVERTER == ddc->dongle_type) ||
(DISPLAY_DONGLE_DP_DVI_CONVERTER == ddc->dongle_type) ||
(DISPLAY_DONGLE_DP_HDMI_CONVERTER ==
ddc->dongle_type)) {
defer_delay = DP_TRANSLATOR_DELAY;
defer_delay =
defer_delay_converter_wa(ddc, defer_delay);
} else /*sink has a delay different from an Active Converter*/
defer_delay = 0;
break;
case DDC_TRANSACTION_TYPE_I2C_OVER_AUX_WITH_DEFER:
defer_delay = DP_TRANSLATOR_DELAY;
break;
default:
break;
}
return defer_delay;
}
static bool submit_aux_command(struct ddc_service *ddc,
struct aux_payload *payload)
{
uint32_t retrieved = 0;
bool ret = false;
if (!ddc)
return false;
if (!payload)
return false;
do {
struct aux_payload current_payload;
bool is_end_of_payload = (retrieved + DEFAULT_AUX_MAX_DATA_SIZE) >=
payload->length;
uint32_t payload_length = is_end_of_payload ?
payload->length - retrieved : DEFAULT_AUX_MAX_DATA_SIZE;
current_payload.address = payload->address;
current_payload.data = &payload->data[retrieved];
current_payload.defer_delay = payload->defer_delay;
current_payload.i2c_over_aux = payload->i2c_over_aux;
current_payload.length = payload_length;
/* set mot (middle of transaction) to false if it is the last payload */
current_payload.mot = is_end_of_payload ? payload->mot:true;
current_payload.write_status_update = false;
current_payload.reply = payload->reply;
current_payload.write = payload->write;
ret = link_aux_transfer_with_retries_no_mutex(ddc, &current_payload);
retrieved += payload_length;
} while (retrieved < payload->length && ret == true);
return ret;
}
bool link_query_ddc_data(
struct ddc_service *ddc,
uint32_t address,
uint8_t *write_buf,
uint32_t write_size,
uint8_t *read_buf,
uint32_t read_size)
{
bool success = true;
uint32_t payload_size =
link_is_in_aux_transaction_mode(ddc) ?
DEFAULT_AUX_MAX_DATA_SIZE : EDID_SEGMENT_SIZE;
uint32_t write_payloads =
(write_size + payload_size - 1) / payload_size;
uint32_t read_payloads =
(read_size + payload_size - 1) / payload_size;
uint32_t payloads_num = write_payloads + read_payloads;
if (!payloads_num)
return false;
if (link_is_in_aux_transaction_mode(ddc)) {
struct aux_payload payload;
payload.i2c_over_aux = true;
payload.address = address;
payload.reply = NULL;
payload.defer_delay = link_get_aux_defer_delay(ddc);
payload.write_status_update = false;
if (write_size != 0) {
payload.write = true;
/* should not set mot (middle of transaction) to 0
* if there are pending read payloads
*/
payload.mot = !(read_size == 0);
payload.length = write_size;
payload.data = write_buf;
success = submit_aux_command(ddc, &payload);
}
if (read_size != 0 && success) {
payload.write = false;
/* should set mot (middle of transaction) to 0
* since it is the last payload to send
*/
payload.mot = false;
payload.length = read_size;
payload.data = read_buf;
success = submit_aux_command(ddc, &payload);
}
} else {
struct i2c_command command = {0};
struct i2c_payloads payloads;
if (!i2c_payloads_create(ddc->ctx, &payloads, payloads_num))
return false;
command.payloads = i2c_payloads_get(&payloads);
command.number_of_payloads = 0;
command.engine = DDC_I2C_COMMAND_ENGINE;
command.speed = ddc->ctx->dc->caps.i2c_speed_in_khz;
i2c_payloads_add(
&payloads, address, write_size, write_buf, true);
i2c_payloads_add(
&payloads, address, read_size, read_buf, false);
command.number_of_payloads = (uint8_t)i2c_payloads_get_count(&payloads);
success = dm_helpers_submit_i2c(
ddc->ctx,
ddc->link,
&command);
i2c_payloads_destroy(&payloads);
}
return success;
}
int link_aux_transfer_raw(struct ddc_service *ddc,
struct aux_payload *payload,
enum aux_return_code_type *operation_result)
{
return dce_aux_transfer_raw(ddc, payload, operation_result);
}
uint32_t link_get_fixed_vs_pe_retimer_write_address(struct dc_link *link)
{
uint32_t vendor_lttpr_write_address = 0xF004F;
uint8_t offset;
switch (link->dpcd_caps.lttpr_caps.phy_repeater_cnt) {
case 0x80: // 1 lttpr repeater
offset = 1;
break;
case 0x40: // 2 lttpr repeaters
offset = 2;
break;
case 0x20: // 3 lttpr repeaters
offset = 3;
break;
case 0x10: // 4 lttpr repeaters
offset = 4;
break;
case 0x08: // 5 lttpr repeaters
offset = 5;
break;
case 0x04: // 6 lttpr repeaters
offset = 6;
break;
case 0x02: // 7 lttpr repeaters
offset = 7;
break;
case 0x01: // 8 lttpr repeaters
offset = 8;
break;
default:
offset = 0xFF;
}
if (offset != 0xFF) {
vendor_lttpr_write_address +=
((DP_REPEATER_CONFIGURATION_AND_STATUS_SIZE) * (offset - 1));
}
return vendor_lttpr_write_address;
}
uint32_t link_get_fixed_vs_pe_retimer_read_address(struct dc_link *link)
{
return link_get_fixed_vs_pe_retimer_write_address(link) + 4;
}
bool link_configure_fixed_vs_pe_retimer(struct ddc_service *ddc, const uint8_t *data, uint32_t length)
{
struct aux_payload write_payload = {
.i2c_over_aux = false,
.write = true,
.address = link_get_fixed_vs_pe_retimer_write_address(ddc->link),
.length = length,
.data = (uint8_t *) data,
.reply = NULL,
.mot = I2C_MOT_UNDEF,
.write_status_update = false,
.defer_delay = 0,
};
return link_aux_transfer_with_retries_no_mutex(ddc,
&write_payload);
}
bool link_query_fixed_vs_pe_retimer(struct ddc_service *ddc, uint8_t *data, uint32_t length)
{
struct aux_payload read_payload = {
.i2c_over_aux = false,
.write = false,
.address = link_get_fixed_vs_pe_retimer_read_address(ddc->link),
.length = length,
.data = data,
.reply = NULL,
.mot = I2C_MOT_UNDEF,
.write_status_update = false,
.defer_delay = 0,
};
return link_aux_transfer_with_retries_no_mutex(ddc,
&read_payload);
}
bool link_aux_transfer_with_retries_no_mutex(struct ddc_service *ddc,
struct aux_payload *payload)
{
return dce_aux_transfer_with_retries(ddc, payload);
}
bool try_to_configure_aux_timeout(struct ddc_service *ddc,
uint32_t timeout)
{
bool result = false;
struct ddc *ddc_pin = ddc->ddc_pin;
if (((ddc->link->chip_caps & AMD_EXT_DISPLAY_PATH_CAPS__EXT_CHIP_MASK) == AMD_EXT_DISPLAY_PATH_CAPS__DP_FIXED_VS_EN) &&
!ddc->link->dc->debug.disable_fixed_vs_aux_timeout_wa &&
ddc->ctx->dce_version == DCN_VERSION_3_1) {
/* Fixed VS workaround for AUX timeout */
const uint32_t fixed_vs_address = 0xF004F;
const uint8_t fixed_vs_data[4] = {0x1, 0x22, 0x63, 0xc};
core_link_write_dpcd(ddc->link,
fixed_vs_address,
fixed_vs_data,
sizeof(fixed_vs_data));
timeout = 3072;
}
/* Do not try to access nonexistent DDC pin. */
if (ddc->link->ep_type != DISPLAY_ENDPOINT_PHY)
return true;
if (ddc->ctx->dc->config.dp_connector_no_native_i2c && ddc->link->no_ddc_pin) {
if (ddc->ctx->dc->res_pool->engines[ddc->link->aux_hw_inst]->funcs->configure_timeout) {
ddc->ctx->dc->res_pool->engines[ddc->link->aux_hw_inst]->funcs->configure_timeout(ddc, timeout);
result = true;
}
} else {
if (ddc->ctx->dc->res_pool->engines[ddc_pin->pin_data->en]->funcs->configure_timeout) {
ddc->ctx->dc->res_pool->engines[ddc_pin->pin_data->en]->funcs->configure_timeout(ddc, timeout);
result = true;
}
}
return result;
}
struct ddc *get_ddc_pin(struct ddc_service *ddc_service)
{
return ddc_service->ddc_pin;
}
void write_scdc_data(struct ddc_service *ddc_service,
uint32_t pix_clk,
bool lte_340_scramble)
{
bool over_340_mhz = pix_clk > 340000 ? 1 : 0;
uint8_t slave_address = HDMI_SCDC_ADDRESS;
uint8_t offset = HDMI_SCDC_SINK_VERSION;
uint8_t sink_version = 0;
uint8_t write_buffer[2] = {0};
/*Lower than 340 Scramble bit from SCDC caps*/
if (ddc_service->link->local_sink &&
(ddc_service->link->local_sink->edid_caps.panel_patch.skip_scdc_overwrite ||
!ddc_service->link->local_sink->edid_caps.scdc_present))
return;
hdmi_frl_LTS_clear_Link_Setting(ddc_service);
hdmi_frl_LTS_clear_Update_flag(ddc_service);
link_query_ddc_data(ddc_service, slave_address, &offset,
sizeof(offset), &sink_version, sizeof(sink_version));
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);
/*Read Request from SCDC caps*/
}
write_buffer[0] = HDMI_SCDC_TMDS_CONFIG;
if (over_340_mhz) {
write_buffer[1] = 3;
} else if (lte_340_scramble) {
write_buffer[1] = 1;
} else {
write_buffer[1] = 0;
}
link_query_ddc_data(ddc_service, slave_address, write_buffer,
sizeof(write_buffer), NULL, 0);
}
void read_scdc_data(struct ddc_service *ddc_service)
{
uint8_t slave_address = HDMI_SCDC_ADDRESS;
uint8_t offset = HDMI_SCDC_TMDS_CONFIG;
uint8_t tmds_config = 0;
if (ddc_service->link->local_sink &&
ddc_service->link->local_sink->edid_caps.panel_patch.skip_scdc_overwrite)
return;
link_query_ddc_data(ddc_service, slave_address, &offset,
sizeof(offset), &tmds_config, sizeof(tmds_config));
if (tmds_config & 0x1) {
union hdmi_scdc_status_flags_data status_data = {0};
uint8_t scramble_status = 0;
offset = HDMI_SCDC_SCRAMBLER_STATUS;
link_query_ddc_data(ddc_service, slave_address,
&offset, sizeof(offset), &scramble_status,
sizeof(scramble_status));
offset = HDMI_SCDC_STATUS_FLAGS;
link_query_ddc_data(ddc_service, slave_address,
&offset, sizeof(offset), &status_data.byte,
sizeof(status_data.byte));
}
}
void write_idcc_data(struct ddc_service *ddc_service, enum hdmi_idcc_scope idcc_scope,
uint8_t *write_buf, uint8_t offset, uint8_t write_len)
{
uint8_t slave_address = HDMI_IDCC_ADDRESS;
uint8_t idcc_header[5] = {0};
uint8_t dummy_buf[1] = {0};
uint8_t checksum = 0;
int i;
idcc_header[0] = HDMI_IDCC_MARKER0;
idcc_header[1] = HDMI_IDCC_MARKER1;
idcc_header[2] = (uint8_t)(HDMI_IDCC_MARKER2 | idcc_scope);
idcc_header[3] = offset;
idcc_header[4] = write_len;
/* Write the IDCC header */
for (i = 0; i < sizeof(idcc_header); ++i) {
link_query_ddc_data(ddc_service, slave_address,
&idcc_header[i], 1,
&dummy_buf[0], 1);
checksum += idcc_header[i];
}
/* Write the payload */
for (i = 0; i < write_len; ++i) {
link_query_ddc_data(ddc_service, slave_address,
&write_buf[i], 1,
&dummy_buf[0], 1);
checksum += write_buf[i];
}
/* Write the checksum */
if (write_len > 0) {
checksum = 0xff - checksum + 1;
link_query_ddc_data(ddc_service, slave_address,
&checksum, 1,
&dummy_buf[0], 1);
}
}
int read_idcc_data(struct ddc_service *ddc_service, enum hdmi_idcc_scope idcc_scope,
uint8_t *read_buf, uint8_t offset, uint8_t read_len)
{
uint8_t slave_address = HDMI_IDCC_ADDRESS;
uint8_t idcc_header[5] = {0};
uint8_t dummy_buf[1] = {0};
uint8_t read_buf_local[6] = {0};
uint8_t checksum = 0;
int i;
idcc_header[0] = HDMI_IDCC_MARKER0;
idcc_header[1] = HDMI_IDCC_MARKER1;
idcc_header[2] = (uint8_t)(HDMI_IDCC_MARKER2 | idcc_scope);
idcc_header[3] = offset;
idcc_header[4] = read_len;
/* Write the IDCC header */
for (i = 0; i < sizeof(idcc_header); ++i) {
link_query_ddc_data(ddc_service, slave_address,
&idcc_header[i], 1,
&dummy_buf[0], 1);
checksum += idcc_header[i];
}
/* Read the payload */
if (read_len > 0) {
dummy_buf[0] = 0x01;
if (read_len > 5)
read_len = 5;
link_query_ddc_data(ddc_service, slave_address,
&dummy_buf[0], 1,
&read_buf_local[0], read_len + 1);
memcpy(read_buf, read_buf_local, read_len);
/* Check checksum */
checksum = read_buf_local[read_len];
for (i = 0; i < 5; ++i)
checksum += idcc_header[i];
for (i = 0; i < read_len; ++i)
checksum += read_buf_local[i];
if (checksum != 0)
return -1;
}
return read_len;
}
@@ -0,0 +1,110 @@
/*
* Copyright 2012-15 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
*
*/
#ifndef __DAL_DDC_SERVICE_H__
#define __DAL_DDC_SERVICE_H__
#include "link_service.h"
#define AUX_POWER_UP_WA_DELAY 500
#define I2C_OVER_AUX_DEFER_WA_DELAY 70
#define DPVGA_DONGLE_AUX_DEFER_WA_DELAY 40
#define I2C_OVER_AUX_DEFER_WA_DELAY_1MS 1
#define LINK_AUX_DEFAULT_LTTPR_TIMEOUT_PERIOD 3200 /*us*/
#define LINK_AUX_DEFAULT_TIMEOUT_PERIOD 552 /*us*/
#define EDID_SEGMENT_SIZE 256
struct ddc_service *link_create_ddc_service(
struct ddc_service_init_data *ddc_init_data);
void link_destroy_ddc_service(struct ddc_service **ddc);
void set_ddc_transaction_type(
struct ddc_service *ddc,
enum ddc_transaction_type type);
uint32_t link_get_aux_defer_delay(struct ddc_service *ddc);
bool link_is_in_aux_transaction_mode(struct ddc_service *ddc);
bool try_to_configure_aux_timeout(struct ddc_service *ddc,
uint32_t timeout);
bool link_query_ddc_data(
struct ddc_service *ddc,
uint32_t address,
uint8_t *write_buf,
uint32_t write_size,
uint8_t *read_buf,
uint32_t read_size);
/* Attempt to submit an aux payload, retrying on timeouts, defers, and busy
* states as outlined in the DP spec. Returns true if the request was
* successful.
*
* NOTE: The function requires explicit mutex on DM side in order to prevent
* potential race condition. DC components should call the dpcd read/write
* function in dm_helpers in order to access dpcd safely
*/
bool link_aux_transfer_with_retries_no_mutex(struct ddc_service *ddc,
struct aux_payload *payload);
bool link_configure_fixed_vs_pe_retimer(
struct ddc_service *ddc,
const uint8_t *data,
uint32_t length);
bool link_query_fixed_vs_pe_retimer(
struct ddc_service *ddc,
uint8_t *data,
uint32_t length);
uint32_t link_get_fixed_vs_pe_retimer_read_address(struct dc_link *link);
uint32_t link_get_fixed_vs_pe_retimer_write_address(struct dc_link *link);
void write_scdc_data(
struct ddc_service *ddc_service,
uint32_t pix_clk,
bool lte_340_scramble);
void read_scdc_data(
struct ddc_service *ddc_service);
void write_idcc_data(struct ddc_service *ddc_service, enum hdmi_idcc_scope idcc_scope,
uint8_t *write_buf, uint8_t offset, uint8_t write_len);
int read_idcc_data(struct ddc_service *ddc_service, enum hdmi_idcc_scope idcc_scope,
uint8_t *read_buf, uint8_t offset, uint8_t read_len);
void set_dongle_type(struct ddc_service *ddc,
enum display_dongle_type dongle_type);
struct ddc *get_ddc_pin(struct ddc_service *ddc_service);
int link_aux_transfer_raw(struct ddc_service *ddc,
struct aux_payload *payload,
enum aux_return_code_type *operation_result);
#endif /* __DAL_DDC_SERVICE_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,117 @@
/*
* 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
*
*/
#ifndef __DC_LINK_DP_CAPABILITY_H__
#define __DC_LINK_DP_CAPABILITY_H__
#include "link_service.h"
bool detect_dp_sink_caps(struct dc_link *link);
void detect_edp_sink_caps(struct dc_link *link);
struct dc_link_settings dp_get_max_link_cap(struct dc_link *link);
bool dp_get_max_link_enc_cap(const struct dc_link *link,
struct dc_link_settings *max_link_enc_cap);
const struct dc_link_settings *dp_get_verified_link_cap(
const struct dc_link *link);
enum dp_link_encoding link_dp_get_encoding_format(
const struct dc_link_settings *link_settings);
enum dc_status dp_retrieve_lttpr_cap(struct dc_link *link);
/* Convert PHY repeater count read from DPCD uint8_t. */
uint8_t dp_parse_lttpr_repeater_count(uint8_t lttpr_repeater_count);
/* Calculate embedded LTTPR address offset for vendor-specific behaviour */
uint32_t dp_get_closest_lttpr_offset(uint8_t lttpr_count);
bool dp_is_sink_present(struct dc_link *link);
bool dp_is_lttpr_present(struct dc_link *link);
bool dp_is_fec_supported(const struct dc_link *link);
bool is_dp_active_dongle(const struct dc_link *link);
bool is_dp_branch_device(const struct dc_link *link);
void dpcd_write_cable_id_to_dprx(struct dc_link *link);
bool dp_should_enable_fec(const struct dc_link *link);
bool dp_is_128b_132b_signal(struct pipe_ctx *pipe_ctx);
/* Initialize output parameter lt_settings. */
void dp_decide_training_settings(
struct dc_link *link,
const struct link_resource *link_res,
const struct dc_link_settings *link_setting,
struct link_training_settings *lt_settings);
bool link_decide_link_settings(
struct dc_stream_state *stream,
struct dc_link_settings *link_setting);
bool edp_decide_link_settings(struct dc_link *link,
struct dc_link_settings *link_setting, uint32_t req_bw);
bool decide_edp_link_settings_with_dsc(struct dc_link *link,
struct dc_link_settings *link_setting,
uint32_t req_bw,
enum dc_link_rate max_link_rate);
enum dp_link_encoding mst_decide_link_encoding_format(const struct dc_link *link);
void dpcd_set_source_specific_data(struct dc_link *link);
/*query dpcd for version and mst cap addresses*/
bool read_is_mst_supported(struct dc_link *link);
bool decide_fallback_link_setting(
struct dc_link *link,
struct dc_link_settings *max,
struct dc_link_settings *cur,
enum link_training_result training_result);
bool dp_verify_link_cap_with_retries(
struct dc_link *link,
struct dc_link_settings *known_limit_link_setting,
int attempts);
uint32_t link_bw_kbps_from_raw_frl_link_rate_data(uint8_t bw);
bool dp_overwrite_extended_receiver_cap(struct dc_link *link);
uint8_t dp_get_lttpr_count(struct dc_link *link);
void edp_get_alpm_support(struct dc_link *link,
bool *auxless_support,
bool *auxwake_support);
#endif /* __DC_LINK_DP_CAPABILITY_H__ */
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: MIT
/*
* Copyright 2021 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
*
*/
#include "dc.h"
#include "inc/core_status.h"
#include "dpcd_defs.h"
#include "link_dp_dpia.h"
#include "link_hwss.h"
#include "dm_helpers.h"
#include "dmub/inc/dmub_cmd.h"
#include "link_dpcd.h"
#include "link_dp_training.h"
#include "dc_dmub_srv.h"
#define DC_LOGGER \
link->ctx->logger
/** @note Can remove once DP tunneling registers in upstream include/drm/drm_dp_helper.h */
/* DPCD DP Tunneling over USB4 */
#define DP_TUNNELING_CAPABILITIES_SUPPORT 0xe000d
#define DP_IN_ADAPTER_INFO 0xe000e
#define DP_USB4_DRIVER_ID 0xe000f
#define DP_USB4_ROUTER_TOPOLOGY_ID 0xe001b
enum dc_status dpcd_get_tunneling_device_data(struct dc_link *link)
{
enum dc_status status = DC_OK;
uint8_t dpcd_dp_tun_data[3] = {0};
uint8_t dpcd_topology_data[DPCD_USB4_TOPOLOGY_ID_LEN] = {0};
uint8_t i = 0;
status = core_link_read_dpcd(
link,
DP_TUNNELING_CAPABILITIES_SUPPORT,
dpcd_dp_tun_data,
sizeof(dpcd_dp_tun_data));
if (status != DC_OK)
goto err;
link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.raw =
dpcd_dp_tun_data[DP_TUNNELING_CAPABILITIES_SUPPORT - DP_TUNNELING_CAPABILITIES_SUPPORT];
if (link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dp_tunneling == false)
goto err;
link->dpcd_caps.usb4_dp_tun_info.dpia_info.raw =
dpcd_dp_tun_data[DP_IN_ADAPTER_INFO - DP_TUNNELING_CAPABILITIES_SUPPORT];
link->dpcd_caps.usb4_dp_tun_info.usb4_driver_id =
dpcd_dp_tun_data[DP_USB4_DRIVER_ID - DP_TUNNELING_CAPABILITIES_SUPPORT] & 0x0F;
if (link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dpia_bw_alloc) {
status = core_link_read_dpcd(link, USB4_DRIVER_BW_CAPABILITY,
dpcd_dp_tun_data, 2);
if (status != DC_OK)
goto err;
link->dpcd_caps.usb4_dp_tun_info.driver_bw_cap.raw =
dpcd_dp_tun_data[USB4_DRIVER_BW_CAPABILITY - USB4_DRIVER_BW_CAPABILITY];
link->dpcd_caps.usb4_dp_tun_info.dpia_tunnel_info.raw =
dpcd_dp_tun_data[DP_IN_ADAPTER_TUNNEL_INFO - USB4_DRIVER_BW_CAPABILITY];
}
DC_LOG_DEBUG("%s: Link[%d] DP tunneling support (RouterId=%d AdapterId=%d) "
"DPIA_BW_Alloc_support=%d "
"CM_BW_Alloc_support=%d ",
__func__, link->link_index,
link->dpcd_caps.usb4_dp_tun_info.usb4_driver_id,
link->dpcd_caps.usb4_dp_tun_info.dpia_info.bits.dpia_num,
link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dpia_bw_alloc,
link->dpcd_caps.usb4_dp_tun_info.driver_bw_cap.bits.driver_bw_alloc_support);
status = core_link_read_dpcd(
link,
DP_USB4_ROUTER_TOPOLOGY_ID,
dpcd_topology_data,
sizeof(dpcd_topology_data));
if (status != DC_OK)
goto err;
for (i = 0; i < DPCD_USB4_TOPOLOGY_ID_LEN; i++)
link->dpcd_caps.usb4_dp_tun_info.usb4_topology_id[i] = dpcd_topology_data[i];
err:
return status;
}
bool dpia_query_hpd_status(struct dc_link *link)
{
union dmub_rb_cmd cmd = {0};
struct dc_dmub_srv *dmub_srv = link->ctx->dmub_srv;
/* prepare QUERY_HPD command */
cmd.query_hpd.header.type = DMUB_CMD__QUERY_HPD_STATE;
cmd.query_hpd.header.payload_bytes = sizeof(cmd.query_hpd.data);
cmd.query_hpd.data.instance = (uint8_t)(link->link_id.enum_id - ENUM_ID_1);
cmd.query_hpd.data.ch_type = AUX_CHANNEL_DPIA;
/* Query dpia hpd status from dmub */
if (dc_wake_and_execute_dmub_cmd(dmub_srv->ctx, &cmd,
DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY) &&
cmd.query_hpd.data.status == AUX_RET_SUCCESS) {
DC_LOG_DEBUG("%s: for link(%d) dpia(%d) success, current_hpd_status(%d) new_hpd_status(%d)\n",
__func__,
link->link_index,
link->link_id.enum_id - ENUM_ID_1,
link->hpd_status,
cmd.query_hpd.data.result);
link->hpd_status = cmd.query_hpd.data.result;
} else {
DC_LOG_ERROR("%s: for link(%d) dpia(%d) failed with status(%d), current_hpd_status(%d) new_hpd_status(0)\n",
__func__,
link->link_index,
link->link_id.enum_id - ENUM_ID_1,
cmd.query_hpd.data.status,
link->hpd_status);
link->hpd_status = false;
}
return link->hpd_status;
}
void link_decide_dp_tunnel_settings(struct dc_stream_state *stream,
struct dc_tunnel_settings *dp_tunnel_setting)
{
struct dc_link *link = stream->link;
memset(dp_tunnel_setting, 0, sizeof(*dp_tunnel_setting));
if ((stream->signal == SIGNAL_TYPE_DISPLAY_PORT) || (stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST)) {
dp_tunnel_setting->should_enable_dp_tunneling =
link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dp_tunneling;
if (link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dpia_bw_alloc
&& link->dpcd_caps.usb4_dp_tun_info.driver_bw_cap.bits.driver_bw_alloc_support) {
dp_tunnel_setting->should_use_dp_bw_allocation = true;
dp_tunnel_setting->cm_id = link->dpcd_caps.usb4_dp_tun_info.usb4_driver_id & 0x0F;
dp_tunnel_setting->group_id = link->dpcd_caps.usb4_dp_tun_info.dpia_tunnel_info.bits.group_id;
dp_tunnel_setting->estimated_bw = link->dpia_bw_alloc_config.estimated_bw;
dp_tunnel_setting->allocated_bw = link->dpia_bw_alloc_config.allocated_bw;
dp_tunnel_setting->bw_granularity = link->dpia_bw_alloc_config.bw_granularity;
}
}
}
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2021 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
*
*/
#ifndef __DC_LINK_DPIA_H__
#define __DC_LINK_DPIA_H__
#include "link_service.h"
/* Read tunneling device capability from DPCD and update link capability
* accordingly.
*/
enum dc_status dpcd_get_tunneling_device_data(struct dc_link *link);
/* Query hot plug status of USB4 DP tunnel.
* Returns true if HPD high.
*/
bool dpia_query_hpd_status(struct dc_link *link);
/* Decide the DP tunneling settings based on the DPCD capabilities
*/
void link_decide_dp_tunnel_settings(struct dc_stream_state *stream,
struct dc_tunnel_settings *dp_tunnel_setting);
#endif /* __DC_LINK_DPIA_H__ */
@@ -0,0 +1,448 @@
/*
* 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
*
*/
/*********************************************************************/
// USB4 DPIA BANDWIDTH ALLOCATION LOGIC
/*********************************************************************/
#include "link_dp_dpia_bw.h"
#include "link_dpcd.h"
#include "dc_dmub_srv.h"
#define DC_LOGGER \
link->ctx->logger
#define Kbps_TO_Gbps (1000 * 1000)
#define MST_TIME_SLOT_COUNT 64
// ------------------------------------------------------------------
// PRIVATE FUNCTIONS
// ------------------------------------------------------------------
/*
* Always Check the following:
* - Is it USB4 link?
* - Is HPD HIGH?
* - Is BW Allocation Support Mode enabled on DP-Tx?
*/
static bool link_dp_is_bw_alloc_available(struct dc_link *link)
{
return (link && link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dp_tunneling
&& link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dpia_bw_alloc
&& link->dpcd_caps.usb4_dp_tun_info.driver_bw_cap.bits.driver_bw_alloc_support);
}
static void reset_bw_alloc_struct(struct dc_link *link)
{
link->dpia_bw_alloc_config.bw_alloc_enabled = false;
link->dpia_bw_alloc_config.link_verified_bw = 0;
link->dpia_bw_alloc_config.link_max_bw = 0;
link->dpia_bw_alloc_config.allocated_bw = 0;
link->dpia_bw_alloc_config.estimated_bw = 0;
link->dpia_bw_alloc_config.bw_granularity = 0;
link->dpia_bw_alloc_config.dp_overhead = 0;
link->dpia_bw_alloc_config.nrd_max_lane_count = 0;
link->dpia_bw_alloc_config.nrd_max_link_rate = 0;
for (int i = 0; i < MAX_SINKS_PER_LINK; i++)
link->dpia_bw_alloc_config.remote_sink_req_bw[i] = 0;
DC_LOG_DEBUG("reset usb4 bw alloc of link(%d)\n", link->link_index);
}
#define BW_GRANULARITY_0 4 // 0.25 Gbps
#define BW_GRANULARITY_1 2 // 0.5 Gbps
#define BW_GRANULARITY_2 1 // 1 Gbps
static uint8_t get_bw_granularity(struct dc_link *link)
{
uint8_t bw_granularity = 0;
core_link_read_dpcd(
link,
DP_BW_GRANULALITY,
&bw_granularity,
sizeof(uint8_t));
switch (bw_granularity & 0x3) {
case 0:
bw_granularity = BW_GRANULARITY_0;
break;
case 1:
bw_granularity = BW_GRANULARITY_1;
break;
case 2:
default:
bw_granularity = BW_GRANULARITY_2;
break;
}
return bw_granularity;
}
static int get_estimated_bw(struct dc_link *link)
{
uint8_t bw_estimated_bw = 0;
core_link_read_dpcd(
link,
ESTIMATED_BW,
&bw_estimated_bw,
sizeof(uint8_t));
return bw_estimated_bw * (Kbps_TO_Gbps / link->dpia_bw_alloc_config.bw_granularity);
}
static uint8_t get_non_reduced_max_link_rate(struct dc_link *link)
{
uint8_t nrd_max_link_rate = 0;
core_link_read_dpcd(
link,
DP_TUNNELING_MAX_LINK_RATE,
&nrd_max_link_rate,
sizeof(uint8_t));
return nrd_max_link_rate;
}
static uint8_t get_non_reduced_max_lane_count(struct dc_link *link)
{
uint8_t nrd_max_lane_count = 0;
core_link_read_dpcd(
link,
DP_TUNNELING_MAX_LANE_COUNT,
&nrd_max_lane_count,
sizeof(uint8_t));
return nrd_max_lane_count;
}
/*
* Read all New BW alloc configuration ex: estimated_bw, allocated_bw,
* granuality, Driver_ID, CM_Group, & populate the BW allocation structs
* for host router and dpia
*/
static void retrieve_usb4_dp_bw_allocation_info(struct dc_link *link)
{
reset_bw_alloc_struct(link);
/* init the known values */
link->dpia_bw_alloc_config.bw_granularity = get_bw_granularity(link);
link->dpia_bw_alloc_config.estimated_bw = get_estimated_bw(link);
link->dpia_bw_alloc_config.nrd_max_link_rate = get_non_reduced_max_link_rate(link);
link->dpia_bw_alloc_config.nrd_max_lane_count = get_non_reduced_max_lane_count(link);
DC_LOG_DEBUG("%s: bw_granularity(%d), estimated_bw(%d)\n",
__func__, link->dpia_bw_alloc_config.bw_granularity,
link->dpia_bw_alloc_config.estimated_bw);
DC_LOG_DEBUG("%s: nrd_max_link_rate(%d), nrd_max_lane_count(%d)\n",
__func__, link->dpia_bw_alloc_config.nrd_max_link_rate,
link->dpia_bw_alloc_config.nrd_max_lane_count);
}
/*
* Cleanup function for when the dpia is unplugged to reset struct
* and perform any required clean up
*
* @link: pointer to the dc_link struct instance
*
* return: none
*/
static void dpia_bw_alloc_unplug(struct dc_link *link)
{
if (link) {
DC_LOG_DEBUG("%s: resetting BW alloc config for link(%d)\n",
__func__, link->link_index);
reset_bw_alloc_struct(link);
}
}
static void link_dpia_send_bw_alloc_request(struct dc_link *link, int req_bw)
{
uint32_t request_reg_val;
uint32_t temp, request_bw;
if (link->dpia_bw_alloc_config.bw_granularity == 0) {
DC_LOG_ERROR("%s: Link[%d]: bw_granularity is zero!", __func__, link->link_index);
return;
}
temp = req_bw * link->dpia_bw_alloc_config.bw_granularity;
request_reg_val = temp / Kbps_TO_Gbps;
/* Always make sure to add more to account for floating points */
if (temp % Kbps_TO_Gbps)
++request_reg_val;
request_bw = request_reg_val * (Kbps_TO_Gbps / link->dpia_bw_alloc_config.bw_granularity);
if (request_bw > (uint32_t)link->dpia_bw_alloc_config.estimated_bw) {
DC_LOG_ERROR("%s: Link[%d]: Request BW (%d --> %d) > Estimated BW (%d)... Set to Estimated BW!",
__func__, link->link_index,
req_bw, request_bw, link->dpia_bw_alloc_config.estimated_bw);
req_bw = link->dpia_bw_alloc_config.estimated_bw;
temp = req_bw * link->dpia_bw_alloc_config.bw_granularity;
request_reg_val = temp / Kbps_TO_Gbps;
if (temp % Kbps_TO_Gbps)
++request_reg_val;
}
link->dpia_bw_alloc_config.allocated_bw = request_bw;
DC_LOG_DC("%s: Link[%d]: Request BW: %d", __func__, link->link_index, request_bw);
uint8_t requested_bw_dpcd = (uint8_t)request_reg_val;
core_link_write_dpcd(link, REQUESTED_BW, &requested_bw_dpcd,
sizeof(uint8_t));
}
// ------------------------------------------------------------------
// PUBLIC FUNCTIONS
// ------------------------------------------------------------------
bool link_dpia_enable_usb4_dp_bw_alloc_mode(struct dc_link *link)
{
bool ret = false;
uint8_t val;
val = DPTX_BW_ALLOC_MODE_ENABLE | DPTX_BW_ALLOC_UNMASK_IRQ;
if (core_link_write_dpcd(link, DPTX_BW_ALLOCATION_MODE_CONTROL, &val, sizeof(uint8_t)) == DC_OK) {
DC_LOG_DEBUG("%s: link[%d] DPTX BW allocation mode enabled", __func__, link->link_index);
retrieve_usb4_dp_bw_allocation_info(link);
if (
link->dpia_bw_alloc_config.nrd_max_link_rate
&& link->dpia_bw_alloc_config.nrd_max_lane_count) {
link->reported_link_cap.link_rate = link->dpia_bw_alloc_config.nrd_max_link_rate;
link->reported_link_cap.lane_count = link->dpia_bw_alloc_config.nrd_max_lane_count;
}
link->dpia_bw_alloc_config.bw_alloc_enabled = true;
ret = true;
if (link->dc->debug.dpia_debug.bits.enable_usb4_bw_zero_alloc_patch) {
/*
* During DP tunnel creation, the CM preallocates BW
* and reduces the estimated BW of other DPIAs.
* The CM releases the preallocation only when the allocation is complete.
* Perform a zero allocation to make the CM release the preallocation
* and correctly update the estimated BW for all DPIAs per host router.
*/
link_dp_dpia_allocate_usb4_bandwidth_for_stream(link, 0);
}
} else
DC_LOG_DEBUG("%s: link[%d] failed to enable DPTX BW allocation mode", __func__, link->link_index);
return ret;
}
/*
* Handle DP BW allocation status register
*
* @link: pointer to the dc_link struct instance
* @status: content of DP tunneling status DPCD register
*
* return: none
*/
void link_dp_dpia_handle_bw_alloc_status(struct dc_link *link, uint8_t status)
{
if (status & DP_TUNNELING_BW_REQUEST_SUCCEEDED) {
DC_LOG_DEBUG("%s: BW Allocation request succeeded on link(%d)",
__func__, link->link_index);
}
if (status & DP_TUNNELING_BW_REQUEST_FAILED) {
DC_LOG_DEBUG("%s: BW Allocation request failed on link(%d) allocated/estimated BW=%d",
__func__, link->link_index, link->dpia_bw_alloc_config.estimated_bw);
link_dpia_send_bw_alloc_request(link, link->dpia_bw_alloc_config.estimated_bw);
}
if (status & DP_TUNNELING_BW_ALLOC_CAP_CHANGED) {
link->dpia_bw_alloc_config.bw_granularity = get_bw_granularity(link);
DC_LOG_DEBUG("%s: Granularity changed on link(%d) new granularity=%d",
__func__, link->link_index, link->dpia_bw_alloc_config.bw_granularity);
}
if (status & DP_TUNNELING_ESTIMATED_BW_CHANGED) {
link->dpia_bw_alloc_config.estimated_bw = get_estimated_bw(link);
DC_LOG_DEBUG("%s: Estimated BW changed on link(%d) new estimated BW=%d",
__func__, link->link_index, link->dpia_bw_alloc_config.estimated_bw);
}
core_link_write_dpcd(
link, DP_TUNNELING_STATUS,
&status, sizeof(status));
}
/*
* Handle the DP Bandwidth allocation for DPIA
*
*/
void dpia_handle_usb4_bandwidth_allocation_for_link(struct dc_link *link, int peak_bw)
{
if (link && link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dp_tunneling
&& link->dpia_bw_alloc_config.bw_alloc_enabled) {
if (peak_bw > 0) {
// If DP over USB4 then we need to check BW allocation
link->dpia_bw_alloc_config.link_max_bw = peak_bw;
link_dpia_send_bw_alloc_request(link, peak_bw);
} else
dpia_bw_alloc_unplug(link);
}
}
void link_dp_dpia_allocate_usb4_bandwidth_for_stream(struct dc_link *link, int req_bw)
{
link->dpia_bw_alloc_config.estimated_bw = get_estimated_bw(link);
DC_LOG_DEBUG("%s: ENTER: link[%d] hpd(%d) Allocated_BW: %d Estimated_BW: %d Req_BW: %d",
__func__, link->link_index, link->hpd_status,
link->dpia_bw_alloc_config.allocated_bw,
link->dpia_bw_alloc_config.estimated_bw,
req_bw);
if (link_dp_is_bw_alloc_available(link))
link_dpia_send_bw_alloc_request(link, req_bw);
else
DC_LOG_DEBUG("%s: BW Allocation mode not available", __func__);
}
uint32_t link_dpia_get_dp_overhead(const struct dc_link *link)
{
uint32_t link_dp_overhead = 0;
if ((link->type == dc_connection_mst_branch) &&
!link->dpcd_caps.channel_coding_cap.bits.DP_128b_132b_SUPPORTED) {
/* For 8b/10b encoding: MTP is 64 time slots long, slot 0 is used for MTPH
* MST overhead is 1/64 of link bandwidth (excluding any overhead)
*/
const struct dc_link_settings *link_cap = dc_link_get_link_cap(link);
if (link_cap) {
uint32_t link_bw_in_kbps = (uint32_t)link_cap->link_rate *
(uint32_t)link_cap->lane_count *
LINK_RATE_REF_FREQ_IN_KHZ * 8;
link_dp_overhead = (link_bw_in_kbps / MST_TIME_SLOT_COUNT)
+ ((link_bw_in_kbps % MST_TIME_SLOT_COUNT) ? 1 : 0);
}
}
return link_dp_overhead;
}
/*
* Aggregates the DPIA bandwidth usage for the respective USB4 Router.
* And then validate if the required bandwidth is within the router's capacity.
*
* @dc_validation_dpia_set: pointer to the dc_validation_dpia_set
* @count: number of DPIA validation sets
*
* return: true if validation is succeeded
*/
bool link_dpia_validate_dp_tunnel_bandwidth(const struct dc_validation_dpia_set *dpia_link_sets, uint8_t count)
{
uint32_t granularity_Gbps;
const struct dc_link *link;
uint32_t link_bw_granularity;
uint32_t link_required_bw;
struct usb4_router_validation_set router_sets[MAX_HOST_ROUTERS_NUM] = { 0 };
uint8_t i;
bool is_success = true;
uint8_t router_count = 0;
if ((dpia_link_sets == NULL) || (count == 0))
return is_success;
// Iterate through each DP tunneling link (DPIA).
// Aggregate its bandwidth requirements onto the respective USB4 router.
for (i = 0; i < count; i++) {
link = dpia_link_sets[i].link;
link_required_bw = dpia_link_sets[i].required_bw;
const struct dc_tunnel_settings *dp_tunnel_settings = dpia_link_sets[i].tunnel_settings;
if ((link == NULL) || (dp_tunnel_settings == NULL) || dp_tunnel_settings->bw_granularity == 0)
break;
if (link->type == dc_connection_mst_branch)
link_required_bw += link_dpia_get_dp_overhead(link);
granularity_Gbps = (Kbps_TO_Gbps / dp_tunnel_settings->bw_granularity);
link_bw_granularity = (link_required_bw / granularity_Gbps) * granularity_Gbps +
((link_required_bw % granularity_Gbps) ? granularity_Gbps : 0);
// Find or add the USB4 router associated with the current DPIA link
for (uint8_t j = 0; j < MAX_HOST_ROUTERS_NUM; j++) {
if (router_sets[j].is_valid == false) {
router_sets[j].is_valid = true;
router_sets[j].cm_id = dp_tunnel_settings->cm_id;
router_count++;
}
if (router_sets[j].cm_id == dp_tunnel_settings->cm_id) {
uint32_t remaining_bw =
dp_tunnel_settings->estimated_bw - dp_tunnel_settings->allocated_bw;
router_sets[j].allocated_bw += dp_tunnel_settings->allocated_bw;
if (remaining_bw > router_sets[j].remaining_bw)
router_sets[j].remaining_bw = remaining_bw;
// Get the max estimated BW within the same CM_ID
if (dp_tunnel_settings->estimated_bw > router_sets[j].estimated_bw)
router_sets[j].estimated_bw = dp_tunnel_settings->estimated_bw;
router_sets[j].required_bw += link_bw_granularity;
router_sets[j].dpia_count++;
break;
}
}
}
// Validate bandwidth for each unique router found.
for (i = 0; i < router_count; i++) {
uint32_t total_bw = 0;
if (router_sets[i].is_valid == false)
break;
// Determine the total available bandwidth for the current router based on aggregated data
if ((router_sets[i].dpia_count == 1) || (router_sets[i].allocated_bw == 0))
total_bw = router_sets[i].estimated_bw;
else
total_bw = router_sets[i].allocated_bw + router_sets[i].remaining_bw;
if (router_sets[i].required_bw > total_bw) {
is_success = false;
break;
}
}
return is_success;
}
@@ -0,0 +1,112 @@
/*
* Copyright 2021 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
*
*/
#ifndef DC_INC_LINK_DP_DPIA_BW_H_
#define DC_INC_LINK_DP_DPIA_BW_H_
#include "link_service.h"
/*
* Host Router BW type
*/
enum bw_type {
HOST_ROUTER_BW_ESTIMATED,
HOST_ROUTER_BW_ALLOCATED,
HOST_ROUTER_BW_INVALID,
};
struct usb4_router_validation_set {
bool is_valid;
uint8_t cm_id;
uint8_t dpia_count;
uint32_t required_bw;
uint32_t allocated_bw;
uint32_t estimated_bw;
uint32_t remaining_bw;
};
/*
* Enable USB4 DP BW allocation mode
*
* @link: pointer to the dc_link struct instance
*
* return: SUCCESS or FAILURE
*/
bool link_dpia_enable_usb4_dp_bw_alloc_mode(struct dc_link *link);
/*
* Allocates only what the stream needs for bw, so if:
* If (stream_req_bw < or > already_allocated_bw_at_HPD)
* => Deallocate Max Bw & then allocate only what the stream needs
*
* @link: pointer to the dc_link struct instance
* @req_bw: Bw requested by the stream
*
*/
void link_dp_dpia_allocate_usb4_bandwidth_for_stream(struct dc_link *link, int req_bw);
/*
* Handle the USB4 BW Allocation related functionality here:
* Plug => Try to allocate max bw from timing parameters supported by the sink
* Unplug => de-allocate bw
*
* @link: pointer to the dc_link struct instance
* @peak_bw: Peak bw used by the link/sink
*
*/
void dpia_handle_usb4_bandwidth_allocation_for_link(struct dc_link *link, int peak_bw);
/*
* Obtain all the DP overheads in dp tunneling for the dpia link
*
* @link: pointer to the dc_link struct instance
*
* return: DP overheads in DP tunneling
*/
uint32_t link_dpia_get_dp_overhead(const struct dc_link *link);
/*
* Handle DP BW allocation status register
*
* @link: pointer to the dc_link struct instance
* @status: content of DP tunneling status register
*
* return: none
*/
void link_dp_dpia_handle_bw_alloc_status(struct dc_link *link, uint8_t status);
/*
* Aggregates the DPIA bandwidth usage for the respective USB4 Router.
*
* @dc_validation_dpia_set: pointer to the dc_validation_dpia_set
* @count: number of DPIA validation sets
*
* return: true if validation is succeeded
*/
bool link_dpia_validate_dp_tunnel_bandwidth(const struct dc_validation_dpia_set *dpia_link_sets, uint8_t count);
#endif /* DC_INC_LINK_DP_DPIA_BW_H_ */
@@ -0,0 +1,618 @@
/*
* 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 DP HPD short pulse handling sequence according to DP
* specifications
*
*/
#include "link_dp_irq_handler.h"
#include "link_dpcd.h"
#include "link_dp_training.h"
#include "link_dp_capability.h"
#include "link_edp_panel_control.h"
#include "link_dp_panel_replay.h"
#include "link/accessories/link_dp_trace.h"
#include "link/link_dpms.h"
#include "dm_helpers.h"
#include "link_dp_dpia_bw.h"
#include "link_dp_panel_replay.h"
#define DC_LOGGER \
link->ctx->logger
#define DC_LOGGER_INIT(logger)
bool dp_parse_link_loss_status(
struct dc_link *link,
union hpd_irq_data *hpd_irq_dpcd_data)
{
uint8_t irq_reg_rx_power_state = 0;
enum dc_status dpcd_result = DC_ERROR_UNEXPECTED;
union lane_status lane_status;
uint32_t lane;
bool sink_status_changed;
bool return_code;
sink_status_changed = false;
return_code = false;
if (link->cur_link_settings.lane_count == 0)
return return_code;
/*1. Check that Link Status changed, before re-training.*/
/*parse lane status*/
for (lane = 0; lane < (uint32_t)link->cur_link_settings.lane_count; lane++) {
/* check status of lanes 0,1
* changed DpcdAddress_Lane01Status (0x202)
*/
lane_status.raw = dp_get_nibble_at_index(
&hpd_irq_dpcd_data->bytes.lane01_status.raw,
lane);
if (!lane_status.bits.CHANNEL_EQ_DONE_0 ||
!lane_status.bits.CR_DONE_0 ||
!lane_status.bits.SYMBOL_LOCKED_0) {
/* if one of the channel equalization, clock
* recovery or symbol lock is dropped
* consider it as (link has been
* dropped) dp sink status has changed
*/
sink_status_changed = true;
break;
}
}
/* Check interlane align.*/
if (link_dp_get_encoding_format(&link->cur_link_settings) == DP_128b_132b_ENCODING &&
(!hpd_irq_dpcd_data->bytes.lane_status_updated.bits.EQ_INTERLANE_ALIGN_DONE_128b_132b ||
!hpd_irq_dpcd_data->bytes.lane_status_updated.bits.CDS_INTERLANE_ALIGN_DONE_128b_132b)) {
sink_status_changed = true;
} else if (!hpd_irq_dpcd_data->bytes.lane_status_updated.bits.INTERLANE_ALIGN_DONE) {
sink_status_changed = true;
}
if (sink_status_changed) {
DC_LOG_HW_HPD_IRQ("%s: Link Status changed.\n", __func__);
return_code = true;
/*2. Check that we can handle interrupt: Not in FS DOS,
* Not in "Display Timeout" state, Link is trained.
*/
dpcd_result = core_link_read_dpcd(link,
DP_SET_POWER,
&irq_reg_rx_power_state,
sizeof(irq_reg_rx_power_state));
if (dpcd_result != DC_OK) {
DC_LOG_HW_HPD_IRQ("%s: DPCD read failed to obtain power state.\n",
__func__);
} else {
if (irq_reg_rx_power_state != DP_SET_POWER_D0)
return_code = false;
}
}
return return_code;
}
static bool handle_hpd_irq_psr_sink(struct dc_link *link)
{
union dpcd_psr_configuration psr_configuration = {0};
if (!link->psr_settings.psr_feature_enabled)
return false;
dm_helpers_dp_read_dpcd(
link->ctx,
link,
368,/*DpcdAddress_PSR_Enable_Cfg*/
&psr_configuration.raw,
sizeof(psr_configuration.raw));
if (psr_configuration.bits.ENABLE) {
unsigned char dpcdbuf[3] = {0};
union psr_error_status psr_error_status;
union psr_sink_psr_status psr_sink_psr_status;
dm_helpers_dp_read_dpcd(
link->ctx,
link,
0x2006, /*DpcdAddress_PSR_Error_Status*/
(unsigned char *) dpcdbuf,
sizeof(dpcdbuf));
/*DPCD 2006h ERROR STATUS*/
psr_error_status.raw = dpcdbuf[0];
/*DPCD 2008h SINK PANEL SELF REFRESH STATUS*/
psr_sink_psr_status.raw = dpcdbuf[2];
if (psr_error_status.bits.LINK_CRC_ERROR ||
psr_error_status.bits.RFB_STORAGE_ERROR ||
psr_error_status.bits.VSC_SDP_ERROR) {
bool allow_active;
/* Acknowledge and clear error bits */
dm_helpers_dp_write_dpcd(
link->ctx,
link,
8198,/*DpcdAddress_PSR_Error_Status*/
&psr_error_status.raw,
sizeof(psr_error_status.raw));
/* PSR error, disable and re-enable PSR */
if (link->psr_settings.psr_allow_active) {
allow_active = false;
edp_set_psr_allow_active(link, &allow_active, true, false, NULL);
allow_active = true;
edp_set_psr_allow_active(link, &allow_active, true, false, NULL);
}
return true;
} else if (psr_sink_psr_status.bits.SINK_SELF_REFRESH_STATUS ==
PSR_SINK_STATE_ACTIVE_DISPLAY_FROM_SINK_RFB){
/* No error is detect, PSR is active.
* We should return with IRQ_HPD handled without
* checking for loss of sync since PSR would have
* powered down main link.
*/
return true;
}
}
return false;
}
static void handle_hpd_irq_vesa_replay_sink(struct dc_link *link)
{
union pr_error_status pr_error_status = {0};
if (!link->replay_settings.replay_feature_enabled ||
link->replay_settings.config.replay_version != DC_VESA_PANEL_REPLAY)
return;
dm_helpers_dp_read_dpcd(
link->ctx,
link,
DP_PR_ERROR_STATUS,
&pr_error_status.raw,
sizeof(pr_error_status.raw));
if (pr_error_status.bits.LINK_CRC_ERROR ||
pr_error_status.bits.RFB_STORAGE_ERROR ||
pr_error_status.bits.VSC_SDP_ERROR ||
pr_error_status.bits.ASSDP_MISSING_ERROR) {
/* Acknowledge and clear error bits */
dm_helpers_dp_write_dpcd(
link->ctx,
link,
DP_PR_ERROR_STATUS, /*DpcdAddress_PR_Error_Status*/
&pr_error_status.raw,
sizeof(pr_error_status.raw));
/* Replay error, disable and re-enable Replay */
if (link->replay_settings.replay_allow_active) {
dp_pr_enable(link, false);
dp_pr_enable(link, true);
}
}
}
static void handle_hpd_irq_replay_sink(struct dc_link *link, bool *need_re_enable, bool *replay_esd_detection_needed)
{
union dpcd_replay_configuration replay_configuration = {0};
union dpcd_replay_configuration replay_sink_status = {0};
/*AMD Replay version reuse DP_PSR_ERROR_STATUS for REPLAY_ERROR status.*/
union psr_error_status replay_error_status = {0};
bool ret = false;
int retries = 0;
if (!link->replay_settings.replay_feature_enabled)
return;
if (link->replay_settings.config.replay_version != DC_FREESYNC_REPLAY) {
handle_hpd_irq_vesa_replay_sink(link);
return;
}
while (retries < 10) {
ret = dm_helpers_dp_read_dpcd(
link->ctx,
link,
DP_SINK_PR_REPLAY_STATUS,
&replay_configuration.raw,
sizeof(replay_configuration.raw));
if (ret)
break;
retries++;
}
if (!ret)
DC_LOG_WARNING("[%s][%d] DPCD read addr.0x%x failed with %d retries\n",
__func__, __LINE__,
DP_SINK_PR_REPLAY_STATUS, retries);
dm_helpers_dp_read_dpcd(
link->ctx,
link,
DP_PSR_ERROR_STATUS,
&replay_error_status.raw,
sizeof(replay_error_status.raw));
dm_helpers_dp_read_dpcd(
link->ctx,
link,
DP_PR_REPLAY_SINK_STATUS,
&replay_sink_status.raw,
1);
if (replay_error_status.bits.LINK_CRC_ERROR ||
replay_configuration.bits.DESYNC_ERROR_STATUS ||
replay_configuration.bits.STATE_TRANSITION_ERROR_STATUS ||
replay_sink_status.bits.SINK_DEVICE_REPLAY_STATUS == 0x7) {
bool allow_active;
link->replay_settings.config.replay_error_status.raw |= replay_error_status.raw;
/* Increment desync error counter if a desync error is detected */
if (replay_configuration.bits.DESYNC_ERROR_STATUS)
link->replay_settings.replay_desync_error_fail_count++;
if (link->replay_settings.config.force_disable_desync_error_check)
return;
/* Acknowledge and clear configuration bits */
dm_helpers_dp_write_dpcd(
link->ctx,
link,
DP_SINK_PR_REPLAY_STATUS,
&replay_configuration.raw,
sizeof(replay_configuration.raw));
/* Acknowledge and clear error bits */
dm_helpers_dp_write_dpcd(
link->ctx,
link,
DP_PSR_ERROR_STATUS,/*DpcdAddress_REPLAY_Error_Status*/
&replay_error_status.raw,
sizeof(replay_error_status.raw));
/* Replay error, disable and re-enable Replay */
if (link->replay_settings.replay_allow_active) {
allow_active = false;
edp_set_replay_allow_active(link, &allow_active, true, false, NULL);
*need_re_enable = true;
}
}
if (link->ctx->dc->debug.enable_replay_esd_recovery) {
if (!link->replay_settings.replay_allow_active &&
replay_sink_status.bits.SINK_DEVICE_REPLAY_STATUS == 0x7) {
/* If sink device replay status is 0x7 and replay is disabled,
* it means sink is in a bad state and link retraining is needed to recover
*/
*replay_esd_detection_needed = true;
}
}
}
void dp_handle_link_loss(struct dc_link *link)
{
struct pipe_ctx *pipes[MAX_PIPES];
struct dc_state *state = link->dc->current_state;
uint8_t count;
int i;
link_get_master_pipes_with_dpms_on(link, state, &count, pipes);
for (i = 0; i < count; i++)
link_set_dpms_off(pipes[i]);
for (i = count - 1; i >= 0; i--) {
// Always use max settings here for DP 1.4a LL Compliance CTS
if (link->skip_fallback_on_link_loss) {
pipes[i]->link_config.dp_link_settings.lane_count =
link->verified_link_cap.lane_count;
pipes[i]->link_config.dp_link_settings.link_rate =
link->verified_link_cap.link_rate;
pipes[i]->link_config.dp_link_settings.link_spread =
link->verified_link_cap.link_spread;
}
link_set_dpms_on(link->dc->current_state, pipes[i]);
}
}
static void dp_handle_tunneling_irq(struct dc_link *link)
{
enum dc_status retval;
uint8_t tunneling_status = 0;
retval = core_link_read_dpcd(
link, DP_TUNNELING_STATUS,
&tunneling_status,
sizeof(tunneling_status));
if (retval == DC_OK) {
DC_LOG_HW_HPD_IRQ("%s: Got DP tunneling status on link %d status=0x%x",
__func__, link->link_index, tunneling_status);
if (tunneling_status & DP_TUNNELING_BW_ALLOC_BITS_MASK)
link_dp_dpia_handle_bw_alloc_status(link, tunneling_status);
}
tunneling_status = DP_TUNNELING_IRQ;
core_link_write_dpcd(
link, DP_LINK_SERVICE_IRQ_VECTOR_ESI0,
&tunneling_status, 1);
}
static void read_dpcd204h_on_irq_hpd(struct dc_link *link, union hpd_irq_data *irq_data)
{
enum dc_status retval;
union lane_align_status_updated dpcd_lane_status_updated = {0};
retval = core_link_read_dpcd(
link,
DP_LANE_ALIGN_STATUS_UPDATED,
&dpcd_lane_status_updated.raw,
sizeof(union lane_align_status_updated));
if (retval == DC_OK) {
irq_data->bytes.lane_status_updated.bits.EQ_INTERLANE_ALIGN_DONE_128b_132b =
dpcd_lane_status_updated.bits.EQ_INTERLANE_ALIGN_DONE_128b_132b;
irq_data->bytes.lane_status_updated.bits.CDS_INTERLANE_ALIGN_DONE_128b_132b =
dpcd_lane_status_updated.bits.CDS_INTERLANE_ALIGN_DONE_128b_132b;
}
}
enum dc_status dp_read_hpd_rx_irq_data(
struct dc_link *link,
union hpd_irq_data *irq_data)
{
static enum dc_status retval;
/* The HW reads 16 bytes from 200h on HPD,
* but if we get an AUX_DEFER, the HW cannot retry
* and this causes the CTS tests 4.3.2.1 - 3.2.4 to
* fail, so we now explicitly read 6 bytes which is
* the req from the above mentioned test cases.
*
* For DP 1.4 we need to read those from 2002h range.
*/
if (link->dpcd_caps.dpcd_rev.raw < DPCD_REV_14) {
retval = core_link_read_dpcd(
link,
DP_SINK_COUNT,
irq_data->raw,
DP_SINK_STATUS - DP_SINK_COUNT + 1);
if (link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dp_tunneling) {
retval = core_link_read_dpcd(
link, DP_LINK_SERVICE_IRQ_VECTOR_ESI0,
&irq_data->bytes.link_service_irq_esi0.raw, 1);
}
} else {
/* Read 14 bytes in a single read and then copy only the required fields.
* This is more efficient than doing it in two separate AUX reads. */
uint8_t tmp[DP_SINK_STATUS_ESI - DP_SINK_COUNT_ESI + 1] = {0};
retval = core_link_read_dpcd(
link,
DP_SINK_COUNT_ESI,
tmp,
sizeof(tmp));
if (retval != DC_OK)
return retval;
irq_data->bytes.sink_cnt.raw = tmp[DP_SINK_COUNT_ESI - DP_SINK_COUNT_ESI];
irq_data->bytes.device_service_irq.raw = tmp[DP_DEVICE_SERVICE_IRQ_VECTOR_ESI0 - DP_SINK_COUNT_ESI];
irq_data->bytes.lane01_status.raw = tmp[DP_LANE0_1_STATUS_ESI - DP_SINK_COUNT_ESI];
irq_data->bytes.lane23_status.raw = tmp[DP_LANE2_3_STATUS_ESI - DP_SINK_COUNT_ESI];
irq_data->bytes.lane_status_updated.raw = tmp[DP_LANE_ALIGN_STATUS_UPDATED_ESI - DP_SINK_COUNT_ESI];
irq_data->bytes.sink_status.raw = tmp[DP_SINK_STATUS_ESI - DP_SINK_COUNT_ESI];
irq_data->bytes.link_service_irq_esi0.raw = tmp[DP_LINK_SERVICE_IRQ_VECTOR_ESI0 - DP_SINK_COUNT_ESI];
/*
* This display doesn't have correct values in DPCD200Eh.
* Read and check DPCD204h instead.
*/
if (link->wa_flags.read_dpcd204h_on_irq_hpd)
read_dpcd204h_on_irq_hpd(link, irq_data);
}
return retval;
}
/*************************Short Pulse IRQ***************************/
bool dp_should_allow_hpd_rx_irq(const struct dc_link *link)
{
/*
* Don't handle RX IRQ unless one of following is met:
* 1) The link is established (cur_link_settings != unknown)
* 2) We know we're dealing with a branch device, SST or MST
* 3) The link is bw_alloc enabled.
*/
if ((link->cur_link_settings.lane_count != LANE_COUNT_UNKNOWN) ||
is_dp_branch_device(link) ||
link->dpia_bw_alloc_config.bw_alloc_enabled)
return true;
return false;
}
bool dp_handle_hpd_rx_irq(struct dc_link *link,
union hpd_irq_data *out_hpd_irq_dpcd_data, bool *out_link_loss,
bool defer_handling, bool *has_left_work)
{
union hpd_irq_data hpd_irq_dpcd_data = {0};
union device_service_irq device_service_clear = {0};
enum dc_status result;
bool status = false;
bool replay_re_enable_needed = false;
bool replay_esd_detection_needed = false;
if (out_link_loss)
*out_link_loss = false;
if (has_left_work)
*has_left_work = false;
/* For use cases related to down stream connection status change,
* PSR and device auto test, refer to function handle_sst_hpd_irq
* in DAL2.1*/
DC_LOG_HW_HPD_IRQ("%s: Got short pulse HPD on link %d\n",
__func__, link->link_index);
handle_hpd_irq_replay_sink(link, &replay_re_enable_needed, &replay_esd_detection_needed);
/* All the "handle_hpd_irq_xxx()" methods
* should be called only after
* dal_dpsst_ls_read_hpd_irq_data
* Order of calls is important too
*/
result = dp_read_hpd_rx_irq_data(link, &hpd_irq_dpcd_data);
if (out_hpd_irq_dpcd_data)
*out_hpd_irq_dpcd_data = hpd_irq_dpcd_data;
if (result != DC_OK) {
DC_LOG_HW_HPD_IRQ("%s: DPCD read failed to obtain irq data\n",
__func__);
return false;
}
if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.AUTOMATED_TEST) {
// Workaround for DP 1.4a LL Compliance CTS as USB4 has to share encoders unlike DP and USBC
if (link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA &&
!link->dc->config.enable_dpia_pre_training)
link->skip_fallback_on_link_loss = true;
device_service_clear.bits.AUTOMATED_TEST = 1;
core_link_write_dpcd(
link,
DP_DEVICE_SERVICE_IRQ_VECTOR,
&device_service_clear.raw,
sizeof(device_service_clear.raw));
device_service_clear.raw = 0;
if (defer_handling && has_left_work)
*has_left_work = true;
else
dc_link_dp_handle_automated_test(link);
return false;
}
if (!dp_should_allow_hpd_rx_irq(link)) {
DC_LOG_HW_HPD_IRQ("%s: skipping HPD handling on %d\n",
__func__, link->link_index);
return false;
}
if (handle_hpd_irq_psr_sink(link))
/* PSR-related error was detected and handled */
return true;
/* If PSR-related error handled, Main link may be off,
* so do not handle as a normal sink status change interrupt.
*/
if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY) {
if (defer_handling && has_left_work)
*has_left_work = true;
return true;
}
/* check if we have MST msg and return since we poll for it */
if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) {
if (defer_handling && has_left_work)
*has_left_work = true;
return false;
}
/* Handle 'Downstream port status' case for all DP link types.
* If we got sink count changed it means
* Downstream port status changed,
* then DM should call DC to do the detection.
* NOTE: Now includes eDP link loss detection and retraining
* Link will be retrained if panel is not EDP or
* Replay ESD recovery is needed.
*/
if (link->connector_signal != SIGNAL_TYPE_EDP || replay_esd_detection_needed) {
if (dp_parse_link_loss_status(
link,
&hpd_irq_dpcd_data)) {
/* Connectivity log: link loss */
CONN_DATA_LINK_LOSS(link,
hpd_irq_dpcd_data.raw,
sizeof(hpd_irq_dpcd_data),
"Status: ");
if (defer_handling && has_left_work)
*has_left_work = true;
else
dp_handle_link_loss(link);
status = false;
if (out_link_loss)
*out_link_loss = true;
dp_trace_link_loss_increment(link);
}
}
if (link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dp_tunneling) {
if (hpd_irq_dpcd_data.bytes.link_service_irq_esi0.bits.DP_LINK_TUNNELING_IRQ)
dp_handle_tunneling_irq(link);
}
if (link->type == dc_connection_sst_branch &&
hpd_irq_dpcd_data.bytes.sink_cnt.bits.SINK_COUNT
!= link->dpcd_sink_count)
status = true;
if (replay_re_enable_needed) {
bool allow_active = true;
edp_set_replay_allow_active(link, &allow_active, true, false, NULL);
}
/* reasons for HPD RX:
* 1. Link Loss - ie Re-train the Link
* 2. MST sideband message
* 3. Automated Test - ie. Internal Commit
* 4. CP (copy protection) - (not interesting for DM???)
* 5. DRR
* 6. Downstream Port status changed
* -ie. Detect - this the only one
* which is interesting for DM because
* it must call dc_link_detect.
*/
return status;
}
@@ -0,0 +1,41 @@
/*
* 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
*
*/
#ifndef __DC_LINK_DP_IRQ_HANDLER_H__
#define __DC_LINK_DP_IRQ_HANDLER_H__
#include "link_service.h"
bool dp_parse_link_loss_status(
struct dc_link *link,
union hpd_irq_data *hpd_irq_dpcd_data);
bool dp_should_allow_hpd_rx_irq(const struct dc_link *link);
void dp_handle_link_loss(struct dc_link *link);
enum dc_status dp_read_hpd_rx_irq_data(
struct dc_link *link,
union hpd_irq_data *irq_data);
bool dp_handle_hpd_rx_irq(struct dc_link *link,
union hpd_irq_data *out_hpd_irq_dpcd_data, bool *out_link_loss,
bool defer_handling, bool *has_left_work);
#endif /* __DC_LINK_DP_IRQ_HANDLER_H__ */
@@ -0,0 +1,428 @@
/*
* Copyright 2025 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
*
*/
#include "link_dp_panel_replay.h"
#include "link_edp_panel_control.h"
#include "link_dpcd.h"
#include "dm_helpers.h"
#include "dc/dc_dmub_srv.h"
#include "dce/dmub_replay.h"
#define DC_LOGGER \
link->ctx->logger
#define DP_SINK_PR_ENABLE_AND_CONFIGURATION 0x37B
#define DP_SINK_ENABLE_FRAME_SKIPPING_MODE_SHIFT (5)
static unsigned int dp_pr_calc_num_static_frames(unsigned int vsync_rate_hz)
{
// at least 2 frames for static screen
unsigned int num_frames = 2;
// get number of frames for at least 50ms
if (vsync_rate_hz > 40)
num_frames = (vsync_rate_hz + 10) / 20;
return num_frames;
}
static void dp_pr_set_static_screen_param(struct dc_link *link)
{
struct dc_static_screen_params params = {0};
struct dc *dc = link->ctx->dc;
// only support DP sst for now
if (!dc_is_dp_sst_signal(link->connector_signal))
return;
for (int i = 0; i < MAX_PIPES; i++) {
if (dc->current_state->res_ctx.pipe_ctx[i].stream &&
dc->current_state->res_ctx.pipe_ctx[i].stream->link == link) {
struct dc_stream_state *stream = dc->current_state->res_ctx.pipe_ctx[i].stream;
unsigned int vsync_rate_hz = (unsigned int)div64_u64(div64_u64(
(stream->timing.pix_clk_100hz * (u64)100),
stream->timing.v_total),
stream->timing.h_total);
params.triggers.cursor_update = true;
params.triggers.overlay_update = true;
params.triggers.surface_update = true;
params.num_frames = dp_pr_calc_num_static_frames(vsync_rate_hz);
dc_stream_set_static_screen_params(dc, &stream, 1, &params);
break;
}
}
}
static bool dp_setup_panel_replay(struct dc_link *link, const struct dc_stream_state *stream)
{
/* To-do: Setup Replay */
struct dc *dc;
struct dmub_replay *replay;
int i;
unsigned int panel_inst;
struct replay_context replay_context = { 0 };
unsigned int lineTimeInNs = 0;
union panel_replay_enable_and_configuration_1 pr_config_1 = { 0 };
union panel_replay_enable_and_configuration_2 pr_config_2 = { 0 };
union dpcd_alpm_configuration alpm_config;
uint8_t data = 0;
replay_context.controllerId = CONTROLLER_ID_UNDEFINED;
if (!link)
return false;
//Clear Panel Replay enable & config
dm_helpers_dp_write_dpcd(link->ctx, link,
DP_PANEL_REPLAY_ENABLE_AND_CONFIGURATION_1,
(uint8_t *)&(pr_config_1.raw), sizeof(uint8_t));
dm_helpers_dp_write_dpcd(link->ctx, link,
DP_PANEL_REPLAY_ENABLE_AND_CONFIGURATION_2,
(uint8_t *)&(pr_config_2.raw), sizeof(uint8_t));
if (!(link->replay_settings.config.replay_supported))
return false;
dc = link->ctx->dc;
//not sure should keep or not
replay = dc->res_pool->replay;
if (!replay)
return false;
if (!dp_pr_get_panel_inst(dc, link, &panel_inst))
return false;
replay_context.aux_inst = link->ddc->ddc_pin->hw_info.ddc_channel;
replay_context.digbe_inst = link->link_enc->transmitter;
replay_context.digfe_inst = link->link_enc->preferred_engine;
for (i = 0; i < MAX_PIPES; i++) {
if (dc->current_state->res_ctx.pipe_ctx[i].stream
== stream) {
/* dmcu -1 for all controller id values,
* therefore +1 here
*/
replay_context.controllerId =
dc->current_state->res_ctx.pipe_ctx[i].stream_res.tg->inst + 1;
break;
}
}
lineTimeInNs =
((stream->timing.h_total * 1000000) /
(stream->timing.pix_clk_100hz / 10)) + 1;
replay_context.line_time_in_ns = lineTimeInNs;
link->replay_settings.replay_feature_enabled = dp_pr_copy_settings(link, &replay_context);
if (link->replay_settings.replay_feature_enabled) {
if (dc_is_embedded_signal(link->connector_signal)) {
pr_config_1.bits.PANEL_REPLAY_ENABLE = 1;
pr_config_1.bits.PANEL_REPLAY_CRC_ENABLE = 1;
pr_config_1.bits.IRQ_HPD_ASSDP_MISSING = 1;
pr_config_1.bits.IRQ_HPD_VSCSDP_UNCORRECTABLE_ERROR = 1;
pr_config_1.bits.IRQ_HPD_RFB_ERROR = 1;
pr_config_1.bits.IRQ_HPD_ACTIVE_FRAME_CRC_ERROR = 1;
pr_config_1.bits.PANEL_REPLAY_SELECTIVE_UPDATE_ENABLE = 1;
pr_config_1.bits.PANEL_REPLAY_EARLY_TRANSPORT_ENABLE = 1;
} else {
pr_config_1.bits.PANEL_REPLAY_ENABLE = 1;
}
pr_config_2.bits.SINK_REFRESH_RATE_UNLOCK_GRANTED = 0;
if (link->dpcd_caps.vesa_replay_caps.bits.SU_Y_GRANULARITY_EXT_CAP_SUPPORTED)
pr_config_2.bits.SU_Y_GRANULARITY_EXT_VALUE_ENABLED = 1;
pr_config_2.bits.SU_REGION_SCAN_LINE_CAPTURE_INDICATION = 0;
dm_helpers_dp_write_dpcd(link->ctx, link,
DP_PANEL_REPLAY_ENABLE_AND_CONFIGURATION_1,
(uint8_t *)&(pr_config_1.raw), sizeof(uint8_t));
dm_helpers_dp_write_dpcd(link->ctx, link,
DP_PANEL_REPLAY_ENABLE_AND_CONFIGURATION_2,
(uint8_t *)&(pr_config_2.raw), sizeof(uint8_t));
//ALPM Setup
memset(&alpm_config, 0, sizeof(alpm_config));
alpm_config.bits.ENABLE = link->replay_settings.config.alpm_mode != DC_ALPM_UNSUPPORTED ? 1 : 0;
if (link->replay_settings.config.alpm_mode == DC_ALPM_AUXLESS) {
alpm_config.bits.ALPM_MODE_SEL = 1;
alpm_config.bits.ACDS_PERIOD_DURATION = 1;
}
dm_helpers_dp_write_dpcd(
link->ctx,
link,
DP_RECEIVER_ALPM_CONFIG,
&alpm_config.raw,
sizeof(alpm_config.raw));
//Enable frame skipping
if (link->replay_settings.config.frame_skip_supported)
data = data | (1 << DP_SINK_ENABLE_FRAME_SKIPPING_MODE_SHIFT);
dm_helpers_dp_write_dpcd(link->ctx, link,
DP_SINK_PR_ENABLE_AND_CONFIGURATION,
(uint8_t *)&(data), sizeof(uint8_t));
}
return true;
}
bool dp_pr_get_panel_inst(const struct dc *dc,
const struct dc_link *link,
unsigned int *inst_out)
{
if (!dc || !link || !inst_out)
return false;
if (dc->config.frame_update_cmd_version2 == false)
return dc_get_edp_link_panel_inst(dc, link, inst_out);
if (!dc_is_dp_sst_signal(link->connector_signal)) /* only supoprt DP sst (eDP included) for now */
return false;
for (unsigned int i = 0; i < MAX_PIPES; i++) {
if (dc->current_state->res_ctx.pipe_ctx[i].stream &&
dc->current_state->res_ctx.pipe_ctx[i].stream->link == link) {
/* *inst_out is equal to otg number */
if (dc->current_state->res_ctx.pipe_ctx[i].stream_res.tg)
*inst_out = dc->current_state->res_ctx.pipe_ctx[i].stream_res.tg->inst;
else
*inst_out = 0;
return true;
}
}
return false;
}
bool dp_setup_replay(struct dc_link *link, const struct dc_stream_state *stream)
{
if (!link)
return false;
if (link->replay_settings.config.replay_version == DC_VESA_PANEL_REPLAY)
return dp_setup_panel_replay(link, stream);
else if (link->replay_settings.config.replay_version == DC_FREESYNC_REPLAY)
return edp_setup_freesync_replay(link, stream);
else
return false;
}
bool dp_pr_enable(struct dc_link *link, bool enable)
{
struct dc *dc = link->ctx->dc;
unsigned int panel_inst = 0;
union dmub_rb_cmd cmd;
if (!dp_pr_get_panel_inst(dc, link, &panel_inst))
return false;
if (link->replay_settings.replay_allow_active == enable)
return true;
if (enable && !dc_is_embedded_signal(link->connector_signal))
dp_pr_set_static_screen_param(link);
// for sending PR enable commands to DMUB
memset(&cmd, 0, sizeof(cmd));
cmd.pr_enable.header.type = DMUB_CMD__PR;
cmd.pr_enable.header.sub_type = DMUB_CMD__PR_ENABLE;
cmd.pr_enable.header.payload_bytes = sizeof(struct dmub_cmd_pr_enable_data);
cmd.pr_enable.data.panel_inst = (uint8_t)panel_inst;
cmd.pr_enable.data.enable = enable ? 1 : 0;
dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
link->replay_settings.replay_allow_active = enable;
return true;
}
bool dp_pr_copy_settings(struct dc_link *link, struct replay_context *replay_context)
{
struct dc *dc = link->ctx->dc;
unsigned int panel_inst = 0;
union dmub_rb_cmd cmd;
struct pipe_ctx *pipe_ctx = NULL;
if (!dp_pr_get_panel_inst(dc, link, &panel_inst))
return false;
for (unsigned int i = 0; i < MAX_PIPES; i++) {
if (dc->current_state->res_ctx.pipe_ctx[i].stream &&
dc->current_state->res_ctx.pipe_ctx[i].stream->link &&
dc->current_state->res_ctx.pipe_ctx[i].stream->link == link &&
dc_is_dp_sst_signal(dc->current_state->res_ctx.pipe_ctx[i].stream->link->connector_signal)) {
pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i];
/* todo: need update for MST */
break;
}
}
if (!pipe_ctx)
return false;
memset(&cmd, 0, sizeof(cmd));
cmd.pr_copy_settings.header.type = DMUB_CMD__PR;
cmd.pr_copy_settings.header.sub_type = DMUB_CMD__PR_COPY_SETTINGS;
cmd.pr_copy_settings.header.payload_bytes = sizeof(struct dmub_cmd_pr_copy_settings_data);
cmd.pr_copy_settings.data.panel_inst = (uint8_t)panel_inst;
// HW inst
cmd.pr_copy_settings.data.aux_inst = replay_context->aux_inst;
cmd.pr_copy_settings.data.digbe_inst = replay_context->digbe_inst;
cmd.pr_copy_settings.data.digfe_inst = replay_context->digfe_inst;
if (pipe_ctx->plane_res.dpp)
cmd.pr_copy_settings.data.dpp_inst = (uint8_t)pipe_ctx->plane_res.dpp->inst;
else
cmd.pr_copy_settings.data.dpp_inst = 0;
if (pipe_ctx->stream_res.tg)
cmd.pr_copy_settings.data.otg_inst = (uint8_t)pipe_ctx->stream_res.tg->inst;
else
cmd.pr_copy_settings.data.otg_inst = 0;
cmd.pr_copy_settings.data.dpphy_inst = link->link_enc->transmitter;
cmd.pr_copy_settings.data.line_time_in_ns = replay_context->line_time_in_ns;
cmd.pr_copy_settings.data.flags.bitfields.fec_enable_status = (link->fec_state == dc_link_fec_enabled);
cmd.pr_copy_settings.data.flags.bitfields.dsc_enable_status = (pipe_ctx->stream->timing.flags.DSC == 1);
cmd.pr_copy_settings.data.debug.u32All = link->replay_settings.config.debug_flags;
// ALPM settings
cmd.pr_copy_settings.data.flags.bitfields.alpm_mode = (enum dmub_alpm_mode)link->replay_settings.config.alpm_mode;
if (link->replay_settings.config.alpm_mode == DC_ALPM_AUXLESS) {
cmd.pr_copy_settings.data.auxless_alpm_data.lfps_setup_ns = (uint16_t)dc->debug.auxless_alpm_lfps_setup_ns;
cmd.pr_copy_settings.data.auxless_alpm_data.lfps_period_ns = (uint16_t)dc->debug.auxless_alpm_lfps_period_ns;
cmd.pr_copy_settings.data.auxless_alpm_data.lfps_silence_ns = (uint16_t)dc->debug.auxless_alpm_lfps_silence_ns;
cmd.pr_copy_settings.data.auxless_alpm_data.lfps_t1_t2_override_us =
(uint16_t)dc->debug.auxless_alpm_lfps_t1t2_us;
cmd.pr_copy_settings.data.auxless_alpm_data.lfps_t1_t2_offset_us =
(uint16_t)dc->debug.auxless_alpm_lfps_t1t2_offset_us;
cmd.pr_copy_settings.data.auxless_alpm_data.lttpr_count = link->dc->link_srv->dp_get_lttpr_count(link);
}
cmd.pr_copy_settings.data.su_granularity_needed = link->dpcd_caps.vesa_replay_caps.bits.PR_SU_GRANULARITY_NEEDED;
cmd.pr_copy_settings.data.su_x_granularity = link->dpcd_caps.vesa_replay_su_info.pr_su_x_granularity;
cmd.pr_copy_settings.data.su_y_granularity = link->dpcd_caps.vesa_replay_su_info.pr_su_y_granularity;
cmd.pr_copy_settings.data.su_y_granularity_extended_caps =
link->dpcd_caps.vesa_replay_su_info.pr_su_y_granularity_extended_caps;
if (pipe_ctx->stream->timing.dsc_cfg.num_slices_v > 0)
cmd.pr_copy_settings.data.dsc_slice_height = (uint16_t)((pipe_ctx->stream->timing.v_addressable +
pipe_ctx->stream->timing.v_border_top + pipe_ctx->stream->timing.v_border_bottom) /
pipe_ctx->stream->timing.dsc_cfg.num_slices_v);
if (dc_is_embedded_signal(link->connector_signal))
cmd.pr_copy_settings.data.main_link_activity_option = OPTION_1C;
else
// For external DP, use option 1-A
cmd.pr_copy_settings.data.main_link_activity_option = OPTION_1A;
dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
return true;
}
bool dp_pr_update_state(struct dc_link *link, struct dmub_cmd_pr_update_state_data *update_state_data)
{
struct dc *dc = link->ctx->dc;
unsigned int panel_inst = 0;
union dmub_rb_cmd cmd;
if (!dp_pr_get_panel_inst(dc, link, &panel_inst))
return false;
memset(&cmd, 0, sizeof(cmd));
memcpy(&cmd.pr_update_state.data, update_state_data, sizeof(struct dmub_cmd_pr_update_state_data));
cmd.pr_update_state.header.type = DMUB_CMD__PR;
cmd.pr_update_state.header.sub_type = DMUB_CMD__PR_UPDATE_STATE;
cmd.pr_update_state.header.payload_bytes = sizeof(struct dmub_cmd_pr_update_state_data);
cmd.pr_update_state.data.panel_inst = (uint8_t)panel_inst;
dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
return true;
}
bool dp_pr_set_general_cmd(struct dc_link *link, struct dmub_cmd_pr_general_cmd_data *general_cmd_data)
{
struct dc *dc = link->ctx->dc;
unsigned int panel_inst = 0;
union dmub_rb_cmd cmd;
if (!dp_pr_get_panel_inst(dc, link, &panel_inst))
return false;
memset(&cmd, 0, sizeof(cmd));
memcpy(&cmd.pr_general_cmd.data, general_cmd_data, sizeof(struct dmub_cmd_pr_general_cmd_data));
cmd.pr_general_cmd.header.type = DMUB_CMD__PR;
cmd.pr_general_cmd.header.sub_type = DMUB_CMD__PR_GENERAL_CMD;
cmd.pr_general_cmd.header.payload_bytes = sizeof(struct dmub_cmd_pr_general_cmd_data);
cmd.pr_general_cmd.data.panel_inst = (uint8_t)panel_inst;
dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
return true;
}
bool dp_pr_get_state(const struct dc_link *link, uint64_t *state)
{
const struct dc *dc = link->ctx->dc;
unsigned int panel_inst = 0;
uint32_t retry_count = 0;
uint32_t replay_state = PR_STATE_INVALID;
if (!dp_pr_get_panel_inst(dc, link, &panel_inst))
return false;
do {
// Send gpint command and wait for ack
if (!dc_wake_and_execute_gpint(dc->ctx, DMUB_GPINT__GET_REPLAY_STATE, (uint16_t)panel_inst,
&replay_state, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY)) {
// Return invalid state when GPINT times out
replay_state = PR_STATE_INVALID;
}
/* Copy 32-bit result into 64-bit output */
*state = replay_state;
} while (++retry_count <= 1000 && *state == PR_STATE_INVALID);
// Assert if max retry hit
if (retry_count >= 1000 && *state == PR_STATE_INVALID) {
ASSERT(0);
/* To-do: Add retry fail log */
}
return true;
}
@@ -0,0 +1,38 @@
/*
* Copyright 2025 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
*
*/
#ifndef __DC_LINK_DP_PANEL_REPLAY_H__
#define __DC_LINK_DP_PANEL_REPLAY_H__
#include "link_service.h"
bool dp_setup_replay(struct dc_link *link, const struct dc_stream_state *stream);
bool dp_pr_get_panel_inst(const struct dc *dc, const struct dc_link *link, unsigned int *inst_out);
bool dp_pr_enable(struct dc_link *link, bool enable);
bool dp_pr_copy_settings(struct dc_link *link, struct replay_context *replay_context);
bool dp_pr_update_state(struct dc_link *link, struct dmub_cmd_pr_update_state_data *update_state_data);
bool dp_pr_set_general_cmd(struct dc_link *link, struct dmub_cmd_pr_general_cmd_data *general_cmd_data);
bool dp_pr_get_state(const struct dc_link *link, uint64_t *state);
#endif /* __DC_LINK_DP_PANEL_REPLAY_H__ */
@@ -0,0 +1,210 @@
/*
* 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 basic dp phy functionality such as enable/disable phy
* output and set lane/drive settings. This file is responsible for maintaining
* and update software state representing current phy status such as current
* link settings.
*/
#include "link_dp_phy.h"
#include "link_dpcd.h"
#include "link_dp_training.h"
#include "link_dp_capability.h"
#include "clk_mgr.h"
#include "resource.h"
#include "link_enc_cfg.h"
#include "atomfirmware.h"
#define DC_LOGGER \
link->ctx->logger
void dpcd_write_rx_power_ctrl(struct dc_link *link, bool on)
{
uint8_t state;
state = on ? DP_POWER_STATE_D0 : DP_POWER_STATE_D3;
if (link->sync_lt_in_progress)
return;
core_link_write_dpcd(link, DP_SET_POWER, &state,
sizeof(state));
}
void dp_enable_link_phy(
struct dc_link *link,
const struct link_resource *link_res,
enum signal_type signal,
enum clock_source_id clock_source,
const struct dc_link_settings *link_settings)
{
link->cur_link_settings = *link_settings;
link->dc->hwss.enable_dp_link_output(link, link_res, signal,
clock_source, link_settings);
dpcd_write_rx_power_ctrl(link, true);
}
void dp_disable_link_phy(struct dc_link *link,
const struct link_resource *link_res,
enum signal_type signal)
{
struct dc *dc = link->ctx->dc;
if (!link->wa_flags.dp_keep_receiver_powered &&
!link->skip_implict_edp_power_control &&
link->type != dc_connection_none)
dpcd_write_rx_power_ctrl(link, false);
dc->hwss.disable_link_output(link, link_res, signal);
/* Clear current link setting.*/
memset(&link->cur_link_settings, 0,
sizeof(link->cur_link_settings));
if (dc->clk_mgr->funcs->notify_link_rate_change)
dc->clk_mgr->funcs->notify_link_rate_change(dc->clk_mgr, link);
}
static inline bool is_immediate_downstream(struct dc_link *link, uint32_t offset)
{
return (dp_parse_lttpr_repeater_count(link->dpcd_caps.lttpr_caps.phy_repeater_cnt) ==
offset);
}
void dp_set_hw_lane_settings(
struct dc_link *link,
const struct link_resource *link_res,
const struct link_training_settings *link_settings,
uint32_t offset)
{
const struct link_hwss *link_hwss = get_link_hwss(link, link_res);
// Don't return here if using FIXED_VS link HWSS and encoding is 128b/132b
if ((link_settings->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT) &&
!is_immediate_downstream(link, offset) &&
(!((link->chip_caps & AMD_EXT_DISPLAY_PATH_CAPS__EXT_CHIP_MASK) == AMD_EXT_DISPLAY_PATH_CAPS__DP_FIXED_VS_EN) ||
link_dp_get_encoding_format(&link_settings->link_settings) == DP_8b_10b_ENCODING))
return;
if (link_hwss->ext.set_dp_lane_settings)
link_hwss->ext.set_dp_lane_settings(link, link_res,
&link_settings->link_settings,
link_settings->hw_lane_settings);
memmove(link->cur_lane_setting,
link_settings->hw_lane_settings,
sizeof(link->cur_lane_setting));
}
void dp_set_drive_settings(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings)
{
/* program ASIC PHY settings*/
dp_set_hw_lane_settings(link, link_res, lt_settings, DPRX);
dp_hw_to_dpcd_lane_settings(lt_settings,
lt_settings->hw_lane_settings,
lt_settings->dpcd_lane_settings);
/* Notify DP sink the PHY settings from source */
dpcd_set_lane_settings(link, lt_settings, DPRX);
}
enum dc_status dp_set_fec_ready(struct dc_link *link, const struct link_resource *link_res, bool ready)
{
/* FEC has to be "set ready" before the link training.
* The policy is to always train with FEC
* if the sink supports it and leave it enabled on link.
* If FEC is not supported, disable it.
*/
struct link_encoder *link_enc = link_res->dio_link_enc;
enum dc_status status = DC_OK;
uint8_t fec_config = 0;
if (!link->dc->config.unify_link_enc_assignment)
link_enc = link_enc_cfg_get_link_enc(link);
ASSERT(link_enc);
if (link_enc->funcs->fec_set_ready == NULL)
return DC_NOT_SUPPORTED;
if (ready && dp_should_enable_fec(link)) {
fec_config = 1;
status = core_link_write_dpcd(link, DP_FEC_CONFIGURATION,
&fec_config, sizeof(fec_config));
if (status == DC_OK) {
link_enc->funcs->fec_set_ready(link_enc, true);
link->fec_state = dc_link_fec_ready;
}
} else {
if (link->fec_state == dc_link_fec_ready) {
fec_config = 0;
if (link->type != dc_connection_none)
core_link_write_dpcd(link, DP_FEC_CONFIGURATION,
&fec_config, sizeof(fec_config));
link_enc->funcs->fec_set_ready(link_enc, false);
link->fec_state = dc_link_fec_not_ready;
}
}
return status;
}
void dp_set_fec_enable(struct dc_link *link, const struct link_resource *link_res, bool enable)
{
struct link_encoder *link_enc = link_res->dio_link_enc;
if (!link->dc->config.unify_link_enc_assignment)
link_enc = link_enc_cfg_get_link_enc(link);
if (link_enc == NULL || link_enc->funcs == NULL || link_enc->funcs->fec_set_enable == NULL)
return;
if (enable && dp_should_enable_fec(link)) {
if (link->fec_state == dc_link_fec_ready) {
/* According to DP spec, FEC enable sequence can first
* be transmitted anytime after 1000 LL codes have
* been transmitted on the link after link training
* completion. Using 1 lane RBR should have the maximum
* time for transmitting 1000 LL codes which is 6.173 us.
* So use 7 microseconds delay instead.
*/
udelay(7);
link_enc->funcs->fec_set_enable(link_enc, true);
link->fec_state = dc_link_fec_enabled;
}
} else {
if (link->fec_state == dc_link_fec_enabled) {
link_enc->funcs->fec_set_enable(link_enc, false);
link->fec_state = dc_link_fec_ready;
}
}
}
@@ -0,0 +1,60 @@
/*
* 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
*
*/
#ifndef __DC_LINK_DP_PHY_H__
#define __DC_LINK_DP_PHY_H__
#include "link_service.h"
void dp_enable_link_phy(
struct dc_link *link,
const struct link_resource *link_res,
enum signal_type signal,
enum clock_source_id clock_source,
const struct dc_link_settings *link_settings);
void dp_disable_link_phy(struct dc_link *link,
const struct link_resource *link_res,
enum signal_type signal);
void dp_set_hw_lane_settings(
struct dc_link *link,
const struct link_resource *link_res,
const struct link_training_settings *link_settings,
uint32_t offset);
void dp_set_drive_settings(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings);
enum dc_status dp_set_fec_ready(struct dc_link *link,
const struct link_resource *link_res, bool ready);
void dp_set_fec_enable(struct dc_link *link,
const struct link_resource *link_res, bool enable);
void dpcd_write_rx_power_ctrl(struct dc_link *link, bool on);
#endif /* __DC_LINK_DP_PHY_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,201 @@
/*
* 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
*
*/
#ifndef __DC_LINK_DP_TRAINING_H__
#define __DC_LINK_DP_TRAINING_H__
#include "link_service.h"
bool perform_link_training_with_retries(
const struct dc_link_settings *link_setting,
bool skip_video_pattern,
int attempts,
struct pipe_ctx *pipe_ctx,
enum signal_type signal,
bool do_fallback);
enum link_training_result dp_perform_link_training(
struct dc_link *link,
const struct link_resource *link_res,
const struct dc_link_settings *link_settings,
bool skip_video_pattern);
bool dp_set_hw_training_pattern(
struct dc_link *link,
const struct link_resource *link_res,
enum dc_dp_training_pattern pattern,
uint32_t offset);
void dp_set_hw_test_pattern(
struct dc_link *link,
const struct link_resource *link_res,
enum dp_test_pattern test_pattern,
uint8_t *custom_pattern,
uint32_t custom_pattern_size);
enum dc_status dpcd_set_training_pattern(
struct dc_link *link,
enum dc_dp_training_pattern training_pattern);
/* Write DPCD drive settings. */
enum dc_status dpcd_set_lane_settings(
struct dc_link *link,
const struct link_training_settings *link_training_setting,
uint32_t offset);
/* Write DPCD link configuration data. */
enum dc_status dpcd_set_link_settings(
struct dc_link *link,
const struct link_training_settings *lt_settings);
void dpcd_set_lt_pattern_and_lane_settings(
struct dc_link *link,
const struct link_training_settings *lt_settings,
enum dc_dp_training_pattern pattern,
uint32_t offset);
/* Read training status and adjustment requests from DPCD. */
enum dc_status dp_get_lane_status_and_lane_adjust(
struct dc_link *link,
const struct link_training_settings *link_training_setting,
union lane_status ln_status[LANE_COUNT_DP_MAX],
union lane_align_status_updated *ln_align,
union lane_adjust ln_adjust[LANE_COUNT_DP_MAX],
uint32_t offset);
enum dc_status dpcd_configure_lttpr_mode(
struct dc_link *link,
struct link_training_settings *lt_settings);
enum dc_status configure_lttpr_mode_transparent(struct dc_link *link);
enum dc_status dpcd_configure_channel_coding(
struct dc_link *link,
struct link_training_settings *lt_settings);
void repeater_training_done(struct dc_link *link, uint32_t offset);
void start_clock_recovery_pattern_early(struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings,
uint32_t offset);
void dp_decide_training_settings(
struct dc_link *link,
const struct link_resource *link_res,
const struct dc_link_settings *link_settings,
struct link_training_settings *lt_settings);
void dp_decide_lane_settings(
const struct link_training_settings *lt_settings,
const union lane_adjust ln_adjust[LANE_COUNT_DP_MAX],
struct dc_lane_settings hw_lane_settings[LANE_COUNT_DP_MAX],
union dpcd_training_lane *dpcd_lane_settings);
enum dc_dp_training_pattern decide_cr_training_pattern(
const struct dc_link_settings *link_settings);
enum dc_dp_training_pattern decide_eq_training_pattern(struct dc_link *link,
const struct link_resource *link_res,
const struct dc_link_settings *link_settings);
enum lttpr_mode dp_decide_lttpr_mode(struct dc_link *link,
struct dc_link_settings *link_setting);
void dp_get_lttpr_mode_override(struct dc_link *link,
enum lttpr_mode *override);
void override_training_settings(
struct dc_link *link,
const struct dc_link_training_overrides *overrides,
struct link_training_settings *lt_settings);
/* Check DPCD training status registers to detect link loss. */
enum link_training_result dp_check_link_loss_status(
struct dc_link *link,
const struct link_training_settings *link_training_setting);
bool dp_is_cr_done(enum dc_lane_count ln_count,
union lane_status *dpcd_lane_status);
bool dp_is_ch_eq_done(enum dc_lane_count ln_count,
union lane_status *dpcd_lane_status);
bool dp_is_symbol_locked(enum dc_lane_count ln_count,
union lane_status *dpcd_lane_status);
bool dp_is_interlane_aligned(union lane_align_status_updated align_status);
bool is_repeater(const struct link_training_settings *lt_settings, uint32_t offset);
bool dp_is_max_vs_reached(
const struct link_training_settings *lt_settings);
uint8_t get_dpcd_link_rate(const struct dc_link_settings *link_settings);
enum link_training_result dp_get_cr_failure(enum dc_lane_count ln_count,
union lane_status *dpcd_lane_status);
void dp_hw_to_dpcd_lane_settings(
const struct link_training_settings *lt_settings,
const struct dc_lane_settings hw_lane_settings[LANE_COUNT_DP_MAX],
union dpcd_training_lane dpcd_lane_settings[LANE_COUNT_DP_MAX]);
void dp_wait_for_training_aux_rd_interval(
struct dc_link *link,
uint32_t wait_in_micro_secs);
enum dpcd_training_patterns
dp_training_pattern_to_dpcd_training_pattern(
struct dc_link *link,
enum dc_dp_training_pattern pattern);
uint8_t dp_initialize_scrambling_data_symbols(
struct dc_link *link,
enum dc_dp_training_pattern pattern);
void dp_log_training_result(
struct dc_link *link,
const struct link_training_settings *lt_settings,
enum link_training_result status);
uint32_t dp_translate_training_aux_read_interval(
uint32_t dpcd_aux_read_interval);
uint8_t dp_get_nibble_at_index(const uint8_t *buf,
uint32_t index);
bool dp_check_interlane_aligned(union lane_align_status_updated align_status,
struct dc_link *link,
uint8_t retries);
uint32_t dp_get_eq_aux_rd_interval(
const struct dc_link *link,
const struct link_training_settings *lt_settings,
uint32_t offset,
uint8_t retries);
bool dp_check_dpcd_reqeust_status(const struct dc_link *link,
enum dc_status status);
#endif /* __DC_LINK_DP_TRAINING_H__ */
@@ -0,0 +1,268 @@
/*
* 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 dp 128b/132b link training software policies and
* sequences.
*/
#include "link_dp_training_128b_132b.h"
#include "link_dp_training_8b_10b.h"
#include "link_dpcd.h"
#include "link_dp_phy.h"
#include "link_dp_capability.h"
#define DC_LOGGER \
link->ctx->logger
static enum dc_status dpcd_128b_132b_set_lane_settings(
struct dc_link *link,
const struct link_training_settings *link_training_setting)
{
enum dc_status status = core_link_write_dpcd(link,
DP_TRAINING_LANE0_SET,
(uint8_t *)(link_training_setting->dpcd_lane_settings),
sizeof(link_training_setting->dpcd_lane_settings));
DC_LOG_HW_LINK_TRAINING("%s:\n 0x%X TX_FFE_PRESET_VALUE = %x\n",
__func__,
DP_TRAINING_LANE0_SET,
link_training_setting->dpcd_lane_settings[0].tx_ffe.PRESET_VALUE);
return status;
}
static void dpcd_128b_132b_get_aux_rd_interval(struct dc_link *link,
uint32_t *interval_in_us)
{
union dp_128b_132b_training_aux_rd_interval dpcd_interval;
uint32_t interval_unit = 0;
dpcd_interval.raw = 0;
core_link_read_dpcd(link, DP_128B132B_TRAINING_AUX_RD_INTERVAL,
&dpcd_interval.raw, sizeof(dpcd_interval.raw));
interval_unit = dpcd_interval.bits.UNIT ? 1 : 2; /* 0b = 2 ms, 1b = 1 ms */
/* (128b/132b_TRAINING_AUX_RD_INTERVAL value + 1) *
* INTERVAL_UNIT. The maximum is 256 ms
*/
*interval_in_us = (dpcd_interval.bits.VALUE + 1) * interval_unit * 1000;
}
static enum link_training_result dp_perform_128b_132b_channel_eq_done_sequence(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings)
{
uint8_t loop_count;
uint32_t aux_rd_interval = 0;
uint32_t wait_time = 0;
union lane_align_status_updated dpcd_lane_status_updated = {0};
union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = {0};
union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0};
enum dc_status status = DC_OK;
enum link_training_result result = LINK_TRAINING_SUCCESS;
/* Transmit 128b/132b_TPS1 over Main-Link */
dp_set_hw_training_pattern(link, link_res, lt_settings->pattern_for_cr, DPRX);
/* Set TRAINING_PATTERN_SET to 01h */
dpcd_set_training_pattern(link, lt_settings->pattern_for_cr);
/* Adjust TX_FFE_PRESET_VALUE and Transmit 128b/132b_TPS2 over Main-Link */
dpcd_128b_132b_get_aux_rd_interval(link, &aux_rd_interval);
dp_get_lane_status_and_lane_adjust(link, lt_settings, dpcd_lane_status,
&dpcd_lane_status_updated, dpcd_lane_adjust, DPRX);
dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
dp_set_hw_lane_settings(link, link_res, lt_settings, DPRX);
dp_set_hw_training_pattern(link, link_res, lt_settings->pattern_for_eq, DPRX);
/* Set loop counter to start from 1 */
loop_count = 1;
/* Set TRAINING_PATTERN_SET to 02h and TX_FFE_PRESET_VALUE in one AUX transaction */
dpcd_set_lt_pattern_and_lane_settings(link, lt_settings,
lt_settings->pattern_for_eq, DPRX);
/* poll for channel EQ done */
while (result == LINK_TRAINING_SUCCESS) {
dp_wait_for_training_aux_rd_interval(link, aux_rd_interval);
wait_time += aux_rd_interval;
status = dp_get_lane_status_and_lane_adjust(link, lt_settings, dpcd_lane_status,
&dpcd_lane_status_updated, dpcd_lane_adjust, DPRX);
dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
dpcd_128b_132b_get_aux_rd_interval(link, &aux_rd_interval);
if (status != DC_OK) {
result = LINK_TRAINING_ABORT;
} else if (dp_is_ch_eq_done(lt_settings->link_settings.lane_count,
dpcd_lane_status)) {
/* pass */
break;
} else if (loop_count >= lt_settings->eq_loop_count_limit) {
result = DP_128b_132b_MAX_LOOP_COUNT_REACHED;
} else if (dpcd_lane_status_updated.bits.LT_FAILED_128b_132b) {
result = DP_128b_132b_LT_FAILED;
} else {
dp_set_hw_lane_settings(link, link_res, lt_settings, DPRX);
dpcd_128b_132b_set_lane_settings(link, lt_settings);
}
loop_count++;
}
/* poll for EQ interlane align done */
while (result == LINK_TRAINING_SUCCESS) {
if (status != DC_OK) {
result = LINK_TRAINING_ABORT;
} else if (dpcd_lane_status_updated.bits.EQ_INTERLANE_ALIGN_DONE_128b_132b) {
/* pass */
break;
} else if (wait_time >= lt_settings->eq_wait_time_limit) {
result = DP_128b_132b_CHANNEL_EQ_DONE_TIMEOUT;
} else if (dpcd_lane_status_updated.bits.LT_FAILED_128b_132b) {
result = DP_128b_132b_LT_FAILED;
} else {
dp_wait_for_training_aux_rd_interval(link,
lt_settings->eq_pattern_time);
wait_time += lt_settings->eq_pattern_time;
status = dp_get_lane_status_and_lane_adjust(link, lt_settings, dpcd_lane_status,
&dpcd_lane_status_updated, dpcd_lane_adjust, DPRX);
}
}
return result;
}
static enum link_training_result dp_perform_128b_132b_cds_done_sequence(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings)
{
(void)link_res;
/* Assumption: assume hardware has transmitted eq pattern */
enum dc_status status = DC_OK;
enum link_training_result result = LINK_TRAINING_SUCCESS;
union lane_align_status_updated dpcd_lane_status_updated = {0};
union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = {0};
union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0};
uint32_t wait_time = 0;
/* initiate CDS done sequence */
dpcd_set_training_pattern(link, lt_settings->pattern_for_cds);
/* poll for CDS interlane align done and symbol lock */
while (result == LINK_TRAINING_SUCCESS) {
dp_wait_for_training_aux_rd_interval(link,
lt_settings->cds_pattern_time);
wait_time += lt_settings->cds_pattern_time;
status = dp_get_lane_status_and_lane_adjust(link, lt_settings, dpcd_lane_status,
&dpcd_lane_status_updated, dpcd_lane_adjust, DPRX);
if (status != DC_OK) {
result = LINK_TRAINING_ABORT;
} else if (dp_is_symbol_locked(lt_settings->link_settings.lane_count, dpcd_lane_status) &&
dpcd_lane_status_updated.bits.CDS_INTERLANE_ALIGN_DONE_128b_132b) {
/* pass */
break;
} else if (dpcd_lane_status_updated.bits.LT_FAILED_128b_132b) {
result = DP_128b_132b_LT_FAILED;
} else if (wait_time >= lt_settings->cds_wait_time_limit) {
result = DP_128b_132b_CDS_DONE_TIMEOUT;
}
}
return result;
}
enum link_training_result dp_perform_128b_132b_link_training(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings)
{
enum link_training_result result = LINK_TRAINING_SUCCESS;
/* TODO - DP2.0 Link: remove legacy_dp2_lt logic */
if (link->dc->debug.legacy_dp2_lt) {
struct link_training_settings legacy_settings;
decide_8b_10b_training_settings(link,
link_res,
&lt_settings->link_settings,
&legacy_settings);
return dp_perform_8b_10b_link_training(link, link_res, &legacy_settings);
}
dpcd_set_link_settings(link, lt_settings);
if (result == LINK_TRAINING_SUCCESS) {
result = dp_perform_128b_132b_channel_eq_done_sequence(link, link_res, lt_settings);
if (result == LINK_TRAINING_SUCCESS)
DC_LOG_HW_LINK_TRAINING("%s: Channel EQ done.\n", __func__);
}
if (result == LINK_TRAINING_SUCCESS) {
result = dp_perform_128b_132b_cds_done_sequence(link, link_res, lt_settings);
if (result == LINK_TRAINING_SUCCESS)
DC_LOG_HW_LINK_TRAINING("%s: CDS done.\n", __func__);
}
return result;
}
void decide_128b_132b_training_settings(struct dc_link *link,
const struct link_resource *link_res,
const struct dc_link_settings *link_settings,
struct link_training_settings *lt_settings)
{
memset(lt_settings, 0, sizeof(*lt_settings));
lt_settings->link_settings = *link_settings;
/* TODO: should decide link spread when populating link_settings */
lt_settings->link_settings.link_spread = link->dp_ss_off ? LINK_SPREAD_DISABLED :
LINK_SPREAD_05_DOWNSPREAD_30KHZ;
lt_settings->pattern_for_cr = decide_cr_training_pattern(link_settings);
lt_settings->pattern_for_eq = decide_eq_training_pattern(link, link_res, link_settings);
lt_settings->eq_pattern_time = 2500;
lt_settings->eq_wait_time_limit = 400000;
lt_settings->eq_loop_count_limit = 20;
lt_settings->pattern_for_cds = DP_128b_132b_TPS2_CDS;
lt_settings->cds_pattern_time = 2500;
lt_settings->cds_wait_time_limit = (dp_parse_lttpr_repeater_count(
link->dpcd_caps.lttpr_caps.phy_repeater_cnt) + 1) * 20000;
lt_settings->disallow_per_lane_settings = true;
lt_settings->lttpr_mode = dp_decide_128b_132b_lttpr_mode(link);
dp_hw_to_dpcd_lane_settings(lt_settings,
lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
}
enum lttpr_mode dp_decide_128b_132b_lttpr_mode(struct dc_link *link)
{
enum lttpr_mode mode = LTTPR_MODE_NON_LTTPR;
if (dp_is_lttpr_present(link))
mode = LTTPR_MODE_NON_TRANSPARENT;
DC_LOG_DC("128b_132b chose LTTPR_MODE %d.\n", mode);
return mode;
}
@@ -0,0 +1,43 @@
/*
* 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
*
*/
#ifndef __DC_LINK_DP_TRAINING_128B_132B_H__
#define __DC_LINK_DP_TRAINING_128B_132B_H__
#include "link_dp_training.h"
enum link_training_result dp_perform_128b_132b_link_training(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings);
void decide_128b_132b_training_settings(struct dc_link *link,
const struct link_resource *link_res,
const struct dc_link_settings *link_settings,
struct link_training_settings *lt_settings);
enum lttpr_mode dp_decide_128b_132b_lttpr_mode(struct dc_link *link);
#endif /* __DC_LINK_DP_TRAINING_128B_132B_H__ */
@@ -0,0 +1,491 @@
/*
* 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 dp 8b/10b link training software policies and
* sequences.
*/
#include "link_dp_training_8b_10b.h"
#include "link_dpcd.h"
#include "link_dp_phy.h"
#include "link_dp_capability.h"
#define DC_LOGGER \
link->ctx->logger
static void get_default_8b_10b_lttpr_aux_rd_interval(
union training_aux_rd_interval *training_rd_interval)
{
/* LTTPR are required to program DPCD 0000Eh to 0x4 (16ms) upon AUX
* read reply to this register. Since old sinks with DPCD rev 1.1
* and earlier may not support this register, assume the mandatory
* value is programmed by the LTTPR to avoid AUX timeout issues.
*/
training_rd_interval->raw = 0x4;
}
static int32_t get_cr_training_aux_rd_interval(struct dc_link *link,
const struct dc_link_settings *link_settings,
enum lttpr_mode lttpr_mode)
{
union training_aux_rd_interval training_rd_interval;
uint32_t wait_in_micro_secs = 100;
memset(&training_rd_interval, 0, sizeof(training_rd_interval));
if (link_dp_get_encoding_format(link_settings) == DP_8b_10b_ENCODING) {
if (link->dpcd_caps.dpcd_rev.raw >= DPCD_REV_12)
core_link_read_dpcd(
link,
DP_TRAINING_AUX_RD_INTERVAL,
(uint8_t *)&training_rd_interval,
sizeof(training_rd_interval));
else if (dp_is_lttpr_present(link))
get_default_8b_10b_lttpr_aux_rd_interval(&training_rd_interval);
if (training_rd_interval.raw != 0) {
if (lttpr_mode != LTTPR_MODE_NON_TRANSPARENT)
wait_in_micro_secs = 400;
if (training_rd_interval.bits.TRAINIG_AUX_RD_INTERVAL)
wait_in_micro_secs = training_rd_interval.bits.TRAINIG_AUX_RD_INTERVAL * 4000;
}
}
return wait_in_micro_secs;
}
static uint32_t get_eq_training_aux_rd_interval(
struct dc_link *link,
const struct dc_link_settings *link_settings)
{
union training_aux_rd_interval training_rd_interval;
memset(&training_rd_interval, 0, sizeof(training_rd_interval));
if (link_dp_get_encoding_format(link_settings) == DP_128b_132b_ENCODING) {
core_link_read_dpcd(
link,
DP_128B132B_TRAINING_AUX_RD_INTERVAL,
(uint8_t *)&training_rd_interval,
sizeof(training_rd_interval));
} else if (link_dp_get_encoding_format(link_settings) == DP_8b_10b_ENCODING) {
if (link->dpcd_caps.dpcd_rev.raw >= DPCD_REV_12)
core_link_read_dpcd(
link,
DP_TRAINING_AUX_RD_INTERVAL,
(uint8_t *)&training_rd_interval,
sizeof(training_rd_interval));
else if (dp_is_lttpr_present(link))
get_default_8b_10b_lttpr_aux_rd_interval(&training_rd_interval);
}
switch (training_rd_interval.bits.TRAINIG_AUX_RD_INTERVAL) {
case 0: return 400;
case 1: return 4000;
case 2: return 8000;
case 3: return 12000;
case 4: return 16000;
case 5: return 32000;
case 6: return 64000;
default: return 400;
}
}
void decide_8b_10b_training_settings(
struct dc_link *link,
const struct link_resource *link_res,
const struct dc_link_settings *link_setting,
struct link_training_settings *lt_settings)
{
memset(lt_settings, '\0', sizeof(struct link_training_settings));
/* Initialize link settings */
lt_settings->link_settings.use_link_rate_set = link_setting->use_link_rate_set;
lt_settings->link_settings.link_rate_set = link_setting->link_rate_set;
lt_settings->link_settings.link_rate = link_setting->link_rate;
lt_settings->link_settings.lane_count = link_setting->lane_count;
/* TODO hard coded to SS for now
* lt_settings.link_settings.link_spread =
* dal_display_path_is_ss_supported(
* path_mode->display_path) ?
* LINK_SPREAD_05_DOWNSPREAD_30KHZ :
* LINK_SPREAD_DISABLED;
*/
lt_settings->link_settings.link_spread = link->dp_ss_off ?
LINK_SPREAD_DISABLED : LINK_SPREAD_05_DOWNSPREAD_30KHZ;
lt_settings->eq_pattern_time = (uint16_t)get_eq_training_aux_rd_interval(link, link_setting);
lt_settings->pattern_for_cr = decide_cr_training_pattern(link_setting);
lt_settings->pattern_for_eq = decide_eq_training_pattern(link, link_res, link_setting);
lt_settings->enhanced_framing = 1;
lt_settings->should_set_fec_ready = true;
lt_settings->disallow_per_lane_settings = true;
lt_settings->always_match_dpcd_with_hw_lane_settings = true;
lt_settings->lttpr_mode = dp_decide_8b_10b_lttpr_mode(link);
lt_settings->cr_pattern_time = (uint16_t)get_cr_training_aux_rd_interval(link, link_setting, lt_settings->lttpr_mode);
dp_hw_to_dpcd_lane_settings(lt_settings, lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
/* Some embedded LTTPRs rely on receiving TPS2 before LT to interop reliably with sensitive VGA dongles
* This allows these LTTPRs to minimize freq/phase and skew variation during lock and deskew sequences
*/
if ((link->chip_caps & AMD_EXT_DISPLAY_PATH_CAPS__EXT_CHIP_MASK) ==
AMD_EXT_DISPLAY_PATH_CAPS__DP_EARLY_8B10B_TPS2) {
lt_settings->lttpr_early_tps2 = true;
}
}
enum lttpr_mode dp_decide_8b_10b_lttpr_mode(struct dc_link *link)
{
bool is_lttpr_present = dp_is_lttpr_present(link);
bool vbios_lttpr_force_non_transparent = link->dc->caps.vbios_lttpr_enable;
bool vbios_lttpr_aware = link->dc->caps.vbios_lttpr_aware;
if (!is_lttpr_present)
return LTTPR_MODE_NON_LTTPR;
if (vbios_lttpr_aware) {
if (vbios_lttpr_force_non_transparent) {
DC_LOG_DC("chose LTTPR_MODE_NON_TRANSPARENT due to VBIOS DCE_INFO_CAPS_LTTPR_SUPPORT_ENABLE set to 1.\n");
return LTTPR_MODE_NON_TRANSPARENT;
} else {
DC_LOG_DC("chose LTTPR_MODE_NON_TRANSPARENT by default due to VBIOS not set DCE_INFO_CAPS_LTTPR_SUPPORT_ENABLE set to 1.\n");
return LTTPR_MODE_TRANSPARENT;
}
}
if (link->dc->config.allow_lttpr_non_transparent_mode.bits.DP1_4A &&
link->dc->caps.extended_aux_timeout_support) {
DC_LOG_DC("chose LTTPR_MODE_NON_TRANSPARENT by default and dc->config.allow_lttpr_non_transparent_mode.bits.DP1_4A set to 1.\n");
return LTTPR_MODE_NON_TRANSPARENT;
}
DC_LOG_DC("chose LTTPR_MODE_NON_LTTPR.\n");
return LTTPR_MODE_NON_LTTPR;
}
static void set_link_settings_and_perform_early_tps2_retimer_pre_lt_sequence(struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings,
uint32_t lttpr_count)
{
/* Vendor-specific LTTPR early TPS2 sequence:
* 1. Output TPS2
* 2. Wait 400us
* 3. Set link settings as usual
* 4. Write TPS1 to DP_TRAINING_PATTERN_SET_PHY_REPEATERx targeting LTTPR closest to host
* 5. Wait 1ms
* 6. Begin link training as usual
* */
uint32_t closest_lttpr_address_offset = dp_get_closest_lttpr_offset((uint8_t)lttpr_count);
union dpcd_training_pattern dpcd_pattern = {0};
dpcd_pattern.v1_4.TRAINING_PATTERN_SET = 1;
dpcd_pattern.v1_4.SCRAMBLING_DISABLE = 1;
DC_LOG_HW_LINK_TRAINING("%s\n GPU sends TPS2. Wait 400us.\n", __func__);
dp_set_hw_training_pattern(link, link_res, DP_TRAINING_PATTERN_SEQUENCE_2, DPRX);
dp_set_hw_lane_settings(link, link_res, lt_settings, DPRX);
udelay(400);
dpcd_set_link_settings(link, lt_settings);
core_link_write_dpcd(link, DP_TRAINING_PATTERN_SET_PHY_REPEATER1 + closest_lttpr_address_offset, &dpcd_pattern.raw, 1);
udelay(1000);
}
enum link_training_result perform_8b_10b_clock_recovery_sequence(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings,
uint32_t offset)
{
enum dc_status status;
uint32_t retries_cr;
uint32_t retry_count;
uint32_t wait_time_microsec;
enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX];
union lane_align_status_updated dpcd_lane_status_updated;
union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0};
retries_cr = 0;
retry_count = 0;
memset(&dpcd_lane_status, '\0', sizeof(dpcd_lane_status));
memset(&dpcd_lane_status_updated, '\0',
sizeof(dpcd_lane_status_updated));
if (!link->ctx->dc->work_arounds.lt_early_cr_pattern)
dp_set_hw_training_pattern(link, link_res, lt_settings->pattern_for_cr, offset);
/* najeeb - The synaptics MST hub can put the LT in
* infinite loop by switching the VS
*/
/* between level 0 and level 1 continuously, here
* we try for CR lock for LinkTrainingMaxCRRetry count*/
while ((retries_cr < LINK_TRAINING_MAX_RETRY_COUNT) &&
(retry_count < LINK_TRAINING_MAX_CR_RETRY)) {
/* 1. call HWSS to set lane settings*/
dp_set_hw_lane_settings(
link,
link_res,
lt_settings,
offset);
/* 2. update DPCD of the receiver*/
if (!retry_count)
/* EPR #361076 - write as a 5-byte burst,
* but only for the 1-st iteration.*/
dpcd_set_lt_pattern_and_lane_settings(
link,
lt_settings,
lt_settings->pattern_for_cr,
offset);
else
dpcd_set_lane_settings(
link,
lt_settings,
offset);
/* 3. wait receiver to lock-on*/
wait_time_microsec = lt_settings->cr_pattern_time;
dp_wait_for_training_aux_rd_interval(
link,
wait_time_microsec);
/* 4. Read lane status and requested drive
* settings as set by the sink
*/
status = dp_get_lane_status_and_lane_adjust(
link,
lt_settings,
dpcd_lane_status,
&dpcd_lane_status_updated,
dpcd_lane_adjust,
offset);
if (dp_check_dpcd_reqeust_status(link, status))
return LINK_TRAINING_ABORT;
/* 5. check CR done*/
if (dp_is_cr_done(lane_count, dpcd_lane_status)) {
DC_LOG_HW_LINK_TRAINING("%s: Clock recovery OK\n", __func__);
return LINK_TRAINING_SUCCESS;
}
/* 6. max VS reached*/
if ((link_dp_get_encoding_format(&lt_settings->link_settings) ==
DP_8b_10b_ENCODING) &&
dp_is_max_vs_reached(lt_settings))
break;
/* 7. same lane settings*/
/* Note: settings are the same for all lanes,
* so comparing first lane is sufficient*/
if ((link_dp_get_encoding_format(&lt_settings->link_settings) == DP_8b_10b_ENCODING) &&
lt_settings->dpcd_lane_settings[0].bits.VOLTAGE_SWING_SET ==
dpcd_lane_adjust[0].bits.VOLTAGE_SWING_LANE)
retries_cr++;
else if ((link_dp_get_encoding_format(&lt_settings->link_settings) == DP_128b_132b_ENCODING) &&
lt_settings->dpcd_lane_settings[0].tx_ffe.PRESET_VALUE ==
dpcd_lane_adjust[0].tx_ffe.PRESET_VALUE)
retries_cr++;
else
retries_cr = 0;
/* 8. update VS/PE/PC2 in lt_settings*/
dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
retry_count++;
}
if (retry_count >= LINK_TRAINING_MAX_CR_RETRY) {
ASSERT(0);
DC_LOG_ERROR("%s: Link Training Error, could not get CR after %d tries. Possibly voltage swing issue",
__func__,
LINK_TRAINING_MAX_CR_RETRY);
}
return dp_get_cr_failure(lane_count, dpcd_lane_status);
}
enum link_training_result perform_8b_10b_channel_equalization_sequence(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings,
uint32_t offset)
{
enum dc_status status;
enum dc_dp_training_pattern tr_pattern;
uint32_t retries_ch_eq;
uint32_t wait_time_microsec;
enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
union lane_align_status_updated dpcd_lane_status_updated = {0};
union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = {0};
union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0};
/* Note: also check that TPS4 is a supported feature*/
tr_pattern = lt_settings->pattern_for_eq;
if (is_repeater(lt_settings, offset) && link_dp_get_encoding_format(&lt_settings->link_settings) == DP_8b_10b_ENCODING)
tr_pattern = DP_TRAINING_PATTERN_SEQUENCE_4;
dp_set_hw_training_pattern(link, link_res, tr_pattern, offset);
for (retries_ch_eq = 0; retries_ch_eq <= LINK_TRAINING_MAX_RETRY_COUNT;
retries_ch_eq++) {
dp_set_hw_lane_settings(link, link_res, lt_settings, offset);
/* 2. update DPCD*/
if (!retries_ch_eq)
/* EPR #361076 - write as a 5-byte burst,
* but only for the 1-st iteration
*/
dpcd_set_lt_pattern_and_lane_settings(
link,
lt_settings,
tr_pattern, offset);
else
dpcd_set_lane_settings(link, lt_settings, offset);
/* 3. wait for receiver to lock-on*/
wait_time_microsec = dp_get_eq_aux_rd_interval(link, lt_settings, offset, (uint8_t)retries_ch_eq);
dp_wait_for_training_aux_rd_interval(
link,
wait_time_microsec);
/* 4. Read lane status and requested
* drive settings as set by the sink*/
status = dp_get_lane_status_and_lane_adjust(
link,
lt_settings,
dpcd_lane_status,
&dpcd_lane_status_updated,
dpcd_lane_adjust,
offset);
if (dp_check_dpcd_reqeust_status(link, status))
return LINK_TRAINING_ABORT;
/* 5. check CR done*/
if (!dp_is_cr_done(lane_count, dpcd_lane_status))
return dpcd_lane_status[0].bits.CR_DONE_0 ?
LINK_TRAINING_EQ_FAIL_CR_PARTIAL :
LINK_TRAINING_EQ_FAIL_CR;
/* 6. check CHEQ done*/
if (dp_is_ch_eq_done(lane_count, dpcd_lane_status) &&
dp_is_symbol_locked(lane_count, dpcd_lane_status) &&
dp_check_interlane_aligned(dpcd_lane_status_updated, link, (uint8_t)retries_ch_eq))
return LINK_TRAINING_SUCCESS;
/* 7. update VS/PE/PC2 in lt_settings*/
dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
}
return LINK_TRAINING_EQ_FAIL_EQ;
}
enum link_training_result dp_perform_8b_10b_link_training(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings)
{
enum link_training_result status = LINK_TRAINING_SUCCESS;
uint8_t repeater_cnt = dp_parse_lttpr_repeater_count(link->dpcd_caps.lttpr_caps.phy_repeater_cnt);
uint8_t repeater_id;
uint8_t lane = 0;
if (link->ctx->dc->work_arounds.lt_early_cr_pattern)
start_clock_recovery_pattern_early(link, link_res, lt_settings, DPRX);
/* 1. set link rate, lane count and spread. */
if (lt_settings->lttpr_early_tps2)
set_link_settings_and_perform_early_tps2_retimer_pre_lt_sequence(link, link_res, lt_settings, repeater_cnt);
else
dpcd_set_link_settings(link, lt_settings);
if (lt_settings->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT) {
/* 2. perform link training (set link training done
* to false is done as well)
*/
for (repeater_id = repeater_cnt; (repeater_id > 0 && status == LINK_TRAINING_SUCCESS);
repeater_id--) {
status = perform_8b_10b_clock_recovery_sequence(link, link_res, lt_settings, repeater_id);
if (status != LINK_TRAINING_SUCCESS) {
repeater_training_done(link, repeater_id);
break;
}
status = perform_8b_10b_channel_equalization_sequence(link,
link_res,
lt_settings,
repeater_id);
if (status == LINK_TRAINING_SUCCESS)
DC_LOG_HW_LINK_TRAINING("%s: Channel EQ done.\n", __func__);
repeater_training_done(link, repeater_id);
if (status != LINK_TRAINING_SUCCESS)
break;
for (lane = 0; lane < LANE_COUNT_DP_MAX; lane++) {
lt_settings->dpcd_lane_settings[lane].raw = 0;
lt_settings->hw_lane_settings[lane].VOLTAGE_SWING = 0;
lt_settings->hw_lane_settings[lane].PRE_EMPHASIS = 0;
}
}
}
if (status == LINK_TRAINING_SUCCESS) {
status = perform_8b_10b_clock_recovery_sequence(link, link_res, lt_settings, DPRX);
if (status == LINK_TRAINING_SUCCESS) {
status = perform_8b_10b_channel_equalization_sequence(link,
link_res,
lt_settings,
DPRX);
if (status == LINK_TRAINING_SUCCESS)
DC_LOG_HW_LINK_TRAINING("%s: Channel EQ done.\n", __func__);
}
}
return status;
}
@@ -0,0 +1,62 @@
/*
* 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
*
*/
#ifndef __DC_LINK_DP_TRAINING_8B_10B_H__
#define __DC_LINK_DP_TRAINING_8B_10B_H__
#include "link_dp_training.h"
/* to avoid infinite loop where-in the receiver
* switches between different VS
*/
#define LINK_TRAINING_MAX_CR_RETRY 100
#define LINK_TRAINING_MAX_RETRY_COUNT 5
enum link_training_result dp_perform_8b_10b_link_training(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings);
enum link_training_result perform_8b_10b_clock_recovery_sequence(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings,
uint32_t offset);
enum link_training_result perform_8b_10b_channel_equalization_sequence(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings,
uint32_t offset);
enum lttpr_mode dp_decide_8b_10b_lttpr_mode(struct dc_link *link);
void decide_8b_10b_training_settings(
struct dc_link *link,
const struct link_resource *link_res,
const struct dc_link_settings *link_setting,
struct link_training_settings *lt_settings);
#endif /* __DC_LINK_DP_TRAINING_8B_10B_H__ */
@@ -0,0 +1,80 @@
/*
* 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:
*
*/
#include "link_dp_training_auxless.h"
#include "link_dp_phy.h"
#define DC_LOGGER \
link->ctx->logger
bool dp_perform_link_training_skip_aux(
struct dc_link *link,
const struct link_resource *link_res,
const struct dc_link_settings *link_setting)
{
struct link_training_settings lt_settings = {0};
dp_decide_training_settings(
link,
link_res,
link_setting,
&lt_settings);
override_training_settings(
link,
&link->preferred_training_settings,
&lt_settings);
/* 1. Perform_clock_recovery_sequence. */
/* transmit training pattern for clock recovery */
dp_set_hw_training_pattern(link, link_res, lt_settings.pattern_for_cr, DPRX);
/* call HWSS to set lane settings*/
dp_set_hw_lane_settings(link, link_res, &lt_settings, DPRX);
/* wait receiver to lock-on*/
dp_wait_for_training_aux_rd_interval(link, lt_settings.cr_pattern_time);
/* 2. Perform_channel_equalization_sequence. */
/* transmit training pattern for channel equalization. */
dp_set_hw_training_pattern(link, link_res, lt_settings.pattern_for_eq, DPRX);
/* call HWSS to set lane settings*/
dp_set_hw_lane_settings(link, link_res, &lt_settings, DPRX);
/* wait receiver to lock-on. */
dp_wait_for_training_aux_rd_interval(link, lt_settings.eq_pattern_time);
/* 3. Perform_link_training_int. */
/* Mainlink output idle pattern. */
dp_set_hw_test_pattern(link, link_res, DP_TEST_PATTERN_VIDEO_MODE, NULL, 0);
dp_log_training_result(link, &lt_settings, LINK_TRAINING_SUCCESS);
return true;
}
@@ -0,0 +1,35 @@
/*
* 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
*
*/
#ifndef __DC_LINK_DP_TRAINING_AUXLESS_H__
#define __DC_LINK_DP_TRAINING_AUXLESS_H__
#include "link_dp_training.h"
bool dp_perform_link_training_skip_aux(
struct dc_link *link,
const struct link_resource *link_res,
const struct dc_link_settings *link_setting);
#endif /* __DC_LINK_DP_TRAINING_AUXLESS_H__ */
@@ -0,0 +1,60 @@
/*
* 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
*
*/
#ifndef __DC_LINK_DP_TRAINING_DPIA_H__
#define __DC_LINK_DP_TRAINING_DPIA_H__
#include "link_dp_training.h"
/* The approximate time (us) it takes to transmit 9 USB4 DP clock sync packets. */
#define DPIA_CLK_SYNC_DELAY 16000
/* Train DP tunneling link for USB4 DPIA display endpoint.
* DPIA equivalent of dc_link_dp_perfrorm_link_training.
* Aborts link training upon detection of sink unplug.
*/
enum link_training_result dpia_perform_link_training(
struct dc_link *link,
const struct link_resource *link_res,
const struct dc_link_settings *link_setting,
bool skip_video_pattern);
void dpia_training_abort(
struct dc_link *link,
struct link_training_settings *lt_settings,
uint32_t hop);
uint32_t dpia_get_eq_aux_rd_interval(
const struct dc_link *link,
const struct link_training_settings *lt_settings,
uint32_t hop);
void dpia_set_tps_notification(
struct dc_link *link,
const struct link_training_settings *lt_settings,
uint8_t pattern,
uint32_t offset);
#endif /* __DC_LINK_DP_TRAINING_DPIA_H__ */
@@ -0,0 +1,553 @@
/*
* 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 8b/10b link training specially modified to support an
* embedded retimer chip. This retimer chip is referred as fixed vs pe retimer.
* Unlike native dp connection this chip requires a modified link training
* protocol based on 8b/10b link training. Since this is a non standard sequence
* and we must support this hardware, we decided to isolate it in its own
* training sequence inside its own file.
*/
#include "link_dp_training_fixed_vs_pe_retimer.h"
#include "link_dp_training_8b_10b.h"
#include "link_dpcd.h"
#include "link_dp_phy.h"
#include "link_dp_capability.h"
#include "link_ddc.h"
#define DC_LOGGER \
link->ctx->logger
void dp_fixed_vs_pe_read_lane_adjust(
struct dc_link *link,
union dpcd_training_lane dpcd_lane_adjust[LANE_COUNT_DP_MAX])
{
const uint8_t vendor_lttpr_write_data_vs[3] = {0x0, 0x53, 0x63};
const uint8_t vendor_lttpr_write_data_pe[3] = {0x0, 0x54, 0x63};
uint8_t dprx_vs = 0;
uint8_t dprx_pe = 0;
uint8_t lane;
/* W/A to read lane settings requested by DPRX */
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_vs[0], sizeof(vendor_lttpr_write_data_vs));
link_query_fixed_vs_pe_retimer(link->ddc, &dprx_vs, 1);
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_pe[0], sizeof(vendor_lttpr_write_data_pe));
link_query_fixed_vs_pe_retimer(link->ddc, &dprx_pe, 1);
for (lane = 0; lane < LANE_COUNT_DP_MAX; lane++) {
dpcd_lane_adjust[lane].bits.VOLTAGE_SWING_SET = (dprx_vs >> (2 * lane)) & 0x3;
dpcd_lane_adjust[lane].bits.PRE_EMPHASIS_SET = (dprx_pe >> (2 * lane)) & 0x3;
}
}
void dp_fixed_vs_pe_set_retimer_lane_settings(
struct dc_link *link,
const union dpcd_training_lane dpcd_lane_adjust[LANE_COUNT_DP_MAX],
uint8_t lane_count)
{
const uint8_t vendor_lttpr_write_data_reset[4] = {0x1, 0x50, 0x63, 0xFF};
uint8_t vendor_lttpr_write_data_vs[4] = {0x1, 0x51, 0x63, 0x0};
uint8_t vendor_lttpr_write_data_pe[4] = {0x1, 0x52, 0x63, 0x0};
uint8_t lane = 0;
for (lane = 0; lane < lane_count; lane++) {
vendor_lttpr_write_data_vs[3] |=
dpcd_lane_adjust[lane].bits.VOLTAGE_SWING_SET << (2 * lane);
vendor_lttpr_write_data_pe[3] |=
dpcd_lane_adjust[lane].bits.PRE_EMPHASIS_SET << (2 * lane);
}
/* Force LTTPR to output desired VS and PE */
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_reset[0], sizeof(vendor_lttpr_write_data_reset));
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_vs[0], sizeof(vendor_lttpr_write_data_vs));
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_pe[0], sizeof(vendor_lttpr_write_data_pe));
}
static enum link_training_result perform_fixed_vs_pe_nontransparent_training_sequence(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings)
{
enum link_training_result status = LINK_TRAINING_SUCCESS;
uint8_t lane = 0;
uint8_t toggle_rate = 0x6;
uint8_t target_rate = 0x6;
bool apply_toggle_rate_wa = false;
uint8_t repeater_cnt;
uint8_t repeater_id;
/* Fixed VS/PE specific: Force CR AUX RD Interval to at least 16ms */
if (lt_settings->cr_pattern_time < 16000)
lt_settings->cr_pattern_time = 16000;
/* Fixed VS/PE specific: Toggle link rate */
apply_toggle_rate_wa = ((link->vendor_specific_lttpr_link_rate_wa == target_rate) || (link->vendor_specific_lttpr_link_rate_wa == 0));
target_rate = get_dpcd_link_rate(&lt_settings->link_settings);
toggle_rate = (target_rate == 0x6) ? 0xA : 0x6;
if (apply_toggle_rate_wa)
lt_settings->link_settings.link_rate = toggle_rate;
if (link->ctx->dc->work_arounds.lt_early_cr_pattern)
start_clock_recovery_pattern_early(link, link_res, lt_settings, DPRX);
/* 1. set link rate, lane count and spread. */
dpcd_set_link_settings(link, lt_settings);
/* Fixed VS/PE specific: Toggle link rate back*/
if (apply_toggle_rate_wa) {
core_link_write_dpcd(
link,
DP_LINK_BW_SET,
&target_rate,
1);
}
link->vendor_specific_lttpr_link_rate_wa = target_rate;
if (lt_settings->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT) {
/* 2. perform link training (set link training done
* to false is done as well)
*/
repeater_cnt = dp_parse_lttpr_repeater_count(link->dpcd_caps.lttpr_caps.phy_repeater_cnt);
for (repeater_id = repeater_cnt; (repeater_id > 0 && status == LINK_TRAINING_SUCCESS);
repeater_id--) {
status = perform_8b_10b_clock_recovery_sequence(link, link_res, lt_settings, repeater_id);
if (status != LINK_TRAINING_SUCCESS) {
repeater_training_done(link, repeater_id);
break;
}
status = perform_8b_10b_channel_equalization_sequence(link,
link_res,
lt_settings,
repeater_id);
repeater_training_done(link, repeater_id);
if (status != LINK_TRAINING_SUCCESS)
break;
for (lane = 0; lane < LANE_COUNT_DP_MAX; lane++) {
lt_settings->dpcd_lane_settings[lane].raw = 0;
lt_settings->hw_lane_settings[lane].VOLTAGE_SWING = 0;
lt_settings->hw_lane_settings[lane].PRE_EMPHASIS = 0;
}
}
}
if (status == LINK_TRAINING_SUCCESS) {
status = perform_8b_10b_clock_recovery_sequence(link, link_res, lt_settings, DPRX);
if (status == LINK_TRAINING_SUCCESS) {
status = perform_8b_10b_channel_equalization_sequence(link,
link_res,
lt_settings,
DPRX);
}
}
return status;
}
enum link_training_result dp_perform_fixed_vs_pe_training_sequence(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings)
{
const uint8_t vendor_lttpr_write_data_reset[4] = {0x1, 0x50, 0x63, 0xFF};
const uint8_t offset = dp_parse_lttpr_repeater_count(
link->dpcd_caps.lttpr_caps.phy_repeater_cnt);
const uint8_t vendor_lttpr_write_data_intercept_en[4] = {0x1, 0x55, 0x63, 0x0};
const uint8_t vendor_lttpr_write_data_intercept_dis[4] = {0x1, 0x55, 0x63, 0x6E};
const uint8_t vendor_lttpr_write_data_adicora_eq1[4] = {0x1, 0x55, 0x63, 0x2E};
const uint8_t vendor_lttpr_write_data_adicora_eq2[4] = {0x1, 0x55, 0x63, 0x01};
const uint8_t vendor_lttpr_write_data_adicora_eq3[4] = {0x1, 0x55, 0x63, 0x68};
uint32_t pre_disable_intercept_delay_ms = 0;
uint8_t vendor_lttpr_write_data_vs[4] = {0x1, 0x51, 0x63, 0x0};
uint8_t vendor_lttpr_write_data_pe[4] = {0x1, 0x52, 0x63, 0x0};
const uint8_t vendor_lttpr_write_data_4lane_1[4] = {0x1, 0x6E, 0xF2, 0x19};
const uint8_t vendor_lttpr_write_data_4lane_2[4] = {0x1, 0x6B, 0xF2, 0x01};
const uint8_t vendor_lttpr_write_data_4lane_3[4] = {0x1, 0x6D, 0xF2, 0x18};
const uint8_t vendor_lttpr_write_data_4lane_4[4] = {0x1, 0x6C, 0xF2, 0x03};
const uint8_t vendor_lttpr_write_data_4lane_5[4] = {0x1, 0x03, 0xF3, 0x06};
const uint8_t vendor_lttpr_write_data_dpmf[4] = {0x1, 0x6, 0x70, 0x87};
enum link_training_result status = LINK_TRAINING_SUCCESS;
uint8_t lane = 0;
union down_spread_ctrl downspread = {0};
union lane_count_set lane_count_set = {0};
uint8_t toggle_rate;
uint8_t rate;
/* Only 8b/10b is supported */
ASSERT(link_dp_get_encoding_format(&lt_settings->link_settings) ==
DP_8b_10b_ENCODING);
if (lt_settings->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT) {
status = perform_fixed_vs_pe_nontransparent_training_sequence(link, link_res, lt_settings);
return status;
}
if (offset != 0xFF) {
if (offset == 2) {
pre_disable_intercept_delay_ms = link->dc->debug.fixed_vs_aux_delay_config_wa;
/* Certain display and cable configuration require extra delay */
} else if (offset > 2) {
pre_disable_intercept_delay_ms = link->dc->debug.fixed_vs_aux_delay_config_wa * 2;
}
}
/* Vendor specific: Reset lane settings */
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_reset[0], sizeof(vendor_lttpr_write_data_reset));
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_vs[0], sizeof(vendor_lttpr_write_data_vs));
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_pe[0], sizeof(vendor_lttpr_write_data_pe));
/* Vendor specific: Enable intercept */
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_intercept_en[0], sizeof(vendor_lttpr_write_data_intercept_en));
/* 1. set link rate, lane count and spread. */
downspread.raw = (uint8_t)(lt_settings->link_settings.link_spread);
lane_count_set.bits.LANE_COUNT_SET =
lt_settings->link_settings.lane_count;
lane_count_set.bits.ENHANCED_FRAMING = lt_settings->enhanced_framing;
lane_count_set.bits.POST_LT_ADJ_REQ_GRANTED = 0;
if (lt_settings->pattern_for_eq < DP_TRAINING_PATTERN_SEQUENCE_4) {
lane_count_set.bits.POST_LT_ADJ_REQ_GRANTED =
link->dpcd_caps.max_ln_count.bits.POST_LT_ADJ_REQ_SUPPORTED;
}
core_link_write_dpcd(link, DP_DOWNSPREAD_CTRL,
&downspread.raw, sizeof(downspread));
core_link_write_dpcd(link, DP_LANE_COUNT_SET,
&lane_count_set.raw, 1);
rate = get_dpcd_link_rate(&lt_settings->link_settings);
// Only perform toggle if FIXED_VS LTTPR reports no IEEE OUI
if (memcmp("\x00\x00\x00", &link->dpcd_caps.lttpr_caps.lttpr_ieee_oui[0], 3) == 0) {
/* Vendor specific: Toggle link rate */
toggle_rate = (rate == 0x6) ? 0xA : 0x6;
if (link->vendor_specific_lttpr_link_rate_wa == rate || link->vendor_specific_lttpr_link_rate_wa == 0) {
core_link_write_dpcd(
link,
DP_LINK_BW_SET,
&toggle_rate,
1);
}
link->vendor_specific_lttpr_link_rate_wa = rate;
}
core_link_write_dpcd(link, DP_LINK_BW_SET, &rate, 1);
DC_LOG_HW_LINK_TRAINING("%s\n %x rate = %x\n %x lane = %x framing = %x\n %x spread = %x\n",
__func__,
DP_LINK_BW_SET,
lt_settings->link_settings.link_rate,
DP_LANE_COUNT_SET,
lt_settings->link_settings.lane_count,
lt_settings->enhanced_framing,
DP_DOWNSPREAD_CTRL,
lt_settings->link_settings.link_spread);
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_dpmf[0],
sizeof(vendor_lttpr_write_data_dpmf));
if (lt_settings->link_settings.lane_count == LANE_COUNT_FOUR) {
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_4lane_1[0], sizeof(vendor_lttpr_write_data_4lane_1));
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_4lane_2[0], sizeof(vendor_lttpr_write_data_4lane_2));
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_4lane_3[0], sizeof(vendor_lttpr_write_data_4lane_3));
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_4lane_4[0], sizeof(vendor_lttpr_write_data_4lane_4));
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_4lane_5[0], sizeof(vendor_lttpr_write_data_4lane_5));
}
/* 2. Perform link training */
/* Perform Clock Recovery Sequence */
if (status == LINK_TRAINING_SUCCESS) {
const uint8_t max_vendor_dpcd_retries = 10;
uint32_t retries_cr;
uint32_t retry_count;
uint32_t wait_time_microsec;
enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX];
union lane_align_status_updated dpcd_lane_status_updated;
union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0};
uint8_t i = 0;
retries_cr = 0;
retry_count = 0;
memset(&dpcd_lane_status, '\0', sizeof(dpcd_lane_status));
memset(&dpcd_lane_status_updated, '\0',
sizeof(dpcd_lane_status_updated));
while ((retries_cr < LINK_TRAINING_MAX_RETRY_COUNT) &&
(retry_count < LINK_TRAINING_MAX_CR_RETRY)) {
/* 1. call HWSS to set lane settings */
dp_set_hw_lane_settings(
link,
link_res,
lt_settings,
0);
/* 2. update DPCD of the receiver */
if (!retry_count) {
/* EPR #361076 - write as a 5-byte burst,
* but only for the 1-st iteration.
*/
dpcd_set_lt_pattern_and_lane_settings(
link,
lt_settings,
lt_settings->pattern_for_cr,
0);
/* Vendor specific: Disable intercept */
for (i = 0; i < max_vendor_dpcd_retries; i++) {
if (pre_disable_intercept_delay_ms != 0)
msleep(pre_disable_intercept_delay_ms);
if (link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_intercept_dis[0],
sizeof(vendor_lttpr_write_data_intercept_dis)))
break;
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_intercept_en[0],
sizeof(vendor_lttpr_write_data_intercept_en));
}
} else {
vendor_lttpr_write_data_vs[3] = 0;
vendor_lttpr_write_data_pe[3] = 0;
for (lane = 0; lane < lane_count; lane++) {
vendor_lttpr_write_data_vs[3] |=
lt_settings->dpcd_lane_settings[lane].bits.VOLTAGE_SWING_SET << (2 * lane);
vendor_lttpr_write_data_pe[3] |=
lt_settings->dpcd_lane_settings[lane].bits.PRE_EMPHASIS_SET << (2 * lane);
}
/* Vendor specific: Update VS and PE to DPRX requested value */
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_vs[0], sizeof(vendor_lttpr_write_data_vs));
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_pe[0], sizeof(vendor_lttpr_write_data_pe));
dpcd_set_lane_settings(
link,
lt_settings,
0);
}
/* 3. wait receiver to lock-on*/
wait_time_microsec = lt_settings->cr_pattern_time;
dp_wait_for_training_aux_rd_interval(
link,
wait_time_microsec);
/* 4. Read lane status and requested drive
* settings as set by the sink
*/
dp_get_lane_status_and_lane_adjust(
link,
lt_settings,
dpcd_lane_status,
&dpcd_lane_status_updated,
dpcd_lane_adjust,
0);
/* 5. check CR done*/
if (dp_is_cr_done(lane_count, dpcd_lane_status)) {
break;
}
/* 6. max VS reached*/
if (dp_is_max_vs_reached(lt_settings))
break;
/* 7. same lane settings */
/* Note: settings are the same for all lanes,
* so comparing first lane is sufficient
*/
if (lt_settings->dpcd_lane_settings[0].bits.VOLTAGE_SWING_SET ==
dpcd_lane_adjust[0].bits.VOLTAGE_SWING_LANE)
retries_cr++;
else
retries_cr = 0;
/* 8. update VS/PE/PC2 in lt_settings*/
dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
retry_count++;
}
if (retry_count >= LINK_TRAINING_MAX_CR_RETRY) {
ASSERT(0);
DC_LOG_ERROR("%s: Link Training Error, could not get CR after %d tries. Possibly voltage swing issue",
__func__,
LINK_TRAINING_MAX_CR_RETRY);
}
status = dp_get_cr_failure(lane_count, dpcd_lane_status);
}
/* Perform Channel EQ Sequence */
if (status == LINK_TRAINING_SUCCESS) {
enum dc_dp_training_pattern tr_pattern;
uint32_t retries_ch_eq;
uint32_t wait_time_microsec;
enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
union lane_align_status_updated dpcd_lane_status_updated = {0};
union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = {0};
union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0};
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_adicora_eq1[0],
sizeof(vendor_lttpr_write_data_adicora_eq1));
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_adicora_eq2[0],
sizeof(vendor_lttpr_write_data_adicora_eq2));
/* Note: also check that TPS4 is a supported feature*/
tr_pattern = lt_settings->pattern_for_eq;
dp_set_hw_training_pattern(link, link_res, tr_pattern, 0);
status = LINK_TRAINING_EQ_FAIL_EQ;
for (retries_ch_eq = 0; retries_ch_eq <= LINK_TRAINING_MAX_RETRY_COUNT;
retries_ch_eq++) {
dp_set_hw_lane_settings(link, link_res, lt_settings, 0);
vendor_lttpr_write_data_vs[3] = 0;
vendor_lttpr_write_data_pe[3] = 0;
for (lane = 0; lane < lane_count; lane++) {
vendor_lttpr_write_data_vs[3] |=
lt_settings->dpcd_lane_settings[lane].bits.VOLTAGE_SWING_SET << (2 * lane);
vendor_lttpr_write_data_pe[3] |=
lt_settings->dpcd_lane_settings[lane].bits.PRE_EMPHASIS_SET << (2 * lane);
}
/* Vendor specific: Update VS and PE to DPRX requested value */
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_vs[0], sizeof(vendor_lttpr_write_data_vs));
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_pe[0], sizeof(vendor_lttpr_write_data_pe));
/* 2. update DPCD*/
if (!retries_ch_eq) {
/* EPR #361076 - write as a 5-byte burst,
* but only for the 1-st iteration
*/
dpcd_set_lt_pattern_and_lane_settings(
link,
lt_settings,
tr_pattern, 0);
link_configure_fixed_vs_pe_retimer(link->ddc,
&vendor_lttpr_write_data_adicora_eq3[0],
sizeof(vendor_lttpr_write_data_adicora_eq3));
} else
dpcd_set_lane_settings(link, lt_settings, 0);
/* 3. wait for receiver to lock-on*/
wait_time_microsec = lt_settings->eq_pattern_time;
dp_wait_for_training_aux_rd_interval(
link,
wait_time_microsec);
/* 4. Read lane status and requested
* drive settings as set by the sink
*/
dp_get_lane_status_and_lane_adjust(
link,
lt_settings,
dpcd_lane_status,
&dpcd_lane_status_updated,
dpcd_lane_adjust,
0);
/* 5. check CR done*/
if (!dp_is_cr_done(lane_count, dpcd_lane_status)) {
status = LINK_TRAINING_EQ_FAIL_CR;
break;
}
/* 6. check CHEQ done*/
if (dp_is_ch_eq_done(lane_count, dpcd_lane_status) &&
dp_is_symbol_locked(lane_count, dpcd_lane_status) &&
dp_is_interlane_aligned(dpcd_lane_status_updated)) {
status = LINK_TRAINING_SUCCESS;
break;
}
/* 7. update VS/PE/PC2 in lt_settings*/
dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
}
}
return status;
}
@@ -0,0 +1,45 @@
/*
* 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
*
*/
#ifndef __DC_LINK_DP_FIXED_VS_PE_RETIMER_H__
#define __DC_LINK_DP_FIXED_VS_PE_RETIMER_H__
#include "link_dp_training.h"
enum link_training_result dp_perform_fixed_vs_pe_training_sequence(
struct dc_link *link,
const struct link_resource *link_res,
struct link_training_settings *lt_settings);
void dp_fixed_vs_pe_set_retimer_lane_settings(
struct dc_link *link,
const union dpcd_training_lane dpcd_lane_adjust[LANE_COUNT_DP_MAX],
uint8_t lane_count);
void dp_fixed_vs_pe_read_lane_adjust(
struct dc_link *link,
union dpcd_training_lane dpcd_lane_adjust[LANE_COUNT_DP_MAX]);
#endif /* __DC_LINK_DP_FIXED_VS_PE_RETIMER_H__ */
@@ -0,0 +1,251 @@
/*
* Copyright 2021 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 basic dpcd read/write functionality. It also does basic
* dpcd range check to ensure that every dpcd request is compliant with specs
* range requirements.
*/
#include "link_dpcd.h"
#include <drm/display/drm_dp_helper.h>
#include "dm_helpers.h"
#define END_ADDRESS(start, size) (start + size - 1)
#define ADDRESS_RANGE_SIZE(start, end) (end - start + 1)
struct dpcd_address_range {
uint32_t start;
uint32_t end;
};
static enum dc_status internal_link_read_dpcd(
struct dc_link *link,
uint32_t address,
uint8_t *data,
uint32_t size)
{
if (!link->aux_access_disabled &&
!dm_helpers_dp_read_dpcd(link->ctx,
link, address, data, size)) {
return DC_ERROR_UNEXPECTED;
}
return DC_OK;
}
static enum dc_status internal_link_write_dpcd(
struct dc_link *link,
uint32_t address,
const uint8_t *data,
uint32_t size)
{
if (!link->aux_access_disabled &&
!dm_helpers_dp_write_dpcd(link->ctx,
link, address, data, size)) {
return DC_ERROR_UNEXPECTED;
}
return DC_OK;
}
/*
* Partition the entire DPCD address space
* XXX: This partitioning must cover the entire DPCD address space,
* and must contain no gaps or overlapping address ranges.
*/
static const struct dpcd_address_range mandatory_dpcd_partitions[] = {
{ 0, DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR1) - 1},
{ DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR1), DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR2) - 1 },
{ DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR2), DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR3) - 1 },
{ DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR3), DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR4) - 1 },
{ DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR4), DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR5) - 1 },
{ DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR5), DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR6) - 1 },
{ DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR6), DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR7) - 1 },
{ DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR7), DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR8) - 1 },
{ DP_TRAINING_PATTERN_SET_PHY_REPEATER(DP_PHY_LTTPR8), DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR1) - 1 },
/*
* The FEC registers are contiguous
*/
{ DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR1), DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR1) - 1 },
{ DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR2), DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR2) - 1 },
{ DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR3), DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR3) - 1 },
{ DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR4), DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR4) - 1 },
{ DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR5), DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR5) - 1 },
{ DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR6), DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR6) - 1 },
{ DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR7), DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR7) - 1 },
{ DP_FEC_STATUS_PHY_REPEATER(DP_PHY_LTTPR8), DP_LTTPR_MAX_ADD },
/* all remaining DPCD addresses */
{ DP_LTTPR_MAX_ADD + 1, DP_DPCD_MAX_ADD } };
static inline bool do_addresses_intersect_with_range(
const struct dpcd_address_range *range,
const uint32_t start_address,
const uint32_t end_address)
{
return start_address <= range->end && end_address >= range->start;
}
static uint32_t dpcd_get_next_partition_size(const uint32_t address, const uint32_t size)
{
const uint32_t end_address = END_ADDRESS(address, size);
uint32_t partition_iterator = 0;
/*
* find current partition
* this loop spins forever if partition map above is not surjective
*/
while (!do_addresses_intersect_with_range(&mandatory_dpcd_partitions[partition_iterator],
address, end_address))
partition_iterator++;
if (end_address < mandatory_dpcd_partitions[partition_iterator].end)
return size;
return ADDRESS_RANGE_SIZE(address, mandatory_dpcd_partitions[partition_iterator].end);
}
/*
* Ranges of DPCD addresses that must be read in a single transaction
* XXX: Do not allow any two address ranges in this array to overlap
*/
static const struct dpcd_address_range mandatory_dpcd_blocks[] = {
{ DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV, DP_PHY_REPEATER_128B132B_RATES }};
/*
* extend addresses to read all mandatory blocks together
*/
static void dpcd_extend_address_range(
const uint32_t in_address,
uint8_t * const in_data,
const uint32_t in_size,
uint32_t *out_address,
uint8_t **out_data,
uint32_t *out_size)
{
const uint32_t end_address = END_ADDRESS(in_address, in_size);
const struct dpcd_address_range *addr_range;
struct dpcd_address_range new_addr_range;
uint32_t i;
new_addr_range.start = in_address;
new_addr_range.end = end_address;
for (i = 0; i < ARRAY_SIZE(mandatory_dpcd_blocks); i++) {
addr_range = &mandatory_dpcd_blocks[i];
if (addr_range->start <= in_address && addr_range->end >= in_address)
new_addr_range.start = addr_range->start;
if (addr_range->start <= end_address && addr_range->end >= end_address)
new_addr_range.end = addr_range->end;
}
*out_address = in_address;
*out_size = in_size;
*out_data = in_data;
if (new_addr_range.start != in_address || new_addr_range.end != end_address) {
*out_address = new_addr_range.start;
*out_size = ADDRESS_RANGE_SIZE(new_addr_range.start, new_addr_range.end);
*out_data = kcalloc(*out_size, sizeof(**out_data), GFP_KERNEL);
ASSERT(*out_data);
}
}
/*
* Reduce the AUX reply down to the values the caller requested
*/
static void dpcd_reduce_address_range(
const uint32_t extended_address,
uint8_t * const extended_data,
const uint32_t extended_size,
const uint32_t reduced_address,
uint8_t * const reduced_data,
const uint32_t reduced_size)
{
(void)extended_size;
const uint32_t offset = reduced_address - extended_address;
/*
* If the address is same, address was not extended.
* So we do not need to free any memory.
* The data is in original buffer(reduced_data).
*/
if (extended_data == reduced_data)
return;
memcpy(&extended_data[offset], reduced_data, reduced_size);
kfree(extended_data);
}
enum dc_status core_link_read_dpcd(
struct dc_link *link,
uint32_t address,
uint8_t *data,
uint32_t size)
{
uint32_t extended_address;
uint32_t partitioned_address;
uint8_t *extended_data;
uint32_t extended_size;
/* size of the remaining partitioned address space */
uint32_t size_left_to_read;
enum dc_status status = DC_ERROR_UNEXPECTED;
/* size of the next partition to be read from */
uint32_t partition_size;
uint32_t data_index = 0;
dpcd_extend_address_range(address, data, size, &extended_address, &extended_data, &extended_size);
partitioned_address = extended_address;
size_left_to_read = extended_size;
while (size_left_to_read) {
partition_size = dpcd_get_next_partition_size(partitioned_address, size_left_to_read);
status = internal_link_read_dpcd(link, partitioned_address, &extended_data[data_index], partition_size);
if (status != DC_OK)
break;
partitioned_address += partition_size;
data_index += partition_size;
size_left_to_read -= partition_size;
}
dpcd_reduce_address_range(extended_address, extended_data, extended_size, address, data, size);
return status;
}
enum dc_status core_link_write_dpcd(
struct dc_link *link,
uint32_t address,
const uint8_t *data,
uint32_t size)
{
uint32_t partition_size;
uint32_t data_index = 0;
enum dc_status status = DC_ERROR_UNEXPECTED;
while (size) {
partition_size = dpcd_get_next_partition_size(address, size);
status = internal_link_write_dpcd(link, address, &data[data_index], partition_size);
if (status != DC_OK)
break;
address += partition_size;
data_index += partition_size;
size -= partition_size;
}
return status;
}
@@ -0,0 +1,42 @@
/*
* Copyright 2021 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
*
*/
#ifndef __LINK_DPCD_H__
#define __LINK_DPCD_H__
#include "link_service.h"
#include "dpcd_defs.h"
enum dc_status core_link_read_dpcd(
struct dc_link *link,
uint32_t address,
uint8_t *data,
uint32_t size);
enum dc_status core_link_write_dpcd(
struct dc_link *link,
uint32_t address,
const uint8_t *data,
uint32_t size);
#endif
@@ -0,0 +1,80 @@
/*
* 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
*
*/
#ifndef __DC_LINK_EDP_PANEL_CONTROL_H__
#define __DC_LINK_EDP_PANEL_CONTROL_H__
#include "link_service.h"
enum dp_panel_mode dp_get_panel_mode(struct dc_link *link);
void dp_set_panel_mode(struct dc_link *link, enum dp_panel_mode panel_mode);
bool set_default_brightness_aux(struct dc_link *link);
bool is_smartmux_suported(struct dc_link *link);
void edp_panel_backlight_power_on(struct dc_link *link, bool wait_for_hpd);
int edp_get_backlight_level(const struct dc_link *link);
bool edp_get_backlight_level_nits(struct dc_link *link,
uint32_t *backlight_millinits_avg,
uint32_t *backlight_millinits_peak);
bool edp_set_backlight_level(const struct dc_link *link,
struct set_backlight_level_params *backlight_level_params);
bool edp_set_backlight_level_nits(struct dc_link *link,
bool isHDR,
uint32_t backlight_millinits,
uint32_t transition_time_in_ms);
int edp_get_target_backlight_pwm(const struct dc_link *link);
bool edp_get_psr_state(const struct dc_link *link, enum dc_psr_state *state);
bool edp_set_psr_allow_active(struct dc_link *link, const bool *allow_active,
bool wait, bool force_static, const unsigned int *power_opts);
bool edp_setup_psr(struct dc_link *link,
const struct dc_stream_state *stream, struct psr_config *psr_config,
struct psr_context *psr_context);
bool edp_set_sink_vtotal_in_psr_active(const struct dc_link *link,
uint16_t psr_vtotal_idle, uint16_t psr_vtotal_su);
void edp_get_psr_residency(const struct dc_link *link, uint32_t *residency, enum psr_residency_mode mode);
bool edp_set_replay_allow_active(struct dc_link *dc_link, const bool *enable,
bool wait, bool force_static, const unsigned int *power_opts);
bool edp_send_replay_cmd(struct dc_link *link,
enum replay_FW_Message_type msg,
union dmub_replay_cmd_set *cmd_data);
bool edp_set_coasting_vtotal(struct dc_link *link, uint32_t coasting_vtotal, uint16_t frame_skip_number);
bool edp_replay_residency(const struct dc_link *link,
unsigned int *residency, const bool is_start, const enum pr_residency_mode mode);
bool edp_get_replay_state(const struct dc_link *link, uint64_t *state);
bool edp_set_replay_power_opt_and_coasting_vtotal(struct dc_link *link,
const unsigned int *power_opts, uint32_t coasting_vtotal, uint16_t frame_skip_number);
bool edp_wait_for_t12(struct dc_link *link);
bool edp_is_ilr_optimization_required(struct dc_link *link,
struct dc_crtc_timing *crtc_timing);
bool edp_is_ilr_optimization_enabled(struct dc_link *link);
enum dc_link_rate get_max_edp_link_rate(struct dc_link *link);
bool edp_backlight_enable_aux(struct dc_link *link, bool enable);
void edp_add_delay_for_T9(struct dc_link *link);
bool edp_receiver_ready_T9(struct dc_link *link);
bool edp_receiver_ready_T7(struct dc_link *link);
bool edp_power_alpm_dpcd_enable(struct dc_link *link, bool enable);
bool edp_setup_freesync_replay(struct dc_link *link, const struct dc_stream_state *stream);
void edp_set_panel_power(struct dc_link *link, bool powerOn);
void edp_set_panel_assr(struct dc_link *link, struct pipe_ctx *pipe_ctx,
enum dp_panel_mode *panel_mode, bool enable);
#endif /* __DC_LINK_EDP_POWER_CONTROL_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,53 @@
/*
* Copyright 2023 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
*
*/
#ifndef __LINK_HDMI_FRL_H__
#define __LINK_HDMI_FRL_H__
#include "link_service.h"
enum clock_source_id hdmi_frl_find_matching_phypll(
struct dc_link *link);
void hdmi_frl_LTS_clear_Update_flag(struct ddc_service *ddc_service);
void hdmi_frl_poll_start(struct ddc_service *ddc_service);
void hdmi_frl_LTS_clear_Link_Setting(struct ddc_service *ddc_service);
void hdmi_frl_retrieve_link_cap(struct dc_link *link, struct dc_sink *sink);
enum link_result hdmi_frl_perform_link_training_with_retries(
struct dc_link *link);
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);
void hdmi_frl_verify_link_cap(struct dc_link *link,
struct dc_hdmi_frl_link_settings *known_limit_link_setting);
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_paddding_params);
bool hdmi_frl_poll_status_flag(struct dc_link *link);
struct dc_hdmi_frl_link_settings *hdmi_frl_get_verified_link_cap(
struct dc_link *link);
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);
void hdmi_frl_write_read_request_enable(
struct ddc_service *ddc_service);
#endif /* __LINK_HDMI_FRL_H__ */
@@ -0,0 +1,177 @@
/*
* 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 functions that manage basic HPD components such as gpio.
* It also provides wrapper functions to execute HPD related programming. This
* file only manages basic HPD functionality. It doesn't manage detection or
* feature or signal specific HPD behaviors.
*/
#include "link_hpd.h"
#include "gpio_service_interface.h"
bool link_get_hpd_state(struct dc_link *link)
{
if (link->link_enc)
return link->link_enc->funcs->get_hpd_state(link->link_enc);
else
return false;
}
void link_enable_hpd(const struct dc_link *link)
{
if (link->link_enc)
link->link_enc->funcs->enable_hpd(link->link_enc);
}
void link_disable_hpd(const struct dc_link *link)
{
if (link->link_enc)
link->link_enc->funcs->disable_hpd(link->link_enc);
}
void link_enable_hpd_filter(struct dc_link *link, bool enable)
{
if (enable) {
link->is_hpd_filter_disabled = false;
program_hpd_filter(link);
} else {
link->is_hpd_filter_disabled = true;
if (link->link_enc)
link->link_enc->funcs->program_hpd_filter(link->link_enc, 0, 0);
}
}
bool program_hpd_filter(const struct dc_link *link)
{
int delay_on_connect_in_ms = 0;
int delay_on_disconnect_in_ms = 0;
if (link->is_hpd_filter_disabled || !link->link_enc) {
ASSERT(link->link_enc);
return false;
}
/* Verify feature is supported */
switch (link->connector_signal) {
case SIGNAL_TYPE_DVI_SINGLE_LINK:
case SIGNAL_TYPE_DVI_DUAL_LINK:
case SIGNAL_TYPE_HDMI_TYPE_A:
case SIGNAL_TYPE_HDMI_FRL:
/* Program hpd filter */
delay_on_connect_in_ms = 500;
delay_on_disconnect_in_ms = 100;
break;
case SIGNAL_TYPE_DISPLAY_PORT:
case SIGNAL_TYPE_DISPLAY_PORT_MST:
/* Program hpd filter to allow DP signal to settle */
/* 500: not able to detect MST <-> SST switch as HPD is low for
* only 100ms on DELL U2413
* 0: some passive dongle still show aux mode instead of i2c
* 20-50: not enough to hide bouncing HPD with passive dongle.
* also see intermittent i2c read issues.
*/
delay_on_connect_in_ms = 80;
delay_on_disconnect_in_ms = 0;
break;
case SIGNAL_TYPE_LVDS:
case SIGNAL_TYPE_EDP:
default:
/* Don't program hpd filter */
return false;
}
return link->link_enc->funcs->program_hpd_filter(link->link_enc, delay_on_connect_in_ms, delay_on_disconnect_in_ms);
}
struct gpio *link_get_hpd_gpio(struct dc_bios *dcb,
struct graphics_object_id link_id,
struct gpio_service *gpio_service)
{
enum bp_result bp_result;
struct graphics_object_hpd_info hpd_info;
struct gpio_pin_info pin_info;
if (dcb->funcs->get_hpd_info(dcb, link_id, &hpd_info) != BP_RESULT_OK)
return NULL;
bp_result = dcb->funcs->get_gpio_pin_info(dcb,
hpd_info.hpd_int_gpio_uid, &pin_info);
if (bp_result != BP_RESULT_OK) {
return NULL;
}
return dal_gpio_service_create_irq(gpio_service,
pin_info.offset,
pin_info.mask);
}
enum hpd_source_id get_hpd_line(struct dc_link *link)
{
struct gpio *hpd;
enum hpd_source_id hpd_id;
hpd_id = HPD_SOURCEID_UNKNOWN;
/* Use GPIO path where supported, otherwise use hardware encoder path */
if (link->ctx && link->ctx->dce_version <= DCN_VERSION_4_01) {
hpd = link_get_hpd_gpio(link->ctx->dc_bios, link->link_id,
link->ctx->gpio_service);
} else {
hpd = NULL;
}
if (hpd) {
switch (dal_irq_get_source(hpd)) {
case DC_IRQ_SOURCE_HPD1:
hpd_id = HPD_SOURCEID1;
break;
case DC_IRQ_SOURCE_HPD2:
hpd_id = HPD_SOURCEID2;
break;
case DC_IRQ_SOURCE_HPD3:
hpd_id = HPD_SOURCEID3;
break;
case DC_IRQ_SOURCE_HPD4:
hpd_id = HPD_SOURCEID4;
break;
case DC_IRQ_SOURCE_HPD5:
hpd_id = HPD_SOURCEID5;
break;
case DC_IRQ_SOURCE_HPD6:
hpd_id = HPD_SOURCEID6;
break;
default:
BREAK_TO_DEBUGGER();
break;
}
dal_gpio_destroy_irq(&hpd);
}
return hpd_id;
}
@@ -0,0 +1,53 @@
/*
* 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
*
*/
#ifndef __DC_LINK_HPD_H__
#define __DC_LINK_HPD_H__
#include "link_service.h"
enum hpd_source_id get_hpd_line(struct dc_link *link);
/*
* Function: program_hpd_filter
*
* @brief
* Programs HPD filter on associated HPD line to default values.
*
* @return
* true on success, false otherwise
*/
bool program_hpd_filter(const struct dc_link *link);
/* Query hot plug status of USB4 DP tunnel.
* Returns true if HPD high.
*/
bool dpia_query_hpd_status(struct dc_link *link);
bool link_get_hpd_state(struct dc_link *link);
struct gpio *link_get_hpd_gpio(struct dc_bios *dcb,
struct graphics_object_id link_id,
struct gpio_service *gpio_service);
void link_enable_hpd(const struct dc_link *link);
void link_disable_hpd(const struct dc_link *link);
void link_enable_hpd_filter(struct dc_link *link, bool enable);
#endif /* __DC_LINK_HPD_H__ */