Contributors: 18
Author Tokens Token Proportion Commits Commit Proportion
Wenjing Liu 1982 81.83% 9 25.71%
Harry Wentland 197 8.13% 3 8.57%
Jimmy Kizito 90 3.72% 4 11.43%
Chiawen Huang 40 1.65% 1 2.86%
Anthony Koo 18 0.74% 3 8.57%
Aurabindo Pillai 14 0.58% 1 2.86%
Alex Deucher 14 0.58% 2 5.71%
David Galiffi 12 0.50% 1 2.86%
Meenakshikumar Somasundaram 9 0.37% 1 2.86%
George Shen 8 0.33% 1 2.86%
Martin Leung 8 0.33% 1 2.86%
Bhawanpreet Lakha 7 0.29% 2 5.71%
Qingqing Zhuo 6 0.25% 1 2.86%
Colin Ian King 6 0.25% 1 2.86%
Jerry (Fangzhi) Zuo 5 0.21% 1 2.86%
Wyatt Wood 3 0.12% 1 2.86%
Yongqiang Sun 2 0.08% 1 2.86%
Julian Parkin 1 0.04% 1 2.86%
Total 2422 35


/*
 * 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
 *
 */

/* FILE POLICY AND INTENDED USAGE:
 * This file owns the creation/destruction of link structure.
 */
#include "link_factory.h"
#include "protocols/link_ddc.h"
#include "protocols/link_edp_panel_control.h"
#include "protocols/link_hpd.h"
#include "gpio_service_interface.h"
#include "atomfirmware.h"

#define DC_LOGGER_INIT(logger)

#define LINK_INFO(...) \
	DC_LOG_HW_HOTPLUG(  \
		__VA_ARGS__)

static enum transmitter translate_encoder_to_transmitter(struct graphics_object_id encoder)
{
	switch (encoder.id) {
	case ENCODER_ID_INTERNAL_UNIPHY:
		switch (encoder.enum_id) {
		case ENUM_ID_1:
			return TRANSMITTER_UNIPHY_A;
		case ENUM_ID_2:
			return TRANSMITTER_UNIPHY_B;
		default:
			return TRANSMITTER_UNKNOWN;
		}
	break;
	case ENCODER_ID_INTERNAL_UNIPHY1:
		switch (encoder.enum_id) {
		case ENUM_ID_1:
			return TRANSMITTER_UNIPHY_C;
		case ENUM_ID_2:
			return TRANSMITTER_UNIPHY_D;
		default:
			return TRANSMITTER_UNKNOWN;
		}
	break;
	case ENCODER_ID_INTERNAL_UNIPHY2:
		switch (encoder.enum_id) {
		case ENUM_ID_1:
			return TRANSMITTER_UNIPHY_E;
		case ENUM_ID_2:
			return TRANSMITTER_UNIPHY_F;
		default:
			return TRANSMITTER_UNKNOWN;
		}
	break;
	case ENCODER_ID_INTERNAL_UNIPHY3:
		switch (encoder.enum_id) {
		case ENUM_ID_1:
			return TRANSMITTER_UNIPHY_G;
		default:
			return TRANSMITTER_UNKNOWN;
		}
	break;
	case ENCODER_ID_EXTERNAL_NUTMEG:
		switch (encoder.enum_id) {
		case ENUM_ID_1:
			return TRANSMITTER_NUTMEG_CRT;
		default:
			return TRANSMITTER_UNKNOWN;
		}
	break;
	case ENCODER_ID_EXTERNAL_TRAVIS:
		switch (encoder.enum_id) {
		case ENUM_ID_1:
			return TRANSMITTER_TRAVIS_CRT;
		case ENUM_ID_2:
			return TRANSMITTER_TRAVIS_LCD;
		default:
			return TRANSMITTER_UNKNOWN;
		}
	break;
	default:
		return TRANSMITTER_UNKNOWN;
	}
}

