new file mode 100644
@@ -0,0 +1,28 @@
+#
+# Makefile for Display Core (dc) component.
+#
+
+DC_LIBS = adapter asic_capability audio basics bios calcs \
+gpio gpu i2caux irq virtual
+
+ifdef CONFIG_DRM_AMD_DAL_DCE11_0
+DC_LIBS += dce110
+endif
+
+ifdef CONFIG_DRM_AMD_DAL_DCE10_0
+DC_LIBS += dce100
+endif
+
+AMD_DC = $(addsuffix /Makefile, $(addprefix $(FULL_AMD_DAL_PATH)/dc/,$(DC_LIBS)))
+
+include $(AMD_DC)
+
+DISPLAY_CORE = dc.o dc_link.o dc_resource.o dc_target.o dc_sink.o dc_stream.o \
+dc_hw_sequencer.o dc_surface.o dc_link_hwss.o dc_link_dp.o dc_link_ddc.o
+
+AMD_DISPLAY_CORE = $(addprefix $(AMDDALPATH)/dc/core/,$(DISPLAY_CORE))
+
+AMD_DAL_FILES += $(AMD_DISPLAY_CORE)
+
+
+
new file mode 100644
@@ -0,0 +1,932 @@
+/*
+ * Copyright 2015 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 "dm_services.h"
+
+#include "dc.h"
+
+#include "core_status.h"
+#include "core_types.h"
+#include "hw_sequencer.h"
+
+#include "resource.h"
+
+#include "adapter_service_interface.h"
+#include "clock_source.h"
+#include "dc_bios_types.h"
+
+#include "bandwidth_calcs.h"
+#include "include/irq_service_interface.h"
+#include "transform.h"
+#include "timing_generator.h"
+#include "virtual/virtual_link_encoder.h"
+
+#include "link_hwss.h"
+#include "link_encoder.h"
+
+#include "dc_link_ddc.h"
+
+/*******************************************************************************
+ * Private structures
+ ******************************************************************************/
+
+struct dc_target_sync_report {
+ uint32_t h_count;
+ uint32_t v_count;
+};
+
+/*******************************************************************************
+ * Private functions
+ ******************************************************************************/
+static void destroy_links(struct dc *dc)
+{
+ uint32_t i;
+
+ for (i = 0; i < dc->link_count; i++) {
+ if (NULL != dc->links[i])
+ link_destroy(&dc->links[i]);
+ }
+}
+
+static bool create_links(struct dc *dc, const struct dc_init_data *init_params)
+{
+ int i;
+ int connectors_num;
+ struct dc_bios *dcb;
+
+ dc->link_count = 0;
+
+ dcb = dal_adapter_service_get_bios_parser(init_params->adapter_srv);
+
+ connectors_num = dcb->funcs->get_connectors_number(dcb);
+
+ if (connectors_num > ENUM_ID_COUNT) {
+ dm_error(
+ "DC: Number of connectors %d exceeds maximum of %d!\n",
+ connectors_num,
+ ENUM_ID_COUNT);
+ return false;
+ }
+
+ if (connectors_num == 0 && init_params->num_virtual_links == 0) {
+ dm_error("DC: Number of connectors can not be zero!\n");
+ return false;
+ }
+
+ dm_output_to_console(
+ "DC: %s: connectors_num: physical:%d, virtual:%d\n",
+ __func__,
+ connectors_num,
+ init_params->num_virtual_links);
+
+ for (i = 0; i < connectors_num; i++) {
+ struct link_init_data link_init_params = {0};
+ struct core_link *link;
+
+ link_init_params.ctx = init_params->ctx;
+ link_init_params.adapter_srv = init_params->adapter_srv;
+ link_init_params.connector_index = i;
+ link_init_params.link_index = dc->link_count;
+ link_init_params.dc = dc;
+ link = link_create(&link_init_params);
+
+ if (link) {
+ dc->links[dc->link_count] = link;
+ link->dc = dc;
+ ++dc->link_count;
+ } else {
+ dm_error("DC: failed to create link!\n");
+ }
+ }
+
+ for (i = 0; i < init_params->num_virtual_links; i++) {
+ struct core_link *link = dm_alloc(
+ dc->ctx,
+ sizeof(*link));
+ struct encoder_init_data enc_init = {0};
+
+ if (link == NULL) {
+ BREAK_TO_DEBUGGER();
+ goto failed_alloc;
+ }
+
+ link->adapter_srv = init_params->adapter_srv;
+ link->ctx = init_params->ctx;
+ link->dc = dc;
+ link->public.connector_signal = SIGNAL_TYPE_VIRTUAL;
+ link->link_id.type = OBJECT_TYPE_CONNECTOR;
+ link->link_id.id = CONNECTOR_ID_VIRTUAL;
+ link->link_id.enum_id = ENUM_ID_1;
+ link->link_enc = dm_alloc(
+ dc->ctx,
+ sizeof(*link->link_enc));
+
+ enc_init.adapter_service = init_params->adapter_srv;
+ enc_init.ctx = init_params->ctx;
+ enc_init.channel = CHANNEL_ID_UNKNOWN;
+ enc_init.hpd_source = HPD_SOURCEID_UNKNOWN;
+ enc_init.transmitter = TRANSMITTER_UNKNOWN;
+ enc_init.connector = link->link_id;
+ enc_init.encoder.type = OBJECT_TYPE_ENCODER;
+ enc_init.encoder.id = ENCODER_ID_INTERNAL_VIRTUAL;
+ enc_init.encoder.enum_id = ENUM_ID_1;
+ virtual_link_encoder_construct(link->link_enc, &enc_init);
+
+ link->public.link_index = dc->link_count;
+ dc->links[dc->link_count] = link;
+ dc->link_count++;
+ }
+
+ return true;
+
+failed_alloc:
+ return false;
+}
+
+
+static void init_hw(struct dc *dc)
+{
+ int i;
+ struct dc_bios *bp;
+ struct transform *xfm;
+
+ bp = dal_adapter_service_get_bios_parser(dc->res_pool.adapter_srv);
+ for(i = 0; i < dc->res_pool.controller_count; i++) {
+ xfm = dc->res_pool.transforms[i];
+
+ dc->hwss.enable_display_power_gating(
+ dc->ctx, i, bp,
+ PIPE_GATING_CONTROL_INIT);
+ dc->hwss.enable_display_power_gating(
+ dc->ctx, i, bp,
+ PIPE_GATING_CONTROL_DISABLE);
+
+ xfm->funcs->transform_power_up(xfm);
+ dc->hwss.enable_display_pipe_clock_gating(
+ dc->ctx,
+ true);
+ }
+
+ dc->hwss.clock_gating_power_up(dc->ctx, false);
+ bp->funcs->power_up(bp);
+ /***************************************/
+
+ for (i = 0; i < dc->link_count; i++) {
+ /****************************************/
+ /* Power up AND update implementation according to the
+ * required signal (which may be different from the
+ * default signal on connector). */
+ struct core_link *link = dc->links[i];
+ link->link_enc->funcs->hw_init(link->link_enc);
+ }
+
+ for(i = 0; i < dc->res_pool.controller_count; i++) {
+ struct timing_generator *tg = dc->res_pool.timing_generators[i];
+
+ tg->funcs->disable_vga(tg);
+
+ /* Blank controller using driver code instead of
+ * command table. */
+ tg->funcs->set_blank(tg, true);
+ }
+
+ for(i = 0; i < dc->res_pool.audio_count; i++) {
+ struct audio *audio = dc->res_pool.audios[i];
+
+ if (dal_audio_power_up(audio) != AUDIO_RESULT_OK)
+ dm_error("Failed audio power up!\n");
+ }
+
+}
+
+static struct adapter_service *create_as(
+ struct dc_init_data *dc_init_data,
+ const struct dal_init_data *init)
+{
+ struct adapter_service *as = NULL;
+ struct as_init_data init_data;
+
+ dm_memset(&init_data, 0, sizeof(init_data));
+
+ init_data.ctx = dc_init_data->ctx;
+
+ /* BIOS parser init data */
+ init_data.bp_init_data.ctx = dc_init_data->ctx;
+ init_data.bp_init_data.bios = init->asic_id.atombios_base_address;
+
+ /* HW init data */
+ init_data.hw_init_data.chip_id = init->asic_id.chip_id;
+ init_data.hw_init_data.chip_family = init->asic_id.chip_family;
+ init_data.hw_init_data.pci_revision_id = init->asic_id.pci_revision_id;
+ init_data.hw_init_data.fake_paths_num = init->asic_id.fake_paths_num;
+ init_data.hw_init_data.feature_flags = init->asic_id.feature_flags;
+ init_data.hw_init_data.hw_internal_rev = init->asic_id.hw_internal_rev;
+ init_data.hw_init_data.runtime_flags = init->asic_id.runtime_flags;
+ init_data.hw_init_data.vram_width = init->asic_id.vram_width;
+ init_data.hw_init_data.vram_type = init->asic_id.vram_type;
+
+ /* bdf is BUS,DEVICE,FUNCTION*/
+ init_data.bdf_info = init->bdf_info;
+
+ init_data.display_param = &init->display_param;
+ init_data.vbios_override = init->vbios_override;
+ init_data.dce_environment = init->dce_environment;
+
+ as = dal_adapter_service_create(&init_data);
+
+ return as;
+}
+
+static void bw_calcs_data_update_from_pplib(struct dc *dc)
+{
+ struct dc_pp_clock_levels clks = {0};
+
+ /*do system clock*/
+ dm_pp_get_clock_levels_by_type(
+ dc->ctx,
+ DC_PP_CLOCK_TYPE_ENGINE_CLK,
+ &clks);
+ /* convert all the clock fro kHz to fix point mHz */
+ dc->bw_vbios.high_sclk = bw_frc_to_fixed(
+ clks.clocks_in_khz[clks.num_levels-1], 1000);
+ dc->bw_vbios.mid_sclk = bw_frc_to_fixed(
+ clks.clocks_in_khz[clks.num_levels>>1], 1000);
+ dc->bw_vbios.low_sclk = bw_frc_to_fixed(
+ clks.clocks_in_khz[0], 1000);
+
+ /*do display clock*/
+ dm_pp_get_clock_levels_by_type(
+ dc->ctx,
+ DC_PP_CLOCK_TYPE_DISPLAY_CLK,
+ &clks);
+
+ dc->bw_vbios.high_voltage_max_dispclk = bw_frc_to_fixed(
+ clks.clocks_in_khz[clks.num_levels-1], 1000);
+ dc->bw_vbios.mid_voltage_max_dispclk = bw_frc_to_fixed(
+ clks.clocks_in_khz[clks.num_levels>>1], 1000);
+ dc->bw_vbios.low_voltage_max_dispclk = bw_frc_to_fixed(
+ clks.clocks_in_khz[0], 1000);
+
+ /*do memory clock*/
+ dm_pp_get_clock_levels_by_type(
+ dc->ctx,
+ DC_PP_CLOCK_TYPE_MEMORY_CLK,
+ &clks);
+
+ dc->bw_vbios.low_yclk = bw_frc_to_fixed(
+ clks.clocks_in_khz[0] * MEMORY_TYPE_MULTIPLIER, 1000);
+ dc->bw_vbios.mid_yclk = bw_frc_to_fixed(
+ clks.clocks_in_khz[clks.num_levels>>1] * MEMORY_TYPE_MULTIPLIER,
+ 1000);
+ dc->bw_vbios.high_yclk = bw_frc_to_fixed(
+ clks.clocks_in_khz[clks.num_levels-1] * MEMORY_TYPE_MULTIPLIER,
+ 1000);
+}
+
+static bool construct(struct dc *dc, const struct dal_init_data *init_params)
+{
+ struct dal_logger *logger;
+ /* Tempory code
+ * TODO: replace dal_init_data with dc_init_data when dal is removed
+ */
+ struct dc_init_data dc_init_data = {0};
+
+ /* Create dc context */
+ /* A temp dc context is used only to allocate the memory for actual
+ * dc context */
+ struct dc_context ctx = {0};
+ ctx.cgs_device = init_params->cgs_device;
+ ctx.dc = dc;
+
+ dc_init_data.ctx = dm_alloc(&ctx, sizeof(*dc_init_data.ctx));
+ if (!dc_init_data.ctx) {
+ dm_error("%s: failed to create ctx\n", __func__);
+ goto ctx_fail;
+ }
+ dc_init_data.ctx->driver_context = init_params->driver;
+ dc_init_data.ctx->cgs_device = init_params->cgs_device;
+ dc_init_data.num_virtual_links = init_params->num_virtual_links;
+ dc_init_data.ctx->dc = dc;
+
+ /* Create logger */
+ logger = dal_logger_create(dc_init_data.ctx);
+
+ if (!logger) {
+ /* can *not* call logger. call base driver 'print error' */
+ dm_error("%s: failed to create Logger!\n", __func__);
+ goto logger_fail;
+ }
+ dc_init_data.ctx->logger = logger;
+
+ /* Create adapter service */
+ dc_init_data.adapter_srv = create_as(&dc_init_data, init_params);
+
+ if (!dc_init_data.adapter_srv) {
+ dm_error("%s: create_as() failed!\n", __func__);
+ goto as_fail;
+ }
+
+ /* Initialize HW controlled by Adapter Service */
+ if (false == dal_adapter_service_initialize_hw_data(
+ dc_init_data.adapter_srv)) {
+ dm_error("%s: dal_adapter_service_initialize_hw_data()"\
+ " failed!\n", __func__);
+ /* Note that AS exist, so have to destroy it.*/
+ goto as_fail;
+ }
+
+ dc->ctx = dc_init_data.ctx;
+
+ dc->ctx->dce_environment = dal_adapter_service_get_dce_environment(
+ dc_init_data.adapter_srv);
+
+ /* Create hardware sequencer */
+ if (!dc_construct_hw_sequencer(dc_init_data.adapter_srv, dc))
+ goto hwss_fail;
+
+ if (!dc_construct_resource_pool(
+ dc_init_data.adapter_srv, dc, dc_init_data.num_virtual_links))
+ goto construct_resource_fail;
+
+ if (!create_links(dc, &dc_init_data))
+ goto create_links_fail;
+
+ bw_calcs_init(&dc->bw_dceip, &dc->bw_vbios);
+
+ bw_calcs_data_update_from_pplib(dc);
+
+ return true;
+
+ /**** error handling here ****/
+construct_resource_fail:
+create_links_fail:
+as_fail:
+ dal_logger_destroy(&dc_init_data.ctx->logger);
+logger_fail:
+hwss_fail:
+ dm_free(&ctx, dc_init_data.ctx);
+ctx_fail:
+ return false;
+}
+
+static void destruct(struct dc *dc)
+{
+ destroy_links(dc);
+ dc->res_pool.funcs->destruct(&dc->res_pool);
+ dal_logger_destroy(&dc->ctx->logger);
+ dm_free(dc->ctx, dc->ctx);
+}
+
+/*******************************************************************************
+ * Public functions
+ ******************************************************************************/
+
+struct dc *dc_create(const struct dal_init_data *init_params)
+ {
+ struct dc_context ctx = {
+ .driver_context = init_params->driver,
+ .cgs_device = init_params->cgs_device
+ };
+ struct dc *dc = dm_alloc(&ctx, sizeof(*dc));
+
+ if (NULL == dc)
+ goto alloc_fail;
+
+ ctx.dc = dc;
+ if (false == construct(dc, init_params))
+ goto construct_fail;
+
+ /*TODO: separate HW and SW initialization*/
+ init_hw(dc);
+
+ return dc;
+
+construct_fail:
+ dm_free(&ctx, dc);
+
+alloc_fail:
+ return NULL;
+}
+
+void dc_destroy(struct dc **dc)
+{
+ struct dc_context ctx = *(*dc)->ctx;
+ destruct(*dc);
+ dm_free(&ctx, *dc);
+ *dc = NULL;
+}
+
+bool dc_validate_resources(
+ const struct dc *dc,
+ const struct dc_validation_set set[],
+ uint8_t set_count)
+{
+ enum dc_status result = DC_ERROR_UNEXPECTED;
+ struct validate_context *context;
+
+ context = dm_alloc(dc->ctx, sizeof(struct validate_context));
+ if(context == NULL)
+ goto context_alloc_fail;
+
+ result = dc->res_pool.funcs->validate_with_context(
+ dc, set, set_count, context);
+
+ dm_free(dc->ctx, context);
+context_alloc_fail:
+
+ return (result == DC_OK);
+
+}
+
+static void program_timing_sync(
+ struct dc_context *dc_ctx,
+ struct validate_context *ctx)
+{
+ uint8_t i;
+ uint8_t j;
+ uint8_t group_size = 0;
+ uint8_t tg_count = ctx->res_ctx.pool.controller_count;
+ struct timing_generator *tg_set[3];
+
+ for (i = 0; i < tg_count; i++) {
+ if (!ctx->res_ctx.controller_ctx[i].stream)
+ continue;
+
+ tg_set[0] = ctx->res_ctx.pool.timing_generators[i];
+ group_size = 1;
+
+ /* Add tg to the set, search rest of the tg's for ones with
+ * same timing, add all tgs with same timing to the group
+ */
+ for (j = i + 1; j < tg_count; j++) {
+ if (!ctx->res_ctx.controller_ctx[j].stream)
+ continue;
+
+ if (is_same_timing(
+ &ctx->res_ctx.controller_ctx[j].stream->public
+ .timing,
+ &ctx->res_ctx.controller_ctx[i].stream->public
+ .timing)) {
+ tg_set[group_size] =
+ ctx->res_ctx.pool.timing_generators[j];
+ group_size++;
+ }
+ }
+
+ /* Right now we limit to one timing sync group so if one is
+ * found we break. A group has to be more than one tg.*/
+ if (group_size > 1)
+ break;
+ }
+
+ if(group_size > 1) {
+ dc_ctx->dc->hwss.enable_timing_synchronization(dc_ctx, group_size, tg_set);
+ }
+}
+
+static bool targets_changed(
+ struct dc *dc,
+ struct dc_target *targets[],
+ uint8_t target_count)
+{
+ uint8_t i;
+
+ if (target_count != dc->current_context.target_count)
+ return true;
+
+ for (i = 0; i < dc->current_context.target_count; i++) {
+ if (&dc->current_context.targets[i]->public != targets[i])
+ return true;
+ }
+
+ return false;
+}
+
+bool dc_commit_targets(
+ struct dc *dc,
+ struct dc_target *targets[],
+ uint8_t target_count)
+{
+ enum dc_status result = DC_ERROR_UNEXPECTED;
+ struct validate_context *context;
+ struct dc_validation_set set[4];
+ uint8_t i;
+
+ if (false == targets_changed(dc, targets, target_count))
+ return DC_OK;
+
+ dal_logger_write(dc->ctx->logger,
+ LOG_MAJOR_INTERFACE_TRACE,
+ LOG_MINOR_COMPONENT_DC,
+ "%s: %d targets\n",
+ __func__,
+ target_count);
+
+ for (i = 0; i < target_count; i++) {
+ struct dc_target *target = targets[i];
+
+ dc_target_log(target,
+ dc->ctx->logger,
+ LOG_MAJOR_INTERFACE_TRACE,
+ LOG_MINOR_COMPONENT_DC);
+
+ set[i].target = targets[i];
+ set[i].surface_count = 0;
+
+ }
+
+ context = dm_alloc(dc->ctx, sizeof(struct validate_context));
+ if (context == NULL)
+ goto context_alloc_fail;
+
+ result = dc->res_pool.funcs->validate_with_context(dc, set, target_count, context);
+ if (result != DC_OK){
+ BREAK_TO_DEBUGGER();
+ goto fail;
+ }
+
+ pplib_apply_safe_state(dc);
+
+ if (!dal_adapter_service_is_in_accelerated_mode(
+ dc->res_pool.adapter_srv)) {
+ dc->hwss.enable_accelerated_mode(dc);
+ }
+
+ for (i = 0; i < dc->current_context.target_count; i++) {
+ /*TODO: optimize this to happen only when necessary*/
+ dc_target_disable_memory_requests(
+ &dc->current_context.targets[i]->public);
+ }
+
+ if (result == DC_OK) {
+ dc->hwss.reset_hw_ctx(dc, context, target_count);
+
+ if (context->target_count > 0)
+ result = dc->hwss.apply_ctx_to_hw(dc, context);
+ }
+
+ for (i = 0; i < context->target_count; i++) {
+ struct dc_target *dc_target = &context->targets[i]->public;
+ if (context->targets[i]->status.surface_count > 0)
+ dc_target_enable_memory_requests(dc_target);
+ }
+
+ /* Release old targets */
+ for (i = 0; i < dc->current_context.target_count; i++) {
+ dc_target_release(
+ &dc->current_context.targets[i]->public);
+ dc->current_context.targets[i] = NULL;
+ }
+ /* Retain new targets*/
+ for (i = 0; i < context->target_count; i++) {
+ dc_target_retain(&context->targets[i]->public);
+ }
+
+ dc->current_context = *context;
+
+ program_timing_sync(dc->ctx, context);
+
+ pplib_apply_display_requirements(dc, context);
+
+ /* TODO: disable unused plls*/
+fail:
+ dm_free(dc->ctx, context);
+
+context_alloc_fail:
+ return (result == DC_OK);
+}
+
+uint8_t dc_get_current_target_count(const struct dc *dc)
+{
+ return dc->current_context.target_count;
+}
+
+struct dc_target *dc_get_target_at_index(const struct dc *dc, uint8_t i)
+{
+ if (i < dc->current_context.target_count)
+ return &dc->current_context.targets[i]->public;
+ return NULL;
+}
+
+const struct dc_link *dc_get_link_at_index(struct dc *dc, uint32_t link_index)
+{
+ return &dc->links[link_index]->public;
+}
+
+const struct graphics_object_id dc_get_link_id_at_index(
+ struct dc *dc, uint32_t link_index)
+{
+ return dc->links[link_index]->link_id;
+}
+
+const struct ddc_service *dc_get_ddc_at_index(
+ struct dc *dc, uint32_t link_index)
+{
+ return dc->links[link_index]->ddc;
+}
+
+const enum dc_irq_source dc_get_hpd_irq_source_at_index(
+ struct dc *dc, uint32_t link_index)
+{
+ return dc->links[link_index]->public.irq_source_hpd;
+}
+
+const struct audio **dc_get_audios(struct dc *dc)
+{
+ return (const struct audio **)dc->res_pool.audios;
+}
+
+void dc_get_caps(const struct dc *dc, struct dc_caps *caps)
+{
+ caps->max_targets = dc->res_pool.controller_count;
+ caps->max_links = dc->link_count;
+ caps->max_audios = dc->res_pool.audio_count;
+}
+
+void dc_flip_surface_addrs(struct dc* dc,
+ const struct dc_surface *const surfaces[],
+ struct dc_flip_addrs flip_addrs[],
+ uint32_t count)
+{
+ uint8_t i;
+ for (i = 0; i < count; i++) {
+ struct core_surface *surface = DC_SURFACE_TO_CORE(surfaces[i]);
+ /*
+ * TODO figure out a good way to keep track of address. Until
+ * then we'll have to awkwardly bypass the "const" surface.
+ */
+ surface->public.address = flip_addrs[i].address;
+ surface->public.flip_immediate = flip_addrs[i].flip_immediate;
+
+ dc->hwss.update_plane_address(
+ dc,
+ surface,
+ DC_TARGET_TO_CORE(surface->status.dc_target));
+ }
+}
+
+enum dc_irq_source dc_interrupt_to_irq_source(
+ struct dc *dc,
+ uint32_t src_id,
+ uint32_t ext_id)
+{
+ return dal_irq_service_to_irq_source(dc->res_pool.irqs, src_id, ext_id);
+}
+
+
+void dc_interrupt_set(const struct dc *dc, enum dc_irq_source src, bool enable)
+{
+ dal_irq_service_set(dc->res_pool.irqs, src, enable);
+}
+
+void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src)
+{
+ dal_irq_service_ack(dc->res_pool.irqs, src);
+}
+
+const struct dc_target *dc_get_target_on_irq_source(
+ const struct dc *dc,
+ enum dc_irq_source src)
+{
+ uint8_t i, j;
+ uint8_t crtc_idx;
+
+ switch (src) {
+ case DC_IRQ_SOURCE_VUPDATE1:
+ case DC_IRQ_SOURCE_VUPDATE2:
+ case DC_IRQ_SOURCE_VUPDATE3:
+ case DC_IRQ_SOURCE_VUPDATE4:
+ case DC_IRQ_SOURCE_VUPDATE5:
+ case DC_IRQ_SOURCE_VUPDATE6:
+ crtc_idx = src - DC_IRQ_SOURCE_VUPDATE1;
+ break;
+ case DC_IRQ_SOURCE_PFLIP1:
+ case DC_IRQ_SOURCE_PFLIP2:
+ case DC_IRQ_SOURCE_PFLIP3:
+ case DC_IRQ_SOURCE_PFLIP4:
+ case DC_IRQ_SOURCE_PFLIP5:
+ case DC_IRQ_SOURCE_PFLIP6:
+ case DC_IRQ_SOURCE_PFLIP_UNDERLAY0:
+ crtc_idx = src - DC_IRQ_SOURCE_PFLIP1;
+ break;
+ default:
+ dm_error("%s: invalid irq source: %d\n!" ,__func__, src);
+ return NULL;
+ }
+
+ for (i = 0; i < dc->current_context.target_count; i++) {
+ struct core_target *target = dc->current_context.targets[i];
+
+ struct dc_target *dc_target;
+
+ if (NULL == target) {
+ dm_error("%s: 'dc_target' is NULL for irq source: %d\n!",
+ __func__, src);
+ continue;
+ }
+
+ dc_target = &target->public;
+
+ for (j = 0; j < target->public.stream_count; j++) {
+ const struct core_stream *stream =
+ DC_STREAM_TO_CORE(dc_target->streams[j]);
+ const uint8_t controller_idx = stream->controller_idx;
+
+ if (controller_idx == crtc_idx)
+ return dc_target;
+ }
+ }
+
+ return NULL;
+}
+
+void dc_set_power_state(
+ struct dc *dc,
+ enum dc_acpi_cm_power_state power_state,
+ enum dc_video_power_state video_power_state)
+{
+ dc->previous_power_state = dc->current_power_state;
+ dc->current_power_state = video_power_state;
+
+ switch (power_state) {
+ case DC_ACPI_CM_POWER_STATE_D0:
+ init_hw(dc);
+ break;
+ default:
+ /* NULL means "reset/release all DC targets" */
+ dc_commit_targets(dc, NULL, 0);
+
+ dc->hwss.power_down(dc);
+ break;
+ }
+
+}
+
+void dc_resume(const struct dc *dc)
+{
+ uint32_t i;
+
+ for (i = 0; i < dc->link_count; i++)
+ core_link_resume(dc->links[i]);
+}
+
+bool dc_read_dpcd(
+ struct dc *dc,
+ uint32_t link_index,
+ uint32_t address,
+ uint8_t *data,
+ uint32_t size)
+{
+ struct core_link *link =
+ DC_LINK_TO_LINK(dc_get_link_at_index(dc, link_index));
+
+ enum ddc_result r = dal_ddc_service_read_dpcd_data(
+ link->ddc,
+ address,
+ data,
+ size);
+ return r == DDC_RESULT_SUCESSFULL;
+}
+
+bool dc_write_dpcd(
+ struct dc *dc,
+ uint32_t link_index,
+ uint32_t address,
+ const uint8_t *data,
+ uint32_t size)
+{
+ struct core_link *link =
+ DC_LINK_TO_LINK(dc_get_link_at_index(dc, link_index));
+
+ enum ddc_result r = dal_ddc_service_write_dpcd_data(
+ link->ddc,
+ address,
+ data,
+ size);
+ return r == DDC_RESULT_SUCESSFULL;
+}
+
+bool dc_link_add_remote_sink(const struct dc_link *link, struct dc_sink *sink)
+{
+ struct core_link *core_link = DC_LINK_TO_LINK(link);
+ struct dc_link *dc_link = &core_link->public;
+
+ if (dc_link->sink_count >= MAX_SINKS_PER_LINK) {
+ BREAK_TO_DEBUGGER();
+ return false;
+ }
+
+ dc_link->remote_sinks[link->sink_count] = sink;
+ dc_link->sink_count++;
+
+ return true;
+}
+
+void dc_link_set_sink(const struct dc_link *link, struct dc_sink *sink)
+{
+ struct core_link *core_link = DC_LINK_TO_LINK(link);
+ struct dc_link *dc_link = &core_link->public;
+
+ dc_link->local_sink = sink;
+
+ if (sink == NULL) {
+ dc_link->sink_count = 0;
+ dc_link->type = dc_connection_none;
+ } else {
+ dc_link->sink_count = 1;
+ dc_link->type = dc_connection_single;
+ }
+}
+
+void dc_link_remove_remote_sink(const struct dc_link *link, const struct dc_sink *sink)
+{
+ int i;
+ struct core_link *core_link = DC_LINK_TO_LINK(link);
+ struct dc_link *dc_link = &core_link->public;
+
+ if (!link->sink_count) {
+ BREAK_TO_DEBUGGER();
+ return;
+ }
+
+ for (i = 0; i < dc_link->sink_count; i++) {
+ if (dc_link->remote_sinks[i] == sink) {
+ dc_sink_release(sink);
+ dc_link->remote_sinks[i] = NULL;
+
+ /* shrink array to remove empty place */
+ while (i < dc_link->sink_count - 1) {
+ dc_link->remote_sinks[i] = dc_link->remote_sinks[i+1];
+ i++;
+ }
+
+ dc_link->sink_count--;
+ return;
+ }
+ }
+}
+
+uint8_t dc_get_dig_index(const struct dc_stream *stream)
+{
+
+ struct core_stream *core_stream = DC_STREAM_TO_CORE(stream);
+
+ switch (core_stream->stream_enc->id) {
+ case ENGINE_ID_DIGA:
+ return 0;
+ case ENGINE_ID_DIGB:
+ return 1;
+ case ENGINE_ID_DIGC:
+ return 2;
+ case ENGINE_ID_DIGD:
+ return 3;
+ case ENGINE_ID_DIGE:
+ return 4;
+ case ENGINE_ID_DIGF:
+ return 5;
+ case ENGINE_ID_DIGG:
+ return 6;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+enum gpio_ddc_line dc_get_ddc_line(
+ const struct dc_stream *stream)
+{
+
+ struct core_sink *core_sink = DC_SINK_TO_CORE(stream->sink);
+ struct ddc *ddc_line = dal_ddc_service_get_ddc_pin(
+ core_sink->link->ddc);
+
+ return dal_ddc_get_line(ddc_line);
+}
+
+enum signal_type dc_get_display_signal(
+ const struct dc_stream *stream)
+{
+ return stream->sink->sink_signal;
+}
new file mode 100644
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015 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 "dm_services.h"
+#include "core_types.h"
+
+#if defined(CONFIG_DRM_AMD_DAL_DCE10_0)
+#include "dce100/dce100_hw_sequencer.h"
+#endif
+#if defined(CONFIG_DRM_AMD_DAL_DCE11_0)
+#include "dce110/dce110_hw_sequencer.h"
+#endif
+
+bool dc_construct_hw_sequencer(
+ struct adapter_service *adapter_serv,
+ struct dc *dc)
+{
+ enum dce_version dce_ver = dal_adapter_service_get_dce_version(adapter_serv);
+
+ switch (dce_ver)
+ {
+#if defined(CONFIG_DRM_AMD_DAL_DCE10_0)
+ case DCE_VERSION_10_0:
+ return dce100_hw_sequencer_construct(dc);
+#endif
+#if defined(CONFIG_DRM_AMD_DAL_DCE11_0)
+ case DCE_VERSION_11_0:
+ return dce110_hw_sequencer_construct(dc);
+#endif
+ default:
+ break;
+ }
+
+ return false;
+}
new file mode 100644
@@ -0,0 +1,1644 @@
+/*
+ * 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
+ *
+ */
+
+#include "dm_services.h"
+#include "dm_helpers.h"
+#include "dc.h"
+#include "core_dc.h"
+#include "adapter_service_interface.h"
+#include "grph_object_id.h"
+#include "gpio_service_interface.h"
+#include "core_status.h"
+#include "dc_link_dp.h"
+#include "dc_link_ddc.h"
+#include "link_hwss.h"
+#include "stream_encoder.h"
+#include "link_encoder.h"
+#include "hw_sequencer.h"
+#include "fixed31_32.h"
+
+
+#define LINK_INFO(...) \
+ dal_logger_write(dc_ctx->logger, \
+ LOG_MAJOR_HW_TRACE, LOG_MINOR_HW_TRACE_HOTPLUG, \
+ __VA_ARGS__)
+
+
+/*******************************************************************************
+ * Private structures
+ ******************************************************************************/
+
+enum {
+ LINK_RATE_REF_FREQ_IN_MHZ = 27,
+ PEAK_FACTOR_X1000 = 1006
+};
+
+/*******************************************************************************
+ * Private functions
+ ******************************************************************************/
+static void destruct(struct core_link *link)
+{
+ if (link->ddc)
+ dal_ddc_service_destroy(&link->ddc);
+
+ if(link->link_enc)
+ link->ctx->dc->res_pool.funcs->link_enc_destroy(&link->link_enc);
+}
+
+/*
+ * Function: program_hpd_filter
+ *
+ * @brief
+ * Programs HPD filter on associated HPD line
+ *
+ * @param [in] delay_on_connect_in_ms: Connect filter timeout
+ * @param [in] delay_on_disconnect_in_ms: Disconnect filter timeout
+ *
+ * @return
+ * true on success, false otherwise
+ */
+static bool program_hpd_filter(
+ const struct core_link *link)
+{
+ bool result = false;
+
+ struct irq *hpd;
+
+ int delay_on_connect_in_ms = 0;
+ int delay_on_disconnect_in_ms = 0;
+
+ /* Verify feature is supported */
+ switch (link->public.connector_signal) {
+ case SIGNAL_TYPE_DVI_SINGLE_LINK:
+ case SIGNAL_TYPE_DVI_DUAL_LINK:
+ case SIGNAL_TYPE_HDMI_TYPE_A:
+ /* 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 */
+ delay_on_connect_in_ms = 20;
+ delay_on_disconnect_in_ms = 0;
+ break;
+ case SIGNAL_TYPE_LVDS:
+ case SIGNAL_TYPE_EDP:
+ default:
+ /* Don't program hpd filter */
+ return false;
+ }
+
+ /* Obtain HPD handle */
+ hpd = dal_adapter_service_obtain_hpd_irq(
+ link->adapter_srv, link->link_id);
+
+ if (!hpd)
+ return result;
+
+ /* Setup HPD filtering */
+ if (dal_irq_open(hpd) == GPIO_RESULT_OK) {
+ struct gpio_hpd_config config;
+
+ config.delay_on_connect = delay_on_connect_in_ms;
+ config.delay_on_disconnect = delay_on_disconnect_in_ms;
+
+ dal_irq_setup_hpd_filter(hpd, &config);
+
+ dal_irq_close(hpd);
+
+ result = true;
+ } else {
+ ASSERT_CRITICAL(false);
+ }
+
+ /* Release HPD handle */
+ dal_adapter_service_release_irq(link->adapter_srv, hpd);
+
+ return result;
+}
+
+static bool detect_sink(struct core_link *link, enum dc_connection_type *type)
+{
+ uint32_t is_hpd_high = 0;
+ struct irq *hpd_pin;
+
+ /* todo: may need to lock gpio access */
+ hpd_pin = dal_adapter_service_obtain_hpd_irq(
+ link->adapter_srv,
+ link->link_id);
+ if (hpd_pin == NULL)
+ goto hpd_gpio_failure;
+
+ dal_irq_open(hpd_pin);
+ dal_irq_get_value(hpd_pin, &is_hpd_high);
+ dal_irq_close(hpd_pin);
+ dal_adapter_service_release_irq(
+ link->adapter_srv,
+ hpd_pin);
+
+ if (is_hpd_high) {
+ *type = dc_connection_single;
+ /* TODO: need to do the actual detection */
+ } else {
+ *type = dc_connection_none;
+ }
+
+ return true;
+
+hpd_gpio_failure:
+ return false;
+}
+
+
+enum ddc_transaction_type get_ddc_transaction_type(
+ enum signal_type sink_signal)
+{
+ enum ddc_transaction_type transaction_type = DDC_TRANSACTION_TYPE_NONE;
+
+
+ switch (sink_signal) {
+ case SIGNAL_TYPE_DVI_SINGLE_LINK:
+ case SIGNAL_TYPE_DVI_DUAL_LINK:
+ case SIGNAL_TYPE_HDMI_TYPE_A:
+ case SIGNAL_TYPE_LVDS:
+ case SIGNAL_TYPE_RGB:
+ transaction_type = DDC_TRANSACTION_TYPE_I2C;
+ break;
+
+ case SIGNAL_TYPE_DISPLAY_PORT:
+ case SIGNAL_TYPE_EDP:
+ transaction_type = DDC_TRANSACTION_TYPE_I2C_OVER_AUX;
+ break;
+
+ case SIGNAL_TYPE_DISPLAY_PORT_MST:
+ /* MST does not use I2COverAux, but there is the
+ * SPECIAL use case for "immediate dwnstrm device
+ * access" (EPR#370830). */
+ transaction_type = DDC_TRANSACTION_TYPE_I2C_OVER_AUX;
+ break;
+
+ default:
+ break;
+ }
+
+
+ return transaction_type;
+}
+
+static enum signal_type get_basic_signal_type(
+ struct graphics_object_id encoder,
+ struct graphics_object_id downstream)
+{
+ if (downstream.type == OBJECT_TYPE_CONNECTOR) {
+ switch (downstream.id) {
+ case CONNECTOR_ID_SINGLE_LINK_DVII:
+ switch (encoder.id) {
+ case ENCODER_ID_INTERNAL_DAC1:
+ case ENCODER_ID_INTERNAL_KLDSCP_DAC1:
+ case ENCODER_ID_INTERNAL_DAC2:
+ case ENCODER_ID_INTERNAL_KLDSCP_DAC2:
+ return SIGNAL_TYPE_RGB;
+ default:
+ return SIGNAL_TYPE_DVI_SINGLE_LINK;
+ }
+ break;
+ case CONNECTOR_ID_DUAL_LINK_DVII:
+ {
+ switch (encoder.id) {
+ case ENCODER_ID_INTERNAL_DAC1:
+ case ENCODER_ID_INTERNAL_KLDSCP_DAC1:
+ case ENCODER_ID_INTERNAL_DAC2:
+ case ENCODER_ID_INTERNAL_KLDSCP_DAC2:
+ return SIGNAL_TYPE_RGB;
+ default:
+ return SIGNAL_TYPE_DVI_DUAL_LINK;
+ }
+ }
+ break;
+ case CONNECTOR_ID_SINGLE_LINK_DVID:
+ return SIGNAL_TYPE_DVI_SINGLE_LINK;
+ case CONNECTOR_ID_DUAL_LINK_DVID:
+ return SIGNAL_TYPE_DVI_DUAL_LINK;
+ case CONNECTOR_ID_VGA:
+ return SIGNAL_TYPE_RGB;
+ case CONNECTOR_ID_HDMI_TYPE_A:
+ return SIGNAL_TYPE_HDMI_TYPE_A;
+ case CONNECTOR_ID_LVDS:
+ return SIGNAL_TYPE_LVDS;
+ case CONNECTOR_ID_DISPLAY_PORT:
+ return SIGNAL_TYPE_DISPLAY_PORT;
+ case CONNECTOR_ID_EDP:
+ return SIGNAL_TYPE_EDP;
+ default:
+ return SIGNAL_TYPE_NONE;
+ }
+ } else if (downstream.type == OBJECT_TYPE_ENCODER) {
+ switch (downstream.id) {
+ case ENCODER_ID_EXTERNAL_NUTMEG:
+ case ENCODER_ID_EXTERNAL_TRAVIS:
+ return SIGNAL_TYPE_DISPLAY_PORT;
+ default:
+ return SIGNAL_TYPE_NONE;
+ }
+ }
+
+ return SIGNAL_TYPE_NONE;
+}
+
+/*
+ * @brief
+ * Check whether there is a dongle on DP connector
+ */
+static bool is_dp_sink_present(struct core_link *link)
+{
+ enum gpio_result gpio_result;
+ uint32_t clock_pin = 0;
+ uint32_t data_pin = 0;
+
+ struct ddc *ddc;
+
+ enum connector_id connector_id =
+ dal_graphics_object_id_get_connector_id(link->link_id);
+
+ bool present =
+ ((connector_id == CONNECTOR_ID_DISPLAY_PORT) ||
+ (connector_id == CONNECTOR_ID_EDP));
+
+ ddc = dal_adapter_service_obtain_ddc(link->adapter_srv, link->link_id);
+
+ if (!ddc)
+ return present;
+
+ /* Open GPIO and set it to I2C mode */
+ /* Note: this GpioMode_Input will be converted
+ * to GpioConfigType_I2cAuxDualMode in GPIO component,
+ * which indicates we need additional delay */
+
+ if (GPIO_RESULT_OK != dal_ddc_open(
+ ddc, GPIO_MODE_INPUT, GPIO_DDC_CONFIG_TYPE_MODE_I2C)) {
+ dal_adapter_service_release_ddc(link->adapter_srv, ddc);
+
+ return present;
+ }
+
+ /* Read GPIO: DP sink is present if both clock and data pins are zero */
+ /* [anaumov] in DAL2, there was no check for GPIO failure */
+
+ gpio_result = dal_ddc_get_clock(ddc, &clock_pin);
+ ASSERT(gpio_result == GPIO_RESULT_OK);
+
+ if (gpio_result == GPIO_RESULT_OK)
+ if (link->link_enc->features.flags.bits.
+ DP_SINK_DETECT_POLL_DATA_PIN)
+ gpio_result = dal_ddc_get_data(ddc, &data_pin);
+
+ present = (gpio_result == GPIO_RESULT_OK) && !(clock_pin || data_pin);
+
+ dal_ddc_close(ddc);
+
+ dal_adapter_service_release_ddc(link->adapter_srv, ddc);
+
+ return present;
+}
+
+/*
+ * @brief
+ * Detect output sink type
+ */
+static enum signal_type link_detect_sink(struct core_link *link)
+{
+ enum signal_type result = get_basic_signal_type(
+ link->link_enc->id, link->link_id);
+
+ /* Internal digital encoder will detect only dongles
+ * that require digital signal */
+
+ /* Detection mechanism is different
+ * for different native connectors.
+ * LVDS connector supports only LVDS signal;
+ * PCIE is a bus slot, the actual connector needs to be detected first;
+ * eDP connector supports only eDP signal;
+ * HDMI should check straps for audio */
+
+ /* PCIE detects the actual connector on add-on board */
+
+ if (link->link_id.id == CONNECTOR_ID_PCIE) {
+ /* ZAZTODO implement PCIE add-on card detection */
+ }
+
+ switch (link->link_id.id) {
+ case CONNECTOR_ID_HDMI_TYPE_A: {
+ /* check audio support:
+ * if native HDMI is not supported, switch to DVI */
+ union audio_support audio_support =
+ dal_adapter_service_get_audio_support(
+ link->adapter_srv);
+
+ if (!audio_support.bits.HDMI_AUDIO_NATIVE)
+ if (link->link_id.id == CONNECTOR_ID_HDMI_TYPE_A)
+ result = SIGNAL_TYPE_DVI_SINGLE_LINK;
+ }
+ break;
+ case CONNECTOR_ID_DISPLAY_PORT: {
+
+ /* Check whether DP signal detected: if not -
+ * we assume signal is DVI; it could be corrected
+ * to HDMI after dongle detection */
+ if (!is_dp_sink_present(link))
+ result = SIGNAL_TYPE_DVI_SINGLE_LINK;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+static enum signal_type decide_signal_from_strap_and_dongle_type(
+ enum display_dongle_type dongle_type,
+ union audio_support *audio_support)
+{
+ enum signal_type signal = SIGNAL_TYPE_NONE;
+
+ switch (dongle_type) {
+ case DISPLAY_DONGLE_DP_HDMI_DONGLE:
+ if (audio_support->bits.HDMI_AUDIO_ON_DONGLE)
+ signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ else
+ signal = SIGNAL_TYPE_DVI_SINGLE_LINK;
+ break;
+ case DISPLAY_DONGLE_DP_DVI_DONGLE:
+ signal = SIGNAL_TYPE_DVI_SINGLE_LINK;
+ break;
+ case DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE:
+ if (audio_support->bits.HDMI_AUDIO_NATIVE)
+ signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ else
+ signal = SIGNAL_TYPE_DVI_SINGLE_LINK;
+ break;
+ default:
+ signal = SIGNAL_TYPE_NONE;
+ break;
+ }
+
+ return signal;
+}
+
+static enum signal_type dp_passive_dongle_detection(
+ struct ddc_service *ddc,
+ struct display_sink_capability *sink_cap,
+ union audio_support *audio_support)
+{
+ /* TODO:These 2 functions should be protected for upstreaming purposes
+ * in case hackers want to save 10 cents hdmi license fee
+ */
+ dal_ddc_service_i2c_query_dp_dual_mode_adaptor(
+ ddc, sink_cap);
+ return decide_signal_from_strap_and_dongle_type(
+ sink_cap->dongle_type,
+ audio_support);
+}
+
+static void link_disconnect_sink(struct core_link *link)
+{
+ if (link->public.local_sink) {
+ dc_sink_release(link->public.local_sink);
+ link->public.local_sink = NULL;
+ }
+
+ link->dpcd_sink_count = 0;
+}
+
+static enum dc_edid_status read_edid(
+ struct core_link *link,
+ struct core_sink *sink)
+{
+ uint32_t edid_retry = 3;
+ enum dc_edid_status edid_status;
+
+ /* some dongles read edid incorrectly the first time,
+ * do check sum and retry to make sure read correct edid.
+ */
+ do {
+ sink->public.dc_edid.length =
+ dal_ddc_service_edid_query(link->ddc);
+
+ if (0 == sink->public.dc_edid.length)
+ return EDID_NO_RESPONSE;
+
+ dal_ddc_service_get_edid_buf(link->ddc,
+ sink->public.dc_edid.raw_edid);
+ edid_status = dm_helpers_parse_edid_caps(
+ sink->ctx,
+ &sink->public.dc_edid,
+ &sink->public.edid_caps);
+ --edid_retry;
+ if (edid_status == EDID_BAD_CHECKSUM)
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_WARNING,
+ LOG_MINOR_DETECTION_EDID_PARSER,
+ "Bad EDID checksum, retry remain: %d\n",
+ edid_retry);
+ } while (edid_status == EDID_BAD_CHECKSUM && edid_retry > 0);
+
+ return edid_status;
+}
+
+static void detect_dp(
+ struct core_link *link,
+ struct display_sink_capability *sink_caps,
+ bool *converter_disable_audio,
+ union audio_support *audio_support,
+ bool boot)
+{
+ sink_caps->signal = link_detect_sink(link);
+ sink_caps->transaction_type =
+ get_ddc_transaction_type(sink_caps->signal);
+
+ if (sink_caps->transaction_type == DDC_TRANSACTION_TYPE_I2C_OVER_AUX) {
+ sink_caps->signal = SIGNAL_TYPE_DISPLAY_PORT;
+ detect_dp_sink_caps(link);
+
+ /* DP active dongles */
+ if (is_dp_active_dongle(link)) {
+ if (!link->dpcd_caps.sink_count.bits.SINK_COUNT) {
+ link->public.type = dc_connection_none;
+ /*
+ * active dongle unplug processing for short irq
+ */
+ link_disconnect_sink(link);
+ return;
+ }
+
+ if (link->dpcd_caps.dongle_type !=
+ DISPLAY_DONGLE_DP_HDMI_CONVERTER) {
+ *converter_disable_audio = true;
+ }
+ }
+ if (is_mst_supported(link)) {
+ sink_caps->signal = SIGNAL_TYPE_DISPLAY_PORT_MST;
+
+ /*
+ * This call will initiate MST topology discovery. Which
+ * will detect MST ports and add new DRM connector DRM
+ * framework. Then read EDID via remote i2c over aux. In
+ * the end, will notify DRM detect result and save EDID
+ * into DRM framework.
+ *
+ * .detect is called by .fill_modes.
+ * .fill_modes is called by user mode ioctl
+ * DRM_IOCTL_MODE_GETCONNECTOR.
+ *
+ * .get_modes is called by .fill_modes.
+ *
+ * call .get_modes, AMDGPU DM implementation will create
+ * new dc_sink and add to dc_link. For long HPD plug
+ * in/out, MST has its own handle.
+ *
+ * Therefore, just after dc_create, link->sink is not
+ * created for MST until user mode app calls
+ * DRM_IOCTL_MODE_GETCONNECTOR.
+ *
+ * Need check ->sink usages in case ->sink = NULL
+ * TODO: s3 resume check
+ */
+
+ if (dm_helpers_dp_mst_start_top_mgr(
+ link->ctx,
+ &link->public, boot)) {
+ link->public.type = dc_connection_mst_branch;
+ } else {
+ /* MST not supported */
+ sink_caps->signal = SIGNAL_TYPE_DISPLAY_PORT;
+ }
+ }
+ } else {
+ /* DP passive dongles */
+ sink_caps->signal = dp_passive_dongle_detection(link->ddc,
+ sink_caps,
+ audio_support);
+ }
+}
+
+bool dc_link_detect(const struct dc_link *dc_link, bool boot)
+{
+ struct core_link *link = DC_LINK_TO_LINK(dc_link);
+ struct dc_sink_init_data sink_init_data = { 0 };
+ struct display_sink_capability sink_caps = { 0 };
+ uint8_t i;
+ bool converter_disable_audio = false;
+ union audio_support audio_support =
+ dal_adapter_service_get_audio_support(
+ link->adapter_srv);
+ enum dc_edid_status edid_status;
+ struct dc_context *dc_ctx = link->ctx;
+ struct dc_sink *dc_sink;
+ struct core_sink *sink = NULL;
+ enum dc_connection_type new_connection_type = dc_connection_none;
+
+ if (link->public.connector_signal == SIGNAL_TYPE_VIRTUAL)
+ return false;
+
+ if (false == detect_sink(link, &new_connection_type)) {
+ BREAK_TO_DEBUGGER();
+ return false;
+ }
+
+ link_disconnect_sink(link);
+
+ if (new_connection_type != dc_connection_none) {
+ link->public.type = new_connection_type;
+
+ /* From Disconnected-to-Connected. */
+ switch (link->public.connector_signal) {
+ case SIGNAL_TYPE_HDMI_TYPE_A: {
+ sink_caps.transaction_type = DDC_TRANSACTION_TYPE_I2C;
+ if (audio_support.bits.HDMI_AUDIO_NATIVE)
+ sink_caps.signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ else
+ sink_caps.signal = SIGNAL_TYPE_DVI_SINGLE_LINK;
+ break;
+ }
+
+ case SIGNAL_TYPE_DVI_SINGLE_LINK: {
+ sink_caps.transaction_type = DDC_TRANSACTION_TYPE_I2C;
+ sink_caps.signal = SIGNAL_TYPE_DVI_SINGLE_LINK;
+ break;
+ }
+
+ case SIGNAL_TYPE_DVI_DUAL_LINK: {
+ sink_caps.transaction_type = DDC_TRANSACTION_TYPE_I2C;
+ sink_caps.signal = SIGNAL_TYPE_DVI_DUAL_LINK;
+ break;
+ }
+
+ case SIGNAL_TYPE_EDP: {
+ detect_dp_sink_caps(link);
+ sink_caps.transaction_type =
+ DDC_TRANSACTION_TYPE_I2C_OVER_AUX;
+ sink_caps.signal = SIGNAL_TYPE_EDP;
+ break;
+ }
+
+ case SIGNAL_TYPE_DISPLAY_PORT: {
+ detect_dp(
+ link,
+ &sink_caps,
+ &converter_disable_audio,
+ &audio_support, boot);
+
+ /* Active dongle downstream unplug */
+ if (link->public.type == dc_connection_none)
+ return true;
+
+ if (link->public.type == dc_connection_mst_branch) {
+ LINK_INFO("link=%d, mst branch is now Connected\n",
+ link->public.link_index);
+ return false;
+ }
+
+ break;
+ }
+
+ default:
+ DC_ERROR("Invalid connector type! signal:%d\n",
+ link->public.connector_signal);
+ return false;
+ } /* switch() */
+
+ if (link->dpcd_caps.sink_count.bits.SINK_COUNT)
+ link->dpcd_sink_count = link->dpcd_caps.sink_count.
+ bits.SINK_COUNT;
+ else
+ link->dpcd_sink_count = 1;
+
+
+ dal_ddc_service_set_transaction_type(
+ link->ddc,
+ sink_caps.transaction_type);
+
+ sink_init_data.link = &link->public;
+ sink_init_data.sink_signal = sink_caps.signal;
+ sink_init_data.dongle_max_pix_clk =
+ sink_caps.max_hdmi_pixel_clock;
+ sink_init_data.converter_disable_audio =
+ converter_disable_audio;
+
+ dc_sink = dc_sink_create(&sink_init_data);
+ if (!dc_sink) {
+ DC_ERROR("Failed to create sink!\n");
+ return false;
+ }
+
+ sink = DC_SINK_TO_CORE(dc_sink);
+ link->public.local_sink = &sink->public;
+
+ edid_status = read_edid(link, sink);
+
+ switch (edid_status) {
+ case EDID_BAD_CHECKSUM:
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_ERROR,
+ LOG_MINOR_DETECTION_EDID_PARSER,
+ "EDID checksum invalid.\n");
+ break;
+ case EDID_NO_RESPONSE:
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_ERROR,
+ LOG_MINOR_DETECTION_EDID_PARSER,
+ "No EDID read.\n");
+ return false;
+
+ default:
+ break;
+ }
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_DETECTION,
+ LOG_MINOR_DETECTION_EDID_PARSER,
+ "%s: "
+ "manufacturer_id = %X, "
+ "product_id = %X, "
+ "serial_number = %X, "
+ "manufacture_week = %d, "
+ "manufacture_year = %d, "
+ "display_name = %s, "
+ "speaker_flag = %d, "
+ "audio_mode_count = %d\n",
+ __func__,
+ sink->public.edid_caps.manufacturer_id,
+ sink->public.edid_caps.product_id,
+ sink->public.edid_caps.serial_number,
+ sink->public.edid_caps.manufacture_week,
+ sink->public.edid_caps.manufacture_year,
+ sink->public.edid_caps.display_name,
+ sink->public.edid_caps.speaker_flags,
+ sink->public.edid_caps.audio_mode_count);
+
+ for (i = 0; i < sink->public.edid_caps.audio_mode_count; i++) {
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_DETECTION,
+ LOG_MINOR_DETECTION_EDID_PARSER,
+ "%s: mode number = %d, "
+ "format_code = %d, "
+ "channel_count = %d, "
+ "sample_rate = %d, "
+ "sample_size = %d\n",
+ __func__,
+ i,
+ sink->public.edid_caps.audio_modes[i].format_code,
+ sink->public.edid_caps.audio_modes[i].channel_count,
+ sink->public.edid_caps.audio_modes[i].sample_rate,
+ sink->public.edid_caps.audio_modes[i].sample_size);
+ }
+
+ } else {
+ /* From Connected-to-Disconnected. */
+ if (link->public.type == dc_connection_mst_branch) {
+ LINK_INFO("link=%d, mst branch is now Disconnected\n",
+ link->public.link_index);
+ dm_helpers_dp_mst_stop_top_mgr(link->ctx, &link->public);
+ }
+
+ link->public.type = dc_connection_none;
+ sink_caps.signal = SIGNAL_TYPE_NONE;
+ }
+
+ LINK_INFO("link=%d, dc_sink_in=%p is now %s\n",
+ link->public.link_index, &sink->public,
+ (sink_caps.signal == SIGNAL_TYPE_NONE ?
+ "Disconnected":"Connected"));
+
+ return true;
+}
+
+static enum hpd_source_id get_hpd_line(
+ struct core_link *link,
+ struct adapter_service *as)
+{
+ struct irq *hpd;
+ enum hpd_source_id hpd_id = HPD_SOURCEID_UNKNOWN;
+
+ hpd = dal_adapter_service_obtain_hpd_irq(as, link->link_id);
+
+ 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_adapter_service_release_irq(as, hpd);
+ }
+
+ return hpd_id;
+}
+
+static enum channel_id get_ddc_line(struct core_link *link, struct adapter_service *as)
+{
+ struct ddc *ddc;
+ enum channel_id channel = CHANNEL_ID_UNKNOWN;
+
+ ddc = dal_adapter_service_obtain_ddc(as, link->link_id);
+
+ 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;
+ }
+
+ dal_adapter_service_release_ddc(as, ddc);
+ }
+
+ return channel;
+}
+
+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 bool construct(
+ struct core_link *link,
+ const struct link_init_data *init_params)
+{
+ uint8_t i;
+ struct adapter_service *as = init_params->adapter_srv;
+ struct irq *hpd_gpio = NULL;
+ 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 integrated_info info = {{{ 0 }}};
+
+ link->dc = init_params->dc;
+ link->adapter_srv = as;
+ link->ctx = dc_ctx;
+ link->public.link_index = init_params->link_index;
+
+ link->link_id = dal_adapter_service_get_connector_obj_id(
+ as,
+ init_params->connector_index);
+
+ if (link->link_id.type != OBJECT_TYPE_CONNECTOR) {
+ dm_error("%s: Invalid Connector ObjectID from Adapter Service for connector index:%d!\n",
+ __func__, init_params->connector_index);
+ goto create_fail;
+ }
+
+ switch (link->link_id.id) {
+ case CONNECTOR_ID_HDMI_TYPE_A:
+ link->public.connector_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ break;
+ case CONNECTOR_ID_SINGLE_LINK_DVID:
+ case CONNECTOR_ID_SINGLE_LINK_DVII:
+ link->public.connector_signal = SIGNAL_TYPE_DVI_SINGLE_LINK;
+ break;
+ case CONNECTOR_ID_DUAL_LINK_DVID:
+ case CONNECTOR_ID_DUAL_LINK_DVII:
+ link->public.connector_signal = SIGNAL_TYPE_DVI_DUAL_LINK;
+ break;
+ case CONNECTOR_ID_DISPLAY_PORT:
+ link->public.connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
+ hpd_gpio = dal_adapter_service_obtain_hpd_irq(
+ as,
+ link->link_id);
+
+ if (hpd_gpio != NULL) {
+ link->public.irq_source_hpd_rx =
+ dal_irq_get_rx_source(hpd_gpio);
+ dal_adapter_service_release_irq(
+ as, hpd_gpio);
+ }
+
+ break;
+ case CONNECTOR_ID_EDP:
+ link->public.connector_signal = SIGNAL_TYPE_EDP;
+ hpd_gpio = dal_adapter_service_obtain_hpd_irq(
+ as,
+ link->link_id);
+
+ if (hpd_gpio != NULL) {
+ link->public.irq_source_hpd_rx =
+ dal_irq_get_rx_source(hpd_gpio);
+ dal_adapter_service_release_irq(
+ as, hpd_gpio);
+ }
+ break;
+ default:
+ dal_logger_write(dc_ctx->logger,
+ LOG_MAJOR_WARNING, LOG_MINOR_TM_LINK_SRV,
+ "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->public.connector_signal);
+
+ hpd_gpio = dal_adapter_service_obtain_hpd_irq(as, link->link_id);
+
+ if (hpd_gpio != NULL) {
+ link->public.irq_source_hpd = dal_irq_get_source(hpd_gpio);
+ dal_adapter_service_release_irq(as, hpd_gpio);
+ }
+
+ ddc_service_init_data.as = as;
+ ddc_service_init_data.ctx = link->ctx;
+ ddc_service_init_data.id = link->link_id;
+ link->ddc = dal_ddc_service_create(&ddc_service_init_data);
+
+ if (NULL == link->ddc) {
+ DC_ERROR("Failed to create ddc_service!\n");
+ goto create_fail;
+ }
+
+ enc_init_data.adapter_service = as;
+ enc_init_data.ctx = dc_ctx;
+ enc_init_data.encoder = dal_adapter_service_get_src_obj(
+ as, link->link_id, 0);
+ enc_init_data.connector = link->link_id;
+ enc_init_data.channel = get_ddc_line(link, as);
+ enc_init_data.hpd_source = get_hpd_line(link, as);
+ enc_init_data.transmitter =
+ translate_encoder_to_transmitter(enc_init_data.encoder);
+ link->link_enc = dc_ctx->dc->res_pool.funcs->link_enc_create(
+ &enc_init_data);
+
+ if( link->link_enc == NULL) {
+ DC_ERROR("Failed to create link encoder!\n");
+ goto create_fail;
+ }
+
+ dal_adapter_service_get_integrated_info(as, &info);
+
+ for (i = 0; ; i++) {
+ if (!dal_adapter_service_get_device_tag(
+ as, link->link_id, i, &link->device_tag)) {
+ DC_ERROR("Failed to find device tag!\n");
+ goto create_fail;
+ }
+
+ /* Look for device tag that matches connector signal,
+ * CRT for rgb, LCD for other supported signal tyes
+ */
+ if (!dal_adapter_service_is_device_id_supported(
+ as, link->device_tag.dev_id))
+ continue;
+ if (link->device_tag.dev_id.device_type == DEVICE_TYPE_CRT
+ && link->public.connector_signal != SIGNAL_TYPE_RGB)
+ continue;
+ if (link->device_tag.dev_id.device_type == DEVICE_TYPE_LCD
+ && link->public.connector_signal == SIGNAL_TYPE_RGB)
+ continue;
+ if (link->device_tag.dev_id.device_type == DEVICE_TYPE_WIRELESS
+ && link->public.connector_signal != SIGNAL_TYPE_WIRELESS)
+ continue;
+ break;
+ }
+
+ /* 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
+ && path->device_acpi_enum
+ == link->device_tag.acpi_device) {
+ link->ddi_channel_mapping = path->channel_mapping;
+ break;
+ }
+ }
+
+ /*
+ * 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);
+
+ return true;
+
+create_fail:
+ return false;
+}
+
+/*******************************************************************************
+ * Public functions
+ ******************************************************************************/
+struct core_link *link_create(const struct link_init_data *init_params)
+{
+ struct core_link *link =
+ dm_alloc(init_params->ctx, sizeof(*link));
+
+ if (NULL == link)
+ goto alloc_fail;
+
+ if (false == construct(link, init_params))
+ goto construct_fail;
+
+ return link;
+
+construct_fail:
+ dm_free(init_params->ctx, link);
+
+alloc_fail:
+ return NULL;
+}
+
+void link_destroy(struct core_link **link)
+{
+ destruct(*link);
+ dm_free((*link)->ctx, *link);
+ *link = NULL;
+}
+
+static void dpcd_configure_panel_mode(
+ struct core_link *link,
+ enum dp_panel_mode panel_mode)
+{
+ union dpcd_edp_config edp_config_set;
+ bool panel_mode_edp = false;
+
+ dm_memset(&edp_config_set, '\0', sizeof(union dpcd_edp_config));
+
+ if (DP_PANEL_MODE_DEFAULT != panel_mode) {
+
+ switch (panel_mode) {
+ case DP_PANEL_MODE_EDP:
+ case DP_PANEL_MODE_SPECIAL:
+ panel_mode_edp = true;
+ break;
+
+ default:
+ break;
+ }
+
+ /*set edp panel mode in receiver*/
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_EDP_CONFIG_SET,
+ &edp_config_set.raw,
+ sizeof(edp_config_set.raw));
+
+ if (edp_config_set.bits.PANEL_MODE_EDP
+ != panel_mode_edp) {
+ enum ddc_result result = DDC_RESULT_UNKNOWN;
+
+ edp_config_set.bits.PANEL_MODE_EDP =
+ panel_mode_edp;
+ result = core_link_write_dpcd(
+ link,
+ DPCD_ADDRESS_EDP_CONFIG_SET,
+ &edp_config_set.raw,
+ sizeof(edp_config_set.raw));
+
+ ASSERT(result == DDC_RESULT_SUCESSFULL);
+ }
+ }
+ dal_logger_write(link->ctx->logger, LOG_MAJOR_DETECTION,
+ LOG_MINOR_DETECTION_DP_CAPS,
+ "Link: %d eDP panel mode supported: %d "
+ "eDP panel mode enabled: %d \n",
+ link->public.link_index,
+ link->dpcd_caps.panel_mode_edp,
+ panel_mode_edp);
+}
+
+static enum dc_status enable_link_dp(struct core_stream *stream)
+{
+ enum dc_status status;
+ bool skip_video_pattern;
+ struct core_link *link = stream->sink->link;
+ struct link_settings link_settings = {0};
+ enum dp_panel_mode panel_mode;
+
+ /* get link settings for video mode timing */
+ decide_link_settings(stream, &link_settings);
+ dp_enable_link_phy(
+ stream->sink->link,
+ stream->signal,
+ &link_settings);
+
+ panel_mode = dp_get_panel_mode(link);
+ dpcd_configure_panel_mode(link, panel_mode);
+
+ skip_video_pattern = true;
+
+ if (link_settings.link_rate == LINK_RATE_LOW)
+ skip_video_pattern = false;
+
+ if (perform_link_training(link, &link_settings, skip_video_pattern)) {
+ link->cur_link_settings = link_settings;
+ status = DC_OK;
+ }
+ else
+ status = DC_ERROR_UNEXPECTED;
+
+ return status;
+}
+
+static enum dc_status enable_link_dp_mst(struct core_stream *stream)
+{
+ struct core_link *link = stream->sink->link;
+
+ /* sink signal type after MST branch is MST. Multiple MST sinks
+ * share one link. Link DP PHY is enable or training only once.
+ */
+ if (link->cur_link_settings.lane_count != LANE_COUNT_UNKNOWN)
+ return DC_OK;
+
+ return enable_link_dp(stream);
+}
+
+static void enable_link_hdmi(struct core_stream *stream)
+{
+ struct core_link *link = stream->sink->link;
+
+ /* enable video output */
+ /* here we need to specify that encoder output settings
+ * need to be calculated as for the set mode,
+ * it will lead to querying dynamic link capabilities
+ * which should be done before enable output */
+ uint32_t normalized_pix_clk = stream->public.timing.pix_clk_khz;
+ switch (stream->public.timing.display_color_depth) {
+ case COLOR_DEPTH_888:
+ break;
+ case COLOR_DEPTH_101010:
+ normalized_pix_clk = (normalized_pix_clk * 30) / 24;
+ break;
+ case COLOR_DEPTH_121212:
+ normalized_pix_clk = (normalized_pix_clk * 36) / 24;
+ break;
+ case COLOR_DEPTH_161616:
+ normalized_pix_clk = (normalized_pix_clk * 48) / 24;
+ break;
+ default:
+ break;
+ }
+
+ if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A)
+ dal_ddc_service_write_scdc_data(
+ stream->sink->link->ddc,
+ normalized_pix_clk,
+ stream->public.timing.flags.LTE_340MCSC_SCRAMBLE);
+
+ dm_memset(&stream->sink->link->cur_link_settings, 0,
+ sizeof(struct link_settings));
+
+ link->link_enc->funcs->enable_tmds_output(
+ link->link_enc,
+ stream->clock_source->id,
+ stream->public.timing.display_color_depth,
+ stream->signal == SIGNAL_TYPE_HDMI_TYPE_A,
+ stream->signal == SIGNAL_TYPE_DVI_DUAL_LINK,
+ stream->public.timing.pix_clk_khz);
+
+ if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A)
+ dal_ddc_service_read_scdc_data(link->ddc);
+}
+
+/****************************enable_link***********************************/
+static enum dc_status enable_link(struct core_stream *stream)
+{
+ enum dc_status status = DC_ERROR_UNEXPECTED;
+ switch (stream->signal) {
+ case SIGNAL_TYPE_DISPLAY_PORT:
+ case SIGNAL_TYPE_EDP:
+ status = enable_link_dp(stream);
+ break;
+ case SIGNAL_TYPE_DISPLAY_PORT_MST:
+ status = enable_link_dp_mst(stream);
+ dm_sleep_in_milliseconds(stream->ctx, 200);
+ break;
+ case SIGNAL_TYPE_DVI_SINGLE_LINK:
+ case SIGNAL_TYPE_DVI_DUAL_LINK:
+ case SIGNAL_TYPE_HDMI_TYPE_A:
+ enable_link_hdmi(stream);
+ status = DC_OK;
+ break;
+ case SIGNAL_TYPE_VIRTUAL:
+ status = DC_OK;
+ break;
+ default:
+ break;
+ }
+
+ if (stream->audio && status == DC_OK) {
+ /* notify audio driver for audio modes of monitor */
+ dal_audio_enable_azalia_audio_jack_presence(stream->audio,
+ stream->stream_enc->id);
+
+ /* un-mute audio */
+ dal_audio_unmute(stream->audio, stream->stream_enc->id,
+ stream->signal);
+ }
+
+ return status;
+}
+
+static void disable_link(struct core_stream *stream)
+{
+ /* TODO dp_set_hw_test_pattern */
+
+ /* here we need to specify that encoder output settings
+ * need to be calculated as for the set mode,
+ * it will lead to querying dynamic link capabilities
+ * which should be done before enable output */
+
+ if (dc_is_dp_signal(stream->signal)) {
+ /* SST DP, eDP */
+ if (dc_is_dp_sst_signal(stream->signal))
+ dp_disable_link_phy(
+ stream->sink->link, stream->signal);
+ else {
+ dp_disable_link_phy_mst(
+ stream->sink->link, stream);
+ }
+ } else {
+ struct link_encoder *encoder =
+ stream->sink->link->link_enc;
+
+ encoder->funcs->disable_output(encoder, stream->signal);
+ }
+}
+
+enum dc_status dc_link_validate_mode_timing(
+ const struct core_sink *sink,
+ struct core_link *link,
+ const struct dc_crtc_timing *timing)
+{
+ uint32_t max_pix_clk = sink->dongle_max_pix_clk;
+
+ if (0 != max_pix_clk && timing->pix_clk_khz > max_pix_clk)
+ return DC_EXCEED_DONGLE_MAX_CLK;
+
+ switch (sink->public.sink_signal) {
+ case SIGNAL_TYPE_DISPLAY_PORT:
+ if(!dp_validate_mode_timing(
+ link,
+ timing))
+ return DC_NO_DP_LINK_BANDWIDTH;
+ break;
+
+ default:
+ break;
+ }
+
+ return DC_OK;
+}
+
+bool dc_link_set_backlight_level(const struct dc_link *public, uint32_t level)
+{
+ struct core_link *link = DC_LINK_TO_CORE(public);
+ struct dc_context *ctx = link->ctx;
+
+ dal_logger_write(ctx->logger, LOG_MAJOR_BACKLIGHT,
+ LOG_MINOR_BACKLIGHT_INTERFACE,
+ "New Backlight level: %d (0x%X)\n", level, level);
+
+ link->link_enc->funcs->set_lcd_backlight_level(link->link_enc, level);
+
+ return true;
+}
+
+void core_link_resume(struct core_link *link)
+{
+ if (link->public.connector_signal != SIGNAL_TYPE_VIRTUAL)
+ program_hpd_filter(link);
+}
+
+static struct fixed31_32 get_pbn_per_slot(struct core_stream *stream)
+{
+ struct link_settings *link_settings =
+ &stream->sink->link->cur_link_settings;
+ uint32_t link_rate_in_mbps =
+ link_settings->link_rate * LINK_RATE_REF_FREQ_IN_MHZ;
+ struct fixed31_32 mbps = dal_fixed31_32_from_int(
+ link_rate_in_mbps * link_settings->lane_count);
+
+ return dal_fixed31_32_div_int(mbps, 54);
+}
+
+static int get_color_depth(struct core_stream *stream)
+{
+ switch (stream->pix_clk_params.color_depth) {
+ case COLOR_DEPTH_666: return 6;
+ case COLOR_DEPTH_888: return 8;
+ case COLOR_DEPTH_101010: return 10;
+ case COLOR_DEPTH_121212: return 12;
+ case COLOR_DEPTH_141414: return 14;
+ case COLOR_DEPTH_161616: return 16;
+ default: return 0;
+ }
+}
+
+static struct fixed31_32 get_pbn_from_timing(struct core_stream *stream)
+{
+ uint32_t bpc;
+ uint64_t kbps;
+ struct fixed31_32 peak_kbps;
+ uint32_t numerator;
+ uint32_t denominator;
+
+ bpc = get_color_depth(stream);
+ kbps = stream->pix_clk_params.requested_pix_clk * bpc * 3;
+
+ /*
+ * margin 5300ppm + 300ppm ~ 0.6% as per spec, factor is 1.006
+ * The unit of 54/64Mbytes/sec is an arbitrary unit chosen based on
+ * common multiplier to render an integer PBN for all link rate/lane
+ * counts combinations
+ * calculate
+ * peak_kbps *= (1006/1000)
+ * peak_kbps *= (64/54)
+ * peak_kbps *= 8 convert to bytes
+ */
+
+ numerator = 64 * PEAK_FACTOR_X1000;
+ denominator = 54 * 8 * 1000 * 1000;
+ kbps *= numerator;
+ peak_kbps = dal_fixed31_32_from_fraction(kbps, denominator);
+
+ return peak_kbps;
+}
+
+static void update_mst_stream_alloc_table(
+ struct core_link *link,
+ struct core_stream *stream,
+ const struct dp_mst_stream_allocation_table *proposed_table)
+{
+ struct link_mst_stream_allocation work_table[MAX_CONTROLLER_NUM] = {
+ { 0 } };
+ struct link_mst_stream_allocation *dc_alloc;
+
+ int i;
+ int j;
+
+ /* if DRM proposed_table has more than one new payload */
+ ASSERT(proposed_table->stream_count -
+ link->mst_stream_alloc_table.stream_count < 2);
+
+ /* copy proposed_table to core_link, add stream encoder */
+ for (i = 0; i < proposed_table->stream_count; i++) {
+
+ for (j = 0; j < link->mst_stream_alloc_table.stream_count; j++) {
+ dc_alloc =
+ &link->mst_stream_alloc_table.stream_allocations[j];
+
+ if (dc_alloc->vcp_id ==
+ proposed_table->stream_allocations[i].vcp_id) {
+
+ work_table[i] = *dc_alloc;
+ break; /* exit j loop */
+ }
+ }
+
+ /* new vcp_id */
+ if (j == link->mst_stream_alloc_table.stream_count) {
+ work_table[i].vcp_id =
+ proposed_table->stream_allocations[i].vcp_id;
+ work_table[i].slot_count =
+ proposed_table->stream_allocations[i].slot_count;
+ work_table[i].stream_enc = stream->stream_enc;
+ }
+ }
+
+ /* update link->mst_stream_alloc_table with work_table */
+ link->mst_stream_alloc_table.stream_count =
+ proposed_table->stream_count;
+ for (i = 0; i < MAX_CONTROLLER_NUM; i++)
+ link->mst_stream_alloc_table.stream_allocations[i] =
+ work_table[i];
+}
+
+/* convert link_mst_stream_alloc_table to dm dp_mst_stream_alloc_table
+ * because stream_encoder is not exposed to dm
+ */
+static enum dc_status allocate_mst_payload(struct core_stream *stream)
+{
+ struct core_link *link = stream->sink->link;
+ struct link_encoder *link_encoder = link->link_enc;
+ struct stream_encoder *stream_encoder = stream->stream_enc;
+ struct dp_mst_stream_allocation_table proposed_table = {0};
+ struct fixed31_32 avg_time_slots_per_mtp;
+ struct fixed31_32 pbn;
+ struct fixed31_32 pbn_per_slot;
+ uint8_t i;
+
+ /* enable_link_dp_mst already check link->enabled_stream_count
+ * and stream is in link->stream[]. This is called during set mode,
+ * stream_enc is available.
+ */
+
+ /* get calculate VC payload for stream: stream_alloc */
+ dm_helpers_dp_mst_write_payload_allocation_table(
+ stream->ctx,
+ &stream->public,
+ &proposed_table,
+ true);
+
+ update_mst_stream_alloc_table(link, stream, &proposed_table);
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_MST,
+ LOG_MINOR_MST_PROGRAMMING,
+ "%s "
+ "stream_count: %d: \n ",
+ __func__,
+ link->mst_stream_alloc_table.stream_count);
+
+ for (i = 0; i < MAX_CONTROLLER_NUM; i++) {
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_MST,
+ LOG_MINOR_MST_PROGRAMMING,
+ "stream_enc[%d]: 0x%x "
+ "stream[%d].vcp_id: %d "
+ "stream[%d].slot_count: %d\n",
+ i,
+ link->mst_stream_alloc_table.stream_allocations[i].stream_enc,
+ i,
+ link->mst_stream_alloc_table.stream_allocations[i].vcp_id,
+ i,
+ link->mst_stream_alloc_table.stream_allocations[i].slot_count);
+ }
+
+ ASSERT(proposed_table.stream_count > 0);
+
+ /*
+ * temporary fix. Unplug of MST chain happened (two displays),
+ * table is empty on first reset mode, and cause 0 division in
+ * avg_time_slots_per_mtp calculation
+ */
+
+ /* to be removed or debugged */
+ if (proposed_table.stream_count == 0)
+ return DC_OK;
+
+ /* program DP source TX for payload */
+ link_encoder->funcs->update_mst_stream_allocation_table(
+ link_encoder,
+ &link->mst_stream_alloc_table);
+
+ /* send down message */
+ dm_helpers_dp_mst_poll_for_allocation_change_trigger(
+ stream->ctx,
+ &stream->public);
+
+ dm_helpers_dp_mst_send_payload_allocation(
+ stream->ctx,
+ &stream->public,
+ true);
+
+ /* slot X.Y for only current stream */
+ pbn_per_slot = get_pbn_per_slot(stream);
+ pbn = get_pbn_from_timing(stream);
+ avg_time_slots_per_mtp = dal_fixed31_32_div(pbn, pbn_per_slot);
+
+
+
+ stream_encoder->funcs->set_mst_bandwidth(
+ stream_encoder,
+ avg_time_slots_per_mtp);
+
+ return DC_OK;
+
+}
+
+static enum dc_status deallocate_mst_payload(struct core_stream *stream)
+{
+ struct core_link *link = stream->sink->link;
+ struct link_encoder *link_encoder = link->link_enc;
+ struct stream_encoder *stream_encoder = stream->stream_enc;
+ struct dp_mst_stream_allocation_table proposed_table = {0};
+ struct fixed31_32 avg_time_slots_per_mtp = dal_fixed31_32_from_int(0);
+ uint8_t i;
+ bool mst_mode = (link->public.type == dc_connection_mst_branch);
+
+ /* deallocate_mst_payload is called before disable link. When mode or
+ * disable/enable monitor, new stream is created which is not in link
+ * stream[] yet. For this, payload is not allocated yet, so de-alloc
+ * should not done. For new mode set, map_resources will get engine
+ * for new stream, so stream_enc->id should be validated until here.
+ */
+
+ /* slot X.Y */
+ stream_encoder->funcs->set_mst_bandwidth(
+ stream_encoder,
+ avg_time_slots_per_mtp);
+
+ /* TODO: which component is responsible for remove payload table? */
+ if (mst_mode)
+ dm_helpers_dp_mst_write_payload_allocation_table(
+ stream->ctx,
+ &stream->public,
+ &proposed_table,
+ false);
+
+ update_mst_stream_alloc_table(link, stream, &proposed_table);
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_MST,
+ LOG_MINOR_MST_PROGRAMMING,
+ "%s"
+ "stream_count: %d: ",
+ __func__,
+ link->mst_stream_alloc_table.stream_count);
+
+ for (i = 0; i < MAX_CONTROLLER_NUM; i++) {
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_MST,
+ LOG_MINOR_MST_PROGRAMMING,
+ "stream_enc[%d]: 0x%x "
+ "stream[%d].vcp_id: %d "
+ "stream[%d].slot_count: %d\n",
+ i,
+ link->mst_stream_alloc_table.stream_allocations[i].stream_enc,
+ i,
+ link->mst_stream_alloc_table.stream_allocations[i].vcp_id,
+ i,
+ link->mst_stream_alloc_table.stream_allocations[i].slot_count);
+ }
+
+ link_encoder->funcs->update_mst_stream_allocation_table(
+ link_encoder,
+ &link->mst_stream_alloc_table);
+
+ if (mst_mode) {
+ dm_helpers_dp_mst_poll_for_allocation_change_trigger(
+ stream->ctx,
+ &stream->public);
+
+ dm_helpers_dp_mst_send_payload_allocation(
+ stream->ctx,
+ &stream->public,
+ false);
+ }
+
+ return DC_OK;
+}
+
+void core_link_enable_stream(
+ struct core_link *link,
+ struct core_stream *stream)
+{
+ struct dc *dc = stream->ctx->dc;
+
+ if (DC_OK != enable_link(stream)) {
+ BREAK_TO_DEBUGGER();
+ return;
+ }
+
+ dc->hwss.enable_stream(stream);
+
+ if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST)
+ allocate_mst_payload(stream);
+}
+
+void core_link_disable_stream(
+ struct core_link *link,
+ struct core_stream *stream)
+{
+ struct dc *dc = stream->ctx->dc;
+
+ if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST)
+ deallocate_mst_payload(stream);
+
+ dc->hwss.disable_stream(stream);
+
+ disable_link(stream);
+
+}
+
+
new file mode 100644
@@ -0,0 +1,1151 @@
+/*
+ * 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
+ *
+ */
+
+#include "dm_services.h"
+
+#include "include/adapter_service_interface.h"
+#include "include/ddc_service_types.h"
+#include "include/grph_object_id.h"
+#include "include/dpcd_defs.h"
+#include "include/logger_interface.h"
+#include "include/vector.h"
+
+#include "dc_link_ddc.h"
+
+#define AUX_POWER_UP_WA_DELAY 500
+#define I2C_OVER_AUX_DEFER_WA_DELAY 70
+
+/* CV smart dongle slave address for retrieving supported HDTV modes*/
+#define CV_SMART_DONGLE_ADDRESS 0x20
+/* DVI-HDMI dongle slave address for retrieving dongle signature*/
+#define DVI_HDMI_DONGLE_ADDRESS 0x68
+static const int8_t dvi_hdmi_dongle_signature_str[] = "6140063500G";
+struct dvi_hdmi_dongle_signature_data {
+ int8_t vendor[3];/* "AMD" */
+ uint8_t version[2];
+ uint8_t size;
+ int8_t id[11];/* "6140063500G"*/
+};
+/* DP-HDMI dongle slave address for retrieving dongle signature*/
+#define DP_HDMI_DONGLE_ADDRESS 0x40
+static const uint8_t dp_hdmi_dongle_signature_str[] = "DP-HDMI ADAPTOR";
+#define DP_HDMI_DONGLE_SIGNATURE_EOT 0x04
+
+struct dp_hdmi_dongle_signature_data {
+ int8_t id[15];/* "DP-HDMI ADAPTOR"*/
+ uint8_t eot;/* end of transmition '\x4' */
+};
+
+/* Address range from 0x00 to 0x1F.*/
+#define DP_ADAPTOR_TYPE2_SIZE 0x20
+#define DP_ADAPTOR_TYPE2_REG_ID 0x10
+#define DP_ADAPTOR_TYPE2_REG_MAX_TMDS_CLK 0x1D
+/* Identifies adaptor as Dual-mode adaptor */
+#define DP_ADAPTOR_TYPE2_ID 0xA0
+/* MHz*/
+#define DP_ADAPTOR_TYPE2_MAX_TMDS_CLK 600
+/* MHz*/
+#define DP_ADAPTOR_TYPE2_MIN_TMDS_CLK 25
+/* kHZ*/
+#define DP_ADAPTOR_DVI_MAX_TMDS_CLK 165000
+/* kHZ*/
+#define DP_ADAPTOR_HDMI_SAFE_MAX_TMDS_CLK 165000
+
+#define DDC_I2C_COMMAND_ENGINE I2C_COMMAND_ENGINE_SW
+
+enum edid_read_result {
+ EDID_READ_RESULT_EDID_MATCH = 0,
+ EDID_READ_RESULT_EDID_MISMATCH,
+ EDID_READ_RESULT_CHECKSUM_READ_ERR,
+ EDID_READ_RESULT_VENDOR_READ_ERR
+};
+
+/* SCDC Address defines (HDMI 2.0)*/
+#define HDMI_SCDC_WRITE_UPDATE_0_ARRAY 3
+#define HDMI_SCDC_ADDRESS 0x54
+#define HDMI_SCDC_SINK_VERSION 0x01
+#define HDMI_SCDC_SOURCE_VERSION 0x02
+#define HDMI_SCDC_UPDATE_0 0x10
+#define HDMI_SCDC_TMDS_CONFIG 0x20
+#define HDMI_SCDC_SCRAMBLER_STATUS 0x21
+#define HDMI_SCDC_CONFIG_0 0x30
+#define HDMI_SCDC_STATUS_FLAGS 0x40
+#define HDMI_SCDC_ERR_DETECT 0x50
+#define HDMI_SCDC_TEST_CONFIG 0xC0
+
+
+union hdmi_scdc_update_read_data {
+ uint8_t byte[2];
+ struct {
+ uint8_t STATUS_UPDATE:1;
+ uint8_t CED_UPDATE:1;
+ uint8_t RR_TEST:1;
+ uint8_t RESERVED:5;
+ uint8_t RESERVED2:8;
+ } fields;
+};
+
+union hdmi_scdc_status_flags_data {
+ uint8_t byte[2];
+ struct {
+ uint8_t CLOCK_DETECTED:1;
+ uint8_t CH0_LOCKED:1;
+ uint8_t CH1_LOCKED:1;
+ uint8_t CH2_LOCKED:1;
+ uint8_t RESERVED:4;
+ uint8_t RESERVED2:8;
+ } fields;
+};
+
+union hdmi_scdc_ced_data {
+ uint8_t byte[7];
+ struct {
+ uint8_t CH0_8LOW:8;
+ uint8_t CH0_7HIGH:7;
+ uint8_t CH0_VALID:1;
+ uint8_t CH1_8LOW:8;
+ uint8_t CH1_7HIGH:7;
+ uint8_t CH1_VALID:1;
+ uint8_t CH2_8LOW:8;
+ uint8_t CH2_7HIGH:7;
+ uint8_t CH2_VALID:1;
+ uint8_t CHECKSUM:8;
+ } fields;
+};
+
+union hdmi_scdc_test_config_Data {
+ uint8_t byte;
+ struct {
+ uint8_t TEST_READ_REQUEST_DELAY:7;
+ uint8_t TEST_READ_REQUEST: 1;
+ } fields;
+};
+
+
+
+union ddc_wa {
+ struct {
+ uint32_t DP_SKIP_POWER_OFF:1;
+ uint32_t DP_AUX_POWER_UP_WA_DELAY:1;
+ } bits;
+ uint32_t raw;
+};
+
+struct ddc_flags {
+ uint8_t EDID_QUERY_DONE_ONCE:1;
+ uint8_t IS_INTERNAL_DISPLAY:1;
+ uint8_t FORCE_READ_REPEATED_START:1;
+ uint8_t EDID_STRESS_READ:1;
+
+};
+
+struct ddc_service {
+ struct ddc *ddc_pin;
+ struct ddc_flags flags;
+ union ddc_wa wa;
+ enum ddc_transaction_type transaction_type;
+ enum display_dongle_type dongle_type;
+ struct dp_receiver_id_info dp_receiver_id_info;
+ struct adapter_service *as;
+ struct dc_context *ctx;
+
+ uint32_t address;
+ uint32_t edid_buf_len;
+ uint8_t edid_buf[MAX_EDID_BUFFER_SIZE];
+};
+
+struct i2c_payloads {
+ struct vector payloads;
+};
+
+struct aux_payloads {
+ struct vector payloads;
+};
+
+struct i2c_payloads *dal_ddc_i2c_payloads_create(struct dc_context *ctx, uint32_t count)
+{
+ struct i2c_payloads *payloads;
+
+ payloads = dm_alloc(ctx, sizeof(struct i2c_payloads));
+
+ if (!payloads)
+ return NULL;
+
+ if (dal_vector_construct(
+ &payloads->payloads, ctx, count, sizeof(struct i2c_payload)))
+ return payloads;
+
+ dm_free(ctx, payloads);
+ return NULL;
+
+}
+
+struct i2c_payload *dal_ddc_i2c_payloads_get(struct i2c_payloads *p)
+{
+ return (struct i2c_payload *)p->payloads.container;
+}
+
+uint32_t dal_ddc_i2c_payloads_get_count(struct i2c_payloads *p)
+{
+ return p->payloads.count;
+}
+
+void dal_ddc_i2c_payloads_destroy(struct i2c_payloads **p)
+{
+ if (!p || !*p)
+ return;
+ dal_vector_destruct(&(*p)->payloads);
+ dm_free((*p)->payloads.ctx, *p);
+ *p = NULL;
+
+}
+
+struct aux_payloads *dal_ddc_aux_payloads_create(struct dc_context *ctx, uint32_t count)
+{
+ struct aux_payloads *payloads;
+
+ payloads = dm_alloc(ctx, sizeof(struct aux_payloads));
+
+ if (!payloads)
+ return NULL;
+
+ if (dal_vector_construct(
+ &payloads->payloads, ctx, count, sizeof(struct aux_payloads)))
+ return payloads;
+
+ dm_free(ctx, payloads);
+ return NULL;
+}
+
+struct aux_payload *dal_ddc_aux_payloads_get(struct aux_payloads *p)
+{
+ return (struct aux_payload *)p->payloads.container;
+}
+
+uint32_t dal_ddc_aux_payloads_get_count(struct aux_payloads *p)
+{
+ return p->payloads.count;
+}
+
+
+void dal_ddc_aux_payloads_destroy(struct aux_payloads **p)
+{
+ if (!p || !*p)
+ return;
+
+ dal_vector_destruct(&(*p)->payloads);
+ dm_free((*p)->payloads.ctx, *p);
+ *p = NULL;
+}
+
+#define DDC_MIN(a, b) (((a) < (b)) ? (a) : (b))
+
+void dal_ddc_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 = address,
+ .length = DDC_MIN(payload_size, len - pos),
+ .data = data + pos };
+ dal_vector_append(&payloads->payloads, &payload);
+ }
+
+}
+
+void dal_ddc_aux_payloads_add(
+ struct aux_payloads *payloads,
+ uint32_t address,
+ uint32_t len,
+ uint8_t *data,
+ bool write)
+{
+ uint32_t payload_size = DEFAULT_AUX_MAX_DATA_SIZE;
+ uint32_t pos;
+
+ for (pos = 0; pos < len; pos += payload_size) {
+ struct aux_payload payload = {
+ .i2c_over_aux = true,
+ .write = write,
+ .address = address,
+ .length = DDC_MIN(payload_size, len - pos),
+ .data = data + pos };
+ dal_vector_append(&payloads->payloads, &payload);
+ }
+}
+
+
+static bool 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);
+
+ ddc_service->ctx = init_data->ctx;
+ ddc_service->as = init_data->as;
+ ddc_service->ddc_pin = dal_adapter_service_obtain_ddc(
+ init_data->as, init_data->id);
+
+ ddc_service->flags.EDID_QUERY_DONE_ONCE = false;
+
+ ddc_service->flags.FORCE_READ_REPEATED_START =
+ dal_adapter_service_is_feature_supported(
+ FEATURE_DDC_READ_FORCE_REPEATED_START);
+
+ ddc_service->flags.EDID_STRESS_READ =
+ dal_adapter_service_is_feature_supported(
+ FEATURE_EDID_STRESS_READ);
+
+
+ ddc_service->flags.IS_INTERNAL_DISPLAY =
+ connector_id == CONNECTOR_ID_EDP ||
+ connector_id == CONNECTOR_ID_LVDS;
+
+ ddc_service->wa.raw = 0;
+ return true;
+}
+
+struct ddc_service *dal_ddc_service_create(
+ struct ddc_service_init_data *init_data)
+{
+ struct ddc_service *ddc_service;
+
+ ddc_service = dm_alloc(init_data->ctx, sizeof(struct ddc_service));
+
+ if (!ddc_service)
+ return NULL;
+
+ if (construct(ddc_service, init_data))
+ return ddc_service;
+
+ dm_free(init_data->ctx, ddc_service);
+ return NULL;
+}
+
+static void destruct(struct ddc_service *ddc)
+{
+ if (ddc->ddc_pin)
+ dal_adapter_service_release_ddc(ddc->as, ddc->ddc_pin);
+}
+
+void dal_ddc_service_destroy(struct ddc_service **ddc)
+{
+ if (!ddc || !*ddc) {
+ BREAK_TO_DEBUGGER();
+ return;
+ }
+ destruct(*ddc);
+ dm_free((*ddc)->ctx, *ddc);
+ *ddc = NULL;
+}
+
+enum ddc_service_type dal_ddc_service_get_type(struct ddc_service *ddc)
+{
+ return DDC_SERVICE_TYPE_CONNECTOR;
+}
+
+void dal_ddc_service_set_transaction_type(
+ struct ddc_service *ddc,
+ enum ddc_transaction_type type)
+{
+ ddc->transaction_type = type;
+}
+
+bool dal_ddc_service_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 ddc_service_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 dp_receiver_id_info dp_rec_info = {0};
+
+ if (dal_ddc_service_get_dp_receiver_id_info(ddc, &dp_rec_info) &&
+ (dp_rec_info.branch_id == DP_BRANCH_DEVICE_ID_4) &&
+ !dm_strncmp(dp_rec_info.branch_name,
+ DP_DVI_CONVERTER_ID_4,
+ sizeof(dp_rec_info.branch_name)))
+ return defer_delay > I2C_OVER_AUX_DEFER_WA_DELAY ?
+ defer_delay : I2C_OVER_AUX_DEFER_WA_DELAY;
+
+ return defer_delay;
+
+}
+
+#define DP_TRANSLATOR_DELAY 5
+
+static uint32_t get_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 i2c_read(
+ struct ddc_service *ddc,
+ uint32_t address,
+ uint8_t *buffer,
+ uint32_t len)
+{
+ uint8_t offs_data = 0;
+ struct i2c_payload payloads[2] = {
+ {
+ .write = true,
+ .address = address,
+ .length = 1,
+ .data = &offs_data },
+ {
+ .write = false,
+ .address = address,
+ .length = len,
+ .data = buffer } };
+
+ struct i2c_command command = {
+ .payloads = payloads,
+ .number_of_payloads = 2,
+ .engine = DDC_I2C_COMMAND_ENGINE,
+ .speed = dal_adapter_service_get_sw_i2c_speed(ddc->as) };
+
+ return dal_i2caux_submit_i2c_command(
+ dal_adapter_service_get_i2caux(ddc->as),
+ ddc->ddc_pin,
+ &command);
+}
+
+static uint8_t aux_read_edid_block(
+ struct ddc_service *ddc,
+ uint8_t address,
+ uint8_t index,
+ uint8_t *buf)
+{
+ struct aux_command cmd = {
+ .payloads = NULL,
+ .number_of_payloads = 0,
+ .defer_delay = get_defer_delay(ddc),
+ .max_defer_write_retry = 0 };
+
+ uint8_t retrieved = 0;
+ uint8_t base_offset =
+ (index % DDC_EDID_BLOCKS_PER_SEGMENT) * DDC_EDID_BLOCK_SIZE;
+ uint8_t segment = index / DDC_EDID_BLOCKS_PER_SEGMENT;
+
+ for (retrieved = 0; retrieved < DDC_EDID_BLOCK_SIZE;
+ retrieved += DEFAULT_AUX_MAX_DATA_SIZE) {
+
+ uint8_t offset = base_offset + retrieved;
+
+ struct aux_payload payloads[3] = {
+ {
+ .i2c_over_aux = true,
+ .write = true,
+ .address = DDC_EDID_SEGMENT_ADDRESS,
+ .length = 1,
+ .data = &segment },
+ {
+ .i2c_over_aux = true,
+ .write = true,
+ .address = address,
+ .length = 1,
+ .data = &offset },
+ {
+ .i2c_over_aux = true,
+ .write = false,
+ .address = address,
+ .length = DEFAULT_AUX_MAX_DATA_SIZE,
+ .data = &buf[retrieved] } };
+
+ if (segment == 0) {
+ cmd.payloads = &payloads[1];
+ cmd.number_of_payloads = 2;
+ } else {
+ cmd.payloads = payloads;
+ cmd.number_of_payloads = 3;
+ }
+
+ if (!dal_i2caux_submit_aux_command(
+ dal_adapter_service_get_i2caux(ddc->as),
+ ddc->ddc_pin,
+ &cmd))
+ /* cannot read, break*/
+ break;
+ }
+
+ /* Reset segment to 0. Needed by some panels */
+ if (0 != segment) {
+ struct aux_payload payloads[1] = { {
+ .i2c_over_aux = true,
+ .write = true,
+ .address = DDC_EDID_SEGMENT_ADDRESS,
+ .length = 1,
+ .data = &segment } };
+ bool result = false;
+
+ segment = 0;
+
+ cmd.number_of_payloads = ARRAY_SIZE(payloads);
+ cmd.payloads = payloads;
+
+ result = dal_i2caux_submit_aux_command(
+ dal_adapter_service_get_i2caux(ddc->as),
+ ddc->ddc_pin,
+ &cmd);
+
+ if (false == result)
+ dal_logger_write(
+ ddc->ctx->logger,
+ LOG_MAJOR_ERROR,
+ LOG_MINOR_COMPONENT_DISPLAY_CAPABILITY_SERVICE,
+ "%s: Writing of EDID Segment (0x30) failed!\n",
+ __func__);
+ }
+
+ return retrieved;
+}
+
+static uint8_t i2c_read_edid_block(
+ struct ddc_service *ddc,
+ uint8_t address,
+ uint8_t index,
+ uint8_t *buf)
+{
+ bool ret = false;
+ uint8_t offset = (index % DDC_EDID_BLOCKS_PER_SEGMENT) *
+ DDC_EDID_BLOCK_SIZE;
+ uint8_t segment = index / DDC_EDID_BLOCKS_PER_SEGMENT;
+
+ struct i2c_command cmd = {
+ .payloads = NULL,
+ .number_of_payloads = 0,
+ .engine = DDC_I2C_COMMAND_ENGINE,
+ .speed = dal_adapter_service_get_sw_i2c_speed(ddc->as) };
+
+ struct i2c_payload payloads[3] = {
+ {
+ .write = true,
+ .address = DDC_EDID_SEGMENT_ADDRESS,
+ .length = 1,
+ .data = &segment },
+ {
+ .write = true,
+ .address = address,
+ .length = 1,
+ .data = &offset },
+ {
+ .write = false,
+ .address = address,
+ .length = DDC_EDID_BLOCK_SIZE,
+ .data = buf } };
+/*
+ * Some I2C engines don't handle stop/start between write-offset and read-data
+ * commands properly. For those displays, we have to force the newer E-DDC
+ * behavior of repeated-start which can be enabled by runtime parameter. */
+/* Originally implemented for OnLive using NXP receiver chip */
+
+ if (index == 0 && !ddc->flags.FORCE_READ_REPEATED_START) {
+ /* base block, use use DDC2B, submit as 2 commands */
+ cmd.payloads = &payloads[1];
+ cmd.number_of_payloads = 1;
+
+ if (dal_i2caux_submit_i2c_command(
+ dal_adapter_service_get_i2caux(ddc->as),
+ ddc->ddc_pin,
+ &cmd)) {
+
+ cmd.payloads = &payloads[2];
+ cmd.number_of_payloads = 1;
+
+ ret = dal_i2caux_submit_i2c_command(
+ dal_adapter_service_get_i2caux(ddc->as),
+ ddc->ddc_pin,
+ &cmd);
+ }
+
+ } else {
+ /*
+ * extension block use E-DDC, submit as 1 command
+ * or if repeated-start is forced by runtime parameter
+ */
+ if (segment != 0) {
+ /* include segment offset in command*/
+ cmd.payloads = payloads;
+ cmd.number_of_payloads = 3;
+ } else {
+ /* we are reading first segment,
+ * segment offset is not required */
+ cmd.payloads = &payloads[1];
+ cmd.number_of_payloads = 2;
+ }
+
+ ret = dal_i2caux_submit_i2c_command(
+ dal_adapter_service_get_i2caux(ddc->as),
+ ddc->ddc_pin,
+ &cmd);
+ }
+
+ return ret ? DDC_EDID_BLOCK_SIZE : 0;
+}
+
+static uint32_t query_edid_block(
+ struct ddc_service *ddc,
+ uint8_t address,
+ uint8_t index,
+ uint8_t *buf,
+ uint32_t size)
+{
+ uint32_t size_retrieved = 0;
+
+ if (size < DDC_EDID_BLOCK_SIZE)
+ return 0;
+
+ if (dal_ddc_service_is_in_aux_transaction_mode(ddc)) {
+
+ ASSERT(index < 2);
+ size_retrieved =
+ aux_read_edid_block(ddc, address, index, buf);
+ } else {
+ size_retrieved =
+ i2c_read_edid_block(ddc, address, index, buf);
+ }
+
+ return size_retrieved;
+}
+
+#define DDC_DPCD_EDID_CHECKSUM_WRITE_ADDRESS 0x261
+#define DDC_TEST_ACK_ADDRESS 0x260
+#define DDC_DPCD_EDID_TEST_ACK 0x04
+#define DDC_DPCD_EDID_TEST_MASK 0x04
+#define DDC_DPCD_TEST_REQUEST_ADDRESS 0x218
+
+/* AG TODO GO throug DM callback here like for DPCD */
+
+static void write_dp_edid_checksum(
+ struct ddc_service *ddc,
+ uint8_t checksum)
+{
+ uint8_t dpcd_data;
+
+ dal_ddc_service_read_dpcd_data(
+ ddc,
+ DDC_DPCD_TEST_REQUEST_ADDRESS,
+ &dpcd_data,
+ 1);
+
+ if (dpcd_data & DDC_DPCD_EDID_TEST_MASK) {
+
+ dal_ddc_service_write_dpcd_data(
+ ddc,
+ DDC_DPCD_EDID_CHECKSUM_WRITE_ADDRESS,
+ &checksum,
+ 1);
+
+ dpcd_data = DDC_DPCD_EDID_TEST_ACK;
+
+ dal_ddc_service_write_dpcd_data(
+ ddc,
+ DDC_TEST_ACK_ADDRESS,
+ &dpcd_data,
+ 1);
+ }
+}
+
+uint32_t dal_ddc_service_edid_query(struct ddc_service *ddc)
+{
+ uint32_t bytes_read = 0;
+ uint32_t ext_cnt = 0;
+
+ uint8_t address;
+ uint32_t i;
+
+ for (address = DDC_EDID_ADDRESS_START;
+ address <= DDC_EDID_ADDRESS_END; ++address) {
+
+ bytes_read = query_edid_block(
+ ddc,
+ address,
+ 0,
+ ddc->edid_buf,
+ sizeof(ddc->edid_buf) - bytes_read);
+
+ if (bytes_read != DDC_EDID_BLOCK_SIZE)
+ continue;
+
+ /* get the number of ext blocks*/
+ ext_cnt = ddc->edid_buf[DDC_EDID_EXT_COUNT_OFFSET];
+
+ /* EDID 2.0, need to read 1 more block because EDID2.0 is
+ * 256 byte in size*/
+ if (ddc->edid_buf[DDC_EDID_20_SIGNATURE_OFFSET] ==
+ DDC_EDID_20_SIGNATURE)
+ ext_cnt = 1;
+
+ for (i = 0; i < ext_cnt; i++) {
+ /* read additional ext blocks accordingly */
+ bytes_read += query_edid_block(
+ ddc,
+ address,
+ i+1,
+ &ddc->edid_buf[bytes_read],
+ sizeof(ddc->edid_buf) - bytes_read);
+ }
+
+ /*this is special code path for DP compliance*/
+ if (DDC_TRANSACTION_TYPE_I2C_OVER_AUX == ddc->transaction_type)
+ write_dp_edid_checksum(
+ ddc,
+ ddc->edid_buf[(ext_cnt * DDC_EDID_BLOCK_SIZE) +
+ DDC_EDID1X_CHECKSUM_OFFSET]);
+
+ /*remembers the address where we fetch the EDID from
+ * for later signature check use */
+ ddc->address = address;
+
+ break;/* already read edid, done*/
+ }
+
+ ddc->edid_buf_len = bytes_read;
+ return bytes_read;
+}
+
+uint32_t dal_ddc_service_get_edid_buf_len(struct ddc_service *ddc)
+{
+ return ddc->edid_buf_len;
+}
+
+void dal_ddc_service_get_edid_buf(struct ddc_service *ddc, uint8_t *edid_buf)
+{
+ dm_memmove(edid_buf,
+ ddc->edid_buf, ddc->edid_buf_len);
+}
+
+void dal_ddc_service_i2c_query_dp_dual_mode_adaptor(
+ struct ddc_service *ddc,
+ struct display_sink_capability *sink_cap)
+{
+ uint8_t i;
+ bool is_valid_hdmi_signature;
+ enum display_dongle_type *dongle = &sink_cap->dongle_type;
+ uint8_t type2_dongle_buf[DP_ADAPTOR_TYPE2_SIZE];
+ bool is_type2_dongle = false;
+ struct dp_hdmi_dongle_signature_data *dongle_signature;
+
+ /* Assume we have no valid DP passive dongle connected */
+ *dongle = DISPLAY_DONGLE_NONE;
+ sink_cap->max_hdmi_pixel_clock = DP_ADAPTOR_HDMI_SAFE_MAX_TMDS_CLK;
+
+ /* Read DP-HDMI dongle I2c (no response interpreted as DP-DVI dongle)*/
+ if (!i2c_read(
+ ddc,
+ DP_HDMI_DONGLE_ADDRESS,
+ type2_dongle_buf,
+ sizeof(type2_dongle_buf))) {
+ dal_logger_write(ddc->ctx->logger,
+ LOG_MAJOR_DCS,
+ LOG_MINOR_DCS_DONGLE_DETECTION,
+ "Detected DP-DVI dongle.\n");
+ *dongle = DISPLAY_DONGLE_DP_DVI_DONGLE;
+ sink_cap->max_hdmi_pixel_clock = DP_ADAPTOR_DVI_MAX_TMDS_CLK;
+ return;
+ }
+
+ /* Check if Type 2 dongle.*/
+ if (type2_dongle_buf[DP_ADAPTOR_TYPE2_REG_ID] == DP_ADAPTOR_TYPE2_ID)
+ is_type2_dongle = true;
+
+ dongle_signature =
+ (struct dp_hdmi_dongle_signature_data *)type2_dongle_buf;
+
+ is_valid_hdmi_signature = true;
+
+ /* Check EOT */
+ if (dongle_signature->eot != DP_HDMI_DONGLE_SIGNATURE_EOT) {
+ is_valid_hdmi_signature = false;
+ }
+
+ /* Check signature */
+ for (i = 0; i < sizeof(dongle_signature->id); ++i) {
+ /* If its not the right signature,
+ * skip mismatch in subversion byte.*/
+ if (dongle_signature->id[i] !=
+ dp_hdmi_dongle_signature_str[i] && i != 3) {
+
+ if (is_type2_dongle) {
+ is_valid_hdmi_signature = false;
+ break;
+ }
+
+ }
+ }
+
+ if (is_type2_dongle) {
+ uint32_t max_tmds_clk =
+ type2_dongle_buf[DP_ADAPTOR_TYPE2_REG_MAX_TMDS_CLK];
+
+ max_tmds_clk = max_tmds_clk * 2 + max_tmds_clk / 2;
+
+ if (0 == max_tmds_clk ||
+ max_tmds_clk < DP_ADAPTOR_TYPE2_MIN_TMDS_CLK ||
+ max_tmds_clk > DP_ADAPTOR_TYPE2_MAX_TMDS_CLK) {
+ dal_logger_write(ddc->ctx->logger,
+ LOG_MAJOR_DCS,
+ LOG_MINOR_DCS_DONGLE_DETECTION,
+ "Invalid Maximum TMDS clock");
+ *dongle = DISPLAY_DONGLE_DP_DVI_DONGLE;
+ } else {
+ if (is_valid_hdmi_signature == true) {
+ *dongle = DISPLAY_DONGLE_DP_HDMI_DONGLE;
+ dal_logger_write(ddc->ctx->logger,
+ LOG_MAJOR_DCS,
+ LOG_MINOR_DCS_DONGLE_DETECTION,
+ "Detected Type 2 DP-HDMI Maximum TMDS "
+ "clock, max TMDS clock: %d MHz",
+ max_tmds_clk);
+ } else {
+ *dongle = DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE;
+ dal_logger_write(ddc->ctx->logger,
+ LOG_MAJOR_DCS,
+ LOG_MINOR_DCS_DONGLE_DETECTION,
+ "Detected Type 2 DP-HDMI (no valid HDMI"
+ " signature) Maximum TMDS clock, max "
+ "TMDS clock: %d MHz",
+ max_tmds_clk);
+ }
+
+ /* Multiply by 1000 to convert to kHz. */
+ sink_cap->max_hdmi_pixel_clock =
+ max_tmds_clk * 1000;
+ }
+
+ } else {
+ if (is_valid_hdmi_signature == true) {
+ dal_logger_write(ddc->ctx->logger,
+ LOG_MAJOR_DCS,
+ LOG_MINOR_DCS_DONGLE_DETECTION,
+ "Detected Type 1 DP-HDMI dongle.\n");
+ *dongle = DISPLAY_DONGLE_DP_HDMI_DONGLE;
+ } else {
+ dal_logger_write(ddc->ctx->logger,
+ LOG_MAJOR_DCS,
+ LOG_MINOR_DCS_DONGLE_DETECTION,
+ "Detected Type 1 DP-HDMI dongle (no valid HDMI "
+ "signature).\n");
+
+ *dongle = DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE;
+ }
+ }
+
+ return;
+}
+
+enum {
+ DP_SINK_CAP_SIZE =
+ DPCD_ADDRESS_EDP_CONFIG_CAP - DPCD_ADDRESS_DPCD_REV + 1
+};
+
+bool dal_ddc_service_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 ret;
+ uint32_t payload_size =
+ dal_ddc_service_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 (write_size > EDID_SEGMENT_SIZE || read_size > EDID_SEGMENT_SIZE)
+ return false;
+
+ /*TODO: len of payload data for i2c and aux is uint8!!!!,
+ * but we want to read 256 over i2c!!!!*/
+ if (dal_ddc_service_is_in_aux_transaction_mode(ddc)) {
+
+ struct aux_payloads *payloads =
+ dal_ddc_aux_payloads_create(ddc->ctx, payloads_num);
+
+ struct aux_command command = {
+ .payloads = dal_ddc_aux_payloads_get(payloads),
+ .number_of_payloads = 0,
+ .defer_delay = get_defer_delay(ddc),
+ .max_defer_write_retry = 0 };
+
+ dal_ddc_aux_payloads_add(
+ payloads, address, write_size, write_buf, true);
+
+ dal_ddc_aux_payloads_add(
+ payloads, address, read_size, read_buf, false);
+
+ command.number_of_payloads =
+ dal_ddc_aux_payloads_get_count(payloads);
+
+ ret = dal_i2caux_submit_aux_command(
+ dal_adapter_service_get_i2caux(ddc->as),
+ ddc->ddc_pin,
+ &command);
+
+ dal_ddc_aux_payloads_destroy(&payloads);
+
+ } else {
+ struct i2c_payloads *payloads =
+ dal_ddc_i2c_payloads_create(ddc->ctx, payloads_num);
+
+ struct i2c_command command = {
+ .payloads = dal_ddc_i2c_payloads_get(payloads),
+ .number_of_payloads = 0,
+ .engine = DDC_I2C_COMMAND_ENGINE,
+ .speed =
+ dal_adapter_service_get_sw_i2c_speed(ddc->as) };
+
+ dal_ddc_i2c_payloads_add(
+ payloads, address, write_size, write_buf, true);
+
+ dal_ddc_i2c_payloads_add(
+ payloads, address, read_size, read_buf, false);
+
+ command.number_of_payloads =
+ dal_ddc_i2c_payloads_get_count(payloads);
+
+ ret = dal_i2caux_submit_i2c_command(
+ dal_adapter_service_get_i2caux(ddc->as),
+ ddc->ddc_pin,
+ &command);
+
+ dal_ddc_i2c_payloads_destroy(&payloads);
+ }
+
+ return ret;
+}
+
+bool dal_ddc_service_get_dp_receiver_id_info(
+ struct ddc_service *ddc,
+ struct dp_receiver_id_info *info)
+{
+ if (!info)
+ return false;
+
+ *info = ddc->dp_receiver_id_info;
+ return true;
+}
+
+enum ddc_result dal_ddc_service_read_dpcd_data(
+ struct ddc_service *ddc,
+ uint32_t address,
+ uint8_t *data,
+ uint32_t len)
+{
+ struct aux_payload read_payload = {
+ .i2c_over_aux = false,
+ .write = false,
+ .address = address,
+ .length = len,
+ .data = data,
+ };
+ struct aux_command command = {
+ .payloads = &read_payload,
+ .number_of_payloads = 1,
+ .defer_delay = 0,
+ .max_defer_write_retry = 0,
+ };
+
+ if (len > DEFAULT_AUX_MAX_DATA_SIZE) {
+ BREAK_TO_DEBUGGER();
+ return DDC_RESULT_FAILED_INVALID_OPERATION;
+ }
+
+ if (dal_i2caux_submit_aux_command(
+ dal_adapter_service_get_i2caux(ddc->as),
+ ddc->ddc_pin,
+ &command))
+ return DDC_RESULT_SUCESSFULL;
+
+ return DDC_RESULT_FAILED_OPERATION;
+}
+
+enum ddc_result dal_ddc_service_write_dpcd_data(
+ struct ddc_service *ddc,
+ uint32_t address,
+ const uint8_t *data,
+ uint32_t len)
+{
+ struct aux_payload write_payload = {
+ .i2c_over_aux = false,
+ .write = true,
+ .address = address,
+ .length = len,
+ .data = (uint8_t *)data,
+ };
+ struct aux_command command = {
+ .payloads = &write_payload,
+ .number_of_payloads = 1,
+ .defer_delay = 0,
+ .max_defer_write_retry = 0,
+ };
+
+ if (len > DEFAULT_AUX_MAX_DATA_SIZE) {
+ BREAK_TO_DEBUGGER();
+ return DDC_RESULT_FAILED_INVALID_OPERATION;
+ }
+
+ if (dal_i2caux_submit_aux_command(
+ dal_adapter_service_get_i2caux(ddc->as),
+ ddc->ddc_pin,
+ &command))
+ return DDC_RESULT_SUCESSFULL;
+
+ return DDC_RESULT_FAILED_OPERATION;
+}
+
+/*test only function*/
+void dal_ddc_service_set_ddc_pin(
+ struct ddc_service *ddc_service,
+ struct ddc *ddc)
+{
+ ddc_service->ddc_pin = ddc;
+}
+
+struct ddc *dal_ddc_service_get_ddc_pin(struct ddc_service *ddc_service)
+{
+ return ddc_service->ddc_pin;
+}
+
+
+void dal_ddc_service_reset_dp_receiver_id_info(struct ddc_service *ddc_service)
+{
+ dm_memset(&ddc_service->dp_receiver_id_info,
+ 0, sizeof(struct dp_receiver_id_info));
+}
+
+void dal_ddc_service_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*/
+
+ dal_ddc_service_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;
+ dal_ddc_service_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;
+ }
+ dal_ddc_service_query_ddc_data(ddc_service, slave_address, write_buffer,
+ sizeof(write_buffer), NULL, 0);
+}
+
+void dal_ddc_service_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;
+
+ dal_ddc_service_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;
+ dal_ddc_service_query_ddc_data(ddc_service, slave_address,
+ &offset, sizeof(offset), &scramble_status,
+ sizeof(scramble_status));
+ offset = HDMI_SCDC_STATUS_FLAGS;
+ dal_ddc_service_query_ddc_data(ddc_service, slave_address,
+ &offset, sizeof(offset), status_data.byte,
+ sizeof(status_data.byte));
+ }
+}
+
new file mode 100644
@@ -0,0 +1,1728 @@
+/* Copyright 2015 Advanced Micro Devices, Inc. */
+#include "dm_services.h"
+#include "dc.h"
+#include "dc_link_dp.h"
+#include "dm_helpers.h"
+
+#include "inc/core_types.h"
+#include "link_hwss.h"
+#include "dc_link_ddc.h"
+#include "core_status.h"
+#include "dpcd_defs.h"
+
+/* maximum pre emphasis level allowed for each voltage swing level*/
+static const enum pre_emphasis voltage_swing_to_pre_emphasis[] = {
+ PRE_EMPHASIS_LEVEL3,
+ PRE_EMPHASIS_LEVEL2,
+ PRE_EMPHASIS_LEVEL1,
+ PRE_EMPHASIS_DISABLED };
+
+enum {
+ POST_LT_ADJ_REQ_LIMIT = 6,
+ POST_LT_ADJ_REQ_TIMEOUT = 200
+};
+
+enum {
+ LINK_TRAINING_MAX_RETRY_COUNT = 5,
+ /* to avoid infinite loop where-in the receiver
+ * switches between different VS
+ */
+ LINK_TRAINING_MAX_CR_RETRY = 100
+};
+
+static const struct link_settings link_training_fallback_table[] = {
+/* 2160 Mbytes/sec*/
+{ LANE_COUNT_FOUR, LINK_RATE_HIGH2, LINK_SPREAD_DISABLED },
+/* 1080 Mbytes/sec*/
+{ LANE_COUNT_FOUR, LINK_RATE_HIGH, LINK_SPREAD_DISABLED },
+/* 648 Mbytes/sec*/
+{ LANE_COUNT_FOUR, LINK_RATE_LOW, LINK_SPREAD_DISABLED },
+/* 1080 Mbytes/sec*/
+{ LANE_COUNT_TWO, LINK_RATE_HIGH2, LINK_SPREAD_DISABLED },
+/* 540 Mbytes/sec*/
+{ LANE_COUNT_TWO, LINK_RATE_HIGH, LINK_SPREAD_DISABLED },
+/* 324 Mbytes/sec*/
+{ LANE_COUNT_TWO, LINK_RATE_LOW, LINK_SPREAD_DISABLED },
+/* 540 Mbytes/sec*/
+{ LANE_COUNT_ONE, LINK_RATE_HIGH2, LINK_SPREAD_DISABLED },
+/* 270 Mbytes/sec*/
+{ LANE_COUNT_ONE, LINK_RATE_HIGH, LINK_SPREAD_DISABLED },
+/* 162 Mbytes/sec*/
+{ LANE_COUNT_ONE, LINK_RATE_LOW, LINK_SPREAD_DISABLED } };
+
+static void wait_for_training_aux_rd_interval(
+ struct core_link* link,
+ uint32_t default_wait_in_micro_secs)
+{
+ uint8_t training_rd_interval;
+
+ /* overwrite the delay if rev > 1.1*/
+ if (link->dpcd_caps.dpcd_rev.raw >= DPCD_REV_12) {
+ /* DP 1.2 or later - retrieve delay through
+ * "DPCD_ADDR_TRAINING_AUX_RD_INTERVAL" register */
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_TRAINING_AUX_RD_INTERVAL,
+ &training_rd_interval,
+ sizeof(training_rd_interval));
+ default_wait_in_micro_secs = training_rd_interval ?
+ (training_rd_interval * 4000) :
+ default_wait_in_micro_secs;
+ }
+
+ dm_delay_in_microseconds(link->ctx, default_wait_in_micro_secs);
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_LINK_TRAINING,
+ "%s:\n wait = %d\n",
+ __func__,
+ default_wait_in_micro_secs);
+}
+
+static void dpcd_set_training_pattern(
+ struct core_link* link,
+ union dpcd_training_pattern dpcd_pattern)
+{
+ core_link_write_dpcd(
+ link,
+ DPCD_ADDRESS_TRAINING_PATTERN_SET,
+ &dpcd_pattern.raw,
+ 1);
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_LINK_TRAINING,
+ "%s\n %x pattern = %x\n",
+ __func__,
+ DPCD_ADDRESS_TRAINING_PATTERN_SET,
+ dpcd_pattern.bits.TRAINING_PATTERN_SET);
+}
+
+static void dpcd_set_link_settings(
+ struct core_link* link,
+ const struct link_training_settings *lt_settings)
+{
+ uint8_t rate = (uint8_t)
+ (lt_settings->link_settings.link_rate);
+
+ union down_spread_ctrl downspread = {{0}};
+ union lane_count_set lane_count_set = {{0}};
+ uint8_t link_set_buffer[2];
+
+
+ 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 = 1;
+
+ lane_count_set.bits.POST_LT_ADJ_REQ_GRANTED =
+ link->dpcd_caps.max_ln_count.bits.POST_LT_ADJ_REQ_SUPPORTED;
+
+ link_set_buffer[0] = rate;
+ link_set_buffer[1] = lane_count_set.raw;
+
+ core_link_write_dpcd(link, DPCD_ADDRESS_LINK_BW_SET,
+ link_set_buffer, 2);
+ core_link_write_dpcd(link, DPCD_ADDRESS_DOWNSPREAD_CNTL,
+ &downspread.raw, sizeof(downspread));
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_LINK_TRAINING,
+ "%s\n %x rate = %x\n %x lane = %x\n %x spread = %x\n",
+ __func__,
+ DPCD_ADDRESS_LINK_BW_SET,
+ lt_settings->link_settings.link_rate,
+ DPCD_ADDRESS_LANE_COUNT_SET,
+ lt_settings->link_settings.lane_count,
+ DPCD_ADDRESS_DOWNSPREAD_CNTL,
+ lt_settings->link_settings.link_spread);
+
+}
+
+static enum dpcd_training_patterns
+ hw_training_pattern_to_dpcd_training_pattern(
+ struct core_link* link,
+ enum hw_dp_training_pattern pattern)
+{
+ enum dpcd_training_patterns dpcd_tr_pattern =
+ DPCD_TRAINING_PATTERN_VIDEOIDLE;
+
+ switch (pattern) {
+ case HW_DP_TRAINING_PATTERN_1:
+ dpcd_tr_pattern = DPCD_TRAINING_PATTERN_1;
+ break;
+ case HW_DP_TRAINING_PATTERN_2:
+ dpcd_tr_pattern = DPCD_TRAINING_PATTERN_2;
+ break;
+ case HW_DP_TRAINING_PATTERN_3:
+ dpcd_tr_pattern = DPCD_TRAINING_PATTERN_3;
+ break;
+ default:
+ ASSERT(0);
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_LINK_TRAINING,
+ "%s: Invalid HW Training pattern: %d\n",
+ __func__, pattern);
+ break;
+ }
+
+ return dpcd_tr_pattern;
+
+}
+
+static void dpcd_set_lt_pattern_and_lane_settings(
+ struct core_link* link,
+ const struct link_training_settings *lt_settings,
+ enum hw_dp_training_pattern pattern)
+{
+ union dpcd_training_lane dpcd_lane[LANE_COUNT_DP_MAX] = {{{0}}};
+ const uint32_t dpcd_base_lt_offset =
+ DPCD_ADDRESS_TRAINING_PATTERN_SET;
+ uint8_t dpcd_lt_buffer[5] = {0};
+ union dpcd_training_pattern dpcd_pattern = {{0}};
+ uint32_t lane;
+ uint32_t size_in_bytes;
+ bool edp_workaround = false; /* TODO link_prop.INTERNAL */
+
+ /*****************************************************************
+ * DpcdAddress_TrainingPatternSet
+ *****************************************************************/
+ dpcd_pattern.bits.TRAINING_PATTERN_SET =
+ hw_training_pattern_to_dpcd_training_pattern(link, pattern);
+
+ dpcd_lt_buffer[DPCD_ADDRESS_TRAINING_PATTERN_SET - dpcd_base_lt_offset]
+ = dpcd_pattern.raw;
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_LINK_TRAINING,
+ "%s\n %x pattern = %x\n",
+ __func__,
+ DPCD_ADDRESS_TRAINING_PATTERN_SET,
+ dpcd_pattern.bits.TRAINING_PATTERN_SET);
+
+
+ /*****************************************************************
+ * DpcdAddress_Lane0Set -> DpcdAddress_Lane3Set
+ *****************************************************************/
+ for (lane = 0; lane <
+ (uint32_t)(lt_settings->link_settings.lane_count); lane++) {
+
+ dpcd_lane[lane].bits.VOLTAGE_SWING_SET =
+ (uint8_t)(lt_settings->lane_settings[lane].VOLTAGE_SWING);
+ dpcd_lane[lane].bits.PRE_EMPHASIS_SET =
+ (uint8_t)(lt_settings->lane_settings[lane].PRE_EMPHASIS);
+
+ dpcd_lane[lane].bits.MAX_SWING_REACHED =
+ (lt_settings->lane_settings[lane].VOLTAGE_SWING ==
+ VOLTAGE_SWING_MAX_LEVEL ? 1 : 0);
+ dpcd_lane[lane].bits.MAX_PRE_EMPHASIS_REACHED =
+ (lt_settings->lane_settings[lane].PRE_EMPHASIS ==
+ PRE_EMPHASIS_MAX_LEVEL ? 1 : 0);
+ }
+
+ /* concatinate everything into one buffer*/
+
+ size_in_bytes = lt_settings->link_settings.lane_count * sizeof(dpcd_lane[0]);
+
+ // 0x00103 - 0x00102
+ dm_memmove(
+ &dpcd_lt_buffer[DPCD_ADDRESS_LANE0_SET - dpcd_base_lt_offset],
+ dpcd_lane,
+ size_in_bytes);
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_LINK_TRAINING,
+ "%s:\n %x VS set = %x PE set = %x \
+ max VS Reached = %x max PE Reached = %x\n",
+ __func__,
+ DPCD_ADDRESS_LANE0_SET,
+ dpcd_lane[0].bits.VOLTAGE_SWING_SET,
+ dpcd_lane[0].bits.PRE_EMPHASIS_SET,
+ dpcd_lane[0].bits.MAX_SWING_REACHED,
+ dpcd_lane[0].bits.MAX_PRE_EMPHASIS_REACHED);
+
+
+ if (edp_workaround) {
+ /* for eDP write in 2 parts because the 5-byte burst is
+ * causing issues on some eDP panels (EPR#366724)
+ */
+ core_link_write_dpcd(
+ link,
+ DPCD_ADDRESS_TRAINING_PATTERN_SET,
+ &dpcd_pattern.raw,
+ sizeof(dpcd_pattern.raw) );
+
+ core_link_write_dpcd(
+ link,
+ DPCD_ADDRESS_LANE0_SET,
+ (uint8_t *)(dpcd_lane),
+ size_in_bytes);
+
+ } else
+ /* write it all in (1 + number-of-lanes)-byte burst*/
+ core_link_write_dpcd(
+ link,
+ dpcd_base_lt_offset,
+ dpcd_lt_buffer,
+ size_in_bytes + sizeof(dpcd_pattern.raw) );
+
+ link->ln_setting = lt_settings->lane_settings[0];
+}
+
+static bool is_cr_done(enum lane_count ln_count,
+ union lane_status *dpcd_lane_status)
+{
+ bool done = true;
+ uint32_t lane;
+ /*LANEx_CR_DONE bits All 1's?*/
+ for (lane = 0; lane < (uint32_t)(ln_count); lane++) {
+ if (!dpcd_lane_status[lane].bits.CR_DONE_0)
+ done = false;
+ }
+ return done;
+
+}
+
+static bool is_ch_eq_done(enum lane_count ln_count,
+ union lane_status *dpcd_lane_status,
+ union lane_align_status_updated *lane_status_updated)
+{
+ bool done = true;
+ uint32_t lane;
+ if (!lane_status_updated->bits.INTERLANE_ALIGN_DONE)
+ done = false;
+ else {
+ for (lane = 0; lane < (uint32_t)(ln_count); lane++) {
+ if (!dpcd_lane_status[lane].bits.SYMBOL_LOCKED_0 ||
+ !dpcd_lane_status[lane].bits.CHANNEL_EQ_DONE_0)
+ done = false;
+ }
+ }
+ return done;
+
+}
+
+static void update_drive_settings(
+ struct link_training_settings *dest,
+ struct link_training_settings src)
+{
+ uint32_t lane;
+ for (lane = 0; lane < src.link_settings.lane_count; lane++) {
+ dest->lane_settings[lane].VOLTAGE_SWING =
+ src.lane_settings[lane].VOLTAGE_SWING;
+ dest->lane_settings[lane].PRE_EMPHASIS =
+ src.lane_settings[lane].PRE_EMPHASIS;
+ dest->lane_settings[lane].POST_CURSOR2 =
+ src.lane_settings[lane].POST_CURSOR2;
+ }
+}
+
+static uint8_t get_nibble_at_index(const uint8_t *buf,
+ uint32_t index)
+{
+ uint8_t nibble;
+ nibble = buf[index / 2];
+
+ if (index % 2)
+ nibble >>= 4;
+ else
+ nibble &= 0x0F;
+
+ return nibble;
+}
+
+static enum pre_emphasis get_max_pre_emphasis_for_voltage_swing(
+ enum voltage_swing voltage)
+{
+ enum pre_emphasis pre_emphasis;
+ pre_emphasis = PRE_EMPHASIS_MAX_LEVEL;
+
+ if (voltage <= VOLTAGE_SWING_MAX_LEVEL)
+ pre_emphasis = voltage_swing_to_pre_emphasis[voltage];
+
+ return pre_emphasis;
+
+}
+
+static void find_max_drive_settings(
+ const struct link_training_settings *link_training_setting,
+ struct link_training_settings *max_lt_setting)
+{
+ uint32_t lane;
+ struct lane_settings max_requested;
+
+ max_requested.VOLTAGE_SWING =
+ link_training_setting->
+ lane_settings[0].VOLTAGE_SWING;
+ max_requested.PRE_EMPHASIS =
+ link_training_setting->
+ lane_settings[0].PRE_EMPHASIS;
+ /*max_requested.postCursor2 =
+ * link_training_setting->laneSettings[0].postCursor2;*/
+
+ /* Determine what the maximum of the requested settings are*/
+ for (lane = 1; lane < link_training_setting->link_settings.lane_count;
+ lane++) {
+ if (link_training_setting->lane_settings[lane].VOLTAGE_SWING >
+ max_requested.VOLTAGE_SWING)
+
+ max_requested.VOLTAGE_SWING =
+ link_training_setting->
+ lane_settings[lane].VOLTAGE_SWING;
+
+
+ if (link_training_setting->lane_settings[lane].PRE_EMPHASIS >
+ max_requested.PRE_EMPHASIS)
+ max_requested.PRE_EMPHASIS =
+ link_training_setting->
+ lane_settings[lane].PRE_EMPHASIS;
+
+ /*
+ if (link_training_setting->laneSettings[lane].postCursor2 >
+ max_requested.postCursor2)
+ {
+ max_requested.postCursor2 =
+ link_training_setting->laneSettings[lane].postCursor2;
+ }
+ */
+ }
+
+ /* make sure the requested settings are
+ * not higher than maximum settings*/
+ if (max_requested.VOLTAGE_SWING > VOLTAGE_SWING_MAX_LEVEL)
+ max_requested.VOLTAGE_SWING = VOLTAGE_SWING_MAX_LEVEL;
+
+ if (max_requested.PRE_EMPHASIS > PRE_EMPHASIS_MAX_LEVEL)
+ max_requested.PRE_EMPHASIS = PRE_EMPHASIS_MAX_LEVEL;
+ /*
+ if (max_requested.postCursor2 > PostCursor2_MaxLevel)
+ max_requested.postCursor2 = PostCursor2_MaxLevel;
+ */
+
+ /* make sure the pre-emphasis matches the voltage swing*/
+ if (max_requested.PRE_EMPHASIS >
+ get_max_pre_emphasis_for_voltage_swing(
+ max_requested.VOLTAGE_SWING))
+ max_requested.PRE_EMPHASIS =
+ get_max_pre_emphasis_for_voltage_swing(
+ max_requested.VOLTAGE_SWING);
+
+ /*
+ * Post Cursor2 levels are completely independent from
+ * pre-emphasis (Post Cursor1) levels. But Post Cursor2 levels
+ * can only be applied to each allowable combination of voltage
+ * swing and pre-emphasis levels */
+ /* if ( max_requested.postCursor2 >
+ * getMaxPostCursor2ForVoltageSwing(max_requested.voltageSwing))
+ * max_requested.postCursor2 =
+ * getMaxPostCursor2ForVoltageSwing(max_requested.voltageSwing);
+ */
+
+ max_lt_setting->link_settings.link_rate =
+ link_training_setting->link_settings.link_rate;
+ max_lt_setting->link_settings.lane_count =
+ link_training_setting->link_settings.lane_count;
+ max_lt_setting->link_settings.link_spread =
+ link_training_setting->link_settings.link_spread;
+
+ for (lane = 0; lane <
+ link_training_setting->link_settings.lane_count;
+ lane++) {
+ max_lt_setting->lane_settings[lane].VOLTAGE_SWING =
+ max_requested.VOLTAGE_SWING;
+ max_lt_setting->lane_settings[lane].PRE_EMPHASIS =
+ max_requested.PRE_EMPHASIS;
+ /*max_lt_setting->laneSettings[lane].postCursor2 =
+ * max_requested.postCursor2;
+ */
+ }
+
+}
+
+static void get_lane_status_and_drive_settings(
+ struct core_link* link,
+ const struct link_training_settings *link_training_setting,
+ union lane_status *ln_status,
+ union lane_align_status_updated *ln_status_updated,
+ struct link_training_settings *req_settings)
+{
+ uint8_t dpcd_buf[6] = {0};
+ union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {{{0}}};
+ struct link_training_settings request_settings = {{0}};
+ uint32_t lane;
+
+ dm_memset(req_settings, '\0', sizeof(struct link_training_settings));
+
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_LANE_01_STATUS,
+ (uint8_t *)(dpcd_buf),
+ sizeof(dpcd_buf));
+
+
+ for (lane = 0; lane <
+ (uint32_t)(link_training_setting->link_settings.lane_count);
+ lane++) {
+
+ ln_status[lane].raw =
+ get_nibble_at_index(&dpcd_buf[0], lane);
+ dpcd_lane_adjust[lane].raw =
+ get_nibble_at_index(&dpcd_buf[4], lane);
+ }
+
+ ln_status_updated->raw = dpcd_buf[2];
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_LINK_TRAINING,
+ "%s:\n%x Lane01Status = %x\n %x Lane23Status = %x\n ",
+ __func__,
+ DPCD_ADDRESS_LANE_01_STATUS, dpcd_buf[0],
+ DPCD_ADDRESS_LANE_23_STATUS, dpcd_buf[1]);
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_LINK_TRAINING,
+ "%s:\n %x Lane01AdjustRequest = %x\n %x Lane23AdjustRequest = %x\n",
+ __func__,
+ DPCD_ADDRESS_ADJUST_REQUEST_LANE0_1,
+ dpcd_buf[4],
+ DPCD_ADDRESS_ADJUST_REQUEST_LANE2_3,
+ dpcd_buf[5]);
+
+ /*copy to req_settings*/
+ request_settings.link_settings.lane_count =
+ link_training_setting->link_settings.lane_count;
+ request_settings.link_settings.link_rate =
+ link_training_setting->link_settings.link_rate;
+ request_settings.link_settings.link_spread =
+ link_training_setting->link_settings.link_spread;
+
+ for (lane = 0; lane <
+ (uint32_t)(link_training_setting->link_settings.lane_count);
+ lane++) {
+
+ request_settings.lane_settings[lane].VOLTAGE_SWING =
+ (enum voltage_swing)(dpcd_lane_adjust[lane].bits.
+ VOLTAGE_SWING_LANE);
+ request_settings.lane_settings[lane].PRE_EMPHASIS =
+ (enum pre_emphasis)(dpcd_lane_adjust[lane].bits.
+ PRE_EMPHASIS_LANE);
+ }
+
+ /*Note: for postcursor2, read adjusted
+ * postcursor2 settings from*/
+ /*DpcdAddress_AdjustRequestPostCursor2 =
+ *0x020C (not implemented yet)*/
+
+ /* we find the maximum of the requested settings across all lanes*/
+ /* and set this maximum for all lanes*/
+ find_max_drive_settings(&request_settings, req_settings);
+
+ /* if post cursor 2 is needed in the future,
+ * read DpcdAddress_AdjustRequestPostCursor2 = 0x020C
+ */
+
+}
+
+static void dpcd_set_lane_settings(
+ struct core_link* link,
+ const struct link_training_settings *link_training_setting)
+{
+ union dpcd_training_lane dpcd_lane[LANE_COUNT_DP_MAX] = {{{0}}};
+ uint32_t lane;
+
+ for (lane = 0; lane <
+ (uint32_t)(link_training_setting->
+ link_settings.lane_count);
+ lane++) {
+ dpcd_lane[lane].bits.VOLTAGE_SWING_SET =
+ (uint8_t)(link_training_setting->
+ lane_settings[lane].VOLTAGE_SWING);
+ dpcd_lane[lane].bits.PRE_EMPHASIS_SET =
+ (uint8_t)(link_training_setting->
+ lane_settings[lane].PRE_EMPHASIS);
+ dpcd_lane[lane].bits.MAX_SWING_REACHED =
+ (link_training_setting->
+ lane_settings[lane].VOLTAGE_SWING ==
+ VOLTAGE_SWING_MAX_LEVEL ? 1 : 0);
+ dpcd_lane[lane].bits.MAX_PRE_EMPHASIS_REACHED =
+ (link_training_setting->
+ lane_settings[lane].PRE_EMPHASIS ==
+ PRE_EMPHASIS_MAX_LEVEL ? 1 : 0);
+ }
+
+ core_link_write_dpcd(link,
+ DPCD_ADDRESS_LANE0_SET,
+ (uint8_t *)(dpcd_lane),
+ link_training_setting->link_settings.lane_count);
+
+ /*
+ if (LTSettings.link.rate == LinkRate_High2)
+ {
+ DpcdTrainingLaneSet2 dpcd_lane2[lane_count_DPMax] = {0};
+ for ( uint32_t lane = 0;
+ lane < lane_count_DPMax; lane++)
+ {
+ dpcd_lane2[lane].bits.post_cursor2_set =
+ static_cast<unsigned char>(
+ LTSettings.laneSettings[lane].postCursor2);
+ dpcd_lane2[lane].bits.max_post_cursor2_reached = 0;
+ }
+ m_pDpcdAccessSrv->WriteDpcdData(
+ DpcdAddress_Lane0Set2,
+ reinterpret_cast<unsigned char*>(dpcd_lane2),
+ LTSettings.link.lanes);
+ }
+ */
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_LINK_TRAINING,
+ "%s\n %x VS set = %x PE set = %x \
+ max VS Reached = %x max PE Reached = %x\n",
+ __func__,
+ DPCD_ADDRESS_LANE0_SET,
+ dpcd_lane[0].bits.VOLTAGE_SWING_SET,
+ dpcd_lane[0].bits.PRE_EMPHASIS_SET,
+ dpcd_lane[0].bits.MAX_SWING_REACHED,
+ dpcd_lane[0].bits.MAX_PRE_EMPHASIS_REACHED);
+
+ link->ln_setting = link_training_setting->lane_settings[0];
+
+}
+
+static bool is_max_vs_reached(
+ const struct link_training_settings *lt_settings)
+{
+ uint32_t lane;
+ for (lane = 0; lane <
+ (uint32_t)(lt_settings->link_settings.lane_count);
+ lane++) {
+ if (lt_settings->lane_settings[lane].VOLTAGE_SWING
+ == VOLTAGE_SWING_MAX_LEVEL)
+ return true;
+ }
+ return false;
+
+}
+
+void set_drive_settings(
+ struct core_link *link,
+ struct link_training_settings *lt_settings)
+{
+ /* program ASIC PHY settings*/
+ dp_set_hw_lane_settings(link, lt_settings);
+
+ /* Notify DP sink the PHY settings from source */
+ dpcd_set_lane_settings(link, lt_settings);
+}
+
+static bool perform_post_lt_adj_req_sequence(
+ struct core_link *link,
+ struct link_training_settings *lt_settings)
+{
+ enum lane_count lane_count =
+ lt_settings->link_settings.lane_count;
+
+ uint32_t adj_req_count;
+ uint32_t adj_req_timer;
+ bool req_drv_setting_changed;
+ uint32_t lane;
+
+ req_drv_setting_changed = false;
+ for (adj_req_count = 0; adj_req_count < POST_LT_ADJ_REQ_LIMIT;
+ adj_req_count++) {
+
+ req_drv_setting_changed = false;
+
+ for (adj_req_timer = 0;
+ adj_req_timer < POST_LT_ADJ_REQ_TIMEOUT;
+ adj_req_timer++) {
+
+ struct link_training_settings req_settings;
+ union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX];
+ union lane_align_status_updated
+ dpcd_lane_status_updated;
+
+ get_lane_status_and_drive_settings(
+ link,
+ lt_settings,
+ dpcd_lane_status,
+ &dpcd_lane_status_updated,
+ &req_settings);
+
+ if (dpcd_lane_status_updated.bits.
+ POST_LT_ADJ_REQ_IN_PROGRESS == 0)
+ return true;
+
+ if (!is_cr_done(lane_count, dpcd_lane_status))
+ return false;
+
+ if (!is_ch_eq_done(
+ lane_count,
+ dpcd_lane_status,
+ &dpcd_lane_status_updated))
+ return false;
+
+ for (lane = 0; lane < (uint32_t)(lane_count); lane++) {
+
+ if (lt_settings->
+ lane_settings[lane].VOLTAGE_SWING !=
+ req_settings.lane_settings[lane].
+ VOLTAGE_SWING ||
+ lt_settings->lane_settings[lane].PRE_EMPHASIS !=
+ req_settings.lane_settings[lane].PRE_EMPHASIS) {
+
+ req_drv_setting_changed = true;
+ break;
+ }
+ }
+
+ if (req_drv_setting_changed) {
+ update_drive_settings(
+ lt_settings,req_settings);
+
+ set_drive_settings(link, lt_settings);
+ break;
+ }
+
+ dm_sleep_in_milliseconds(link->ctx, 1);
+ }
+
+ if (!req_drv_setting_changed) {
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_WARNING,
+ LOG_MINOR_COMPONENT_LINK_SERVICE,
+ "%s: Post Link Training Adjust Request Timed out\n",
+ __func__);
+
+ ASSERT(0);
+ return true;
+ }
+ }
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_WARNING,
+ LOG_MINOR_COMPONENT_LINK_SERVICE,
+ "%s: Post Link Training Adjust Request limit reached\n",
+ __func__);
+
+ ASSERT(0);
+ return true;
+
+}
+
+static bool perform_channel_equalization_sequence(
+ struct core_link *link,
+ struct link_training_settings *lt_settings)
+{
+ struct link_training_settings req_settings;
+ enum hw_dp_training_pattern hw_tr_pattern;
+ uint32_t retries_ch_eq;
+ enum 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}}};;
+
+ /*TODO hw_tr_pattern = HW_DP_TRAINING_PATTERN_3;*/
+ hw_tr_pattern = HW_DP_TRAINING_PATTERN_2;
+
+ dp_set_hw_training_pattern(link, hw_tr_pattern);
+
+ for (retries_ch_eq = 0; retries_ch_eq <= LINK_TRAINING_MAX_RETRY_COUNT;
+ retries_ch_eq++) {
+
+ dp_set_hw_lane_settings(link, lt_settings);
+
+ /* 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,
+ hw_tr_pattern);
+ else
+ dpcd_set_lane_settings(link, lt_settings);
+
+ /* 3. wait for receiver to lock-on*/
+ wait_for_training_aux_rd_interval(link, 400);
+
+ /* 4. Read lane status and requested
+ * drive settings as set by the sink*/
+
+ get_lane_status_and_drive_settings(
+ link,
+ lt_settings,
+ dpcd_lane_status,
+ &dpcd_lane_status_updated,
+ &req_settings);
+
+ /* 5. check CR done*/
+ if (!is_cr_done(lane_count, dpcd_lane_status))
+ return false;
+
+ /* 6. check CHEQ done*/
+ if (is_ch_eq_done(lane_count,
+ dpcd_lane_status,
+ &dpcd_lane_status_updated))
+ return true;
+
+ /* 7. update VS/PE/PC2 in lt_settings*/
+ update_drive_settings(lt_settings, req_settings);
+ }
+
+ return false;
+
+}
+
+static bool perform_clock_recovery_sequence(
+ struct core_link *link,
+ struct link_training_settings *lt_settings)
+{
+ uint32_t retries_cr;
+ uint32_t retry_count;
+ uint32_t lane;
+ struct link_training_settings req_settings;
+ enum lane_count lane_count =
+ lt_settings->link_settings.lane_count;
+ enum hw_dp_training_pattern hw_tr_pattern = HW_DP_TRAINING_PATTERN_1;
+ union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX];
+ union lane_align_status_updated dpcd_lane_status_updated;
+
+ retries_cr = 0;
+ retry_count = 0;
+ /* initial drive setting (VS/PE/PC2)*/
+ for (lane = 0; lane < LANE_COUNT_DP_MAX; lane++) {
+ lt_settings->lane_settings[lane].VOLTAGE_SWING =
+ VOLTAGE_SWING_LEVEL0;
+ lt_settings->lane_settings[lane].PRE_EMPHASIS =
+ PRE_EMPHASIS_DISABLED;
+ lt_settings->lane_settings[lane].POST_CURSOR2 =
+ POST_CURSOR2_DISABLED;
+ }
+
+ dp_set_hw_training_pattern(link, hw_tr_pattern);
+
+ /* 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)) {
+
+ dm_memset(&dpcd_lane_status, '\0', sizeof(dpcd_lane_status));
+ dm_memset(&dpcd_lane_status_updated, '\0',
+ sizeof(dpcd_lane_status_updated));
+
+ /* 1. call HWSS to set lane settings*/
+ dp_set_hw_lane_settings(
+ link,
+ lt_settings);
+
+ /* 2. update DPCD of the receiver*/
+ if (!retries_cr)
+ /* 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,
+ hw_tr_pattern);
+ else
+ dpcd_set_lane_settings(
+ link,
+ lt_settings);
+
+
+ /* 3. wait receiver to lock-on*/
+ wait_for_training_aux_rd_interval(
+ link,
+ 100);
+
+ /* 4. Read lane status and requested drive
+ * settings as set by the sink
+ */
+ get_lane_status_and_drive_settings(
+ link,
+ lt_settings,
+ dpcd_lane_status,
+ &dpcd_lane_status_updated,
+ &req_settings);
+
+
+ /* 5. check CR done*/
+ if (is_cr_done(lane_count, dpcd_lane_status))
+ return true;
+
+ /* 6. max VS reached*/
+ if (is_max_vs_reached(lt_settings))
+ return false;
+
+ /* 7. same voltage*/
+ /* Note: VS same for all lanes,
+ * so comparing first lane is sufficient*/
+ if (lt_settings->lane_settings[0].VOLTAGE_SWING ==
+ req_settings.lane_settings[0].VOLTAGE_SWING)
+ retries_cr++;
+ else
+ retries_cr = 0;
+
+
+ /* 8. update VS/PE/PC2 in lt_settings*/
+ update_drive_settings(lt_settings, req_settings);
+
+ retry_count++;
+ }
+
+ if (retry_count >= LINK_TRAINING_MAX_CR_RETRY) {
+ ASSERT(0);
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_ERROR,
+ LOG_MINOR_COMPONENT_LINK_SERVICE,
+ "%s: Link Training Error, could not \
+ get CR after %d tries. \
+ Possibly voltage swing issue", __func__,
+ LINK_TRAINING_MAX_CR_RETRY);
+
+ }
+
+ return false;
+}
+
+ bool perform_link_training(
+ struct core_link *link,
+ const struct link_settings *link_setting,
+ bool skip_video_pattern)
+{
+ bool status;
+ union dpcd_training_pattern dpcd_pattern = {{0}};
+ union lane_count_set lane_count_set = {{0}};
+ const int8_t *link_rate = "Unknown";
+ struct link_training_settings lt_settings;
+
+ status = false;
+ dm_memset(<_settings, '\0', sizeof(lt_settings));
+
+ lt_settings.link_settings.link_rate = link_setting->link_rate;
+ lt_settings.link_settings.lane_count = link_setting->lane_count;
+
+ /*@todo[vdevulap] move SS to LS, should not be handled by displaypath*/
+
+ /* 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_SPREAD_05_DOWNSPREAD_30KHZ;
+
+ /* 1. set link rate, lane count and spread*/
+ dpcd_set_link_settings(link, <_settings);
+
+ /* 2. perform link training (set link training done
+ * to false is done as well)*/
+ if (perform_clock_recovery_sequence(link, <_settings)) {
+
+ if (perform_channel_equalization_sequence(link, <_settings))
+ status = true;
+ }
+
+ if (status || !skip_video_pattern) {
+
+ /* 3. set training not in progress*/
+ dpcd_pattern.bits.TRAINING_PATTERN_SET =
+ DPCD_TRAINING_PATTERN_VIDEOIDLE;
+ dpcd_set_training_pattern(link, dpcd_pattern);
+
+ /* 4. mainlink output idle pattern*/
+ dp_set_hw_test_pattern(link, DP_TEST_PATTERN_VIDEO_MODE);
+
+ /* 5. post training adjust if required*/
+ if (link->dpcd_caps.max_ln_count.bits.POST_LT_ADJ_REQ_SUPPORTED
+ == 1) {
+ if (status == true) {
+ if (perform_post_lt_adj_req_sequence(
+ link, <_settings) == false)
+ status = false;
+ }
+
+ lane_count_set.bits.LANE_COUNT_SET =
+ lt_settings.link_settings.lane_count;
+ lane_count_set.bits.ENHANCED_FRAMING = 1;
+ lane_count_set.bits.POST_LT_ADJ_REQ_GRANTED = 0;
+
+ core_link_write_dpcd(
+ link,
+ DPCD_ADDRESS_LANE_COUNT_SET,
+ &lane_count_set.raw,
+ sizeof(lane_count_set));
+ }
+ }
+
+ /* 6. print status message*/
+ switch (lt_settings.link_settings.link_rate) {
+
+ case LINK_RATE_LOW:
+ link_rate = "Low";
+ break;
+ case LINK_RATE_HIGH:
+ link_rate = "High";
+ break;
+ case LINK_RATE_HIGH2:
+ link_rate = "High2";
+ break;
+ case LINK_RATE_RBR2:
+ link_rate = "RBR2";
+ break;
+ default:
+ break;
+ }
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_MST,
+ LOG_MINOR_MST_PROGRAMMING,
+ "Link training for %d lanes at %s rate %s with PE %d, VS %d\n",
+ lt_settings.link_settings.lane_count,
+ link_rate,
+ status ? "succeeded" : "failed",
+ lt_settings.lane_settings[0].PRE_EMPHASIS,
+ lt_settings.lane_settings[0].VOLTAGE_SWING);
+
+ return status;
+}
+
+/*TODO add more check to see if link support request link configuration */
+static bool is_link_setting_supported(
+ const struct link_settings *link_setting,
+ const struct link_settings *max_link_setting)
+{
+ if (link_setting->lane_count > max_link_setting->lane_count ||
+ link_setting->link_rate > max_link_setting->link_rate)
+ return false;
+ return true;
+}
+
+static const uint32_t get_link_training_fallback_table_len(
+ struct core_link *link)
+{
+ return ARRAY_SIZE(link_training_fallback_table);
+}
+
+static const struct link_settings *get_link_training_fallback_table(
+ struct core_link *link, uint32_t i)
+{
+ return &link_training_fallback_table[i];
+}
+
+static bool exceeded_limit_link_setting(const struct link_settings *link_setting,
+ const struct link_settings *limit_link_setting)
+{
+ return (link_setting->lane_count * link_setting->link_rate
+ > limit_link_setting->lane_count * limit_link_setting->link_rate ?
+ true : false);
+}
+
+
+bool dp_hbr_verify_link_cap(
+ struct core_link *link,
+ struct link_settings *known_limit_link_setting)
+{
+ struct link_settings max_link_cap = {0};
+ bool success;
+ bool skip_link_training;
+ const struct link_settings *cur;
+ bool skip_video_pattern;
+ uint32_t i;
+
+ success = false;
+ skip_link_training = false;
+
+ /* TODO confirm this is correct for cz */
+ max_link_cap.lane_count = LANE_COUNT_FOUR;
+ max_link_cap.link_rate = LINK_RATE_HIGH2;
+ max_link_cap.link_spread = LINK_SPREAD_05_DOWNSPREAD_30KHZ;
+
+ /* TODO implement override and monitor patch later */
+
+ /* try to train the link from high to low to
+ * find the physical link capability
+ */
+ /* disable PHY done possible by BIOS, will be done by driver itself */
+ dp_disable_link_phy(link, link->public.connector_signal);
+
+ for (i = 0; i < get_link_training_fallback_table_len(link) &&
+ !success; i++) {
+ cur = get_link_training_fallback_table(link, i);
+
+ if (known_limit_link_setting->lane_count != LANE_COUNT_UNKNOWN &&
+ exceeded_limit_link_setting(cur,
+ known_limit_link_setting))
+ continue;
+
+ if (!is_link_setting_supported(cur, &max_link_cap))
+ continue;
+
+ skip_video_pattern = true;
+ if (cur->link_rate == LINK_RATE_LOW)
+ skip_video_pattern = false;
+
+ dp_enable_link_phy(
+ link,
+ link->public.connector_signal,
+ cur);
+
+ if (skip_link_training)
+ success = true;
+ else {
+ uint8_t num_retries = 3;
+ uint8_t j;
+ uint8_t delay_between_retries = 10;
+
+ for (j = 0; j < num_retries; ++j) {
+ success = perform_link_training(
+ link,
+ cur,
+ skip_video_pattern);
+
+ if (success)
+ break;
+
+ dm_sleep_in_milliseconds(
+ link->ctx,
+ delay_between_retries);
+
+ delay_between_retries += 10;
+ }
+ }
+
+ if (success)
+ link->verified_link_cap = *cur;
+
+ /* always disable the link before trying another
+ * setting or before returning we'll enable it later
+ * based on the actual mode we're driving
+ */
+ dp_disable_link_phy(link, link->public.connector_signal);
+ }
+
+ /* Link Training failed for all Link Settings
+ * (Lane Count is still unknown)
+ */
+ if (!success) {
+ /* If all LT fails for all settings,
+ * set verified = failed safe (1 lane low)
+ */
+ link->verified_link_cap.lane_count = LANE_COUNT_ONE;
+ link->verified_link_cap.link_rate = LINK_RATE_LOW;
+
+ link->verified_link_cap.link_spread =
+ LINK_SPREAD_DISABLED;
+ }
+
+ link->max_link_setting = link->verified_link_cap;
+
+ return success;
+}
+
+static uint32_t bandwidth_in_kbps_from_timing(
+ const struct dc_crtc_timing *timing)
+{
+ uint32_t bits_per_channel = 0;
+ uint32_t kbps;
+ switch (timing->display_color_depth) {
+
+ case COLOR_DEPTH_666:
+ bits_per_channel = 6;
+ break;
+ case COLOR_DEPTH_888:
+ bits_per_channel = 8;
+ break;
+ case COLOR_DEPTH_101010:
+ bits_per_channel = 10;
+ break;
+ case COLOR_DEPTH_121212:
+ bits_per_channel = 12;
+ break;
+ case COLOR_DEPTH_141414:
+ bits_per_channel = 14;
+ break;
+ case COLOR_DEPTH_161616:
+ bits_per_channel = 16;
+ break;
+ default:
+ break;
+ }
+ ASSERT(bits_per_channel != 0);
+
+ kbps = timing->pix_clk_khz;
+ kbps *= bits_per_channel;
+
+ if (timing->flags.Y_ONLY != 1)
+ /*Only YOnly make reduce bandwidth by 1/3 compares to RGB*/
+ kbps *= 3;
+
+ return kbps;
+
+}
+
+static uint32_t bandwidth_in_kbps_from_link_settings(
+ const struct link_settings *link_setting)
+{
+ uint32_t link_rate_in_kbps = link_setting->link_rate *
+ LINK_RATE_REF_FREQ_IN_KHZ;
+
+ uint32_t lane_count = link_setting->lane_count;
+ uint32_t kbps = link_rate_in_kbps;
+ kbps *= lane_count;
+ kbps *= 8; /* 8 bits per byte*/
+
+ return kbps;
+
+}
+
+bool dp_validate_mode_timing(
+ struct core_link *link,
+ const struct dc_crtc_timing *timing)
+{
+ uint32_t req_bw;
+ uint32_t max_bw;
+
+ const struct link_settings *link_setting;
+
+ /*always DP fail safe mode*/
+ if (timing->pix_clk_khz == (uint32_t)25175 &&
+ timing->h_addressable == (uint32_t)640 &&
+ timing->v_addressable == (uint32_t)480)
+ return true;
+
+ /* For static validation we always use reported
+ * link settings for other cases, when no modelist
+ * changed we can use verified link setting*/
+ link_setting = &link->reported_link_cap;
+
+ /* TODO: DYNAMIC_VALIDATION needs to be implemented */
+ /*if (flags.DYNAMIC_VALIDATION == 1 &&
+ link->verified_link_cap.lane_count != LANE_COUNT_UNKNOWN)
+ link_setting = &link->verified_link_cap;
+ */
+
+ req_bw = bandwidth_in_kbps_from_timing(timing);
+ max_bw = bandwidth_in_kbps_from_link_settings(link_setting);
+
+ if (req_bw < max_bw) {
+ /* remember the biggest mode here, during
+ * initial link training (to get
+ * verified_link_cap), LS sends event about
+ * cannot train at reported cap to upper
+ * layer and upper layer will re-enumerate modes.
+ * this is not necessary if the lower
+ * verified_link_cap is enough to drive
+ * all the modes */
+
+ /* TODO: DYNAMIC_VALIDATION needs to be implemented */
+ /* if (flags.DYNAMIC_VALIDATION == 1)
+ dpsst->max_req_bw_for_verified_linkcap = dal_max(
+ dpsst->max_req_bw_for_verified_linkcap, req_bw); */
+ return true;
+ } else
+ return false;
+}
+
+void decide_link_settings(struct core_stream *stream,
+ struct link_settings *link_setting)
+{
+
+ const struct link_settings *cur_ls;
+ struct core_link* link;
+ uint32_t req_bw;
+ uint32_t link_bw;
+ uint32_t i;
+
+ req_bw = bandwidth_in_kbps_from_timing(
+ &stream->public.timing);
+
+ /* if preferred is specified through AMDDP, use it, if it's enough
+ * to drive the mode
+ */
+ link = stream->sink->link;
+
+ if ((link->reported_link_cap.lane_count != LANE_COUNT_UNKNOWN) &&
+ (link->reported_link_cap.link_rate <=
+ link->verified_link_cap.link_rate)) {
+
+ link_bw = bandwidth_in_kbps_from_link_settings(
+ &link->reported_link_cap);
+
+ if (req_bw < link_bw) {
+ *link_setting = link->reported_link_cap;
+ return;
+ }
+ }
+
+ /* search for first suitable setting for the requested
+ * bandwidth
+ */
+ for (i = 0; i < get_link_training_fallback_table_len(link); i++) {
+
+ cur_ls = get_link_training_fallback_table(link, i);
+
+ link_bw =
+ bandwidth_in_kbps_from_link_settings(
+ cur_ls);
+
+ if (req_bw < link_bw) {
+ if (is_link_setting_supported(
+ cur_ls,
+ &link->max_link_setting)) {
+ *link_setting = *cur_ls;
+ return;
+ }
+ }
+ }
+
+ BREAK_TO_DEBUGGER();
+ ASSERT(link->verified_link_cap.lane_count !=
+ LANE_COUNT_UNKNOWN);
+
+ *link_setting = link->verified_link_cap;
+}
+
+/*************************Short Pulse IRQ***************************/
+
+static bool hpd_rx_irq_check_link_loss_status(
+ struct core_link *link,
+ union hpd_irq_data *hpd_irq_dpcd_data)
+{
+ uint8_t irq_reg_rx_power_state;
+ 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 we can handle interrupt: Not in FS DOS,
+ * Not in "Display Timeout" state, Link is trained.
+ */
+
+ dpcd_result = core_link_read_dpcd(link,
+ DPCD_ADDRESS_POWER_STATE,
+ &irq_reg_rx_power_state,
+ sizeof(irq_reg_rx_power_state));
+
+ if (dpcd_result != DC_OK) {
+ irq_reg_rx_power_state = DP_PWR_STATE_D0;
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_HPD_IRQ,
+ "%s: DPCD read failed to obtain power state.\n",
+ __func__);
+ }
+
+ if (irq_reg_rx_power_state == DP_PWR_STATE_D0) {
+
+ /*2. Check that Link Status changed, before re-training.*/
+
+ /*parse lane status*/
+ for (lane = 0;
+ lane < link->cur_link_settings.lane_count;
+ lane++) {
+
+ /* check status of lanes 0,1
+ * changed DpcdAddress_Lane01Status (0x202)*/
+ lane_status.raw = 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 (sink_status_changed ||
+ !hpd_irq_dpcd_data->bytes.lane_status_updated.bits.
+ INTERLANE_ALIGN_DONE) {
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_HPD_IRQ,
+ "%s: Link Status changed.\n",
+ __func__);
+
+ return_code = true;
+ }
+ }
+
+ return return_code;
+}
+
+static enum dc_status read_hpd_rx_irq_data(
+ struct core_link *link,
+ union hpd_irq_data *irq_data)
+{
+ /* 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.
+ */
+ return core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_SINK_COUNT,
+ irq_data->raw,
+ sizeof(union hpd_irq_data));
+}
+
+static bool allow_hpd_rx_irq(const struct core_link *link)
+{
+ /*
+ * Don't handle RX IRQ unless one of following is met:
+ * 1) The link is established (cur_link_settings != unknown)
+ * 2) We kicked off MST detection
+ * 3) We know we're dealing with an active dongle
+ */
+
+ if ((link->cur_link_settings.lane_count != LANE_COUNT_UNKNOWN) ||
+ (link->public.type == dc_connection_mst_branch) ||
+ is_dp_active_dongle(link))
+ return true;
+
+ return false;
+}
+
+bool dc_link_handle_hpd_rx_irq(const struct dc_link *dc_link)
+{
+ struct core_link *link = DC_LINK_TO_LINK(dc_link);
+ union hpd_irq_data hpd_irq_dpcd_data = {{{{0}}}};
+ enum dc_status result = DDC_RESULT_UNKNOWN;
+ bool status = 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*/
+
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_HPD_IRQ,
+ "%s: Got short pulse HPD on link %d\n",
+ __func__, link->public.link_index);
+
+ if (!allow_hpd_rx_irq(link)) {
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_HPD_IRQ,
+ "%s: skipping HPD handling on %d\n",
+ __func__, link->public.link_index);
+ return false;
+ }
+
+ /* 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 = read_hpd_rx_irq_data(link, &hpd_irq_dpcd_data);
+
+ if (result != DC_OK) {
+ dal_logger_write(link->ctx->logger,
+ LOG_MAJOR_HW_TRACE,
+ LOG_MINOR_HW_TRACE_HPD_IRQ,
+ "%s: DPCD read failed to obtain irq data\n",
+ __func__);
+ return false;
+ }
+
+ /* 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 ||
+ hpd_irq_dpcd_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY)
+ return false;
+
+
+ /* For now we only handle 'Downstream port status' case. */
+ /* If we got sink count changed it means Downstream port status changed,
+ * then DM should call DC to do the detection. */
+ if (hpd_rx_irq_check_link_loss_status(
+ link,
+ &hpd_irq_dpcd_data)) {
+ perform_link_training(link, &link->cur_link_settings, true);
+ status = false;
+ }
+
+ if (hpd_irq_dpcd_data.bytes.sink_cnt.bits.SINK_COUNT
+ != link->dpcd_sink_count)
+ status = true;
+
+ /* 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;
+}
+
+/*query dpcd for version and mst cap addresses*/
+bool is_mst_supported(struct core_link *link)
+{
+ bool mst = false;
+ enum dc_status st = DC_OK;
+ union dpcd_rev rev;
+ union mstm_cap cap;
+
+ rev.raw = 0;
+ cap.raw = 0;
+
+ st = core_link_read_dpcd(link, DPCD_ADDRESS_DPCD_REV, &rev.raw,
+ sizeof(rev));
+
+ if (st == DC_OK && rev.raw >= DPCD_REV_12) {
+
+ st = core_link_read_dpcd(link, DPCD_ADDRESS_MSTM_CAP,
+ &cap.raw, sizeof(cap));
+ if (st == DC_OK && cap.bits.MST_CAP == 1)
+ mst = true;
+ }
+ return mst;
+
+}
+
+bool is_dp_active_dongle(const struct core_link *link)
+{
+ enum display_dongle_type dongle_type = link->dpcd_caps.dongle_type;
+
+ return (dongle_type == DISPLAY_DONGLE_DP_VGA_CONVERTER) ||
+ (dongle_type == DISPLAY_DONGLE_DP_DVI_CONVERTER) ||
+ (dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER);
+}
+
+static void get_active_converter_info(
+ uint8_t data, struct core_link *link)
+{
+ union dp_downstream_port_present ds_port = { .byte = data };
+
+ /* decode converter info*/
+ if (!ds_port.fields.PORT_PRESENT) {
+ link->dpcd_caps.dongle_type = DISPLAY_DONGLE_NONE;
+ ddc_service_set_dongle_type(link->ddc,
+ link->dpcd_caps.dongle_type);
+ return;
+ }
+
+ switch (ds_port.fields.PORT_TYPE) {
+ case DOWNSTREAM_VGA:
+ link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_VGA_CONVERTER;
+ break;
+ case DOWNSTREAM_DVI_HDMI:
+ /* At this point we don't know is it DVI or HDMI,
+ * assume DVI.*/
+ link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_CONVERTER;
+ break;
+ default:
+ link->dpcd_caps.dongle_type = DISPLAY_DONGLE_NONE;
+ break;
+ }
+
+ if (link->dpcd_caps.dpcd_rev.raw >= DCS_DPCD_REV_11) {
+ uint8_t det_caps[4];
+ union dwnstream_port_caps_byte0 *port_caps =
+ (union dwnstream_port_caps_byte0 *)det_caps;
+ core_link_read_dpcd(link, DPCD_ADDRESS_DWN_STRM_PORT0_CAPS,
+ det_caps, sizeof(det_caps));
+
+ switch (port_caps->bits.DWN_STRM_PORTX_TYPE) {
+ case DOWN_STREAM_DETAILED_VGA:
+ link->dpcd_caps.dongle_type =
+ DISPLAY_DONGLE_DP_VGA_CONVERTER;
+ break;
+ case DOWN_STREAM_DETAILED_DVI:
+ link->dpcd_caps.dongle_type =
+ DISPLAY_DONGLE_DP_DVI_CONVERTER;
+ break;
+ case DOWN_STREAM_DETAILED_HDMI:
+ link->dpcd_caps.dongle_type =
+ DISPLAY_DONGLE_DP_HDMI_CONVERTER;
+
+ if (ds_port.fields.DETAILED_CAPS) {
+
+ union dwnstream_port_caps_byte3_hdmi
+ hdmi_caps = {.raw = det_caps[3] };
+
+ link->dpcd_caps.is_dp_hdmi_s3d_converter =
+ hdmi_caps.bits.FRAME_SEQ_TO_FRAME_PACK;
+ }
+ break;
+ }
+ }
+ ddc_service_set_dongle_type(link->ddc,
+ link->dpcd_caps.dongle_type);
+}
+
+static void dp_wa_power_up_0010FA(struct core_link *link, uint8_t *dpcd_data,
+ int length)
+{
+ int retry = 0;
+ struct dp_device_vendor_id dp_id;
+ union dp_downstream_port_present ds_port = { 0 };
+
+ if (!link->dpcd_caps.dpcd_rev.raw) {
+ do {
+ dp_receiver_power_ctrl(link, true);
+ core_link_read_dpcd(link, DPCD_ADDRESS_DPCD_REV,
+ dpcd_data, length);
+ link->dpcd_caps.dpcd_rev.raw = dpcd_data[
+ DPCD_ADDRESS_DPCD_REV -
+ DPCD_ADDRESS_DPCD_REV];
+ } while (retry++ < 4 && !link->dpcd_caps.dpcd_rev.raw);
+ }
+
+ ds_port.byte = dpcd_data[DPCD_ADDRESS_DOWNSTREAM_PORT_PRESENT -
+ DPCD_ADDRESS_DPCD_REV];
+
+ get_active_converter_info(ds_port.byte, link);
+
+ /* read IEEE branch device id */
+ core_link_read_dpcd(link, DPCD_ADDRESS_BRANCH_DEVICE_ID_START,
+ (uint8_t *)&dp_id, sizeof(dp_id));
+ link->dpcd_caps.branch_dev_id =
+ (dp_id.ieee_oui[0] << 16) +
+ (dp_id.ieee_oui[1] << 8) +
+ dp_id.ieee_oui[2];
+
+ if (link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_VGA_CONVERTER) {
+ switch (link->dpcd_caps.branch_dev_id) {
+ /* Some active dongles (DP-VGA, DP-DLDVI converters) power down
+ * all internal circuits including AUX communication preventing
+ * reading DPCD table and EDID (spec violation).
+ * Encoder will skip DP RX power down on disable_output to
+ * keep receiver powered all the time.*/
+ case DP_BRANCH_DEVICE_ID_1:
+ case DP_BRANCH_DEVICE_ID_4:
+ link->wa_flags.dp_keep_receiver_powered = true;
+ break;
+
+ /* TODO: May need work around for other dongles. */
+ default:
+ link->wa_flags.dp_keep_receiver_powered = false;
+ break;
+ }
+ } else
+ link->wa_flags.dp_keep_receiver_powered = false;
+}
+
+static void retrieve_link_cap(struct core_link *link)
+{
+ uint8_t dpcd_data[
+ DPCD_ADDRESS_EDP_CONFIG_CAP -
+ DPCD_ADDRESS_DPCD_REV + 1];
+
+ union down_stream_port_count down_strm_port_count;
+ union edp_configuration_cap edp_config_cap;
+ union max_down_spread max_down_spread;
+ union dp_downstream_port_present ds_port = { 0 };
+
+ dm_memset(dpcd_data, '\0', sizeof(dpcd_data));
+ dm_memset(&down_strm_port_count,
+ '\0', sizeof(union down_stream_port_count));
+ dm_memset(&edp_config_cap, '\0',
+ sizeof(union edp_configuration_cap));
+ dm_memset(&max_down_spread, '\0',
+ sizeof(union max_down_spread));
+
+ core_link_read_dpcd(link, DPCD_ADDRESS_DPCD_REV,
+ dpcd_data, sizeof(dpcd_data));
+ link->dpcd_caps.dpcd_rev.raw = dpcd_data[
+ DPCD_ADDRESS_DPCD_REV -
+ DPCD_ADDRESS_DPCD_REV];
+
+ ds_port.byte = dpcd_data[DPCD_ADDRESS_DOWNSTREAM_PORT_PRESENT -
+ DPCD_ADDRESS_DPCD_REV];
+
+ get_active_converter_info(ds_port.byte, link);
+
+ dp_wa_power_up_0010FA(link, dpcd_data, sizeof(dpcd_data));
+
+ link->dpcd_caps.allow_invalid_MSA_timing_param =
+ down_strm_port_count.bits.IGNORE_MSA_TIMING_PARAM;
+
+ link->dpcd_caps.max_ln_count.raw = dpcd_data[
+ DPCD_ADDRESS_MAX_LANE_COUNT - DPCD_ADDRESS_DPCD_REV];
+
+ max_down_spread.raw = dpcd_data[
+ DPCD_ADDRESS_MAX_DOWNSPREAD - DPCD_ADDRESS_DPCD_REV];
+
+ link->reported_link_cap.lane_count =
+ link->dpcd_caps.max_ln_count.bits.MAX_LANE_COUNT;
+ link->reported_link_cap.link_rate = dpcd_data[
+ DPCD_ADDRESS_MAX_LINK_RATE - DPCD_ADDRESS_DPCD_REV];
+ link->reported_link_cap.link_spread =
+ max_down_spread.bits.MAX_DOWN_SPREAD ?
+ LINK_SPREAD_05_DOWNSPREAD_30KHZ : LINK_SPREAD_DISABLED;
+
+ edp_config_cap.raw = dpcd_data[
+ DPCD_ADDRESS_EDP_CONFIG_CAP - DPCD_ADDRESS_DPCD_REV];
+ link->dpcd_caps.panel_mode_edp =
+ edp_config_cap.bits.ALT_SCRAMBLER_RESET;
+
+ link->edp_revision = DPCD_EDP_REVISION_EDP_UNKNOWN;
+
+ /* read sink count */
+ core_link_read_dpcd(link,
+ DPCD_ADDRESS_SINK_COUNT,
+ &link->dpcd_caps.sink_count.raw,
+ sizeof(link->dpcd_caps.sink_count.raw));
+
+ /* Display control registers starting at DPCD 700h are only valid and
+ * enabled if this eDP config cap bit is set. */
+ if (edp_config_cap.bits.DPCD_DISPLAY_CONTROL_CAPABLE) {
+ /* Read the Panel's eDP revision at DPCD 700h. */
+ core_link_read_dpcd(link,
+ DPCD_ADDRESS_EDP_REV,
+ (uint8_t *)(&link->edp_revision),
+ sizeof(link->edp_revision));
+ }
+ /* TODO: Confirm if need retrieve_psr_link_cap */
+}
+
+void detect_dp_sink_caps(struct core_link *link)
+{
+ retrieve_link_cap(link);
+
+ /* dc init_hw has power encoder using default
+ * signal for connector. For native DP, no
+ * need to power up encoder again. If not native
+ * DP, hw_init may need check signal or power up
+ * encoder here.
+ */
+
+ if (is_mst_supported(link)) {
+ link->verified_link_cap = link->reported_link_cap;
+ } else {
+ dp_hbr_verify_link_cap(link,
+ &link->reported_link_cap);
+ }
+ /* TODO save sink caps in link->sink */
+}
new file mode 100644
@@ -0,0 +1,201 @@
+/* Copyright 2015 Advanced Micro Devices, Inc. */
+
+#include "dm_services.h"
+#include "dc.h"
+#include "inc/core_dc.h"
+#include "include/ddc_service_types.h"
+#include "include/i2caux_interface.h"
+#include "link_hwss.h"
+#include "hw_sequencer.h"
+#include "dc_link_ddc.h"
+#include "dm_helpers.h"
+#include "dce110/dce110_link_encoder.h"
+#include "dce110/dce110_stream_encoder.h"
+
+
+enum dc_status core_link_read_dpcd(
+ struct core_link* link,
+ uint32_t address,
+ uint8_t *data,
+ uint32_t size)
+{
+ if (!dm_helper_dp_read_dpcd(link->ctx,
+ &link->public,
+ address, data, size))
+ return DC_ERROR_UNEXPECTED;
+
+ return DC_OK;
+}
+
+enum dc_status core_link_write_dpcd(
+ struct core_link* link,
+ uint32_t address,
+ const uint8_t *data,
+ uint32_t size)
+{
+ if (!dm_helper_dp_write_dpcd(link->ctx,
+ &link->public,
+ address, data, size))
+ return DC_ERROR_UNEXPECTED;
+
+ return DC_OK;
+}
+
+void dp_receiver_power_ctrl(struct core_link *link, bool on)
+{
+ uint8_t state;
+
+ state = on ? DP_POWER_STATE_D0 : DP_POWER_STATE_D3;
+
+ core_link_write_dpcd(link, DPCD_ADDRESS_POWER_STATE, &state,
+ sizeof(state));
+}
+
+void dp_enable_link_phy(
+ struct core_link *link,
+ enum signal_type signal,
+ const struct link_settings *link_settings)
+{
+ struct link_encoder *link_enc = link->link_enc;
+
+ if (dc_is_dp_sst_signal(signal)) {
+ if (signal == SIGNAL_TYPE_EDP) {
+ link_enc->funcs->power_control(link_enc, true);
+ link_enc->funcs->backlight_control(link_enc, true);
+ }
+
+ link_enc->funcs->enable_dp_output(
+ link_enc,
+ link_settings,
+ CLOCK_SOURCE_ID_EXTERNAL);
+ } else {
+ link_enc->funcs->enable_dp_mst_output(
+ link_enc,
+ link_settings,
+ CLOCK_SOURCE_ID_EXTERNAL);
+ }
+
+ dp_receiver_power_ctrl(link, true);
+}
+
+void dp_disable_link_phy(struct core_link *link, enum signal_type signal)
+{
+ if (!link->wa_flags.dp_keep_receiver_powered)
+ dp_receiver_power_ctrl(link, false);
+
+ if (signal == SIGNAL_TYPE_EDP)
+ link->link_enc->funcs->backlight_control(link->link_enc, false);
+
+ link->link_enc->funcs->disable_output(link->link_enc, signal);
+
+ /* Clear current link setting.*/
+ dm_memset(&link->cur_link_settings, 0,
+ sizeof(link->cur_link_settings));
+}
+
+void dp_disable_link_phy_mst(struct core_link *link, struct core_stream *stream)
+{
+ /* MST disable link only when no stream use the link */
+ if (link->mst_stream_alloc_table.stream_count > 0)
+ return;
+
+ dp_disable_link_phy(link, stream->signal);
+}
+
+bool dp_set_hw_training_pattern(
+ struct core_link *link,
+ enum hw_dp_training_pattern pattern)
+{
+ enum dp_test_pattern test_pattern = DP_TEST_PATTERN_UNSUPPORTED;
+ struct encoder_set_dp_phy_pattern_param pattern_param = {0};
+ struct link_encoder *encoder = link->link_enc;
+
+ switch (pattern) {
+ case HW_DP_TRAINING_PATTERN_1:
+ test_pattern = DP_TEST_PATTERN_TRAINING_PATTERN1;
+ break;
+ case HW_DP_TRAINING_PATTERN_2:
+ test_pattern = DP_TEST_PATTERN_TRAINING_PATTERN2;
+ break;
+ case HW_DP_TRAINING_PATTERN_3:
+ test_pattern = DP_TEST_PATTERN_TRAINING_PATTERN3;
+ break;
+ default:
+ break;
+ }
+
+ pattern_param.dp_phy_pattern = test_pattern;
+ pattern_param.custom_pattern = NULL;
+ pattern_param.custom_pattern_size = 0;
+ pattern_param.dp_panel_mode = dp_get_panel_mode(link);
+
+ encoder->funcs->dp_set_phy_pattern(encoder, &pattern_param);
+
+ return true;
+}
+
+
+void dp_set_hw_lane_settings(
+ struct core_link *link,
+ const struct link_training_settings *link_settings)
+{
+ struct link_encoder *encoder = link->link_enc;
+
+ /* call Encoder to set lane settings */
+ encoder->funcs->dp_set_lane_settings(encoder, link_settings);
+}
+
+enum dp_panel_mode dp_get_panel_mode(struct core_link *link)
+{
+ /* We need to explicitly check that connector
+ * is not DP. Some Travis_VGA get reported
+ * by video bios as DP.
+ */
+ if (link->public.connector_signal != SIGNAL_TYPE_DISPLAY_PORT) {
+
+ switch (link->dpcd_caps.branch_dev_id) {
+ case DP_BRANCH_DEVICE_ID_2:
+ if (strncmp(
+ link->dpcd_caps.branch_dev_name,
+ DP_VGA_LVDS_CONVERTER_ID_2,
+ sizeof(
+ link->dpcd_caps.
+ branch_dev_name)) == 0) {
+ return DP_PANEL_MODE_SPECIAL;
+ }
+ break;
+ case DP_BRANCH_DEVICE_ID_3:
+ if (strncmp(link->dpcd_caps.branch_dev_name,
+ DP_VGA_LVDS_CONVERTER_ID_3,
+ sizeof(
+ link->dpcd_caps.
+ branch_dev_name)) == 0) {
+ return DP_PANEL_MODE_SPECIAL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (link->dpcd_caps.panel_mode_edp) {
+ return DP_PANEL_MODE_EDP;
+ }
+ }
+
+ return DP_PANEL_MODE_DEFAULT;
+}
+
+void dp_set_hw_test_pattern(
+ struct core_link *link,
+ enum dp_test_pattern test_pattern)
+{
+ struct encoder_set_dp_phy_pattern_param pattern_param = {0};
+ struct link_encoder *encoder = link->link_enc;
+
+ pattern_param.dp_phy_pattern = test_pattern;
+ pattern_param.custom_pattern = NULL;
+ pattern_param.custom_pattern_size = 0;
+ pattern_param.dp_panel_mode = dp_get_panel_mode(link);
+
+ encoder->funcs->dp_set_phy_pattern(encoder, &pattern_param);
+}
new file mode 100644
@@ -0,0 +1,1243 @@
+/*
+* 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
+ *
+ */
+#include "dm_services.h"
+
+#include "resource.h"
+#include "include/irq_service_interface.h"
+#include "link_encoder.h"
+#include "stream_encoder.h"
+#include "opp.h"
+#include "transform.h"
+
+#if defined(CONFIG_DRM_AMD_DAL_DCE10_0)
+#include "dce100/dce100_resource.h"
+#endif
+#if defined(CONFIG_DRM_AMD_DAL_DCE11_0)
+#include "dce110/dce110_resource.h"
+#endif
+
+bool dc_construct_resource_pool(struct adapter_service *adapter_serv,
+ struct dc *dc,
+ uint8_t num_virtual_links)
+{
+ enum dce_version dce_ver = dal_adapter_service_get_dce_version(adapter_serv);
+
+ switch (dce_ver) {
+#if defined(CONFIG_DRM_AMD_DAL_DCE10_0)
+ case DCE_VERSION_10_0:
+ return dce100_construct_resource_pool(
+ adapter_serv, num_virtual_links, dc, &dc->res_pool);
+#endif
+#if defined(CONFIG_DRM_AMD_DAL_DCE11_0)
+ case DCE_VERSION_11_0:
+ return dce110_construct_resource_pool(
+ adapter_serv, num_virtual_links, dc, &dc->res_pool);
+#endif
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void unreference_clock_source(
+ struct resource_context *res_ctx,
+ struct clock_source *clock_source)
+{
+ int i;
+ for (i = 0; i < res_ctx->pool.clk_src_count; i++) {
+ if (res_ctx->pool.clock_sources[i] == clock_source) {
+ res_ctx->clock_source_ref_count[i]--;
+
+ if (res_ctx->clock_source_ref_count[i] == 0)
+ clock_source->funcs->cs_power_down(clock_source);
+ }
+ }
+
+
+}
+
+void reference_clock_source(
+ struct resource_context *res_ctx,
+ struct clock_source *clock_source)
+{
+ int i;
+ for (i = 0; i < res_ctx->pool.clk_src_count; i++) {
+ if (res_ctx->pool.clock_sources[i] == clock_source) {
+ res_ctx->clock_source_ref_count[i]++;
+ }
+ }
+}
+
+bool is_same_timing(
+ const struct dc_crtc_timing *timing1,
+ const struct dc_crtc_timing *timing2)
+{
+ return dm_memcmp(timing1, timing2, sizeof(struct dc_crtc_timing)) == 0;
+}
+
+static bool is_sharable_clk_src(
+ const struct core_stream *stream_with_clk_src,
+ const struct core_stream *stream)
+{
+ enum clock_source_id id = stream_with_clk_src->clock_source->id;
+
+ if (stream_with_clk_src->clock_source == NULL)
+ return false;
+
+ if (id == CLOCK_SOURCE_ID_EXTERNAL)
+ return false;
+
+ /* Sharing dual link is not working */
+ if (stream->signal == SIGNAL_TYPE_DVI_DUAL_LINK ||
+ stream_with_clk_src->signal == SIGNAL_TYPE_DVI_DUAL_LINK)
+ return false;
+
+ if(!is_same_timing(
+ &stream_with_clk_src->public.timing, &stream->public.timing))
+ return false;
+
+ return true;
+}
+
+struct clock_source *find_used_clk_src_for_sharing(
+ struct validate_context *context,
+ struct core_stream *stream)
+{
+ uint8_t i, j;
+ for (i = 0; i < context->target_count; i++) {
+ struct core_target *target = context->targets[i];
+ for (j = 0; j < target->public.stream_count; j++) {
+ struct core_stream *clock_source_stream =
+ DC_STREAM_TO_CORE(target->public.streams[j]);
+
+ if (clock_source_stream->clock_source == NULL)
+ continue;
+
+ if (is_sharable_clk_src(clock_source_stream, stream))
+ return clock_source_stream->clock_source;
+ }
+ }
+
+ return NULL;
+}
+
+static enum pixel_format convert_pixel_format_to_dalsurface(
+ enum surface_pixel_format surface_pixel_format)
+{
+ enum pixel_format dal_pixel_format = PIXEL_FORMAT_UNKNOWN;
+
+ switch (surface_pixel_format) {
+ case SURFACE_PIXEL_FORMAT_GRPH_PALETA_256_COLORS:
+ dal_pixel_format = PIXEL_FORMAT_INDEX8;
+ break;
+ case SURFACE_PIXEL_FORMAT_GRPH_ARGB1555:
+ dal_pixel_format = PIXEL_FORMAT_RGB565;
+ break;
+ case SURFACE_PIXEL_FORMAT_GRPH_RGB565:
+ dal_pixel_format = PIXEL_FORMAT_RGB565;
+ break;
+ case SURFACE_PIXEL_FORMAT_GRPH_ARGB8888:
+ dal_pixel_format = PIXEL_FORMAT_ARGB8888;
+ break;
+ case SURFACE_PIXEL_FORMAT_GRPH_BGRA8888:
+ dal_pixel_format = PIXEL_FORMAT_ARGB8888;
+ break;
+ case SURFACE_PIXEL_FORMAT_GRPH_ARGB2101010:
+ dal_pixel_format = PIXEL_FORMAT_ARGB2101010;
+ break;
+ case SURFACE_PIXEL_FORMAT_GRPH_ABGR2101010:
+ dal_pixel_format = PIXEL_FORMAT_ARGB2101010;
+ break;
+ case SURFACE_PIXEL_FORMAT_GRPH_ABGR2101010_XR_BIAS:
+ dal_pixel_format = PIXEL_FORMAT_ARGB2101010_XRBIAS;
+ break;
+ case SURFACE_PIXEL_FORMAT_GRPH_ARGB16161616:
+ dal_pixel_format = PIXEL_FORMAT_FP16;
+ break;
+ case SURFACE_PIXEL_FORMAT_GRPH_ABGR16161616F:
+ dal_pixel_format = PIXEL_FORMAT_FP16;
+ break;
+
+
+ case SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr:
+ dal_pixel_format = PIXEL_FORMAT_420BPP12;
+ break;
+ case SURFACE_PIXEL_FORMAT_VIDEO_420_YCrCb:
+ dal_pixel_format = PIXEL_FORMAT_420BPP12;
+ break;
+ case SURFACE_PIXEL_FORMAT_VIDEO_422_YCb:
+ dal_pixel_format = PIXEL_FORMAT_422BPP16;
+ break;
+ case SURFACE_PIXEL_FORMAT_VIDEO_422_YCr:
+ dal_pixel_format = PIXEL_FORMAT_422BPP16;
+ break;
+ case SURFACE_PIXEL_FORMAT_VIDEO_422_CbY:
+ dal_pixel_format = PIXEL_FORMAT_422BPP16;
+ break;
+ case SURFACE_PIXEL_FORMAT_VIDEO_422_CrY:
+ dal_pixel_format = PIXEL_FORMAT_422BPP16;
+ break;
+ case SURFACE_PIXEL_FORMAT_VIDEO_444_ACrYCb1555:
+ dal_pixel_format = PIXEL_FORMAT_444BPP16;
+ break;
+ case SURFACE_PIXEL_FORMAT_VIDEO_444_CrYCb565:
+ dal_pixel_format = PIXEL_FORMAT_444BPP16;
+ break;
+ case SURFACE_PIXEL_FORMAT_VIDEO_444_ACrYCb4444:
+ dal_pixel_format = PIXEL_FORMAT_444BPP16;
+ break;
+ case SURFACE_PIXEL_FORMAT_VIDEO_444_CbYCrA5551:
+ dal_pixel_format = PIXEL_FORMAT_444BPP16;
+ break;
+ case SURFACE_PIXEL_FORMAT_VIDEO_444_ACrYCb8888:
+ dal_pixel_format = PIXEL_FORMAT_444BPP32;
+ break;
+ case SURFACE_PIXEL_FORMAT_VIDEO_444_ACrYCb2101010:
+ dal_pixel_format = PIXEL_FORMAT_444BPP32;
+ break;
+ case SURFACE_PIXEL_FORMAT_VIDEO_444_CbYCrA1010102:
+ dal_pixel_format = PIXEL_FORMAT_444BPP32;
+ break;
+ default:
+ dal_pixel_format = PIXEL_FORMAT_UNKNOWN;
+ break;
+ }
+ return dal_pixel_format;
+}
+
+static void calculate_viewport(
+ const struct dc_surface *surface,
+ struct core_stream *stream)
+{
+ const struct rect src = surface->src_rect;
+ const struct rect clip = surface->clip_rect;
+ const struct rect dst = surface->dst_rect;
+
+ /* offset = src.ofs + (clip.ofs - dst.ofs) * scl_ratio
+ * num_pixels = clip.num_pix * scl_ratio
+ */
+ stream->viewport.x = src.x + (clip.x - dst.x) * src.width / dst.width;
+ stream->viewport.width = clip.width * src.width / dst.width;
+
+ stream->viewport.y = src.y + (clip.y - dst.y) * src.height / dst.height;
+ stream->viewport.height = clip.height * src.height / dst.height;
+
+ /* Minimum viewport such that 420/422 chroma vp is non 0 */
+ if (stream->viewport.width < 2)
+ {
+ stream->viewport.width = 2;
+ }
+ if (stream->viewport.height < 2)
+ {
+ stream->viewport.height = 2;
+ }
+}
+
+static void calculate_overscan(
+ const struct dc_surface *surface,
+ struct core_stream *stream)
+{
+ stream->overscan.left = stream->public.dst.x;
+ if (stream->public.src.x < surface->clip_rect.x)
+ stream->overscan.left += (surface->clip_rect.x
+ - stream->public.src.x) * stream->public.dst.width
+ / stream->public.src.width;
+
+ stream->overscan.right = stream->public.timing.h_addressable
+ - stream->public.dst.x - stream->public.dst.width;
+ if (stream->public.src.x + stream->public.src.width
+ > surface->clip_rect.x + surface->clip_rect.width)
+ stream->overscan.right = stream->public.timing.h_addressable -
+ dal_fixed31_32_floor(dal_fixed31_32_div(
+ dal_fixed31_32_from_int(
+ stream->viewport.width),
+ stream->ratios.horz)) -
+ stream->overscan.left;
+
+
+ stream->overscan.top = stream->public.dst.y;
+ if (stream->public.src.y < surface->clip_rect.y)
+ stream->overscan.top += (surface->clip_rect.y
+ - stream->public.src.y) * stream->public.dst.height
+ / stream->public.src.height;
+
+ stream->overscan.bottom = stream->public.timing.v_addressable
+ - stream->public.dst.y - stream->public.dst.height;
+ if (stream->public.src.y + stream->public.src.height
+ > surface->clip_rect.y + surface->clip_rect.height)
+ stream->overscan.bottom = stream->public.timing.v_addressable -
+ dal_fixed31_32_floor(dal_fixed31_32_div(
+ dal_fixed31_32_from_int(
+ stream->viewport.height),
+ stream->ratios.vert)) -
+ stream->overscan.top;
+
+
+ /* TODO: Add timing overscan to finalize overscan calculation*/
+}
+
+static void calculate_scaling_ratios(
+ const struct dc_surface *surface,
+ struct core_stream *stream)
+{
+ const uint32_t in_w = stream->public.src.width;
+ const uint32_t in_h = stream->public.src.height;
+ const uint32_t out_w = stream->public.dst.width;
+ const uint32_t out_h = stream->public.dst.height;
+
+ stream->ratios.horz = dal_fixed31_32_from_fraction(
+ surface->src_rect.width,
+ surface->dst_rect.width);
+ stream->ratios.vert = dal_fixed31_32_from_fraction(
+ surface->src_rect.height,
+ surface->dst_rect.height);
+
+ if (surface->stereo_format == PLANE_STEREO_FORMAT_SIDE_BY_SIDE)
+ stream->ratios.horz.value *= 2;
+ else if (surface->stereo_format
+ == PLANE_STEREO_FORMAT_TOP_AND_BOTTOM)
+ stream->ratios.vert.value *= 2;
+
+ stream->ratios.vert.value = div64_s64(stream->ratios.vert.value * in_h,
+ out_h);
+ stream->ratios.horz.value = div64_s64(stream->ratios.horz.value * in_w ,
+ out_w);
+
+ stream->ratios.horz_c = stream->ratios.horz;
+ stream->ratios.vert_c = stream->ratios.vert;
+
+ if (stream->format == PIXEL_FORMAT_420BPP12) {
+ stream->ratios.horz_c.value /= 2;
+ stream->ratios.vert_c.value /= 2;
+ } else if (stream->format == PIXEL_FORMAT_422BPP16) {
+ stream->ratios.horz_c.value /= 2;
+ }
+}
+
+/*TODO: per pipe not per stream*/
+void build_scaling_params(
+ const struct dc_surface *surface,
+ struct core_stream *stream)
+{
+ /* Important: scaling ratio calculation requires pixel format,
+ * overscan calculation requires scaling ratios and viewport
+ * and lb depth/taps calculation requires overscan. Call sequence
+ * is therefore important */
+ stream->format = convert_pixel_format_to_dalsurface(surface->format);
+
+ calculate_viewport(surface, stream);
+
+ calculate_scaling_ratios(surface, stream);
+
+ calculate_overscan(surface, stream);
+
+ /* Check if scaling is required update taps if not */
+ if (dal_fixed31_32_u2d19(stream->ratios.horz) == 1 << 19)
+ stream->taps.h_taps = 1;
+ else
+ stream->taps.h_taps = surface->scaling_quality.h_taps;
+
+ if (dal_fixed31_32_u2d19(stream->ratios.horz_c) == 1 << 19)
+ stream->taps.h_taps_c = 1;
+ else
+ stream->taps.h_taps_c = surface->scaling_quality.h_taps_c;
+
+ if (dal_fixed31_32_u2d19(stream->ratios.vert) == 1 << 19)
+ stream->taps.v_taps = 1;
+ else
+ stream->taps.v_taps = surface->scaling_quality.v_taps;
+
+ if (dal_fixed31_32_u2d19(stream->ratios.vert_c) == 1 << 19)
+ stream->taps.v_taps_c = 1;
+ else
+ stream->taps.v_taps_c = surface->scaling_quality.v_taps_c;
+
+ dal_logger_write(stream->ctx->logger,
+ LOG_MAJOR_DCP,
+ LOG_MINOR_DCP_SCALER,
+ "%s: Overscan:\n bot:%d left:%d right:%d "
+ "top:%d\nViewport:\nheight:%d width:%d x:%d "
+ "y:%d\n dst_rect:\nheight:%d width:%d x:%d "
+ "y:%d\n",
+ __func__,
+ stream->overscan.bottom,
+ stream->overscan.left,
+ stream->overscan.right,
+ stream->overscan.top,
+ stream->viewport.height,
+ stream->viewport.width,
+ stream->viewport.x,
+ stream->viewport.y,
+ surface->dst_rect.height,
+ surface->dst_rect.width,
+ surface->dst_rect.x,
+ surface->dst_rect.y);
+}
+
+void build_scaling_params_for_context(
+ const struct dc *dc,
+ struct validate_context *context)
+{
+ uint8_t i, j, k;
+ for (i = 0; i < context->target_count; i++) {
+ struct core_target *target = context->targets[i];
+ if (context->target_flags[i].unchanged)
+ continue;
+ for (j = 0; j < target->status.surface_count; j++) {
+ const struct dc_surface *surface =
+ target->status.surfaces[j];
+ for (k = 0; k < target->public.stream_count; k++) {
+ struct core_stream *stream =
+ DC_STREAM_TO_CORE(
+ target->public.streams[k]);
+
+ build_scaling_params(surface, stream);
+ }
+ }
+ }
+}
+
+bool logical_attach_surfaces_to_target(
+ struct dc_surface *surfaces[],
+ uint8_t surface_count,
+ struct dc_target *dc_target)
+{
+ uint8_t i;
+ struct core_target *target = DC_TARGET_TO_CORE(dc_target);
+
+ if (surface_count > MAX_SURFACE_NUM) {
+ dm_error("Surface: can not attach %d surfaces! Maximum is: %d\n",
+ surface_count, MAX_SURFACE_NUM);
+ return false;
+ }
+
+ for (i = 0; i < target->status.surface_count; i++)
+ dc_surface_release(target->status.surfaces[i]);
+
+ for (i = 0; i < surface_count; i++) {
+ struct core_surface *surface = DC_SURFACE_TO_CORE(surfaces[i]);
+ surface->status.dc_target = &target->public;
+ target->status.surfaces[i] = surfaces[i];
+ dc_surface_retain(target->status.surfaces[i]);
+ }
+ target->status.surface_count = surface_count;
+
+ return true;
+}
+
+static uint32_t get_min_vblank_time_us(const struct validate_context *context)
+{
+ uint8_t i, j;
+ uint32_t min_vertical_blank_time = -1;
+
+ for (i = 0; i < context->target_count; i++) {
+ const struct core_target *target = context->targets[i];
+
+ for (j = 0; j < target->public.stream_count; j++) {
+ const struct dc_stream *stream =
+ target->public.streams[j];
+ uint32_t vertical_blank_in_pixels = 0;
+ uint32_t vertical_blank_time = 0;
+
+ vertical_blank_in_pixels = stream->timing.h_total *
+ (stream->timing.v_total
+ - stream->timing.v_addressable);
+ vertical_blank_time = vertical_blank_in_pixels
+ * 1000 / stream->timing.pix_clk_khz;
+ if (min_vertical_blank_time > vertical_blank_time)
+ min_vertical_blank_time = vertical_blank_time;
+ }
+ }
+ return min_vertical_blank_time;
+}
+
+static void fill_display_configs(
+ const struct validate_context *context,
+ struct dc_pp_display_configuration *pp_display_cfg)
+{
+ uint8_t i, j;
+ uint8_t num_cfgs = 0;
+
+ for (i = 0; i < context->target_count; i++) {
+ const struct core_target *target = context->targets[i];
+
+ for (j = 0; j < target->public.stream_count; j++) {
+ const struct core_stream *stream =
+ DC_STREAM_TO_CORE(target->public.streams[j]);
+ struct dc_pp_single_disp_config *cfg =
+ &pp_display_cfg->disp_configs[num_cfgs];
+
+ num_cfgs++;
+ cfg->signal = stream->signal;
+ cfg->pipe_idx = stream->opp->inst;
+ cfg->src_height = stream->public.src.height;
+ cfg->src_width = stream->public.src.width;
+ cfg->ddi_channel_mapping =
+ stream->sink->link->ddi_channel_mapping.raw;
+ cfg->transmitter =
+ stream->sink->link->link_enc->transmitter;
+ cfg->link_settings =
+ stream->sink->link->cur_link_settings;
+ cfg->sym_clock = stream->public.timing.pix_clk_khz;
+ switch (stream->public.timing.display_color_depth) {
+ case COLOR_DEPTH_101010:
+ cfg->sym_clock = (cfg->sym_clock * 30) / 24;
+ break;
+ case COLOR_DEPTH_121212:
+ cfg->sym_clock = (cfg->sym_clock * 36) / 24;
+ break;
+ case COLOR_DEPTH_161616:
+ cfg->sym_clock = (cfg->sym_clock * 48) / 24;
+ break;
+ default:
+ break;
+ }
+ /* TODO: unhardcode*/
+ cfg->v_refresh = 60;
+ }
+ }
+ pp_display_cfg->display_count = num_cfgs;
+}
+
+void pplib_apply_safe_state(
+ const struct dc *dc)
+{
+ dm_pp_apply_safe_state(dc->ctx);
+}
+
+void pplib_apply_display_requirements(
+ const struct dc *dc,
+ const struct validate_context *context)
+{
+ struct dc_pp_display_configuration pp_display_cfg = { 0 };
+
+ pp_display_cfg.all_displays_in_sync =
+ context->bw_results.all_displays_in_sync;
+ pp_display_cfg.nb_pstate_switch_disable =
+ context->bw_results.nbp_state_change_enable == false;
+ pp_display_cfg.cpu_cc6_disable =
+ context->bw_results.cpuc_state_change_enable == false;
+ pp_display_cfg.cpu_pstate_disable =
+ context->bw_results.cpup_state_change_enable == false;
+ pp_display_cfg.cpu_pstate_separation_time =
+ context->bw_results.required_blackout_duration_us;
+
+ pp_display_cfg.min_memory_clock_khz = context->bw_results.required_yclk
+ / MEMORY_TYPE_MULTIPLIER;
+ pp_display_cfg.min_engine_clock_khz = context->bw_results.required_sclk;
+ pp_display_cfg.min_engine_clock_deep_sleep_khz
+ = context->bw_results.required_sclk_deep_sleep;
+
+ pp_display_cfg.avail_mclk_switch_time_us =
+ get_min_vblank_time_us(context);
+ pp_display_cfg.avail_mclk_switch_time_in_disp_active_us = 0;
+
+ pp_display_cfg.disp_clk_khz = context->bw_results.dispclk_khz;
+
+ fill_display_configs(context, &pp_display_cfg);
+
+ /* TODO: is this still applicable?*/
+ if (pp_display_cfg.display_count == 1) {
+ const struct dc_crtc_timing *timing =
+ &context->targets[0]->public.streams[0]->timing;
+
+ pp_display_cfg.crtc_index =
+ pp_display_cfg.disp_configs[0].pipe_idx;
+ pp_display_cfg.line_time_in_us = timing->h_total * 1000
+ / timing->pix_clk_khz;
+ }
+
+ dm_pp_apply_display_requirements(dc->ctx, &pp_display_cfg);
+}
+
+/* Maximum TMDS single link pixel clock 165MHz */
+#define TMDS_MAX_PIXEL_CLOCK_IN_KHZ 165000
+
+static void attach_stream_to_controller(
+ struct resource_context *res_ctx,
+ struct core_stream *stream)
+{
+ res_ctx->controller_ctx[stream->controller_idx].stream = stream;
+}
+
+static void set_stream_engine_in_use(
+ struct resource_context *res_ctx,
+ struct stream_encoder *stream_enc)
+{
+ int i;
+
+ for (i = 0; i < res_ctx->pool.stream_enc_count; i++) {
+ if (res_ctx->pool.stream_enc[i] == stream_enc)
+ res_ctx->is_stream_enc_acquired[i] = true;
+ }
+}
+
+/* TODO: release audio object */
+static void set_audio_in_use(
+ struct resource_context *res_ctx,
+ struct audio *audio)
+{
+ int i;
+ for (i = 0; i < res_ctx->pool.audio_count; i++) {
+ if (res_ctx->pool.audios[i] == audio) {
+ res_ctx->is_audio_acquired[i] = true;
+ }
+ }
+}
+
+static bool assign_first_free_controller(
+ struct resource_context *res_ctx,
+ struct core_stream *stream)
+{
+ uint8_t i;
+ for (i = 0; i < res_ctx->pool.controller_count; i++) {
+ if (!res_ctx->controller_ctx[i].stream) {
+ stream->tg = res_ctx->pool.timing_generators[i];
+ stream->mi = res_ctx->pool.mis[i];
+ stream->ipp = res_ctx->pool.ipps[i];
+ stream->xfm = res_ctx->pool.transforms[i];
+ stream->opp = res_ctx->pool.opps[i];
+ stream->controller_idx = i;
+ stream->dis_clk = res_ctx->pool.display_clock;
+ return true;
+ }
+ }
+ return false;
+}
+
+static struct stream_encoder *find_first_free_match_stream_enc_for_link(
+ struct resource_context *res_ctx,
+ struct core_link *link)
+{
+ uint8_t i;
+ int8_t j = -1;
+ const struct dc_sink *sink = NULL;
+
+ for (i = 0; i < res_ctx->pool.stream_enc_count; i++) {
+ if (!res_ctx->is_stream_enc_acquired[i] &&
+ res_ctx->pool.stream_enc[i]) {
+ /* Store first available for MST second display
+ * in daisy chain use case */
+ j = i;
+ if (res_ctx->pool.stream_enc[i]->id ==
+ link->link_enc->preferred_engine)
+ return res_ctx->pool.stream_enc[i];
+ }
+ }
+
+ /*
+ * below can happen in cases when stream encoder is acquired:
+ * 1) for second MST display in chain, so preferred engine already
+ * acquired;
+ * 2) for another link, which preferred engine already acquired by any
+ * MST configuration.
+ *
+ * If signal is of DP type and preferred engine not found, return last available
+ *
+ * TODO - This is just a patch up and a generic solution is
+ * required for non DP connectors.
+ */
+
+ sink = link->public.local_sink ? link->public.local_sink : link->public.remote_sinks[0];
+
+ if (sink && j >= 0 && dc_is_dp_signal(sink->sink_signal))
+ return res_ctx->pool.stream_enc[j];
+
+ return NULL;
+}
+
+static struct audio *find_first_free_audio(struct resource_context *res_ctx)
+{
+ int i;
+ for (i = 0; i < res_ctx->pool.audio_count; i++) {
+ if (res_ctx->is_audio_acquired[i] == false) {
+ return res_ctx->pool.audios[i];
+ }
+ }
+
+ return 0;
+}
+
+static bool check_timing_change(struct core_stream *cur_stream,
+ struct core_stream *new_stream)
+{
+ if (cur_stream == NULL)
+ return true;
+
+ /* If sink pointer changed, it means this is a hotplug, we should do
+ * full hw setting.
+ */
+ if (cur_stream->sink != new_stream->sink)
+ return true;
+
+ return !is_same_timing(
+ &cur_stream->public.timing,
+ &new_stream->public.timing);
+}
+
+static void set_stream_signal(struct core_stream *stream)
+{
+ struct dc_sink *dc_sink = (struct dc_sink *)stream->public.sink;
+
+ /* For asic supports dual link DVI, we should adjust signal type
+ * based on timing pixel clock. If pixel clock more than 165Mhz,
+ * signal is dual link, otherwise, single link.
+ */
+ if (dc_sink->sink_signal == SIGNAL_TYPE_DVI_SINGLE_LINK ||
+ dc_sink->sink_signal == SIGNAL_TYPE_DVI_DUAL_LINK) {
+ if (stream->public.timing.pix_clk_khz >
+ TMDS_MAX_PIXEL_CLOCK_IN_KHZ)
+ dc_sink->sink_signal = SIGNAL_TYPE_DVI_DUAL_LINK;
+ else
+ dc_sink->sink_signal = SIGNAL_TYPE_DVI_SINGLE_LINK;
+ }
+
+ stream->signal = dc_sink->sink_signal;
+}
+
+enum dc_status map_resources(
+ const struct dc *dc,
+ struct validate_context *context)
+{
+ uint8_t i, j;
+
+ /* mark resources used for targets that are already active */
+ for (i = 0; i < context->target_count; i++) {
+ struct core_target *target = context->targets[i];
+
+ if (!context->target_flags[i].unchanged)
+ continue;
+
+ for (j = 0; j < target->public.stream_count; j++) {
+ struct core_stream *stream =
+ DC_STREAM_TO_CORE(target->public.streams[j]);
+
+ attach_stream_to_controller(
+ &context->res_ctx,
+ stream);
+
+ set_stream_engine_in_use(
+ &context->res_ctx,
+ stream->stream_enc);
+
+ reference_clock_source(
+ &context->res_ctx,
+ stream->clock_source);
+
+ if (stream->audio) {
+ set_audio_in_use(&context->res_ctx,
+ stream->audio);
+ }
+ }
+ }
+
+ /* acquire new resources */
+ for (i = 0; i < context->target_count; i++) {
+ struct core_target *target = context->targets[i];
+
+ if (context->target_flags[i].unchanged)
+ continue;
+
+ for (j = 0; j < target->public.stream_count; j++) {
+ struct core_stream *stream =
+ DC_STREAM_TO_CORE(target->public.streams[j]);
+ struct core_stream *curr_stream;
+
+ if (!assign_first_free_controller(
+ &context->res_ctx, stream))
+ return DC_NO_CONTROLLER_RESOURCE;
+
+ attach_stream_to_controller(&context->res_ctx, stream);
+
+ set_stream_signal(stream);
+
+ curr_stream =
+ dc->current_context.res_ctx.controller_ctx
+ [stream->controller_idx].stream;
+ context->res_ctx.controller_ctx[stream->controller_idx]
+ .flags.timing_changed =
+ check_timing_change(curr_stream, stream);
+
+ stream->stream_enc =
+ find_first_free_match_stream_enc_for_link(
+ &context->res_ctx,
+ stream->sink->link);
+
+ if (!stream->stream_enc)
+ return DC_NO_STREAM_ENG_RESOURCE;
+
+ set_stream_engine_in_use(
+ &context->res_ctx,
+ stream->stream_enc);
+
+ /* TODO: Add check if ASIC support and EDID audio */
+ if (!stream->sink->converter_disable_audio &&
+ dc_is_audio_capable_signal(
+ stream->signal)) {
+ stream->audio = find_first_free_audio(
+ &context->res_ctx);
+
+ if (!stream->audio)
+ return DC_NO_STREAM_AUDIO_RESOURCE;
+
+ set_audio_in_use(&context->res_ctx,
+ stream->audio);
+ }
+ }
+ }
+
+ return DC_OK;
+}
+
+static enum ds_color_space build_default_color_space(
+ struct core_stream *stream)
+{
+ enum ds_color_space color_space =
+ DS_COLOR_SPACE_SRGB_FULLRANGE;
+ struct dc_crtc_timing *timing = &stream->public.timing;
+
+ switch (stream->signal) {
+ /* TODO: implement other signal color space setting */
+ case SIGNAL_TYPE_DISPLAY_PORT:
+ case SIGNAL_TYPE_DISPLAY_PORT_MST:
+ case SIGNAL_TYPE_EDP:
+ break;
+ case SIGNAL_TYPE_HDMI_TYPE_A:
+ {
+ uint32_t pix_clk_khz;
+
+ if (timing->pixel_encoding == PIXEL_ENCODING_YCBCR422 &&
+ timing->pixel_encoding == PIXEL_ENCODING_YCBCR444) {
+ if (timing->timing_standard ==
+ TIMING_STANDARD_CEA770 &&
+ timing->timing_standard ==
+ TIMING_STANDARD_CEA861)
+ color_space = DS_COLOR_SPACE_SRGB_FULLRANGE;
+
+ pix_clk_khz = timing->pix_clk_khz / 10;
+ if (timing->h_addressable == 640 &&
+ timing->v_addressable == 480 &&
+ (pix_clk_khz == 2520 || pix_clk_khz == 2517))
+ color_space = DS_COLOR_SPACE_SRGB_FULLRANGE;
+ } else {
+ if (timing->timing_standard ==
+ TIMING_STANDARD_CEA770 ||
+ timing->timing_standard ==
+ TIMING_STANDARD_CEA861) {
+
+ color_space =
+ (timing->pix_clk_khz > PIXEL_CLOCK) ?
+ DS_COLOR_SPACE_YCBCR709 :
+ DS_COLOR_SPACE_YCBCR601;
+ }
+ }
+ break;
+ }
+ default:
+ switch (timing->pixel_encoding) {
+ case PIXEL_ENCODING_YCBCR422:
+ case PIXEL_ENCODING_YCBCR444:
+ if (timing->pix_clk_khz > PIXEL_CLOCK)
+ color_space = DS_COLOR_SPACE_YCBCR709;
+ else
+ color_space = DS_COLOR_SPACE_YCBCR601;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ return color_space;
+}
+
+static void translate_info_frame(const struct hw_info_frame *hw_info_frame,
+ struct encoder_info_frame *encoder_info_frame)
+{
+ dm_memset(
+ encoder_info_frame, 0, sizeof(struct encoder_info_frame));
+
+ /* For gamut we recalc checksum */
+ if (hw_info_frame->gamut_packet.valid) {
+ uint8_t chk_sum = 0;
+ uint8_t *ptr;
+ uint8_t i;
+
+ dm_memmove(
+ &encoder_info_frame->gamut,
+ &hw_info_frame->gamut_packet,
+ sizeof(struct hw_info_packet));
+
+ /*start of the Gamut data. */
+ ptr = &encoder_info_frame->gamut.sb[3];
+
+ for (i = 0; i <= encoder_info_frame->gamut.sb[1]; i++)
+ chk_sum += ptr[i];
+
+ encoder_info_frame->gamut.sb[2] = (uint8_t) (0x100 - chk_sum);
+ }
+
+ if (hw_info_frame->avi_info_packet.valid) {
+ dm_memmove(
+ &encoder_info_frame->avi,
+ &hw_info_frame->avi_info_packet,
+ sizeof(struct hw_info_packet));
+ }
+
+ if (hw_info_frame->vendor_info_packet.valid) {
+ dm_memmove(
+ &encoder_info_frame->vendor,
+ &hw_info_frame->vendor_info_packet,
+ sizeof(struct hw_info_packet));
+ }
+
+ if (hw_info_frame->spd_packet.valid) {
+ dm_memmove(
+ &encoder_info_frame->spd,
+ &hw_info_frame->spd_packet,
+ sizeof(struct hw_info_packet));
+ }
+
+ if (hw_info_frame->vsc_packet.valid) {
+ dm_memmove(
+ &encoder_info_frame->vsc,
+ &hw_info_frame->vsc_packet,
+ sizeof(struct hw_info_packet));
+ }
+}
+
+static void set_avi_info_frame(struct hw_info_packet *info_packet,
+ struct core_stream *stream)
+{
+ enum ds_color_space color_space = DS_COLOR_SPACE_UNKNOWN;
+ struct info_frame info_frame = { {0} };
+ uint32_t pixel_encoding = 0;
+ enum scanning_type scan_type = SCANNING_TYPE_NODATA;
+ enum dc_aspect_ratio aspect = ASPECT_RATIO_NO_DATA;
+ bool itc = false;
+ uint8_t cn0_cn1 = 0;
+ uint8_t *check_sum = NULL;
+ uint8_t byte_index = 0;
+
+ if (info_packet == NULL)
+ return;
+
+ color_space = build_default_color_space(stream);
+
+ /* Initialize header */
+ info_frame.avi_info_packet.info_packet_hdmi.bits.header.
+ info_frame_type = INFO_FRAME_AVI;
+ /* InfoFrameVersion_3 is defined by CEA861F (Section 6.4), but shall
+ * not be used in HDMI 2.0 (Section 10.1) */
+ info_frame.avi_info_packet.info_packet_hdmi.bits.header.version =
+ INFO_FRAME_VERSION_2;
+ info_frame.avi_info_packet.info_packet_hdmi.bits.header.length =
+ INFO_FRAME_SIZE_AVI;
+
+ /* IDO-defined (Y2,Y1,Y0 = 1,1,1) shall not be used by devices built
+ * according to HDMI 2.0 spec (Section 10.1)
+ * Add "case PixelEncoding_YCbCr420: pixelEncoding = 3; break;"
+ * when YCbCr 4:2:0 is supported by DAL hardware. */
+
+ switch (stream->public.timing.pixel_encoding) {
+ case PIXEL_ENCODING_YCBCR422:
+ pixel_encoding = 1;
+ break;
+
+ case PIXEL_ENCODING_YCBCR444:
+ pixel_encoding = 2;
+ break;
+
+ case PIXEL_ENCODING_RGB:
+ default:
+ pixel_encoding = 0;
+ }
+
+ /* Y0_Y1_Y2 : The pixel encoding */
+ /* H14b AVI InfoFrame has extension on Y-field from 2 bits to 3 bits */
+ info_frame.avi_info_packet.info_packet_hdmi.bits.Y0_Y1_Y2 =
+ pixel_encoding;
+
+
+ /* A0 = 1 Active Format Information valid */
+ info_frame.avi_info_packet.info_packet_hdmi.bits.A0 =
+ ACTIVE_FORMAT_VALID;
+
+ /* B0, B1 = 3; Bar info data is valid */
+ info_frame.avi_info_packet.info_packet_hdmi.bits.B0_B1 =
+ BAR_INFO_BOTH_VALID;
+
+ info_frame.avi_info_packet.info_packet_hdmi.bits.SC0_SC1 =
+ PICTURE_SCALING_UNIFORM;
+
+ /* S0, S1 : Underscan / Overscan */
+ /* TODO: un-hardcode scan type */
+ scan_type = SCANNING_TYPE_UNDERSCAN;
+ info_frame.avi_info_packet.info_packet_hdmi.bits.S0_S1 = scan_type;
+
+ /* C0, C1 : Colorimetry */
+ if (color_space == DS_COLOR_SPACE_YCBCR709)
+ info_frame.avi_info_packet.info_packet_hdmi.bits.C0_C1 =
+ COLORIMETRY_ITU709;
+ else if (color_space == DS_COLOR_SPACE_YCBCR601)
+ info_frame.avi_info_packet.info_packet_hdmi.bits.C0_C1 =
+ COLORIMETRY_ITU601;
+ else
+ info_frame.avi_info_packet.info_packet_hdmi.bits.C0_C1 =
+ COLORIMETRY_NO_DATA;
+
+
+ /* TODO: un-hardcode aspect ratio */
+ aspect = stream->public.timing.aspect_ratio;
+
+ switch (aspect) {
+ case ASPECT_RATIO_4_3:
+ case ASPECT_RATIO_16_9:
+ info_frame.avi_info_packet.info_packet_hdmi.bits.M0_M1 = aspect;
+ break;
+
+ case ASPECT_RATIO_NO_DATA:
+ case ASPECT_RATIO_64_27:
+ case ASPECT_RATIO_256_135:
+ default:
+ info_frame.avi_info_packet.info_packet_hdmi.bits.M0_M1 = 0;
+ }
+
+ /* Active Format Aspect ratio - same as Picture Aspect Ratio. */
+ info_frame.avi_info_packet.info_packet_hdmi.bits.R0_R3 =
+ ACTIVE_FORMAT_ASPECT_RATIO_SAME_AS_PICTURE;
+
+ /* TODO: un-hardcode cn0_cn1 and itc */
+ cn0_cn1 = 0;
+ itc = false;
+
+ if (itc) {
+ info_frame.avi_info_packet.info_packet_hdmi.bits.ITC = 1;
+ info_frame.avi_info_packet.info_packet_hdmi.bits.CN0_CN1 =
+ cn0_cn1;
+ }
+
+ /* TODO: un-hardcode q0_q1 */
+ if (color_space == DS_COLOR_SPACE_SRGB_FULLRANGE)
+ info_frame.avi_info_packet.info_packet_hdmi.bits.Q0_Q1 =
+ RGB_QUANTIZATION_FULL_RANGE;
+ else if (color_space == DS_COLOR_SPACE_SRGB_LIMITEDRANGE)
+ info_frame.avi_info_packet.info_packet_hdmi.bits.Q0_Q1 =
+ RGB_QUANTIZATION_LIMITED_RANGE;
+ else
+ info_frame.avi_info_packet.info_packet_hdmi.bits.Q0_Q1 =
+ RGB_QUANTIZATION_DEFAULT_RANGE;
+
+ /* TODO : We should handle YCC quantization,
+ * but we do not have matrix calculation */
+ info_frame.avi_info_packet.info_packet_hdmi.bits.YQ0_YQ1 =
+ YYC_QUANTIZATION_LIMITED_RANGE;
+
+ info_frame.avi_info_packet.info_packet_hdmi.bits.VIC0_VIC7 =
+ stream->public.timing.vic;
+
+ /* pixel repetition
+ * PR0 - PR3 start from 0 whereas pHwPathMode->mode.timing.flags.pixel
+ * repetition start from 1 */
+ info_frame.avi_info_packet.info_packet_hdmi.bits.PR0_PR3 = 0;
+
+ /* Bar Info
+ * barTop: Line Number of End of Top Bar.
+ * barBottom: Line Number of Start of Bottom Bar.
+ * barLeft: Pixel Number of End of Left Bar.
+ * barRight: Pixel Number of Start of Right Bar. */
+ info_frame.avi_info_packet.info_packet_hdmi.bits.bar_top =
+ stream->public.timing.v_border_top;
+ info_frame.avi_info_packet.info_packet_hdmi.bits.bar_bottom =
+ (stream->public.timing.v_border_top
+ - stream->public.timing.v_border_bottom + 1);
+ info_frame.avi_info_packet.info_packet_hdmi.bits.bar_left =
+ stream->public.timing.h_border_left;
+ info_frame.avi_info_packet.info_packet_hdmi.bits.bar_right =
+ (stream->public.timing.h_total
+ - stream->public.timing.h_border_right + 1);
+
+ /* check_sum - Calculate AFMT_AVI_INFO0 ~ AFMT_AVI_INFO3 */
+ check_sum =
+ &info_frame.
+ avi_info_packet.info_packet_hdmi.packet_raw_data.sb[0];
+ *check_sum = INFO_FRAME_AVI + INFO_FRAME_SIZE_AVI
+ + INFO_FRAME_VERSION_2;
+
+ for (byte_index = 1; byte_index <= INFO_FRAME_SIZE_AVI; byte_index++)
+ *check_sum += info_frame.avi_info_packet.info_packet_hdmi.
+ packet_raw_data.sb[byte_index];
+
+ /* one byte complement */
+ *check_sum = (uint8_t) (0x100 - *check_sum);
+
+ /* Store in hw_path_mode */
+ info_packet->hb0 =
+ info_frame.avi_info_packet.info_packet_hdmi.packet_raw_data.hb0;
+ info_packet->hb1 =
+ info_frame.avi_info_packet.info_packet_hdmi.packet_raw_data.hb1;
+ info_packet->hb2 =
+ info_frame.avi_info_packet.info_packet_hdmi.packet_raw_data.hb2;
+
+ for (byte_index = 0; byte_index < sizeof(info_packet->sb); byte_index++)
+ info_packet->sb[byte_index] = info_frame.avi_info_packet.
+ info_packet_hdmi.packet_raw_data.sb[byte_index];
+
+ info_packet->valid = true;
+}
+
+static void set_vendor_info_packet(struct core_stream *stream,
+ struct hw_info_packet *info_packet)
+{
+ uint32_t length = 0;
+ bool hdmi_vic_mode = false;
+ uint8_t checksum = 0;
+ uint32_t i = 0;
+ enum dc_timing_3d_format format;
+
+ ASSERT_CRITICAL(stream != NULL);
+ ASSERT_CRITICAL(info_packet != NULL);
+
+ format = stream->public.timing.timing_3d_format;
+
+ /* Can be different depending on packet content */
+ length = 5;
+
+ if (stream->public.timing.hdmi_vic != 0
+ && stream->public.timing.h_total >= 3840
+ && stream->public.timing.v_total >= 2160)
+ hdmi_vic_mode = true;
+
+ /* According to HDMI 1.4a CTS, VSIF should be sent
+ * for both 3D stereo and HDMI VIC modes.
+ * For all other modes, there is no VSIF sent. */
+
+ if (format == TIMING_3D_FORMAT_NONE && !hdmi_vic_mode)
+ return;
+
+ /* 24bit IEEE Registration identifier (0x000c03). LSB first. */
+ info_packet->sb[1] = 0x03;
+ info_packet->sb[2] = 0x0C;
+ info_packet->sb[3] = 0x00;
+
+ /*PB4: 5 lower bytes = 0 (reserved). 3 higher bits = HDMI_Video_Format.
+ * The value for HDMI_Video_Format are:
+ * 0x0 (0b000) - No additional HDMI video format is presented in this
+ * packet
+ * 0x1 (0b001) - Extended resolution format present. 1 byte of HDMI_VIC
+ * parameter follows
+ * 0x2 (0b010) - 3D format indication present. 3D_Structure and
+ * potentially 3D_Ext_Data follows
+ * 0x3..0x7 (0b011..0b111) - reserved for future use */
+ if (format != TIMING_3D_FORMAT_NONE)
+ info_packet->sb[4] = (2 << 5);
+ else if (hdmi_vic_mode)
+ info_packet->sb[4] = (1 << 5);
+
+ /* PB5: If PB4 claims 3D timing (HDMI_Video_Format = 0x2):
+ * 4 lower bites = 0 (reserved). 4 higher bits = 3D_Structure.
+ * The value for 3D_Structure are:
+ * 0x0 - Frame Packing
+ * 0x1 - Field Alternative
+ * 0x2 - Line Alternative
+ * 0x3 - Side-by-Side (full)
+ * 0x4 - L + depth
+ * 0x5 - L + depth + graphics + graphics-depth
+ * 0x6 - Top-and-Bottom
+ * 0x7 - Reserved for future use
+ * 0x8 - Side-by-Side (Half)
+ * 0x9..0xE - Reserved for future use
+ * 0xF - Not used */
+ switch (format) {
+ case TIMING_3D_FORMAT_HW_FRAME_PACKING:
+ case TIMING_3D_FORMAT_SW_FRAME_PACKING:
+ info_packet->sb[5] = (0x0 << 4);
+ break;
+
+ case TIMING_3D_FORMAT_SIDE_BY_SIDE:
+ case TIMING_3D_FORMAT_SBS_SW_PACKED:
+ info_packet->sb[5] = (0x8 << 4);
+ length = 6;
+ break;
+
+ case TIMING_3D_FORMAT_TOP_AND_BOTTOM:
+ case TIMING_3D_FORMAT_TB_SW_PACKED:
+ info_packet->sb[5] = (0x6 << 4);
+ break;
+
+ default:
+ break;
+ }
+
+ /*PB5: If PB4 is set to 0x1 (extended resolution format)
+ * fill PB5 with the correct HDMI VIC code */
+ if (hdmi_vic_mode)
+ info_packet->sb[5] = stream->public.timing.hdmi_vic;
+
+ /* Header */
+ info_packet->hb0 = 0x81; /* VSIF packet type. */
+ info_packet->hb1 = 0x01; /* Version */
+
+ /* 4 lower bits = Length, 4 higher bits = 0 (reserved) */
+ info_packet->hb2 = (uint8_t) (length);
+
+ /* Calculate checksum */
+ checksum = 0;
+ checksum += info_packet->hb0;
+ checksum += info_packet->hb1;
+ checksum += info_packet->hb2;
+
+ for (i = 1; i <= length; i++)
+ checksum += info_packet->sb[i];
+
+ info_packet->sb[0] = (uint8_t) (0x100 - checksum);
+
+ info_packet->valid = true;
+}
+
+void build_info_frame(struct core_stream *stream)
+{
+ enum signal_type signal = SIGNAL_TYPE_NONE;
+ struct hw_info_frame info_frame = { { 0 } };
+
+ /* default all packets to invalid */
+ info_frame.avi_info_packet.valid = false;
+ info_frame.gamut_packet.valid = false;
+ info_frame.vendor_info_packet.valid = false;
+ info_frame.spd_packet.valid = false;
+ info_frame.vsc_packet.valid = false;
+
+ signal = stream->sink->public.sink_signal;
+
+ /* HDMi and DP have different info packets*/
+ if (signal == SIGNAL_TYPE_HDMI_TYPE_A) {
+ set_avi_info_frame(&info_frame.avi_info_packet,
+ stream);
+ set_vendor_info_packet(stream, &info_frame.vendor_info_packet);
+ }
+
+ translate_info_frame(&info_frame,
+ &stream->encoder_info_frame);
+}
new file mode 100644
@@ -0,0 +1,116 @@
+/*
+ * 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
+ *
+ */
+
+#include "dm_services.h"
+#include "dm_helpers.h"
+#include "core_types.h"
+
+/*******************************************************************************
+ * Private definitions
+ ******************************************************************************/
+
+struct sink {
+ struct core_sink protected;
+ int ref_count;
+};
+
+#define DC_SINK_TO_SINK(dc_sink) \
+ container_of(dc_sink, struct sink, protected.public)
+
+/*******************************************************************************
+ * Private functions
+ ******************************************************************************/
+
+static void destruct(struct sink *sink)
+{
+
+}
+
+static bool construct(struct sink *sink, const struct dc_sink_init_data *init_params)
+{
+
+ struct core_link *core_link = DC_LINK_TO_LINK(init_params->link);
+
+ sink->protected.public.sink_signal = init_params->sink_signal;
+ sink->protected.link = core_link;
+ sink->protected.ctx = core_link->ctx;
+ sink->protected.dongle_max_pix_clk = init_params->dongle_max_pix_clk;
+ sink->protected.converter_disable_audio =
+ init_params->converter_disable_audio;
+
+ return true;
+}
+
+/*******************************************************************************
+ * Public functions
+ ******************************************************************************/
+
+void dc_sink_retain(const struct dc_sink *dc_sink)
+{
+ struct sink *sink = DC_SINK_TO_SINK(dc_sink);
+
+ ++sink->ref_count;
+}
+
+void dc_sink_release(const struct dc_sink *dc_sink)
+{
+ struct core_sink *core_sink = DC_SINK_TO_CORE(dc_sink);
+ struct sink *sink = DC_SINK_TO_SINK(dc_sink);
+
+ --sink->ref_count;
+
+ if (sink->ref_count == 0) {
+ destruct(sink);
+ dm_free(core_sink->ctx, sink);
+ }
+}
+
+struct dc_sink *dc_sink_create(const struct dc_sink_init_data *init_params)
+{
+ struct core_link *core_link = DC_LINK_TO_LINK(init_params->link);
+
+ struct sink *sink = dm_alloc(core_link->ctx, sizeof(*sink));
+
+ if (NULL == sink)
+ goto alloc_fail;
+
+ if (false == construct(sink, init_params))
+ goto construct_fail;
+
+ /* TODO should we move this outside to where the assignment actually happens? */
+ dc_sink_retain(&sink->protected.public);
+
+ return &sink->protected.public;
+
+construct_fail:
+ dm_free(core_link->ctx, sink);
+
+alloc_fail:
+ return NULL;
+}
+
+/*******************************************************************************
+ * Protected functions - visible only inside of DC (not visible in DM)
+ ******************************************************************************/
new file mode 100644
@@ -0,0 +1,188 @@
+/*
+ * 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
+ *
+ */
+
+#include "dm_services.h"
+#include "dc.h"
+#include "core_types.h"
+#include "resource.h"
+
+/*******************************************************************************
+ * Private definitions
+ ******************************************************************************/
+
+struct stream {
+ struct core_stream protected;
+ int ref_count;
+};
+
+#define DC_STREAM_TO_STREAM(dc_stream) container_of(dc_stream, struct stream, protected.public)
+
+/*******************************************************************************
+ * Private functions
+ ******************************************************************************/
+static void build_bit_depth_reduction_params(
+ const struct core_stream *stream,
+ struct bit_depth_reduction_params *fmt_bit_depth)
+{
+ dm_memset(fmt_bit_depth, 0, sizeof(*fmt_bit_depth));
+
+ /*TODO: Need to un-hardcode, refer to function with same name
+ * in dal2 hw_sequencer*/
+
+ fmt_bit_depth->flags.TRUNCATE_ENABLED = 0;
+ fmt_bit_depth->flags.SPATIAL_DITHER_ENABLED = 0;
+ fmt_bit_depth->flags.FRAME_MODULATION_ENABLED = 0;
+
+ /* Diagnostics need consistent CRC of the image, that means
+ * dithering should not be enabled for Diagnostics. */
+ if (IS_DIAG_DC(stream->ctx->dce_environment) == false) {
+
+ fmt_bit_depth->flags.SPATIAL_DITHER_DEPTH = 1;
+ fmt_bit_depth->flags.SPATIAL_DITHER_ENABLED = 1;
+
+ /* frame random is on by default */
+ fmt_bit_depth->flags.FRAME_RANDOM = 1;
+ /* apply RGB dithering */
+ fmt_bit_depth->flags.RGB_RANDOM = true;
+ }
+
+ return;
+}
+
+static void setup_pixel_encoding(
+ struct clamping_and_pixel_encoding_params *clamping)
+{
+ /*TODO: Need to un-hardcode, refer to function with same name
+ * in dal2 hw_sequencer*/
+
+ clamping->pixel_encoding = PIXEL_ENCODING_RGB;
+
+ return;
+}
+
+static bool construct(struct core_stream *stream,
+ const struct dc_sink *dc_sink_data)
+{
+ uint32_t i = 0;
+
+ stream->sink = DC_SINK_TO_CORE(dc_sink_data);
+ stream->ctx = stream->sink->ctx;
+ stream->public.sink = dc_sink_data;
+
+ dc_sink_retain(dc_sink_data);
+
+ build_bit_depth_reduction_params(stream, &stream->bit_depth_params);
+ setup_pixel_encoding(&stream->clamping);
+
+ /* Copy audio modes */
+ /* TODO - Remove this translation */
+ for (i = 0; i < (dc_sink_data->edid_caps.audio_mode_count); i++)
+ {
+ stream->public.audio_info.modes[i].channel_count = dc_sink_data->edid_caps.audio_modes[i].channel_count;
+ stream->public.audio_info.modes[i].format_code = dc_sink_data->edid_caps.audio_modes[i].format_code;
+ stream->public.audio_info.modes[i].sample_rates.all = dc_sink_data->edid_caps.audio_modes[i].sample_rate;
+ stream->public.audio_info.modes[i].sample_size = dc_sink_data->edid_caps.audio_modes[i].sample_size;
+ }
+ stream->public.audio_info.mode_count = dc_sink_data->edid_caps.audio_mode_count;
+ stream->public.audio_info.audio_latency = dc_sink_data->edid_caps.audio_latency;
+ stream->public.audio_info.video_latency = dc_sink_data->edid_caps.video_latency;
+ dm_memmove(
+ stream->public.audio_info.display_name,
+ dc_sink_data->edid_caps.display_name,
+ AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS);
+ stream->public.audio_info.manufacture_id = dc_sink_data->edid_caps.manufacturer_id;
+ stream->public.audio_info.product_id = dc_sink_data->edid_caps.product_id;
+ stream->public.audio_info.flags.all = dc_sink_data->edid_caps.speaker_flags;
+
+ /* TODO - Unhardcode port_id */
+ stream->public.audio_info.port_id[0] = 0x5558859e;
+ stream->public.audio_info.port_id[1] = 0xd989449;
+
+ /* EDID CAP translation for HDMI 2.0 */
+ stream->public.timing.flags.LTE_340MCSC_SCRAMBLE = dc_sink_data->edid_caps.lte_340mcsc_scramble;
+ return true;
+}
+
+static void destruct(struct core_stream *stream)
+{
+ dc_sink_release(&stream->sink->public);
+}
+
+void dc_stream_retain(struct dc_stream *dc_stream)
+{
+ struct stream *stream = DC_STREAM_TO_STREAM(dc_stream);
+ stream->ref_count++;
+}
+
+void dc_stream_release(struct dc_stream *public)
+{
+ struct stream *stream = DC_STREAM_TO_STREAM(public);
+ struct core_stream *protected = DC_STREAM_TO_CORE(public);
+ struct dc_context *ctx = protected->ctx;
+ stream->ref_count--;
+
+ if (stream->ref_count == 0) {
+ destruct(protected);
+ dm_free(ctx, stream);
+ }
+}
+
+struct dc_stream *dc_create_stream_for_sink(const struct dc_sink *dc_sink)
+{
+ struct core_sink *sink = DC_SINK_TO_CORE(dc_sink);
+ struct stream *stream;
+
+ if (sink == NULL)
+ goto alloc_fail;
+
+ stream = dm_alloc(sink->ctx, sizeof(struct stream));
+
+ if (NULL == stream)
+ goto alloc_fail;
+
+ if (false == construct(&stream->protected, dc_sink))
+ goto construct_fail;
+
+ dc_stream_retain(&stream->protected.public);
+
+ return &stream->protected.public;
+
+construct_fail:
+ dm_free(sink->ctx, stream);
+
+alloc_fail:
+ return NULL;
+}
+
+void dc_update_stream(const struct dc_stream *dc_stream,
+ struct rect *src,
+ struct rect *dst)
+{
+ struct core_stream *stream = DC_STREAM_TO_CORE(dc_stream);
+
+ stream->public.src = *src;
+ stream->public.dst = *dst;
+}
+
new file mode 100644
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2015 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
+ *
+ */
+
+/* DC interface (public) */
+#include "dm_services.h"
+#include "dc.h"
+
+/* DC core (private) */
+#include "core_dc.h"
+#include "inc/transform.h"
+
+/*******************************************************************************
+ * Private structures
+ ******************************************************************************/
+struct surface {
+ struct core_surface protected;
+ enum dc_irq_source irq_source;
+ int ref_count;
+};
+
+#define DC_SURFACE_TO_SURFACE(dc_surface) container_of(dc_surface, struct surface, protected.public)
+#define CORE_SURFACE_TO_SURFACE(core_surface) container_of(core_surface, struct surface, protected)
+
+/*******************************************************************************
+ * Private functions
+ ******************************************************************************/
+static bool construct(struct dc_context *ctx, struct surface *surface)
+{
+ uint32_t i;
+ struct gamma_ramp *gamma =
+ &surface->protected.public.gamma_correction;
+
+ /* construct gamma default value. */
+ for (i = 0; i < NUM_OF_RAW_GAMMA_RAMP_RGB_256; i++) {
+ gamma->gamma_ramp_rgb256x3x16.red[i] =
+ (unsigned short) (i << 8);
+ gamma->gamma_ramp_rgb256x3x16.green[i] =
+ (unsigned short) (i << 8);
+ gamma->gamma_ramp_rgb256x3x16.blue[i] =
+ (unsigned short) (i << 8);
+ }
+ gamma->type = GAMMA_RAMP_TYPE_RGB256;
+ gamma->size = sizeof(gamma->gamma_ramp_rgb256x3x16);
+
+ surface->protected.ctx = ctx;
+ return true;
+}
+
+static void destruct(struct surface *surface)
+{
+}
+
+/*******************************************************************************
+ * Public functions
+ ******************************************************************************/
+void enable_surface_flip_reporting(struct dc_surface *dc_surface,
+ uint32_t controller_id)
+{
+ struct surface *surface = DC_SURFACE_TO_SURFACE(dc_surface);
+ surface->irq_source = controller_id + DC_IRQ_SOURCE_PFLIP1 - 1;
+ /*register_flip_interrupt(surface);*/
+}
+
+struct dc_surface *dc_create_surface(const struct dc *dc)
+{
+ struct surface *surface = dm_alloc(dc->ctx, sizeof(*surface));
+
+ if (NULL == surface)
+ goto alloc_fail;
+
+ if (false == construct(dc->ctx, surface))
+ goto construct_fail;
+
+ dc_surface_retain(&surface->protected.public);
+
+ return &surface->protected.public;
+
+construct_fail:
+ dm_free(dc->ctx, surface);
+
+alloc_fail:
+ return NULL;
+}
+
+void dc_surface_retain(const struct dc_surface *dc_surface)
+{
+ struct surface *surface = DC_SURFACE_TO_SURFACE(dc_surface);
+
+ ++surface->ref_count;
+}
+
+void dc_surface_release(const struct dc_surface *dc_surface)
+{
+ struct surface *surface = DC_SURFACE_TO_SURFACE(dc_surface);
+ --surface->ref_count;
+
+ if (surface->ref_count == 0) {
+ destruct(surface);
+ dm_free(surface->protected.ctx, surface);
+ }
+}
new file mode 100644
@@ -0,0 +1,548 @@
+/*
+ * 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
+ *
+ */
+
+#include "dm_services.h"
+#include "core_types.h"
+#include "hw_sequencer.h"
+#include "resource.h"
+#include "ipp.h"
+#include "timing_generator.h"
+
+#define COEFF_RANGE 3
+#define REGAMMA_COEFF_A0 31308
+#define REGAMMA_COEFF_A1 12920
+#define REGAMMA_COEFF_A2 55
+#define REGAMMA_COEFF_A3 55
+#define REGAMMA_COEFF_GAMMA 2400
+
+struct target {
+ struct core_target protected;
+ int ref_count;
+};
+
+#define DC_TARGET_TO_TARGET(dc_target) \
+ container_of(dc_target, struct target, protected.public)
+#define CORE_TARGET_TO_TARGET(core_target) \
+ container_of(core_target, struct target, protected)
+
+static void construct(
+ struct core_target *target,
+ struct dc_context *ctx,
+ struct dc_stream *dc_streams[],
+ uint8_t stream_count)
+{
+ uint8_t i;
+ for (i = 0; i < stream_count; i++) {
+ target->public.streams[i] = dc_streams[i];
+ dc_stream_retain(dc_streams[i]);
+ }
+
+ target->ctx = ctx;
+ target->public.stream_count = stream_count;
+}
+
+static void destruct(struct core_target *core_target)
+{
+ int i;
+
+ for (i = 0; i < core_target->status.surface_count; i++) {
+ dc_surface_release(core_target->status.surfaces[i]);
+ core_target->status.surfaces[i] = NULL;
+ }
+ for (i = 0; i < core_target->public.stream_count; i++) {
+ dc_stream_release(
+ (struct dc_stream *)core_target->public.streams[i]);
+ core_target->public.streams[i] = NULL;
+ }
+}
+
+void dc_target_retain(struct dc_target *dc_target)
+{
+ struct target *target = DC_TARGET_TO_TARGET(dc_target);
+
+ target->ref_count++;
+}
+
+void dc_target_release(struct dc_target *dc_target)
+{
+ struct target *target = DC_TARGET_TO_TARGET(dc_target);
+ struct core_target *protected = DC_TARGET_TO_CORE(dc_target);
+
+ ASSERT(target->ref_count > 0);
+ target->ref_count--;
+ if (target->ref_count == 0) {
+ destruct(protected);
+ dm_free(protected->ctx, target);
+ }
+}
+
+const struct dc_target_status *dc_target_get_status(
+ const struct dc_target* dc_target)
+{
+ struct core_target* target = DC_TARGET_TO_CORE(dc_target);
+ return &target->status;
+}
+
+struct dc_target *dc_create_target_for_streams(
+ struct dc_stream *dc_streams[],
+ uint8_t stream_count)
+{
+ struct core_stream *stream;
+ struct target *target;
+
+ if (0 == stream_count)
+ goto target_alloc_fail;
+
+ stream = DC_STREAM_TO_CORE(dc_streams[0]);
+
+ target = dm_alloc(stream->ctx, sizeof(struct target));
+
+ if (NULL == target)
+ goto target_alloc_fail;
+
+ construct(&target->protected, stream->ctx, dc_streams, stream_count);
+
+ dc_target_retain(&target->protected.public);
+
+ return &target->protected.public;
+
+
+target_alloc_fail:
+ return NULL;
+}
+
+static void build_gamma_params(
+ enum pixel_format pixel_format,
+ struct gamma_parameters *gamma_param)
+{
+ uint32_t i;
+
+ /* translate parameters */
+ gamma_param->surface_pixel_format = pixel_format;
+
+ gamma_param->regamma_adjust_type = GRAPHICS_REGAMMA_ADJUST_SW;
+ gamma_param->degamma_adjust_type = GRAPHICS_REGAMMA_ADJUST_SW;
+
+ gamma_param->selected_gamma_lut = GRAPHICS_GAMMA_LUT_REGAMMA;
+
+ /* TODO support non-legacy gamma */
+ gamma_param->disable_adjustments = false;
+ gamma_param->flag.bits.config_is_changed = 0;
+ gamma_param->flag.bits.regamma_update = 1;
+ gamma_param->flag.bits.gamma_update = 1;
+
+ /* Set regamma */
+ gamma_param->regamma.features.bits.GRAPHICS_DEGAMMA_SRGB = 1;
+ gamma_param->regamma.features.bits.OVERLAY_DEGAMMA_SRGB = 1;
+ gamma_param->regamma.features.bits.GAMMA_RAMP_ARRAY = 0;
+ gamma_param->regamma.features.bits.APPLY_DEGAMMA = 0;
+
+ for (i = 0; i < COEFF_RANGE; i++) {
+ gamma_param->regamma.gamma_coeff.a0[i] = REGAMMA_COEFF_A0;
+ gamma_param->regamma.gamma_coeff.a1[i] = REGAMMA_COEFF_A1;
+ gamma_param->regamma.gamma_coeff.a2[i] = REGAMMA_COEFF_A2;
+ gamma_param->regamma.gamma_coeff.a3[i] = REGAMMA_COEFF_A3;
+ gamma_param->regamma.gamma_coeff.gamma[i] = REGAMMA_COEFF_GAMMA;
+ }
+}
+
+
+static bool program_gamma(
+ struct dc_context *ctx,
+ struct dc_surface *surface,
+ struct input_pixel_processor *ipp,
+ struct output_pixel_processor *opp)
+{
+ struct gamma_parameters *gamma_param;
+ bool result= false;
+
+ gamma_param = dm_alloc(ctx, sizeof(struct gamma_parameters));
+
+ if (!gamma_param)
+ goto gamma_param_fail;
+
+ build_gamma_params(surface->format, gamma_param);
+
+ result = ctx->dc->hwss.set_gamma_ramp(ipp, opp,
+ &surface->gamma_correction,
+ gamma_param);
+
+ dm_free(ctx, gamma_param);
+
+gamma_param_fail:
+ return result;
+}
+
+static bool validate_surface_address(
+ struct dc_plane_address address)
+{
+ bool is_valid_address = false;
+
+ switch (address.type) {
+ case PLN_ADDR_TYPE_GRAPHICS:
+ if (address.grph.addr.quad_part != 0)
+ is_valid_address = true;
+ break;
+ case PLN_ADDR_TYPE_GRPH_STEREO:
+ if ((address.grph_stereo.left_addr.quad_part != 0) &&
+ (address.grph_stereo.right_addr.quad_part != 0)) {
+ is_valid_address = true;
+ }
+ break;
+ case PLN_ADDR_TYPE_VIDEO_PROGRESSIVE:
+ default:
+ /* not supported */
+ BREAK_TO_DEBUGGER();
+ break;
+ }
+
+ return is_valid_address;
+}
+
+bool dc_commit_surfaces_to_target(
+ struct dc *dc,
+ struct dc_surface *new_surfaces[],
+ uint8_t new_surface_count,
+ struct dc_target *dc_target)
+
+{
+ int i, j;
+ uint32_t prev_disp_clk = dc->current_context.bw_results.dispclk_khz;
+ struct core_target *target = DC_TARGET_TO_CORE(dc_target);
+
+ int current_enabled_surface_count = 0;
+ int new_enabled_surface_count = 0;
+
+ if (!dal_adapter_service_is_in_accelerated_mode(
+ dc->res_pool.adapter_srv) ||
+ dc->current_context.target_count == 0) {
+ return false;
+ }
+
+ for (i = 0; i < dc->current_context.target_count; i++)
+ if (target == dc->current_context.targets[i])
+ break;
+
+ /* Cannot commit surface to a target that is not commited */
+ if (i == dc->current_context.target_count)
+ return false;
+
+ for (i = 0; i < target->status.surface_count; i++)
+ if (target->status.surfaces[i]->visible)
+ current_enabled_surface_count++;
+
+ for (i = 0; i < new_surface_count; i++)
+ if (new_surfaces[i]->visible)
+ new_enabled_surface_count++;
+
+ dal_logger_write(dc->ctx->logger,
+ LOG_MAJOR_INTERFACE_TRACE,
+ LOG_MINOR_COMPONENT_DC,
+ "%s: commit %d surfaces to target 0x%x\n",
+ __func__,
+ new_surface_count,
+ dc_target);
+
+
+ if (!logical_attach_surfaces_to_target(
+ new_surfaces,
+ new_surface_count,
+ dc_target)) {
+ BREAK_TO_DEBUGGER();
+ goto unexpected_fail;
+ }
+
+ for (i = 0; i < new_surface_count; i++)
+ for (j = 0; j < target->public.stream_count; j++)
+ build_scaling_params(
+ new_surfaces[i],
+ DC_STREAM_TO_CORE(target->public.streams[j]));
+
+ if (dc->res_pool.funcs->validate_bandwidth(dc, &dc->current_context)
+ != DC_OK) {
+ BREAK_TO_DEBUGGER();
+ goto unexpected_fail;
+ }
+
+ if (prev_disp_clk < dc->current_context.bw_results.dispclk_khz) {
+ dc->hwss.program_bw(dc, &dc->current_context);
+ pplib_apply_display_requirements(dc, &dc->current_context);
+ }
+
+ if (current_enabled_surface_count > 0 && new_enabled_surface_count == 0)
+ dc_target_disable_memory_requests(dc_target);
+
+ for (i = 0; i < new_surface_count; i++) {
+ struct dc_surface *surface = new_surfaces[i];
+ struct core_surface *core_surface = DC_SURFACE_TO_CORE(surface);
+ bool is_valid_address =
+ validate_surface_address(surface->address);
+
+ dal_logger_write(dc->ctx->logger,
+ LOG_MAJOR_INTERFACE_TRACE,
+ LOG_MINOR_COMPONENT_DC,
+ "0x%x:",
+ surface);
+
+ program_gamma(dc->ctx, surface,
+ DC_STREAM_TO_CORE(target->public.streams[0])->ipp,
+ DC_STREAM_TO_CORE(target->public.streams[0])->opp);
+
+ dc->hwss.set_plane_config(dc, core_surface, target);
+
+ if (is_valid_address)
+ dc->hwss.update_plane_address(dc, core_surface, target);
+ }
+
+ if (current_enabled_surface_count == 0 && new_enabled_surface_count > 0)
+ dc_target_enable_memory_requests(dc_target);
+
+ /* Lower display clock if necessary */
+ if (prev_disp_clk > dc->current_context.bw_results.dispclk_khz) {
+ dc->hwss.program_bw(dc, &dc->current_context);
+ pplib_apply_display_requirements(dc, &dc->current_context);
+ }
+
+ return true;
+
+unexpected_fail:
+ for (i = 0; i < new_surface_count; i++) {
+ target->status.surfaces[i] = NULL;
+ }
+ target->status.surface_count = 0;
+
+ return false;
+}
+
+bool dc_target_is_connected_to_sink(
+ const struct dc_target * dc_target,
+ const struct dc_sink *dc_sink)
+{
+ struct core_target *target = DC_TARGET_TO_CORE(dc_target);
+ uint8_t i;
+ for (i = 0; i < target->public.stream_count; i++) {
+ if (target->public.streams[i]->sink == dc_sink)
+ return true;
+ }
+ return false;
+}
+
+void dc_target_enable_memory_requests(struct dc_target *target)
+{
+ uint8_t i;
+ struct core_target *core_target = DC_TARGET_TO_CORE(target);
+ for (i = 0; i < core_target->public.stream_count; i++) {
+ struct timing_generator *tg =
+ DC_STREAM_TO_CORE(core_target->public.streams[i])->tg;
+
+ if (!tg->funcs->set_blank(tg, false)) {
+ dm_error("DC: failed to unblank crtc!\n");
+ BREAK_TO_DEBUGGER();
+ }
+ }
+}
+
+void dc_target_disable_memory_requests(struct dc_target *target)
+{
+ uint8_t i;
+ struct core_target *core_target = DC_TARGET_TO_CORE(target);
+ for (i = 0; i < core_target->public.stream_count; i++) {
+ struct timing_generator *tg =
+ DC_STREAM_TO_CORE(core_target->public.streams[i])->tg;
+
+ if (NULL == tg) {
+ dm_error("DC: timing generator is NULL!\n");
+ BREAK_TO_DEBUGGER();
+ continue;
+ }
+
+ if (false == tg->funcs->set_blank(tg, true)) {
+ dm_error("DC: failed to blank crtc!\n");
+ BREAK_TO_DEBUGGER();
+ }
+ }
+}
+
+/**
+ * Update the cursor attributes and set cursor surface address
+ */
+bool dc_target_set_cursor_attributes(
+ struct dc_target *dc_target,
+ const struct dc_cursor_attributes *attributes)
+{
+ struct core_target *core_target;
+ struct input_pixel_processor *ipp;
+
+ if (NULL == dc_target) {
+ dm_error("DC: dc_target is NULL!\n");
+ return false;
+
+ }
+
+ core_target = DC_TARGET_TO_CORE(dc_target);
+ ipp = DC_STREAM_TO_CORE(core_target->public.streams[0])->ipp;
+
+ if (NULL == ipp) {
+ dm_error("DC: input pixel processor is NULL!\n");
+ return false;
+ }
+
+ if (true == ipp->funcs->ipp_cursor_set_attributes(ipp, attributes))
+ return true;
+
+ return false;
+}
+
+bool dc_target_set_cursor_position(
+ struct dc_target *dc_target,
+ const struct dc_cursor_position *position)
+{
+ struct core_target *core_target;
+ struct input_pixel_processor *ipp;
+
+ if (NULL == dc_target) {
+ dm_error("DC: dc_target is NULL!\n");
+ return false;
+ }
+
+ if (NULL == position) {
+ dm_error("DC: cursor position is NULL!\n");
+ return false;
+ }
+
+ core_target = DC_TARGET_TO_CORE(dc_target);
+ ipp = DC_STREAM_TO_CORE(core_target->public.streams[0])->ipp;
+
+ if (NULL == ipp) {
+ dm_error("DC: input pixel processor is NULL!\n");
+ return false;
+ }
+
+
+ if (true == ipp->funcs->ipp_cursor_set_position(ipp, position))
+ return true;
+
+ return false;
+}
+
+/* TODO: #flip temporary to make flip work */
+uint8_t dc_target_get_link_index(const struct dc_target *dc_target)
+{
+ const struct core_target *target = CONST_DC_TARGET_TO_CORE(dc_target);
+ const struct core_sink *sink =
+ DC_SINK_TO_CORE(target->public.streams[0]->sink);
+
+ return sink->link->public.link_index;
+}
+
+uint32_t dc_target_get_vblank_counter(const struct dc_target *dc_target)
+{
+ struct core_target *core_target = DC_TARGET_TO_CORE(dc_target);
+ struct timing_generator *tg =
+ DC_STREAM_TO_CORE(core_target->public.streams[0])->tg;
+
+ return tg->funcs->get_frame_count(tg);
+}
+
+enum dc_irq_source dc_target_get_irq_src(
+ const struct dc_target *dc_target, const enum irq_type irq_type)
+{
+ struct core_target *core_target = DC_TARGET_TO_CORE(dc_target);
+
+ /* #TODO - Remove the assumption that the controller is always in the
+ * first stream of a core target */
+ struct core_stream *stream =
+ DC_STREAM_TO_CORE(core_target->public.streams[0]);
+ uint8_t controller_idx = stream->controller_idx;
+
+ /* Get controller id */
+ enum controller_id crtc_id = controller_idx + 1;
+
+ /* Calculate controller offset */
+ unsigned int offset = crtc_id - CONTROLLER_ID_D0;
+ unsigned int base = irq_type;
+
+ /* Calculate irq source */
+ enum dc_irq_source src = base + offset;
+
+ return src;
+}
+
+void dc_target_log(
+ const struct dc_target *dc_target,
+ struct dal_logger *dal_logger,
+ enum log_major log_major,
+ enum log_minor log_minor)
+{
+ int i;
+
+ const struct core_target *core_target =
+ CONST_DC_TARGET_TO_CORE(dc_target);
+
+ dal_logger_write(dal_logger,
+ log_major,
+ log_minor,
+ "core_target 0x%x: surface_count=%d, stream_count=%d\n",
+ core_target,
+ core_target->status.surface_count,
+ core_target->public.stream_count);
+
+ for (i = 0; i < core_target->public.stream_count; i++) {
+ const struct core_stream *core_stream =
+ DC_STREAM_TO_CORE(core_target->public.streams[i]);
+
+ dal_logger_write(dal_logger,
+ log_major,
+ log_minor,
+ "core_stream 0x%x: src: %d, %d, %d, %d; dst: %d, %d, %d, %d;\n",
+ core_stream,
+ core_stream->public.src.x,
+ core_stream->public.src.y,
+ core_stream->public.src.width,
+ core_stream->public.src.height,
+ core_stream->public.dst.x,
+ core_stream->public.dst.y,
+ core_stream->public.dst.width,
+ core_stream->public.dst.height);
+ dal_logger_write(dal_logger,
+ log_major,
+ log_minor,
+ "\tpix_clk_khz: %d, h_total: %d, v_total: %d\n",
+ core_stream->public.timing.pix_clk_khz,
+ core_stream->public.timing.h_total,
+ core_stream->public.timing.v_total);
+ dal_logger_write(dal_logger,
+ log_major,
+ log_minor,
+ "\tsink name: %s, serial: %d\n",
+ core_stream->sink->public.edid_caps.display_name,
+ core_stream->sink->public.edid_caps.serial_number);
+ dal_logger_write(dal_logger,
+ log_major,
+ log_minor,
+ "\tlink: %d\n",
+ core_stream->sink->link->public.link_index);
+ }
+}