static void link_destruct(struct dc_link *link)
{
	int i;

	if (link->hpd_gpio) {
		dal_gpio_destroy_irq(&link->hpd_gpio);
		link->hpd_gpio = NULL;
	}

	if (link->ddc)
		link_destroy_ddc_service(&link->ddc);

	if (link->panel_cntl)
		link->panel_cntl->funcs->destroy(&link->panel_cntl);

	if (link->link_enc) {
		/* Update link encoder resource tracking variables. These are used for
		 * the dynamic assignment of link encoders to streams. Virtual links
		 * are not assigned encoder resources on creation.
		 */
		if (link->link_id.id != CONNECTOR_ID_VIRTUAL) {
			link->dc->res_pool->link_encoders[link->eng_id - ENGINE_ID_DIGA] = NULL;
			link->dc->res_pool->dig_link_enc_count--;
		}
		link->link_enc->funcs->destroy(&link->link_enc);
	}

	if (link->local_sink)
		dc_sink_release(link->local_sink);

	for (i = 0; i < link->sink_count; ++i)
		dc_sink_release(link->remote_sinks[i]);
}

static enum channel_id get_ddc_line(struct dc_link *link)
{
	struct ddc *ddc;
	enum channel_id channel;

	channel = CHANNEL_ID_UNKNOWN;

	ddc = get_ddc_pin(link->ddc);

	if (ddc) {
		switch (dal_ddc_get_line(ddc)) {
		case GPIO_DDC_LINE_DDC1:
			channel = CHANNEL_ID_DDC1;
			break;
		case GPIO_DDC_LINE_DDC2:
			channel = CHANNEL_ID_DDC2;
			break;
		case GPIO_DDC_LINE_DDC3:
			channel = CHANNEL_ID_DDC3;
			break;
		case GPIO_DDC_LINE_DDC4:
			channel = CHANNEL_ID_DDC4;
			break;
		case GPIO_DDC_LINE_DDC5:
			channel = CHANNEL_ID_DDC5;
			break;
		case GPIO_DDC_LINE_DDC6:
			channel = CHANNEL_ID_DDC6;
			break;
		case GPIO_DDC_LINE_DDC_VGA:
			channel = CHANNEL_ID_DDC_VGA;
			break;
		case GPIO_DDC_LINE_I2C_PAD:
			channel = CHANNEL_ID_I2C_PAD;
			break;
		default:
			BREAK_TO_DEBUGGER();
			break;
		}
	}

	return channel;
}

static bool dc_link_construct_phy(struct dc_link *link,
			      const struct link_init_data *init_params)
{
	uint8_t i;
	struct ddc_service_init_data ddc_service_init_data = { 0 };
	struct dc_context *dc_ctx = init_params->ctx;
	struct encoder_init_data enc_init_data = { 0 };
	struct panel_cntl_init_data panel_cntl_init_data = { 0 };
	struct integrated_info info = { 0 };
	struct dc_bios *bios = init_params->dc->ctx->dc_bios;
	const struct dc_vbios_funcs *bp_funcs = bios->funcs;
	struct bp_disp_connector_caps_info disp_connect_caps_info = { 0 };

	DC_LOGGER_INIT(dc_ctx->logger);

	link->irq_source_hpd = DC_IRQ_SOURCE_INVALID;
	link->irq_source_hpd_rx = DC_IRQ_SOURCE_INVALID;
	link->link_status.dpcd_caps = &link->dpcd_caps;

	link->dc = init_params->dc;
	link->ctx = dc_ctx;
	link->link_index = init_params->link_index;

	memset(&link->preferred_training_settings, 0,
	       sizeof(struct dc_link_training_overrides));
	memset(&link->preferred_link_setting, 0,
	       sizeof(struct dc_link_settings));

	link->link_id =
		bios->funcs->get_connector_id(bios, init_params->connector_index);

	link->ep_type = DISPLAY_ENDPOINT_PHY;

	DC_LOG_DC("BIOS object table - link_id: %d", link->link_id.id);

	if (bios->funcs->get_disp_connector_caps_info) {
		bios->funcs->get_disp_connector_caps_info(bios, link->link_id, &disp_connect_caps_info);
		link->is_internal_display = disp_connect_caps_info.INTERNAL_DISPLAY;
		DC_LOG_DC("BIOS object table - is_internal_display: %d", link->is_internal_display);
	}

	if (link->link_id.type != OBJECT_TYPE_CONNECTOR) {
		dm_output_to_console("%s: Invalid Connector ObjectID from Adapter Service for connector index:%d! type %d expected %d\n",
				     __func__, init_params->connector_index,
				     link->link_id.type, OBJECT_TYPE_CONNECTOR);
		goto create_fail;
	}

	if (link->dc->res_pool->funcs->link_init)
		link->dc->res_pool->funcs->link_init(link);

	link->hpd_gpio = link_get_hpd_gpio(link->ctx->dc_bios, link->link_id,
				      link->ctx->gpio_service);

	if (link->hpd_gpio) {
		dal_gpio_open(link->hpd_gpio, GPIO_MODE_INTERRUPT);
		dal_gpio_unlock_pin(link->hpd_gpio);
		link->irq_source_hpd = dal_irq_get_source(link->hpd_gpio);

		DC_LOG_DC("BIOS object table - hpd_gpio id: %d", link->hpd_gpio->id);
		DC_LOG_DC("BIOS object table - hpd_gpio en: %d", link->hpd_gpio->en);
	}

	switch (link->link_id.id) {
	case CONNECTOR_ID_HDMI_TYPE_A:
		link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A;

		break;
	case CONNECTOR_ID_SINGLE_LINK_DVID:
	case CONNECTOR_ID_SINGLE_LINK_DVII:
		link->connector_signal = SIGNAL_TYPE_DVI_SINGLE_LINK;
		break;
	case CONNECTOR_ID_DUAL_LINK_DVID:
	case CONNECTOR_ID_DUAL_LINK_DVII:
		link->connector_signal = SIGNAL_TYPE_DVI_DUAL_LINK;
		break;
	case CONNECTOR_ID_DISPLAY_PORT:
	case CONNECTOR_ID_USBC:
		link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;

		if (link->hpd_gpio)
			link->irq_source_hpd_rx =
					dal_irq_get_rx_source(link->hpd_gpio);

		break;
	case CONNECTOR_ID_EDP:
		link->connector_signal = SIGNAL_TYPE_EDP;

		if (link->hpd_gpio) {
			if (!link->dc->config.allow_edp_hotplug_detection)
				link->irq_source_hpd = DC_IRQ_SOURCE_INVALID;

			switch (link->dc->config.allow_edp_hotplug_detection) {
			case 1: // only the 1st eDP handles hotplug
				if (link->link_index == 0)
					link->irq_source_hpd_rx =
						dal_irq_get_rx_source(link->hpd_gpio);
				else
					link->irq_source_hpd = DC_IRQ_SOURCE_INVALID;
				break;
			case 2: // only the 2nd eDP handles hotplug
				if (link->link_index == 1)
					link->irq_source_hpd_rx =
						dal_irq_get_rx_source(link->hpd_gpio);
				else
					link->irq_source_hpd = DC_IRQ_SOURCE_INVALID;
				break;
			default:
				break;
			}
		}

		break;
	case CONNECTOR_ID_LVDS:
		link->connector_signal = SIGNAL_TYPE_LVDS;
		break;
	default:
		DC_LOG_WARNING("Unsupported Connector type:%d!\n",
			       link->link_id.id);
		goto create_fail;
	}

	/* TODO: #DAL3 Implement id to str function.*/
	LINK_INFO("Connector[%d] description:"
		  "signal %d\n",
		  init_params->connector_index,
		  link->connector_signal);

	ddc_service_init_data.ctx = link->ctx;
	ddc_service_init_data.id = link->link_id;
	ddc_service_init_data.link = link;
	link->ddc = link_create_ddc_service(&ddc_service_init_data);

	if (!link->ddc) {
		DC_ERROR("Failed to create ddc_service!\n");
		goto ddc_create_fail;
	}

	if (!link->ddc->ddc_pin) {
		DC_ERROR("Failed to get I2C info for connector!\n");
		goto ddc_create_fail;
	}

	link->ddc_hw_inst =
		dal_ddc_get_line(get_ddc_pin(link->ddc));


	if (link->dc->res_pool->funcs->panel_cntl_create &&
		(link->link_id.id == CONNECTOR_ID_EDP ||
			link->link_id.id == CONNECTOR_ID_LVDS)) {
		panel_cntl_init_data.ctx = dc_ctx;
		panel_cntl_init_data.inst =
			panel_cntl_init_data.ctx->dc_edp_id_count;
		link->panel_cntl =
			link->dc->res_pool->funcs->panel_cntl_create(
								&panel_cntl_init_data);
		panel_cntl_init_data.ctx->dc_edp_id_count++;

		if (link->panel_cntl == NULL) {
			DC_ERROR("Failed to create link panel_cntl!\n");
			goto panel_cntl_create_fail;
		}
	}

	enc_init_data.ctx = dc_ctx;
	bp_funcs->get_src_obj(dc_ctx->dc_bios, link->link_id, 0,
			      &enc_init_data.encoder);
	enc_init_data.connector = link->link_id;
	enc_init_data.channel = get_ddc_line(link);
	enc_init_data.hpd_source = get_hpd_line(link);

	link->hpd_src = enc_init_data.hpd_source;

	enc_init_data.transmitter =
		translate_encoder_to_transmitter(enc_init_data.encoder);
	link->link_enc =
		link->dc->res_pool->funcs->link_enc_create(dc_ctx, &enc_init_data);

	DC_LOG_DC("BIOS object table - DP_IS_USB_C: %d", link->link_enc->features.flags.bits.DP_IS_USB_C);
	DC_LOG_DC("BIOS object table - IS_DP2_CAPABLE: %d", link->link_enc->features.flags.bits.IS_DP2_CAPABLE);

	if (!link->link_enc) {
		DC_ERROR("Failed to create link encoder!\n");
		goto link_enc_create_fail;
	}

	/* Update link encoder tracking variables. These are used for the dynamic
	 * assignment of link encoders to streams.
	 */
	link->eng_id = link->link_enc->preferred_engine;
	link->dc->res_pool->link_encoders[link->eng_id - ENGINE_ID_DIGA] = link->link_enc;
	link->dc->res_pool->dig_link_enc_count++;

	link->link_enc_hw_inst = link->link_enc->transmitter;
	for (i = 0; i < 4; i++) {
		if (bp_funcs->get_device_tag(dc_ctx->dc_bios,
					     link->link_id, i,
					     &link->device_tag) != BP_RESULT_OK) {
			DC_ERROR("Failed to find device tag!\n");
			goto device_tag_fail;
		}

		/* Look for device tag that matches connector signal,
		 * CRT for rgb, LCD for other supported signal tyes
		 */
		if (!bp_funcs->is_device_id_supported(dc_ctx->dc_bios,
						      link->device_tag.dev_id))
			continue;
		if (link->device_tag.dev_id.device_type == DEVICE_TYPE_CRT &&
		    link->connector_signal != SIGNAL_TYPE_RGB)
			continue;
		if (link->device_tag.dev_id.device_type == DEVICE_TYPE_LCD &&
		    link->connector_signal == SIGNAL_TYPE_RGB)
			continue;

		DC_LOG_DC("BIOS object table - device_tag.acpi_device: %d", link->device_tag.acpi_device);
		DC_LOG_DC("BIOS object table - device_tag.dev_id.device_type: %d", link->device_tag.dev_id.device_type);
		DC_LOG_DC("BIOS object table - device_tag.dev_id.enum_id: %d", link->device_tag.dev_id.enum_id);
		break;
	}

	if (bios->integrated_info)
		info = *bios->integrated_info;

	/* Look for channel mapping corresponding to connector and device tag */
	for (i = 0; i < MAX_NUMBER_OF_EXT_DISPLAY_PATH; i++) {
		struct external_display_path *path =
			&info.ext_disp_conn_info.path[i];

		if (path->device_connector_id.enum_id == link->link_id.enum_id &&
		    path->device_connector_id.id == link->link_id.id &&
		    path->device_connector_id.type == link->link_id.type) {
			if (link->device_tag.acpi_device != 0 &&
			    path->device_acpi_enum == link->device_tag.acpi_device) {
				link->ddi_channel_mapping = path->channel_mapping;
				link->chip_caps = path->caps;
				DC_LOG_DC("BIOS object table - ddi_channel_mapping: 0x%04X", link->ddi_channel_mapping.raw);
				DC_LOG_DC("BIOS object table - chip_caps: %d", link->chip_caps);
			} else if (path->device_tag ==
				   link->device_tag.dev_id.raw_device_tag) {
				link->ddi_channel_mapping = path->channel_mapping;
				link->chip_caps = path->caps;
				DC_LOG_DC("BIOS object table - ddi_channel_mapping: 0x%04X", link->ddi_channel_mapping.raw);
				DC_LOG_DC("BIOS object table - chip_caps: %d", link->chip_caps);
			}

			if (link->chip_caps & EXT_DISPLAY_PATH_CAPS__DP_FIXED_VS_EN) {
				link->bios_forced_drive_settings.VOLTAGE_SWING =
						(info.ext_disp_conn_info.fixdpvoltageswing & 0x3);
				link->bios_forced_drive_settings.PRE_EMPHASIS =
						((info.ext_disp_conn_info.fixdpvoltageswing >> 2) & 0x3);
			}

			break;
		}
	}

	if (bios->funcs->get_atom_dc_golden_table)
		bios->funcs->get_atom_dc_golden_table(bios);

	/*
	 * TODO check if GPIO programmed correctly
	 *
	 * If GPIO isn't programmed correctly HPD might not rise or drain
	 * fast enough, leading to bounces.
	 */
	program_hpd_filter(link);

	link->psr_settings.psr_vtotal_control_support = false;
	link->psr_settings.psr_version = DC_PSR_VERSION_UNSUPPORTED;

	DC_LOG_DC("BIOS object table - %s finished successfully.\n", __func__);
	return true;
device_tag_fail:
	link->link_enc->funcs->destroy(&link->link_enc);
link_enc_create_fail:
	if (link->panel_cntl != NULL)
		link->panel_cntl->funcs->destroy(&link->panel_cntl);
panel_cntl_create_fail:
	link_destroy_ddc_service(&link->ddc);
ddc_create_fail:
create_fail:

	if (link->hpd_gpio) {
		dal_gpio_destroy_irq(&link->hpd_gpio);
		link->hpd_gpio = NULL;
	}

	DC_LOG_DC("BIOS object table - %s failed.\n", __func__);
	return false;
}

static bool dc_link_construct_dpia(struct dc_link *link,
			      const struct link_init_data *init_params)
{
	struct ddc_service_init_data ddc_service_init_data = { 0 };
	struct dc_context *dc_ctx = init_params->ctx;

	DC_LOGGER_INIT(dc_ctx->logger);

	/* Initialized irq source for hpd and hpd rx */
	link->irq_source_hpd = DC_IRQ_SOURCE_INVALID;
	link->irq_source_hpd_rx = DC_IRQ_SOURCE_INVALID;
	link->link_status.dpcd_caps = &link->dpcd_caps;

	link->dc = init_params->dc;
	link->ctx = dc_ctx;
	link->link_index = init_params->link_index;

	memset(&link->preferred_training_settings, 0,
	       sizeof(struct dc_link_training_overrides));
	memset(&link->preferred_link_setting, 0,
	       sizeof(struct dc_link_settings));

	/* Dummy Init for linkid */
	link->link_id.type = OBJECT_TYPE_CONNECTOR;
	link->link_id.id = CONNECTOR_ID_DISPLAY_PORT;
	link->link_id.enum_id = ENUM_ID_1 + init_params->connector_index;
	link->is_internal_display = false;
	link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
	LINK_INFO("Connector[%d] description:signal %d\n",
		  init_params->connector_index,
		  link->connector_signal);

	link->ep_type = DISPLAY_ENDPOINT_USB4_DPIA;
	link->is_dig_mapping_flexible = true;

	/* TODO: Initialize link : funcs->link_init */

	ddc_service_init_data.ctx = link->ctx;
	ddc_service_init_data.id = link->link_id;
	ddc_service_init_data.link = link;
	/* Set indicator for dpia link so that ddc wont be created */
	ddc_service_init_data.is_dpia_link = true;

	link->ddc = link_create_ddc_service(&ddc_service_init_data);
	if (!link->ddc) {
		DC_ERROR("Failed to create ddc_service!\n");
		goto ddc_create_fail;
	}

	/* Set dpia port index : 0 to number of dpia ports */
	link->ddc_hw_inst = init_params->connector_index;

	/* TODO: Create link encoder */

	link->psr_settings.psr_version = DC_PSR_VERSION_UNSUPPORTED;

	/* Some docks seem to NAK I2C writes to segment pointer with mot=0. */
	link->wa_flags.dp_mot_reset_segment = true;

	return true;

ddc_create_fail:
	return false;
}

static bool link_construct(struct dc_link *link,
			      const struct link_init_data *init_params)
{
	/* Handle dpia case */
	if (init_params->is_dpia_link == true)
		return dc_link_construct_dpia(link, init_params);
	else
		return dc_link_construct_phy(link, init_params);
}

struct dc_link *link_create(const struct link_init_data *init_params)
{
	struct dc_link *link =
			kzalloc(sizeof(*link), GFP_KERNEL);

	if (NULL == link)
		goto alloc_fail;

	if (false == link_construct(link, init_params))
		goto construct_fail;

	return link;

construct_fail:
	kfree(link);

alloc_fail:
	return NULL;
}

void link_destroy(struct dc_link **link)
{
	link_destruct(*link);
	kfree(*link);
	*link = NULL;
}