summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/amd/display/dc/core
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/amd/display/dc/core')
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc.c1846
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc_debug.c270
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c93
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc_link.c1899
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc_link_ddc.c1098
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc_link_dp.c2462
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc_link_hwss.c222
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc_resource.c1934
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc_sink.c113
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc_stream.c141
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc_surface.c213
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc_target.c334
12 files changed, 10625 insertions, 0 deletions
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c
new file mode 100644
index 000000000000..f7638f84421b
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/core/dc.c
@@ -0,0 +1,1846 @@
+/*
+ * 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 "clock_source.h"
+#include "dc_bios_types.h"
+
+#include "bandwidth_calcs.h"
+#include "bios_parser_interface.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"
+#include "dm_helpers.h"
+#include "mem_input.h"
+
+/*******************************************************************************
+ * Private structures
+ ******************************************************************************/
+
+struct dc_target_sync_report {
+ uint32_t h_count;
+ uint32_t v_count;
+};
+
+/*******************************************************************************
+ * Private functions
+ ******************************************************************************/
+static void destroy_links(struct core_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 core_dc *dc,
+ uint32_t num_virtual_links)
+{
+ int i;
+ int connectors_num;
+ struct dc_bios *bios = dc->ctx->dc_bios;
+
+ dc->link_count = 0;
+
+ connectors_num = bios->funcs->get_connectors_number(bios);
+
+ 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 && num_virtual_links == 0) {
+ dm_error("DC: Number of connectors is zero!\n");
+ }
+
+ dm_output_to_console(
+ "DC: %s: connectors_num: physical:%d, virtual:%d\n",
+ __func__,
+ connectors_num,
+ 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 = dc->ctx;
+ 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 < num_virtual_links; i++) {
+ struct core_link *link = dm_alloc(sizeof(*link));
+ struct encoder_init_data enc_init = {0};
+
+ if (link == NULL) {
+ BREAK_TO_DEBUGGER();
+ goto failed_alloc;
+ }
+
+ link->ctx = dc->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(sizeof(*link->link_enc));
+
+ enc_init.ctx = dc->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 bool stream_adjust_vmin_vmax(struct dc *dc,
+ const struct dc_stream **stream, int num_streams,
+ int vmin, int vmax)
+{
+ /* TODO: Support multiple streams */
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ struct core_stream *core_stream = DC_STREAM_TO_CORE(stream[0]);
+ int i = 0;
+ bool ret = false;
+ struct pipe_ctx *pipes;
+ unsigned int underlay_idx = core_dc->res_pool->underlay_pipe_index;
+
+ for (i = 0; i < MAX_PIPES; i++) {
+ if (core_dc->current_context->res_ctx.pipe_ctx[i].stream == core_stream
+ && i != underlay_idx) {
+
+ pipes = &core_dc->current_context->res_ctx.pipe_ctx[i];
+ core_dc->hwss.set_drr(&pipes, 1, vmin, vmax);
+
+ /* build and update the info frame */
+ resource_build_info_frame(pipes);
+ core_dc->hwss.update_info_frame(pipes);
+
+ ret = true;
+ }
+ }
+
+ return ret;
+}
+
+
+static bool set_gamut_remap(struct dc *dc,
+ const struct dc_stream **stream, int num_streams)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ struct core_stream *core_stream = DC_STREAM_TO_CORE(stream[0]);
+ int i = 0;
+ bool ret = false;
+ struct pipe_ctx *pipes;
+
+ for (i = 0; i < MAX_PIPES; i++) {
+ if (core_dc->current_context->res_ctx.pipe_ctx[i].stream
+ == core_stream) {
+
+ pipes = &core_dc->current_context->res_ctx.pipe_ctx[i];
+ core_dc->hwss.set_plane_config(core_dc, pipes,
+ &core_dc->current_context->res_ctx);
+ ret = true;
+ }
+ }
+
+ return ret;
+}
+
+/* This function is not expected to fail, proper implementation of
+ * validation will prevent this from ever being called for unsupported
+ * configurations.
+ */
+static void stream_update_scaling(
+ const struct dc *dc,
+ const struct dc_stream *dc_stream,
+ const struct rect *src,
+ const struct rect *dst)
+{
+ struct core_stream *stream = DC_STREAM_TO_CORE(dc_stream);
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ struct validate_context *cur_ctx = core_dc->current_context;
+ int i, j;
+
+ if (src)
+ stream->public.src = *src;
+
+ if (dst)
+ stream->public.dst = *dst;
+
+ for (i = 0; i < cur_ctx->target_count; i++) {
+ struct core_target *target = cur_ctx->targets[i];
+ struct dc_target_status *status = &cur_ctx->target_status[i];
+
+ for (j = 0; j < target->public.stream_count; j++) {
+ if (target->public.streams[j] != dc_stream)
+ continue;
+
+ if (status->surface_count)
+ if (!dc_commit_surfaces_to_target(
+ &core_dc->public,
+ status->surfaces,
+ status->surface_count,
+ &target->public))
+ /* Need to debug validation */
+ BREAK_TO_DEBUGGER();
+
+ return;
+ }
+ }
+}
+
+static bool set_backlight(struct dc *dc, unsigned int backlight_level,
+ unsigned int frame_ramp, const struct dc_stream *stream)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ int i;
+
+ if (stream->sink->sink_signal == SIGNAL_TYPE_EDP) {
+ for (i = 0; i < core_dc->link_count; i++)
+ dc_link_set_backlight_level(&core_dc->links[i]->public,
+ backlight_level, frame_ramp, stream);
+ }
+
+ return true;
+
+}
+
+static bool init_dmcu_backlight_settings(struct dc *dc)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ int i;
+
+ for (i = 0; i < core_dc->link_count; i++)
+ dc_link_init_dmcu_backlight_settings
+ (&core_dc->links[i]->public);
+
+ return true;
+}
+
+
+static bool set_abm_level(struct dc *dc, unsigned int abm_level)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ int i;
+
+ for (i = 0; i < core_dc->link_count; i++)
+ dc_link_set_abm_level(&core_dc->links[i]->public,
+ abm_level);
+
+ return true;
+}
+
+static bool set_psr_enable(struct dc *dc, bool enable)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ int i;
+
+ for (i = 0; i < core_dc->link_count; i++)
+ dc_link_set_psr_enable(&core_dc->links[i]->public,
+ enable);
+
+ return true;
+}
+
+
+static bool setup_psr(struct dc *dc, const struct dc_stream *stream)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ struct core_stream *core_stream = DC_STREAM_TO_CORE(stream);
+ struct pipe_ctx *pipes;
+ int i;
+ unsigned int underlay_idx = core_dc->res_pool->underlay_pipe_index;
+
+ for (i = 0; i < core_dc->link_count; i++) {
+ if (core_stream->sink->link == core_dc->links[i])
+ dc_link_setup_psr(&core_dc->links[i]->public,
+ stream);
+ }
+
+ for (i = 0; i < MAX_PIPES; i++) {
+ if (core_dc->current_context->res_ctx.pipe_ctx[i].stream
+ == core_stream && i != underlay_idx) {
+ pipes = &core_dc->current_context->res_ctx.pipe_ctx[i];
+ core_dc->hwss.set_static_screen_control(&pipes, 1,
+ 0x182);
+ }
+ }
+
+ return true;
+}
+
+static void set_drive_settings(struct dc *dc,
+ struct link_training_settings *lt_settings)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ int i;
+
+ for (i = 0; i < core_dc->link_count; i++)
+ dc_link_dp_set_drive_settings(&core_dc->links[i]->public,
+ lt_settings);
+}
+
+static void perform_link_training(struct dc *dc,
+ struct dc_link_settings *link_setting,
+ bool skip_video_pattern)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ int i;
+
+ for (i = 0; i < core_dc->link_count; i++)
+ dc_link_dp_perform_link_training(
+ &core_dc->links[i]->public,
+ link_setting,
+ skip_video_pattern);
+}
+
+static void set_preferred_link_settings(struct dc *dc,
+ struct dc_link_settings *link_setting)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ int i;
+
+ for (i = 0; i < core_dc->link_count; i++) {
+ core_dc->links[i]->public.verified_link_cap.lane_count =
+ link_setting->lane_count;
+ core_dc->links[i]->public.verified_link_cap.link_rate =
+ link_setting->link_rate;
+ }
+}
+
+static void enable_hpd(const struct dc_link *link)
+{
+ dc_link_dp_enable_hpd(link);
+}
+
+static void disable_hpd(const struct dc_link *link)
+{
+ dc_link_dp_disable_hpd(link);
+}
+
+
+static void set_test_pattern(
+ const struct dc_link *link,
+ enum dp_test_pattern test_pattern,
+ const struct link_training_settings *p_link_settings,
+ const unsigned char *p_custom_pattern,
+ unsigned int cust_pattern_size)
+{
+ if (link != NULL)
+ dc_link_dp_set_test_pattern(
+ link,
+ test_pattern,
+ p_link_settings,
+ p_custom_pattern,
+ cust_pattern_size);
+}
+
+static void allocate_dc_stream_funcs(struct core_dc *core_dc)
+{
+ core_dc->public.stream_funcs.stream_update_scaling = stream_update_scaling;
+ if (core_dc->hwss.set_drr != NULL) {
+ core_dc->public.stream_funcs.adjust_vmin_vmax =
+ stream_adjust_vmin_vmax;
+ }
+
+ core_dc->public.stream_funcs.set_gamut_remap =
+ set_gamut_remap;
+
+ core_dc->public.stream_funcs.set_backlight =
+ set_backlight;
+
+ core_dc->public.stream_funcs.init_dmcu_backlight_settings =
+ init_dmcu_backlight_settings;
+
+ core_dc->public.stream_funcs.set_abm_level =
+ set_abm_level;
+
+ core_dc->public.stream_funcs.set_psr_enable =
+ set_psr_enable;
+
+ core_dc->public.stream_funcs.setup_psr =
+ setup_psr;
+
+ core_dc->public.link_funcs.set_drive_settings =
+ set_drive_settings;
+
+ core_dc->public.link_funcs.perform_link_training =
+ perform_link_training;
+
+ core_dc->public.link_funcs.set_preferred_link_settings =
+ set_preferred_link_settings;
+
+ core_dc->public.link_funcs.enable_hpd =
+ enable_hpd;
+
+ core_dc->public.link_funcs.disable_hpd =
+ disable_hpd;
+
+ core_dc->public.link_funcs.set_test_pattern =
+ set_test_pattern;
+}
+
+static void destruct(struct core_dc *dc)
+{
+ resource_validate_ctx_destruct(dc->current_context);
+
+ dm_free(dc->temp_flip_context);
+ dc->temp_flip_context = NULL;
+
+ destroy_links(dc);
+
+ dc_destroy_resource_pool(dc);
+
+ if (dc->ctx->gpio_service)
+ dal_gpio_service_destroy(&dc->ctx->gpio_service);
+
+ if (dc->ctx->i2caux)
+ dal_i2caux_destroy(&dc->ctx->i2caux);
+
+ if (dc->ctx->created_bios)
+ dal_bios_parser_destroy(&dc->ctx->dc_bios);
+
+ if (dc->ctx->logger)
+ dal_logger_destroy(&dc->ctx->logger);
+
+ dm_free(dc->current_context);
+ dc->current_context = NULL;
+
+ dm_free(dc->ctx);
+ dc->ctx = NULL;
+}
+
+static bool construct(struct core_dc *dc,
+ const struct dc_init_data *init_params)
+{
+ struct dal_logger *logger;
+ struct dc_context *dc_ctx = dm_alloc(sizeof(*dc_ctx));
+ enum dce_version dc_version = DCE_VERSION_UNKNOWN;
+
+ if (!dc_ctx) {
+ dm_error("%s: failed to create ctx\n", __func__);
+ goto ctx_fail;
+ }
+
+ dc->current_context = dm_alloc(sizeof(*dc->current_context));
+ dc->temp_flip_context = dm_alloc(sizeof(*dc->temp_flip_context));
+
+ if (!dc->current_context || !dc->temp_flip_context) {
+ dm_error("%s: failed to create validate ctx\n", __func__);
+ goto val_ctx_fail;
+ }
+
+ dc_ctx->cgs_device = init_params->cgs_device;
+ dc_ctx->driver_context = init_params->driver;
+ dc_ctx->dc = &dc->public;
+ dc_ctx->asic_id = init_params->asic_id;
+
+ /* Create logger */
+ logger = dal_logger_create(dc_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_ctx->logger = logger;
+ dc->ctx = dc_ctx;
+ dc->ctx->dce_environment = init_params->dce_environment;
+
+ dc_version = resource_parse_asic_id(init_params->asic_id);
+ dc->ctx->dce_version = dc_version;
+
+ /* Resource should construct all asic specific resources.
+ * This should be the only place where we need to parse the asic id
+ */
+ if (init_params->vbios_override)
+ dc_ctx->dc_bios = init_params->vbios_override;
+ else {
+ /* Create BIOS parser */
+ struct bp_init_data bp_init_data;
+ bp_init_data.ctx = dc_ctx;
+ bp_init_data.bios = init_params->asic_id.atombios_base_address;
+
+ dc_ctx->dc_bios = dal_bios_parser_create(
+ &bp_init_data, dc_version);
+
+ if (!dc_ctx->dc_bios) {
+ ASSERT_CRITICAL(false);
+ goto bios_fail;
+ }
+
+ dc_ctx->created_bios = true;
+ }
+
+ /* Create I2C AUX */
+ dc_ctx->i2caux = dal_i2caux_create(dc_ctx);
+
+ if (!dc_ctx->i2caux) {
+ ASSERT_CRITICAL(false);
+ goto failed_to_create_i2caux;
+ }
+
+ /* Create GPIO service */
+ dc_ctx->gpio_service = dal_gpio_service_create(
+ dc_version,
+ dc_ctx->dce_environment,
+ dc_ctx);
+
+ if (!dc_ctx->gpio_service) {
+ ASSERT_CRITICAL(false);
+ goto gpio_fail;
+ }
+
+ dc->res_pool = dc_create_resource_pool(
+ dc,
+ init_params->num_virtual_links,
+ dc_version,
+ init_params->asic_id);
+ if (!dc->res_pool)
+ goto create_resource_fail;
+
+ if (!create_links(dc, init_params->num_virtual_links))
+ goto create_links_fail;
+
+ allocate_dc_stream_funcs(dc);
+
+ return true;
+
+ /**** error handling here ****/
+create_links_fail:
+create_resource_fail:
+gpio_fail:
+failed_to_create_i2caux:
+bios_fail:
+logger_fail:
+val_ctx_fail:
+ctx_fail:
+ destruct(dc);
+ return false;
+}
+
+/*
+void ProgramPixelDurationV(unsigned int pixelClockInKHz )
+{
+ fixed31_32 pixel_duration = Fixed31_32(100000000, pixelClockInKHz) * 10;
+ unsigned int pixDurationInPico = round(pixel_duration);
+
+ DPG_PIPE_ARBITRATION_CONTROL1 arb_control;
+
+ arb_control.u32All = ReadReg (mmDPGV0_PIPE_ARBITRATION_CONTROL1);
+ arb_control.bits.PIXEL_DURATION = pixDurationInPico;
+ WriteReg (mmDPGV0_PIPE_ARBITRATION_CONTROL1, arb_control.u32All);
+
+ arb_control.u32All = ReadReg (mmDPGV1_PIPE_ARBITRATION_CONTROL1);
+ arb_control.bits.PIXEL_DURATION = pixDurationInPico;
+ WriteReg (mmDPGV1_PIPE_ARBITRATION_CONTROL1, arb_control.u32All);
+
+ WriteReg (mmDPGV0_PIPE_ARBITRATION_CONTROL2, 0x4000800);
+ WriteReg (mmDPGV0_REPEATER_PROGRAM, 0x11);
+
+ WriteReg (mmDPGV1_PIPE_ARBITRATION_CONTROL2, 0x4000800);
+ WriteReg (mmDPGV1_REPEATER_PROGRAM, 0x11);
+}
+*/
+
+/*******************************************************************************
+ * Public functions
+ ******************************************************************************/
+
+struct dc *dc_create(const struct dc_init_data *init_params)
+ {
+ struct core_dc *core_dc = dm_alloc(sizeof(*core_dc));
+ unsigned int full_pipe_count;
+
+ if (NULL == core_dc)
+ goto alloc_fail;
+
+ if (false == construct(core_dc, init_params))
+ goto construct_fail;
+
+ /*TODO: separate HW and SW initialization*/
+ core_dc->hwss.init_hw(core_dc);
+
+ full_pipe_count = core_dc->res_pool->pipe_count;
+ if (core_dc->res_pool->underlay_pipe_index >= 0)
+ full_pipe_count--;
+ core_dc->public.caps.max_targets = dm_min(
+ full_pipe_count,
+ core_dc->res_pool->stream_enc_count);
+
+ core_dc->public.caps.max_links = core_dc->link_count;
+ core_dc->public.caps.max_audios = core_dc->res_pool->audio_count;
+
+ core_dc->public.config = init_params->flags;
+
+ dm_logger_write(core_dc->ctx->logger, LOG_DC,
+ "Display Core initialized\n");
+
+
+ /* TODO: missing feature to be enabled */
+ core_dc->public.debug.disable_dfs_bypass = true;
+
+ return &core_dc->public;
+
+construct_fail:
+ dm_free(core_dc);
+
+alloc_fail:
+ return NULL;
+}
+
+void dc_destroy(struct dc **dc)
+{
+ struct core_dc *core_dc = DC_TO_CORE(*dc);
+ destruct(core_dc);
+ dm_free(core_dc);
+ *dc = NULL;
+}
+
+static bool is_validation_required(
+ const struct core_dc *dc,
+ const struct dc_validation_set set[],
+ int set_count)
+{
+ const struct validate_context *context = dc->current_context;
+ int i, j;
+
+ if (context->target_count != set_count)
+ return true;
+
+ for (i = 0; i < set_count; i++) {
+
+ if (set[i].surface_count != context->target_status[i].surface_count)
+ return true;
+ if (!is_target_unchanged(DC_TARGET_TO_CORE(set[i].target), context->targets[i]))
+ return true;
+
+ for (j = 0; j < set[i].surface_count; j++) {
+ struct dc_surface temp_surf = { 0 };
+
+ temp_surf = *context->target_status[i].surfaces[j];
+ temp_surf.clip_rect = set[i].surfaces[j]->clip_rect;
+ temp_surf.dst_rect.x = set[i].surfaces[j]->dst_rect.x;
+ temp_surf.dst_rect.y = set[i].surfaces[j]->dst_rect.y;
+
+ if (memcmp(&temp_surf, set[i].surfaces[j], sizeof(temp_surf)) != 0)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool dc_validate_resources(
+ const struct dc *dc,
+ const struct dc_validation_set set[],
+ uint8_t set_count)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ enum dc_status result = DC_ERROR_UNEXPECTED;
+ struct validate_context *context;
+
+ if (!is_validation_required(core_dc, set, set_count))
+ return true;
+
+ context = dm_alloc(sizeof(struct validate_context));
+ if(context == NULL)
+ goto context_alloc_fail;
+
+ result = core_dc->res_pool->funcs->validate_with_context(
+ core_dc, set, set_count, context);
+
+ resource_validate_ctx_destruct(context);
+ dm_free(context);
+
+context_alloc_fail:
+ if (result != DC_OK) {
+ dm_logger_write(core_dc->ctx->logger, LOG_WARNING,
+ "%s:resource validation failed, dc_status:%d\n",
+ __func__,
+ result);
+ }
+
+ return (result == DC_OK);
+
+}
+
+bool dc_validate_guaranteed(
+ const struct dc *dc,
+ const struct dc_target *dc_target)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ enum dc_status result = DC_ERROR_UNEXPECTED;
+ struct validate_context *context;
+
+ context = dm_alloc(sizeof(struct validate_context));
+ if (context == NULL)
+ goto context_alloc_fail;
+
+ result = core_dc->res_pool->funcs->validate_guaranteed(
+ core_dc, dc_target, context);
+
+ resource_validate_ctx_destruct(context);
+ dm_free(context);
+
+context_alloc_fail:
+ if (result != DC_OK) {
+ dm_logger_write(core_dc->ctx->logger, LOG_WARNING,
+ "%s:guaranteed validation failed, dc_status:%d\n",
+ __func__,
+ result);
+ }
+
+ return (result == DC_OK);
+}
+
+static void program_timing_sync(
+ struct core_dc *core_dc,
+ struct validate_context *ctx)
+{
+ int i, j;
+ int group_index = 0;
+ int pipe_count = ctx->res_ctx.pool->pipe_count;
+ struct pipe_ctx *unsynced_pipes[MAX_PIPES] = { NULL };
+
+ for (i = 0; i < pipe_count; i++) {
+ if (!ctx->res_ctx.pipe_ctx[i].stream || ctx->res_ctx.pipe_ctx[i].top_pipe)
+ continue;
+
+ unsynced_pipes[i] = &ctx->res_ctx.pipe_ctx[i];
+ }
+
+ for (i = 0; i < pipe_count; i++) {
+ int group_size = 1;
+ struct pipe_ctx *pipe_set[MAX_PIPES];
+
+ if (!unsynced_pipes[i])
+ continue;
+
+ pipe_set[0] = unsynced_pipes[i];
+ unsynced_pipes[i] = NULL;
+
+ /* 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 < pipe_count; j++) {
+ if (!unsynced_pipes[j])
+ continue;
+
+ if (resource_are_streams_timing_synchronizable(
+ unsynced_pipes[j]->stream,
+ pipe_set[0]->stream)) {
+ pipe_set[group_size] = unsynced_pipes[j];
+ unsynced_pipes[j] = NULL;
+ group_size++;
+ }
+ }
+
+ /* set first unblanked pipe as master */
+ for (j = 0; j < group_size; j++) {
+ struct pipe_ctx *temp;
+
+ if (!pipe_set[j]->tg->funcs->is_blanked(pipe_set[j]->tg)) {
+ if (j == 0)
+ break;
+
+ temp = pipe_set[0];
+ pipe_set[0] = pipe_set[j];
+ pipe_set[j] = temp;
+ break;
+ }
+ }
+
+ /* remove any other unblanked pipes as they have already been synced */
+ for (j = j + 1; j < group_size; j++) {
+ if (!pipe_set[j]->tg->funcs->is_blanked(pipe_set[j]->tg)) {
+ group_size--;
+ pipe_set[j] = pipe_set[group_size];
+ j--;
+ }
+ }
+
+ if (group_size > 1) {
+ core_dc->hwss.enable_timing_synchronization(
+ core_dc, group_index, group_size, pipe_set);
+ group_index++;
+ }
+ }
+}
+
+static bool targets_changed(
+ struct core_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;
+}
+
+static void fill_display_configs(
+ const struct validate_context *context,
+ struct dm_pp_display_configuration *pp_display_cfg)
+{
+ uint8_t i, j, k;
+ 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 dm_pp_single_disp_config *cfg =
+ &pp_display_cfg->disp_configs[num_cfgs];
+ const struct pipe_ctx *pipe_ctx = NULL;
+
+ for (k = 0; k < MAX_PIPES; k++)
+ if (stream ==
+ context->res_ctx.pipe_ctx[k].stream) {
+ pipe_ctx = &context->res_ctx.pipe_ctx[k];
+ break;
+ }
+
+ ASSERT(pipe_ctx != NULL);
+
+ num_cfgs++;
+ cfg->signal = pipe_ctx->stream->signal;
+ cfg->pipe_idx = pipe_ctx->pipe_idx;
+ 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.lane_count = stream->sink->link->public.cur_link_settings.lane_count;
+ cfg->link_settings.link_rate = stream->sink->link->public.cur_link_settings.link_rate;
+ cfg->link_settings.link_spread = stream->sink->link->public.cur_link_settings.link_spread;
+ cfg->sym_clock = stream->phy_pix_clk;
+ /* Round v_refresh*/
+ cfg->v_refresh = stream->public.timing.pix_clk_khz * 1000;
+ cfg->v_refresh /= stream->public.timing.h_total;
+ cfg->v_refresh = (cfg->v_refresh + stream->public.timing.v_total / 2)
+ / stream->public.timing.v_total;
+ }
+ }
+ pp_display_cfg->display_count = num_cfgs;
+}
+
+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 int determine_sclk_from_bounding_box(
+ const struct core_dc *dc,
+ int required_sclk)
+{
+ int i;
+
+ /*
+ * Some asics do not give us sclk levels, so we just report the actual
+ * required sclk
+ */
+ if (dc->sclk_lvls.num_levels == 0)
+ return required_sclk;
+
+ for (i = 0; i < dc->sclk_lvls.num_levels; i++) {
+ if (dc->sclk_lvls.clocks_in_khz[i] >= required_sclk)
+ return dc->sclk_lvls.clocks_in_khz[i];
+ }
+ /*
+ * even maximum level could not satisfy requirement, this
+ * is unexpected at this stage, should have been caught at
+ * validation time
+ */
+ ASSERT(0);
+ return dc->sclk_lvls.clocks_in_khz[dc->sclk_lvls.num_levels - 1];
+}
+
+void pplib_apply_display_requirements(
+ struct core_dc *dc,
+ const struct validate_context *context,
+ struct dm_pp_display_configuration *pp_display_cfg)
+{
+ 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.blackout_recovery_time_us;
+
+ pp_display_cfg->min_memory_clock_khz = context->bw_results.required_yclk
+ / MEMORY_TYPE_MULTIPLIER;
+
+ pp_display_cfg->min_engine_clock_khz = determine_sclk_from_bounding_box(
+ dc,
+ 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);
+ /* TODO: dce11.2*/
+ 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;
+ }
+
+ if (memcmp(&dc->prev_display_config, pp_display_cfg, sizeof(
+ struct dm_pp_display_configuration)) != 0)
+ dm_pp_apply_display_requirements(dc->ctx, pp_display_cfg);
+
+ dc->prev_display_config = *pp_display_cfg;
+
+}
+
+bool dc_commit_targets(
+ struct dc *dc,
+ struct dc_target *targets[],
+ uint8_t target_count)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ struct dc_bios *dcb = core_dc->ctx->dc_bios;
+ enum dc_status result = DC_ERROR_UNEXPECTED;
+ struct validate_context *context;
+ struct dc_validation_set set[MAX_TARGETS];
+ int i, j, k;
+
+ if (false == targets_changed(core_dc, targets, target_count))
+ return DC_OK;
+
+ dm_logger_write(core_dc->ctx->logger, LOG_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,
+ core_dc->ctx->logger,
+ LOG_DC);
+
+ set[i].target = targets[i];
+ set[i].surface_count = 0;
+
+ }
+
+ context = dm_alloc(sizeof(struct validate_context));
+ if (context == NULL)
+ goto context_alloc_fail;
+
+ result = core_dc->res_pool->funcs->validate_with_context(core_dc, set, target_count, context);
+ if (result != DC_OK){
+ dm_logger_write(core_dc->ctx->logger, LOG_ERROR,
+ "%s: Context validation failed! dc_status:%d\n",
+ __func__,
+ result);
+ BREAK_TO_DEBUGGER();
+ resource_validate_ctx_destruct(context);
+ goto fail;
+ }
+
+ if (!dcb->funcs->is_accelerated_mode(dcb)) {
+ core_dc->hwss.enable_accelerated_mode(core_dc);
+ }
+
+ if (result == DC_OK) {
+ result = core_dc->hwss.apply_ctx_to_hw(core_dc, context);
+ }
+
+ program_timing_sync(core_dc, context);
+
+ for (i = 0; i < context->target_count; i++) {
+ struct dc_target *dc_target = &context->targets[i]->public;
+ struct core_sink *sink = DC_SINK_TO_CORE(dc_target->streams[0]->sink);
+
+ for (j = 0; j < context->target_status[i].surface_count; j++) {
+ const struct dc_surface *dc_surface =
+ context->target_status[i].surfaces[j];
+
+ for (k = 0; k < context->res_ctx.pool->pipe_count; k++) {
+ struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[k];
+
+ if (dc_surface != &pipe->surface->public
+ || !dc_surface->visible)
+ continue;
+
+ pipe->tg->funcs->set_blank(pipe->tg, false);
+ }
+ }
+
+ CONN_MSG_MODE(sink->link, "{%dx%d, %dx%d@%dKhz}",
+ dc_target->streams[0]->timing.h_addressable,
+ dc_target->streams[0]->timing.v_addressable,
+ dc_target->streams[0]->timing.h_total,
+ dc_target->streams[0]->timing.v_total,
+ dc_target->streams[0]->timing.pix_clk_khz);
+ }
+
+ pplib_apply_display_requirements(core_dc,
+ context, &context->pp_display_cfg);
+
+ resource_validate_ctx_destruct(core_dc->current_context);
+
+ dm_free(core_dc->current_context);
+ core_dc->current_context = context;
+
+ return (result == DC_OK);
+
+fail:
+ dm_free(context);
+
+context_alloc_fail:
+ return (result == DC_OK);
+}
+
+bool dc_pre_update_surfaces_to_target(
+ struct dc *dc,
+ const struct dc_surface *const *new_surfaces,
+ uint8_t new_surface_count,
+ struct dc_target *dc_target)
+{
+ int i, j;
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ uint32_t prev_disp_clk = core_dc->current_context->bw_results.dispclk_khz;
+ struct core_target *target = DC_TARGET_TO_CORE(dc_target);
+ struct dc_target_status *target_status = NULL;
+ struct validate_context *context;
+ struct validate_context *temp_context;
+ bool ret = true;
+
+ pre_surface_trace(dc, new_surfaces, new_surface_count);
+
+ if (core_dc->current_context->target_count == 0)
+ return false;
+
+ /* Cannot commit surface to a target that is not commited */
+ for (i = 0; i < core_dc->current_context->target_count; i++)
+ if (target == core_dc->current_context->targets[i])
+ break;
+
+ if (i == core_dc->current_context->target_count)
+ return false;
+
+ target_status = &core_dc->current_context->target_status[i];
+
+ if (new_surface_count == target_status->surface_count) {
+ bool skip_pre = true;
+
+ for (i = 0; i < target_status->surface_count; i++) {
+ struct dc_surface temp_surf = { 0 };
+
+ temp_surf = *target_status->surfaces[i];
+ temp_surf.clip_rect = new_surfaces[i]->clip_rect;
+ temp_surf.dst_rect.x = new_surfaces[i]->dst_rect.x;
+ temp_surf.dst_rect.y = new_surfaces[i]->dst_rect.y;
+
+ if (memcmp(&temp_surf, new_surfaces[i], sizeof(temp_surf)) != 0) {
+ skip_pre = false;
+ break;
+ }
+ }
+
+ if (skip_pre)
+ return true;
+ }
+
+ context = dm_alloc(sizeof(struct validate_context));
+
+ if (!context) {
+ dm_error("%s: failed to create validate ctx\n", __func__);
+ ret = false;
+ goto val_ctx_fail;
+ }
+
+ resource_validate_ctx_copy_construct(core_dc->current_context, context);
+
+ dm_logger_write(core_dc->ctx->logger, LOG_DC,
+ "%s: commit %d surfaces to target 0x%x\n",
+ __func__,
+ new_surface_count,
+ dc_target);
+
+ if (!resource_attach_surfaces_to_context(
+ new_surfaces, new_surface_count, dc_target, context)) {
+ BREAK_TO_DEBUGGER();
+ ret = false;
+ goto unexpected_fail;
+ }
+
+ for (i = 0; i < new_surface_count; i++)
+ for (j = 0; j < context->res_ctx.pool->pipe_count; j++) {
+ if (context->res_ctx.pipe_ctx[j].surface !=
+ DC_SURFACE_TO_CORE(new_surfaces[i]))
+ continue;
+
+ resource_build_scaling_params(
+ new_surfaces[i], &context->res_ctx.pipe_ctx[j]);
+
+ if (dc->debug.surface_visual_confirm) {
+ context->res_ctx.pipe_ctx[j].scl_data.recout.height -= 2;
+ context->res_ctx.pipe_ctx[j].scl_data.recout.width -= 2;
+ }
+ }
+
+ if (core_dc->res_pool->funcs->validate_bandwidth(core_dc, context) != DC_OK) {
+ BREAK_TO_DEBUGGER();
+ ret = false;
+ goto unexpected_fail;
+ }
+
+ if (core_dc->res_pool->funcs->apply_clk_constraints) {
+ temp_context = core_dc->res_pool->funcs->apply_clk_constraints(
+ core_dc,
+ context);
+ if (!temp_context) {
+ dm_error("%s:failed apply clk constraints\n", __func__);
+ ret = false;
+ goto unexpected_fail;
+ }
+ resource_validate_ctx_destruct(context);
+ dm_free(context);
+ context = temp_context;
+ }
+
+ if (prev_disp_clk < context->bw_results.dispclk_khz) {
+ pplib_apply_display_requirements(core_dc, context,
+ &context->pp_display_cfg);
+ core_dc->hwss.set_display_clock(context);
+ core_dc->current_context->bw_results.dispclk_khz =
+ context->bw_results.dispclk_khz;
+ }
+
+ for (i = 0; i < new_surface_count; i++)
+ for (j = 0; j < context->res_ctx.pool->pipe_count; j++) {
+ if (context->res_ctx.pipe_ctx[j].surface !=
+ DC_SURFACE_TO_CORE(new_surfaces[i]))
+ continue;
+
+ core_dc->hwss.prepare_pipe_for_context(
+ core_dc,
+ &context->res_ctx.pipe_ctx[j],
+ context);
+
+ if (!new_surfaces[i]->visible)
+ context->res_ctx.pipe_ctx[j].tg->funcs->set_blank(
+ context->res_ctx.pipe_ctx[j].tg, true);
+ }
+
+unexpected_fail:
+ resource_validate_ctx_destruct(context);
+ dm_free(context);
+val_ctx_fail:
+
+ return ret;
+}
+
+bool dc_post_update_surfaces_to_target(struct dc *dc)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ int i;
+
+ post_surface_trace(dc);
+
+ for (i = 0; i < core_dc->current_context->res_ctx.pool->pipe_count; i++)
+ if (core_dc->current_context->res_ctx.pipe_ctx[i].stream == NULL)
+ core_dc->hwss.power_down_front_end(
+ core_dc, &core_dc->current_context->res_ctx.pipe_ctx[i]);
+
+ if (core_dc->res_pool->funcs->validate_bandwidth(core_dc, core_dc->current_context)
+ != DC_OK) {
+ BREAK_TO_DEBUGGER();
+ return false;
+ }
+
+ core_dc->hwss.set_bandwidth(core_dc);
+
+ pplib_apply_display_requirements(
+ core_dc, core_dc->current_context, &core_dc->current_context->pp_display_cfg);
+
+ return true;
+}
+
+bool dc_commit_surfaces_to_target(
+ struct dc *dc,
+ const struct dc_surface **new_surfaces,
+ uint8_t new_surface_count,
+ struct dc_target *dc_target)
+{
+ struct dc_surface_update updates[MAX_SURFACES] = { 0 };
+ struct dc_flip_addrs flip_addr[MAX_SURFACES] = { 0 };
+ struct dc_plane_info plane_info[MAX_SURFACES] = { 0 };
+ struct dc_scaling_info scaling_info[MAX_SURFACES] = { 0 };
+ int i;
+
+ if (!dc_pre_update_surfaces_to_target(
+ dc, new_surfaces, new_surface_count, dc_target))
+ return false;
+
+ for (i = 0; i < new_surface_count; i++) {
+ updates[i].surface = new_surfaces[i];
+ updates[i].gamma = (struct dc_gamma *)new_surfaces[i]->gamma_correction;
+
+ flip_addr[i].address = new_surfaces[i]->address;
+ flip_addr[i].flip_immediate = new_surfaces[i]->flip_immediate;
+ plane_info[i].color_space = new_surfaces[i]->color_space;
+ plane_info[i].format = new_surfaces[i]->format;
+ plane_info[i].plane_size = new_surfaces[i]->plane_size;
+ plane_info[i].rotation = new_surfaces[i]->rotation;
+ plane_info[i].horizontal_mirror = new_surfaces[i]->horizontal_mirror;
+ plane_info[i].stereo_format = new_surfaces[i]->stereo_format;
+ plane_info[i].tiling_info = new_surfaces[i]->tiling_info;
+ plane_info[i].visible = new_surfaces[i]->visible;
+ scaling_info[i].scaling_quality = new_surfaces[i]->scaling_quality;
+ scaling_info[i].src_rect = new_surfaces[i]->src_rect;
+ scaling_info[i].dst_rect = new_surfaces[i]->dst_rect;
+ scaling_info[i].clip_rect = new_surfaces[i]->clip_rect;
+
+ updates[i].flip_addr = &flip_addr[i];
+ updates[i].plane_info = &plane_info[i];
+ updates[i].scaling_info = &scaling_info[i];
+ }
+ dc_update_surfaces_for_target(dc, updates, new_surface_count, dc_target);
+
+ return dc_post_update_surfaces_to_target(dc);
+}
+
+void dc_update_surfaces_for_target(struct dc *dc, struct dc_surface_update *updates,
+ int surface_count, struct dc_target *dc_target)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ struct validate_context *context = core_dc->temp_flip_context;
+ int i, j;
+ bool is_new_pipe_surface[MAX_SURFACES];
+ const struct dc_surface *new_surfaces[MAX_SURFACES] = { 0 };
+
+ update_surface_trace(dc, updates, surface_count);
+
+ *context = *core_dc->current_context;
+
+ for (i = 0; i < context->res_ctx.pool->pipe_count; i++) {
+ struct pipe_ctx *cur_pipe = &context->res_ctx.pipe_ctx[i];
+
+ if (cur_pipe->top_pipe)
+ cur_pipe->top_pipe =
+ &context->res_ctx.pipe_ctx[cur_pipe->top_pipe->pipe_idx];
+
+ if (cur_pipe->bottom_pipe)
+ cur_pipe->bottom_pipe =
+ &context->res_ctx.pipe_ctx[cur_pipe->bottom_pipe->pipe_idx];
+ }
+
+ for (j = 0; j < MAX_SURFACES; j++)
+ is_new_pipe_surface[j] = true;
+
+ for (i = 0 ; i < surface_count; i++) {
+ struct core_surface *surface = DC_SURFACE_TO_CORE(updates[i].surface);
+
+ new_surfaces[i] = updates[i].surface;
+ for (j = 0; j < context->res_ctx.pool->pipe_count; j++) {
+ struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j];
+
+ if (surface == pipe_ctx->surface)
+ is_new_pipe_surface[i] = false;
+ }
+ }
+
+ if (dc_target) {
+ struct core_target *target = DC_TARGET_TO_CORE(dc_target);
+
+ if (core_dc->current_context->target_count == 0)
+ return;
+
+ /* Cannot commit surface to a target that is not commited */
+ for (i = 0; i < core_dc->current_context->target_count; i++)
+ if (target == core_dc->current_context->targets[i])
+ break;
+ if (i == core_dc->current_context->target_count)
+ return;
+
+ if (!resource_attach_surfaces_to_context(
+ new_surfaces, surface_count, dc_target, context)) {
+ BREAK_TO_DEBUGGER();
+ return;
+ }
+ }
+
+ for (i = 0; i < surface_count; i++) {
+ struct core_surface *surface = DC_SURFACE_TO_CORE(updates[i].surface);
+
+ for (j = 0; j < context->res_ctx.pool->pipe_count; j++) {
+ struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j];
+
+ if (pipe_ctx->surface != surface)
+ continue;
+
+ if (updates[i].flip_addr) {
+ surface->public.address = updates[i].flip_addr->address;
+ surface->public.flip_immediate =
+ updates[i].flip_addr->flip_immediate;
+ }
+
+ if (updates[i].plane_info || updates[i].scaling_info
+ || is_new_pipe_surface[j]) {
+
+ if (updates[i].plane_info) {
+ surface->public.color_space =
+ updates[i].plane_info->color_space;
+ surface->public.format =
+ updates[i].plane_info->format;
+ surface->public.plane_size =
+ updates[i].plane_info->plane_size;
+ surface->public.rotation =
+ updates[i].plane_info->rotation;
+ surface->public.horizontal_mirror =
+ updates[i].plane_info->horizontal_mirror;
+ surface->public.stereo_format =
+ updates[i].plane_info->stereo_format;
+ surface->public.tiling_info =
+ updates[i].plane_info->tiling_info;
+ surface->public.visible =
+ updates[i].plane_info->visible;
+ }
+
+ if (updates[i].scaling_info) {
+ surface->public.scaling_quality =
+ updates[i].scaling_info->scaling_quality;
+ surface->public.dst_rect =
+ updates[i].scaling_info->dst_rect;
+ surface->public.src_rect =
+ updates[i].scaling_info->src_rect;
+ surface->public.clip_rect =
+ updates[i].scaling_info->clip_rect;
+ }
+
+ resource_build_scaling_params(updates[i].surface, pipe_ctx);
+ if (dc->debug.surface_visual_confirm) {
+ pipe_ctx->scl_data.recout.height -= 2;
+ pipe_ctx->scl_data.recout.width -= 2;
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < surface_count; i++) {
+ struct core_surface *surface = DC_SURFACE_TO_CORE(updates[i].surface);
+ bool apply_ctx = false;
+
+ for (j = 0; j < context->res_ctx.pool->pipe_count; j++) {
+ struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j];
+
+ if (pipe_ctx->surface != surface)
+ continue;
+
+ if (updates[i].flip_addr) {
+ core_dc->hwss.pipe_control_lock(
+ core_dc->hwseq,
+ pipe_ctx->pipe_idx,
+ PIPE_LOCK_CONTROL_SURFACE,
+ true);
+ core_dc->hwss.update_plane_addr(core_dc, pipe_ctx);
+ }
+
+ if (updates[i].plane_info || updates[i].scaling_info
+ || is_new_pipe_surface[j]) {
+
+ apply_ctx = true;
+
+ if (!pipe_ctx->tg->funcs->is_blanked(pipe_ctx->tg)) {
+ core_dc->hwss.pipe_control_lock(
+ core_dc->hwseq,
+ pipe_ctx->pipe_idx,
+ PIPE_LOCK_CONTROL_SURFACE |
+ PIPE_LOCK_CONTROL_GRAPHICS |
+ PIPE_LOCK_CONTROL_SCL |
+ PIPE_LOCK_CONTROL_BLENDER |
+ PIPE_LOCK_CONTROL_MODE,
+ true);
+ }
+ }
+
+ if (updates[i].gamma)
+ core_dc->hwss.prepare_pipe_for_context(
+ core_dc, pipe_ctx, context);
+ }
+ if (apply_ctx)
+ core_dc->hwss.apply_ctx_for_surface(core_dc, surface, context);
+ }
+
+ for (i = context->res_ctx.pool->pipe_count - 1; i >= 0; i--) {
+ struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];
+
+ for (j = 0; j < surface_count; j++) {
+ if (updates[j].surface == &pipe_ctx->surface->public) {
+ if (!pipe_ctx->tg->funcs->is_blanked(pipe_ctx->tg)) {
+ core_dc->hwss.pipe_control_lock(
+ core_dc->hwseq,
+ pipe_ctx->pipe_idx,
+ PIPE_LOCK_CONTROL_GRAPHICS |
+ PIPE_LOCK_CONTROL_SCL |
+ PIPE_LOCK_CONTROL_BLENDER |
+ PIPE_LOCK_CONTROL_SURFACE,
+ false);
+ }
+ break;
+ }
+ }
+ }
+
+ core_dc->temp_flip_context = core_dc->current_context;
+ core_dc->current_context = context;
+}
+
+uint8_t dc_get_current_target_count(const struct dc *dc)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ return core_dc->current_context->target_count;
+}
+
+struct dc_target *dc_get_target_at_index(const struct dc *dc, uint8_t i)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ if (i < core_dc->current_context->target_count)
+ return &(core_dc->current_context->targets[i]->public);
+ return NULL;
+}
+
+const struct dc_link *dc_get_link_at_index(const struct dc *dc, uint32_t link_index)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ return &core_dc->links[link_index]->public;
+}
+
+const struct graphics_object_id dc_get_link_id_at_index(
+ struct dc *dc, uint32_t link_index)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ return core_dc->links[link_index]->link_id;
+}
+
+const struct ddc_service *dc_get_ddc_at_index(
+ struct dc *dc, uint32_t link_index)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ return core_dc->links[link_index]->ddc;
+}
+
+enum dc_irq_source dc_get_hpd_irq_source_at_index(
+ struct dc *dc, uint32_t link_index)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ return core_dc->links[link_index]->public.irq_source_hpd;
+}
+
+const struct audio **dc_get_audios(struct dc *dc)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ return (const struct audio **)core_dc->res_pool->audios;
+}
+
+void dc_flip_surface_addrs(
+ struct dc *dc,
+ const struct dc_surface *const surfaces[],
+ struct dc_flip_addrs flip_addrs[],
+ uint32_t count)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ int i, j;
+
+ for (i = 0; i < count; i++) {
+ struct core_surface *surface = DC_SURFACE_TO_CORE(surfaces[i]);
+
+ surface->public.address = flip_addrs[i].address;
+ surface->public.flip_immediate = flip_addrs[i].flip_immediate;
+
+ for (j = 0; j < core_dc->res_pool->pipe_count; j++) {
+ struct pipe_ctx *pipe_ctx = &core_dc->current_context->res_ctx.pipe_ctx[j];
+
+ if (pipe_ctx->surface != surface)
+ continue;
+
+ core_dc->hwss.update_plane_addr(core_dc, pipe_ctx);
+ }
+ }
+}
+
+enum dc_irq_source dc_interrupt_to_irq_source(
+ struct dc *dc,
+ uint32_t src_id,
+ uint32_t ext_id)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ return dal_irq_service_to_irq_source(core_dc->res_pool->irqs, src_id, ext_id);
+}
+
+void dc_interrupt_set(const struct dc *dc, enum dc_irq_source src, bool enable)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ dal_irq_service_set(core_dc->res_pool->irqs, src, enable);
+}
+
+void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ dal_irq_service_ack(core_dc->res_pool->irqs, src);
+}
+
+void dc_set_power_state(
+ struct dc *dc,
+ enum dc_acpi_cm_power_state power_state,
+ enum dc_video_power_state video_power_state)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+
+ core_dc->previous_power_state = core_dc->current_power_state;
+ core_dc->current_power_state = video_power_state;
+
+ switch (power_state) {
+ case DC_ACPI_CM_POWER_STATE_D0:
+ core_dc->hwss.init_hw(core_dc);
+ break;
+ default:
+ /* NULL means "reset/release all DC targets" */
+ dc_commit_targets(dc, NULL, 0);
+
+ core_dc->hwss.power_down(core_dc);
+
+ /* Zero out the current context so that on resume we start with
+ * clean state, and dc hw programming optimizations will not
+ * cause any trouble.
+ */
+ memset(core_dc->current_context, 0,
+ sizeof(*core_dc->current_context));
+
+ core_dc->current_context->res_ctx.pool = core_dc->res_pool;
+
+ break;
+ }
+
+}
+
+void dc_resume(const struct dc *dc)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+
+ uint32_t i;
+
+ for (i = 0; i < core_dc->link_count; i++)
+ core_link_resume(core_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_dc *core_dc = DC_TO_CORE(dc);
+
+ struct core_link *link = core_dc->links[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_dc *core_dc = DC_TO_CORE(dc);
+
+ struct core_link *link = core_dc->links[link_index];
+
+ enum ddc_result r = dal_ddc_service_write_dpcd_data(
+ link->ddc,
+ address,
+ data,
+ size);
+ return r == DDC_RESULT_SUCESSFULL;
+}
+
+bool dc_submit_i2c(
+ struct dc *dc,
+ uint32_t link_index,
+ struct i2c_command *cmd)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+
+ struct core_link *link = core_dc->links[link_index];
+ struct ddc_service *ddc = link->ddc;
+
+ return dal_i2caux_submit_i2c_command(
+ ddc->ctx->i2caux,
+ ddc->ddc_pin,
+ cmd);
+}
+
+static bool link_add_remote_sink_helper(struct core_link *core_link, struct dc_sink *sink)
+{
+ struct dc_link *dc_link = &core_link->public;
+
+ if (dc_link->sink_count >= MAX_SINKS_PER_LINK) {
+ BREAK_TO_DEBUGGER();
+ return false;
+ }
+
+ dc_sink_retain(sink);
+
+ dc_link->remote_sinks[dc_link->sink_count] = sink;
+ dc_link->sink_count++;
+
+ return true;
+}
+
+struct dc_sink *dc_link_add_remote_sink(
+ const struct dc_link *link,
+ const uint8_t *edid,
+ int len,
+ struct dc_sink_init_data *init_data)
+{
+ struct dc_sink *dc_sink;
+ enum dc_edid_status edid_status;
+ struct core_link *core_link = DC_LINK_TO_LINK(link);
+
+ if (len > MAX_EDID_BUFFER_SIZE) {
+ dm_error("Max EDID buffer size breached!\n");
+ return NULL;
+ }
+
+ if (!init_data) {
+ BREAK_TO_DEBUGGER();
+ return NULL;
+ }
+
+ if (!init_data->link) {
+ BREAK_TO_DEBUGGER();
+ return NULL;
+ }
+
+ dc_sink = dc_sink_create(init_data);
+
+ if (!dc_sink)
+ return NULL;
+
+ memmove(dc_sink->dc_edid.raw_edid, edid, len);
+ dc_sink->dc_edid.length = len;
+
+ if (!link_add_remote_sink_helper(
+ core_link,
+ dc_sink))
+ goto fail_add_sink;
+
+ edid_status = dm_helpers_parse_edid_caps(
+ core_link->ctx,
+ &dc_sink->dc_edid,
+ &dc_sink->edid_caps);
+
+ if (edid_status != EDID_OK)
+ goto fail;
+
+ return dc_sink;
+fail:
+ dc_link_remove_remote_sink(link, dc_sink);
+fail_add_sink:
+ dc_sink_release(dc_sink);
+ return NULL;
+}
+
+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->type = dc_connection_none;
+ } else {
+ 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;
+ }
+ }
+}
+
+const struct dc_stream_status *dc_stream_get_status(
+ const struct dc_stream *dc_stream)
+{
+ struct core_stream *stream = DC_STREAM_TO_CORE(dc_stream);
+
+ return &stream->status;
+}
+
+bool dc_init_dchub(struct dc *dc, struct dchub_init_data *dh_data)
+{
+ int i;
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ struct mem_input *mi = NULL;
+
+ for (i = 0; i < core_dc->res_pool->pipe_count; i++) {
+ if (core_dc->res_pool->mis[i] != NULL) {
+ mi = core_dc->res_pool->mis[i];
+ break;
+ }
+ }
+ if (mi == NULL) {
+ dm_error("no mem_input!\n");
+ return false;
+ }
+
+ if (mi->funcs->mem_input_update_dchub)
+ mi->funcs->mem_input_update_dchub(mi, dh_data);
+ else
+ ASSERT(mi->funcs->mem_input_update_dchub);
+
+
+ return true;
+
+}
+
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_debug.c b/drivers/gpu/drm/amd/display/dc/core/dc_debug.c
new file mode 100644
index 000000000000..8ca0f1e0369a
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_debug.c
@@ -0,0 +1,270 @@
+/*
+ * dc_debug.c
+ *
+ * Created on: Nov 3, 2016
+ * Author: yonsun
+ */
+
+#include "dm_services.h"
+
+#include "dc.h"
+
+#include "core_status.h"
+#include "core_types.h"
+#include "hw_sequencer.h"
+
+#include "resource.h"
+
+#define SURFACE_TRACE(...) do {\
+ if (dc->debug.surface_trace) \
+ dm_logger_write(logger, \
+ LOG_IF_TRACE, \
+ ##__VA_ARGS__); \
+} while (0)
+
+void pre_surface_trace(
+ const struct dc *dc,
+ const struct dc_surface *const *surfaces,
+ int surface_count)
+{
+ int i;
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ struct dal_logger *logger = core_dc->ctx->logger;
+
+ for (i = 0; i < surface_count; i++) {
+ const struct dc_surface *surface = surfaces[i];
+
+ SURFACE_TRACE("Surface %d:\n", i);
+
+ SURFACE_TRACE(
+ "surface->visible = %d;\n"
+ "surface->flip_immediate = %d;\n"
+ "surface->address.type = %d;\n"
+ "surface->address.grph.addr.quad_part = 0x%X;\n"
+ "surface->address.grph.meta_addr.quad_part = 0x%X;\n"
+ "surface->scaling_quality.h_taps = %d;\n"
+ "surface->scaling_quality.v_taps = %d;\n"
+ "surface->scaling_quality.h_taps_c = %d;\n"
+ "surface->scaling_quality.v_taps_c = %d;\n",
+ surface->visible,
+ surface->flip_immediate,
+ surface->address.type,
+ surface->address.grph.addr.quad_part,
+ surface->address.grph.meta_addr.quad_part,
+ surface->scaling_quality.h_taps,
+ surface->scaling_quality.v_taps,
+ surface->scaling_quality.h_taps_c,
+ surface->scaling_quality.v_taps_c);
+
+ SURFACE_TRACE(
+ "surface->src_rect.x = %d;\n"
+ "surface->src_rect.y = %d;\n"
+ "surface->src_rect.width = %d;\n"
+ "surface->src_rect.height = %d;\n"
+ "surface->dst_rect.x = %d;\n"
+ "surface->dst_rect.y = %d;\n"
+ "surface->dst_rect.width = %d;\n"
+ "surface->dst_rect.height = %d;\n"
+ "surface->clip_rect.x = %d;\n"
+ "surface->clip_rect.y = %d;\n"
+ "surface->clip_rect.width = %d;\n"
+ "surface->clip_rect.height = %d;\n",
+ surface->src_rect.x,
+ surface->src_rect.y,
+ surface->src_rect.width,
+ surface->src_rect.height,
+ surface->dst_rect.x,
+ surface->dst_rect.y,
+ surface->dst_rect.width,
+ surface->dst_rect.height,
+ surface->clip_rect.x,
+ surface->clip_rect.y,
+ surface->clip_rect.width,
+ surface->clip_rect.height);
+
+ SURFACE_TRACE(
+ "surface->plane_size.grph.surface_size.x = %d;\n"
+ "surface->plane_size.grph.surface_size.y = %d;\n"
+ "surface->plane_size.grph.surface_size.width = %d;\n"
+ "surface->plane_size.grph.surface_size.height = %d;\n"
+ "surface->plane_size.grph.surface_pitch = %d;\n"
+ "surface->plane_size.grph.meta_pitch = %d;\n",
+ surface->plane_size.grph.surface_size.x,
+ surface->plane_size.grph.surface_size.y,
+ surface->plane_size.grph.surface_size.width,
+ surface->plane_size.grph.surface_size.height,
+ surface->plane_size.grph.surface_pitch,
+ surface->plane_size.grph.meta_pitch);
+
+
+ SURFACE_TRACE(
+ "surface->tiling_info.gfx8.num_banks = %d;\n"
+ "surface->tiling_info.gfx8.bank_width = %d;\n"
+ "surface->tiling_info.gfx8.bank_width_c = %d;\n"
+ "surface->tiling_info.gfx8.bank_height = %d;\n"
+ "surface->tiling_info.gfx8.bank_height_c = %d;\n"
+ "surface->tiling_info.gfx8.tile_aspect = %d;\n"
+ "surface->tiling_info.gfx8.tile_aspect_c = %d;\n"
+ "surface->tiling_info.gfx8.tile_split = %d;\n"
+ "surface->tiling_info.gfx8.tile_split_c = %d;\n"
+ "surface->tiling_info.gfx8.tile_mode = %d;\n"
+ "surface->tiling_info.gfx8.tile_mode_c = %d;\n",
+ surface->tiling_info.gfx8.num_banks,
+ surface->tiling_info.gfx8.bank_width,
+ surface->tiling_info.gfx8.bank_width_c,
+ surface->tiling_info.gfx8.bank_height,
+ surface->tiling_info.gfx8.bank_height_c,
+ surface->tiling_info.gfx8.tile_aspect,
+ surface->tiling_info.gfx8.tile_aspect_c,
+ surface->tiling_info.gfx8.tile_split,
+ surface->tiling_info.gfx8.tile_split_c,
+ surface->tiling_info.gfx8.tile_mode,
+ surface->tiling_info.gfx8.tile_mode_c);
+
+ SURFACE_TRACE(
+ "surface->tiling_info.gfx8.pipe_config = %d;\n"
+ "surface->tiling_info.gfx8.array_mode = %d;\n"
+ "surface->color_space = %d;\n"
+ "surface->dcc.enable = %d;\n"
+ "surface->format = %d;\n"
+ "surface->rotation = %d;\n"
+ "surface->stereo_format = %d;\n",
+ surface->tiling_info.gfx8.pipe_config,
+ surface->tiling_info.gfx8.array_mode,
+ surface->color_space,
+ surface->dcc.enable,
+ surface->format,
+ surface->rotation,
+ surface->stereo_format);
+ SURFACE_TRACE("\n");
+ }
+ SURFACE_TRACE("\n");
+}
+
+void update_surface_trace(
+ const struct dc *dc,
+ const struct dc_surface_update *updates,
+ int surface_count)
+{
+ int i;
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ struct dal_logger *logger = core_dc->ctx->logger;
+
+ for (i = 0; i < surface_count; i++) {
+ const struct dc_surface_update *update = &updates[i];
+
+ SURFACE_TRACE("Update %d\n", i);
+ if (update->flip_addr) {
+ SURFACE_TRACE("flip_addr->address.type = %d;\n"
+ "flip_addr->address.grph.addr.quad_part = 0x%X;\n"
+ "flip_addr->address.grph.meta_addr.quad_part = 0x%X;\n"
+ "flip_addr->flip_immediate = %d;\n",
+ update->flip_addr->address.type,
+ update->flip_addr->address.grph.addr.quad_part,
+ update->flip_addr->address.grph.meta_addr.quad_part,
+ update->flip_addr->flip_immediate);
+ }
+
+ if (update->plane_info) {
+ SURFACE_TRACE(
+ "plane_info->color_space = %d;\n"
+ "plane_info->format = %d;\n"
+ "plane_info->plane_size.grph.meta_pitch = %d;\n"
+ "plane_info->plane_size.grph.surface_pitch = %d;\n"
+ "plane_info->plane_size.grph.surface_size.height = %d;\n"
+ "plane_info->plane_size.grph.surface_size.width = %d;\n"
+ "plane_info->plane_size.grph.surface_size.x = %d;\n"
+ "plane_info->plane_size.grph.surface_size.y = %d;\n"
+ "plane_info->rotation = %d;\n",
+ update->plane_info->color_space,
+ update->plane_info->format,
+ update->plane_info->plane_size.grph.meta_pitch,
+ update->plane_info->plane_size.grph.surface_pitch,
+ update->plane_info->plane_size.grph.surface_size.height,
+ update->plane_info->plane_size.grph.surface_size.width,
+ update->plane_info->plane_size.grph.surface_size.x,
+ update->plane_info->plane_size.grph.surface_size.y,
+ update->plane_info->rotation,
+ update->plane_info->stereo_format);
+
+ SURFACE_TRACE(
+ "plane_info->tiling_info.gfx8.num_banks = %d;\n"
+ "plane_info->tiling_info.gfx8.bank_width = %d;\n"
+ "plane_info->tiling_info.gfx8.bank_width_c = %d;\n"
+ "plane_info->tiling_info.gfx8.bank_height = %d;\n"
+ "plane_info->tiling_info.gfx8.bank_height_c = %d;\n"
+ "plane_info->tiling_info.gfx8.tile_aspect = %d;\n"
+ "plane_info->tiling_info.gfx8.tile_aspect_c = %d;\n"
+ "plane_info->tiling_info.gfx8.tile_split = %d;\n"
+ "plane_info->tiling_info.gfx8.tile_split_c = %d;\n"
+ "plane_info->tiling_info.gfx8.tile_mode = %d;\n"
+ "plane_info->tiling_info.gfx8.tile_mode_c = %d;\n",
+ update->plane_info->tiling_info.gfx8.num_banks,
+ update->plane_info->tiling_info.gfx8.bank_width,
+ update->plane_info->tiling_info.gfx8.bank_width_c,
+ update->plane_info->tiling_info.gfx8.bank_height,
+ update->plane_info->tiling_info.gfx8.bank_height_c,
+ update->plane_info->tiling_info.gfx8.tile_aspect,
+ update->plane_info->tiling_info.gfx8.tile_aspect_c,
+ update->plane_info->tiling_info.gfx8.tile_split,
+ update->plane_info->tiling_info.gfx8.tile_split_c,
+ update->plane_info->tiling_info.gfx8.tile_mode,
+ update->plane_info->tiling_info.gfx8.tile_mode_c);
+
+ SURFACE_TRACE(
+ "plane_info->tiling_info.gfx8.pipe_config = %d;\n"
+ "plane_info->tiling_info.gfx8.array_mode = %d;\n"
+ "plane_info->visible = %d;\n",
+ update->plane_info->tiling_info.gfx8.pipe_config,
+ update->plane_info->tiling_info.gfx8.array_mode,
+ update->plane_info->visible);
+ }
+
+ if (update->scaling_info) {
+ SURFACE_TRACE(
+ "scaling_info->src_rect.x = %d;\n"
+ "scaling_info->src_rect.y = %d;\n"
+ "scaling_info->src_rect.width = %d;\n"
+ "scaling_info->src_rect.height = %d;\n"
+ "scaling_info->dst_rect.x = %d;\n"
+ "scaling_info->dst_rect.y = %d;\n"
+ "scaling_info->dst_rect.width = %d;\n"
+ "scaling_info->dst_rect.height = %d;\n"
+ "scaling_info->clip_rect.x = %d;\n"
+ "scaling_info->clip_rect.y = %d;\n"
+ "scaling_info->clip_rect.width = %d;\n"
+ "scaling_info->clip_rect.height = %d;\n"
+ "scaling_info->scaling_quality.h_taps = %d;\n"
+ "scaling_info->scaling_quality.v_taps = %d;\n"
+ "scaling_info->scaling_quality.h_taps_c = %d;\n"
+ "scaling_info->scaling_quality.v_taps_c = %d;\n",
+ update->scaling_info->src_rect.x,
+ update->scaling_info->src_rect.y,
+ update->scaling_info->src_rect.width,
+ update->scaling_info->src_rect.height,
+ update->scaling_info->dst_rect.x,
+ update->scaling_info->dst_rect.y,
+ update->scaling_info->dst_rect.width,
+ update->scaling_info->dst_rect.height,
+ update->scaling_info->clip_rect.x,
+ update->scaling_info->clip_rect.y,
+ update->scaling_info->clip_rect.width,
+ update->scaling_info->clip_rect.height,
+ update->scaling_info->scaling_quality.h_taps,
+ update->scaling_info->scaling_quality.v_taps,
+ update->scaling_info->scaling_quality.h_taps_c,
+ update->scaling_info->scaling_quality.v_taps_c);
+ }
+ SURFACE_TRACE("\n");
+ }
+ SURFACE_TRACE("\n");
+}
+
+void post_surface_trace(const struct dc *dc)
+{
+ struct core_dc *core_dc = DC_TO_CORE(dc);
+ struct dal_logger *logger = core_dc->ctx->logger;
+
+ SURFACE_TRACE("post surface process.\n");
+
+}
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c
new file mode 100644
index 000000000000..d5cffa51ca96
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c
@@ -0,0 +1,93 @@
+/*
+ * 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"
+#include "core_dc.h"
+#include "timing_generator.h"
+#include "hw_sequencer.h"
+
+/* used as index in array of black_color_format */
+enum black_color_format {
+ BLACK_COLOR_FORMAT_RGB_FULLRANGE = 0,
+ BLACK_COLOR_FORMAT_RGB_LIMITED,
+ BLACK_COLOR_FORMAT_YUV_TV,
+ BLACK_COLOR_FORMAT_YUV_CV,
+ BLACK_COLOR_FORMAT_YUV_SUPER_AA,
+ BLACK_COLOR_FORMAT_DEBUG,
+};
+
+static const struct tg_color black_color_format[] = {
+ /* BlackColorFormat_RGB_FullRange */
+ {0, 0, 0},
+ /* BlackColorFormat_RGB_Limited */
+ {0x40, 0x40, 0x40},
+ /* BlackColorFormat_YUV_TV */
+ {0x200, 0x40, 0x200},
+ /* BlackColorFormat_YUV_CV */
+ {0x1f4, 0x40, 0x1f4},
+ /* BlackColorFormat_YUV_SuperAA */
+ {0x1a2, 0x20, 0x1a2},
+ /* visual confirm debug */
+ {0xff, 0xff, 0},
+};
+
+void color_space_to_black_color(
+ const struct core_dc *dc,
+ enum dc_color_space colorspace,
+ struct tg_color *black_color)
+{
+ if (dc->public.debug.surface_visual_confirm) {
+ *black_color =
+ black_color_format[BLACK_COLOR_FORMAT_DEBUG];
+ return;
+ }
+
+ switch (colorspace) {
+ case COLOR_SPACE_YPBPR601:
+ *black_color = black_color_format[BLACK_COLOR_FORMAT_YUV_TV];
+ break;
+
+ case COLOR_SPACE_YPBPR709:
+ case COLOR_SPACE_YCBCR601:
+ case COLOR_SPACE_YCBCR709:
+ case COLOR_SPACE_YCBCR601_LIMITED:
+ case COLOR_SPACE_YCBCR709_LIMITED:
+ *black_color = black_color_format[BLACK_COLOR_FORMAT_YUV_CV];
+ break;
+
+ case COLOR_SPACE_SRGB_LIMITED:
+ *black_color =
+ black_color_format[BLACK_COLOR_FORMAT_RGB_LIMITED];
+ break;
+
+ default:
+ /* fefault is sRGB black (full range). */
+ *black_color =
+ black_color_format[BLACK_COLOR_FORMAT_RGB_FULLRANGE];
+ /* default is sRGB black 0. */
+ break;
+ }
+}
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_link.c b/drivers/gpu/drm/amd/display/dc/core/dc_link.c
new file mode 100644
index 000000000000..70a25546de1e
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_link.c
@@ -0,0 +1,1899 @@
+/*
+ * 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 "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 "resource.h"
+#include "fixed31_32.h"
+#include "include/asic_capability_interface.h"
+
+#include "dce/dce_11_0_d.h"
+#include "dce/dce_11_0_enum.h"
+#include "dce/dce_11_0_sh_mask.h"
+
+#ifndef mmDMCU_STATUS__UC_IN_RESET__SHIFT
+#define mmDMCU_STATUS__UC_IN_RESET__SHIFT 0x0
+#endif
+
+#ifndef mmDMCU_STATUS__UC_IN_RESET_MASK
+#define mmDMCU_STATUS__UC_IN_RESET_MASK 0x00000001L
+#endif
+
+#define LINK_INFO(...) \
+ dm_logger_write(dc_ctx->logger, LOG_HW_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)
+{
+ int i;
+
+ if (link->ddc)
+ dal_ddc_service_destroy(&link->ddc);
+
+ if(link->link_enc)
+ link->link_enc->funcs->destroy(&link->link_enc);
+
+ if (link->public.local_sink)
+ dc_sink_release(link->public.local_sink);
+
+ for (i = 0; i < link->public.sink_count; ++i)
+ dc_sink_release(link->public.remote_sinks[i]);
+}
+
+static struct gpio *get_hpd_gpio(const struct core_link *link)
+{
+ enum bp_result bp_result;
+ struct dc_bios *dcb = link->ctx->dc_bios;
+ struct graphics_object_hpd_info hpd_info;
+ struct gpio_pin_info pin_info;
+
+ if (dcb->funcs->get_hpd_info(dcb, link->link_id, &hpd_info) != BP_RESULT_OK)
+ return NULL;
+
+ bp_result = dcb->funcs->get_gpio_pin_info(dcb,
+ hpd_info.hpd_int_gpio_uid, &pin_info);
+
+ if (bp_result != BP_RESULT_OK) {
+ ASSERT(bp_result == BP_RESULT_NORECORD);
+ return NULL;
+ }
+
+ return dal_gpio_service_create_irq(
+ link->ctx->gpio_service,
+ pin_info.offset,
+ pin_info.mask);
+}
+
+/*
+ * 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 gpio *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 */
+ /* 500: not able to detect MST <-> SST switch as HPD is low for
+ * only 100ms on DELL U2413
+ * 0: some passive dongle still show aux mode instead of i2c
+ * 20-50:not enough to hide bouncing HPD with passive dongle.
+ * also see intermittent i2c read issues.
+ */
+ delay_on_connect_in_ms = 80;
+ delay_on_disconnect_in_ms = 0;
+ break;
+ case SIGNAL_TYPE_LVDS:
+ case SIGNAL_TYPE_EDP:
+ default:
+ /* Don't program hpd filter */
+ return false;
+ }
+
+ /* Obtain HPD handle */
+ hpd = get_hpd_gpio(link);
+
+ if (!hpd)
+ return result;
+
+ /* Setup HPD filtering */
+ if (dal_gpio_open(hpd, GPIO_MODE_INTERRUPT) == 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_gpio_close(hpd);
+
+ result = true;
+ } else {
+ ASSERT_CRITICAL(false);
+ }
+
+ /* Release HPD handle */
+ dal_gpio_destroy_irq(&hpd);
+
+ return result;
+}
+
+static bool detect_sink(struct core_link *link, enum dc_connection_type *type)
+{
+ uint32_t is_hpd_high = 0;
+ struct gpio *hpd_pin;
+
+ /* todo: may need to lock gpio access */
+ hpd_pin = get_hpd_gpio(link);
+ if (hpd_pin == NULL)
+ goto hpd_gpio_failure;
+
+ dal_gpio_open(hpd_pin, GPIO_MODE_INTERRUPT);
+ dal_gpio_get_value(hpd_pin, &is_hpd_high);
+ dal_gpio_close(hpd_pin);
+ dal_gpio_destroy_irq(&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_ddc_service_get_ddc_pin(link->ddc);
+
+ if (!ddc) {
+ BREAK_TO_DEBUGGER();
+ 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_gpio_destroy_ddc(&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_gpio_get_value(ddc->pin_clock, &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_gpio_get_value(ddc->pin_data, &data_pin);
+
+ present = (gpio_result == GPIO_RESULT_OK) && !(clock_pin || data_pin);
+
+ dal_ddc_close(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 */
+ struct audio_support *aud_support = &link->dc->res_pool->audio_support;
+
+ if (!aud_support->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,
+ struct audio_support *audio_support)
+{
+ enum signal_type signal = SIGNAL_TYPE_NONE;
+
+ switch (dongle_type) {
+ case DISPLAY_DONGLE_DP_HDMI_DONGLE:
+ if (audio_support->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->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,
+ struct audio_support *audio_support)
+{
+ 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)
+ dm_logger_write(link->ctx->logger, LOG_WARNING,
+ "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,
+ struct 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)) {
+ link->public.type = dc_connection_active_dongle;
+ if (!link->dpcd_caps.sink_count.bits.SINK_COUNT) {
+ /*
+ * 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;
+ struct audio_support *aud_support = &link->dc->res_pool->audio_support;
+ 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;
+ }
+
+ if (link->public.connector_signal == SIGNAL_TYPE_EDP &&
+ link->public.local_sink)
+ return true;
+
+ 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 (aud_support->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,
+ aud_support, boot);
+
+ /* Active dongle downstream unplug */
+ if (link->public.type == dc_connection_active_dongle
+ && link->dpcd_caps.sink_count.
+ bits.SINK_COUNT == 0)
+ 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:
+ dm_logger_write(link->ctx->logger, LOG_ERROR,
+ "EDID checksum invalid.\n");
+ break;
+ case EDID_NO_RESPONSE:
+ dm_logger_write(link->ctx->logger, LOG_ERROR,
+ "No EDID read.\n");
+ return false;
+
+ default:
+ break;
+ }
+
+ /* HDMI-DVI Dongle */
+ if (dc_sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A &&
+ !dc_sink->edid_caps.edid_hdmi)
+ dc_sink->sink_signal = SIGNAL_TYPE_DVI_SINGLE_LINK;
+
+ /* Connectivity log: detection */
+ for (i = 0; i < sink->public.dc_edid.length / EDID_BLOCK_SIZE; i++) {
+ CONN_DATA_DETECT(link,
+ &sink->public.dc_edid.raw_edid[i * EDID_BLOCK_SIZE],
+ EDID_BLOCK_SIZE,
+ "%s: [Block %d] ", sink->public.edid_caps.display_name, i);
+ }
+
+ dm_logger_write(link->ctx->logger, LOG_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++) {
+ dm_logger_write(link->ctx->logger, LOG_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->mst_stream_alloc_table.stream_count = 0;
+ memset(link->mst_stream_alloc_table.stream_allocations, 0, sizeof(link->mst_stream_alloc_table.stream_allocations));
+ }
+
+ 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 gpio *hpd;
+ enum hpd_source_id hpd_id = HPD_SOURCEID_UNKNOWN;
+
+ hpd = get_hpd_gpio(link);
+
+ if (hpd) {
+ switch (dal_irq_get_source(hpd)) {
+ case DC_IRQ_SOURCE_HPD1:
+ hpd_id = HPD_SOURCEID1;
+ break;
+ case DC_IRQ_SOURCE_HPD2:
+ hpd_id = HPD_SOURCEID2;
+ break;
+ case DC_IRQ_SOURCE_HPD3:
+ hpd_id = HPD_SOURCEID3;
+ break;
+ case DC_IRQ_SOURCE_HPD4:
+ hpd_id = HPD_SOURCEID4;
+ break;
+ case DC_IRQ_SOURCE_HPD5:
+ hpd_id = HPD_SOURCEID5;
+ break;
+ case DC_IRQ_SOURCE_HPD6:
+ hpd_id = HPD_SOURCEID6;
+ break;
+ default:
+ BREAK_TO_DEBUGGER();
+ break;
+ }
+
+ dal_gpio_destroy_irq(&hpd);
+ }
+
+ return hpd_id;
+}
+
+static enum channel_id get_ddc_line(struct core_link *link)
+{
+ struct ddc *ddc;
+ enum channel_id channel = CHANNEL_ID_UNKNOWN;
+
+ ddc = dal_ddc_service_get_ddc_pin(link->ddc);
+
+ if (ddc) {
+ switch (dal_ddc_get_line(ddc)) {
+ case GPIO_DDC_LINE_DDC1:
+ channel = CHANNEL_ID_DDC1;
+ break;
+ case GPIO_DDC_LINE_DDC2:
+ channel = CHANNEL_ID_DDC2;
+ break;
+ case GPIO_DDC_LINE_DDC3:
+ channel = CHANNEL_ID_DDC3;
+ break;
+ case GPIO_DDC_LINE_DDC4:
+ channel = CHANNEL_ID_DDC4;
+ break;
+ case GPIO_DDC_LINE_DDC5:
+ channel = CHANNEL_ID_DDC5;
+ break;
+ case GPIO_DDC_LINE_DDC6:
+ channel = CHANNEL_ID_DDC6;
+ break;
+ case GPIO_DDC_LINE_DDC_VGA:
+ channel = CHANNEL_ID_DDC_VGA;
+ break;
+ case GPIO_DDC_LINE_I2C_PAD:
+ channel = CHANNEL_ID_I2C_PAD;
+ break;
+ default:
+ BREAK_TO_DEBUGGER();
+ break;
+ }
+ }
+
+ return channel;
+}
+
+static 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 gpio *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 }}};
+ struct dc_bios *bios = init_params->dc->ctx->dc_bios;
+ const struct dc_vbios_funcs *bp_funcs = bios->funcs;
+
+ link->public.irq_source_hpd = DC_IRQ_SOURCE_INVALID;
+ link->public.irq_source_hpd_rx = DC_IRQ_SOURCE_INVALID;
+
+ link->link_status.dpcd_caps = &link->dpcd_caps;
+
+ link->dc = init_params->dc;
+ link->ctx = dc_ctx;
+ link->public.link_index = init_params->link_index;
+
+ link->link_id = bios->funcs->get_connector_id(bios, 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;
+ }
+
+ hpd_gpio = get_hpd_gpio(link);
+
+ if (hpd_gpio != NULL)
+ link->public.irq_source_hpd = dal_irq_get_source(hpd_gpio);
+
+ 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;
+
+ if (hpd_gpio != NULL)
+ link->public.irq_source_hpd_rx =
+ dal_irq_get_rx_source(hpd_gpio);
+
+ break;
+ case CONNECTOR_ID_EDP:
+ link->public.connector_signal = SIGNAL_TYPE_EDP;
+
+ if (hpd_gpio != NULL) {
+ link->public.irq_source_hpd = DC_IRQ_SOURCE_INVALID;
+ link->public.irq_source_hpd_rx =
+ dal_irq_get_rx_source(hpd_gpio);
+ }
+ break;
+ default:
+ dm_logger_write(dc_ctx->logger, LOG_WARNING,
+ "Unsupported Connector type:%d!\n", link->link_id.id);
+ goto create_fail;
+ }
+
+ if (hpd_gpio != NULL) {
+ dal_gpio_destroy_irq(&hpd_gpio);
+ hpd_gpio = NULL;
+ }
+
+ /* TODO: #DAL3 Implement id to str function.*/
+ LINK_INFO("Connector[%d] description:"
+ "signal %d\n",
+ init_params->connector_index,
+ link->public.connector_signal);
+
+ ddc_service_init_data.ctx = link->ctx;
+ ddc_service_init_data.id = link->link_id;
+ ddc_service_init_data.link = link;
+ link->ddc = dal_ddc_service_create(&ddc_service_init_data);
+
+ if (NULL == link->ddc) {
+ DC_ERROR("Failed to create ddc_service!\n");
+ goto ddc_create_fail;
+ }
+
+ link->public.ddc_hw_inst =
+ dal_ddc_get_line(
+ dal_ddc_service_get_ddc_pin(link->ddc));
+
+ enc_init_data.ctx = dc_ctx;
+ bp_funcs->get_src_obj(dc_ctx->dc_bios, link->link_id, 0, &enc_init_data.encoder);
+ enc_init_data.connector = link->link_id;
+ enc_init_data.channel = get_ddc_line(link);
+ enc_init_data.hpd_source = get_hpd_line(link);
+ enc_init_data.transmitter =
+ translate_encoder_to_transmitter(enc_init_data.encoder);
+ link->link_enc = link->dc->res_pool->funcs->link_enc_create(
+ &enc_init_data);
+
+ if( link->link_enc == NULL) {
+ DC_ERROR("Failed to create link encoder!\n");
+ goto link_enc_create_fail;
+ }
+
+ link->public.link_enc_hw_inst = link->link_enc->transmitter;
+
+ for (i = 0; i < 4; i++) {
+ if (BP_RESULT_OK !=
+ bp_funcs->get_device_tag(dc_ctx->dc_bios, link->link_id, i, &link->device_tag)) {
+ DC_ERROR("Failed to find device tag!\n");
+ goto device_tag_fail;
+ }
+
+ /* Look for device tag that matches connector signal,
+ * CRT for rgb, LCD for other supported signal tyes
+ */
+ if (!bp_funcs->is_device_id_supported(dc_ctx->dc_bios, link->device_tag.dev_id))
+ continue;
+ if (link->device_tag.dev_id.device_type == DEVICE_TYPE_CRT
+ && link->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;
+ }
+
+ if (bios->integrated_info)
+ info = *bios->integrated_info;
+
+ /* Look for channel mapping corresponding to connector and device tag */
+ for (i = 0; i < MAX_NUMBER_OF_EXT_DISPLAY_PATH; i++) {
+ struct external_display_path *path =
+ &info.ext_disp_conn_info.path[i];
+ if (path->device_connector_id.enum_id == link->link_id.enum_id
+ && path->device_connector_id.id == link->link_id.id
+ && path->device_connector_id.type == link->link_id.type
+ && 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;
+device_tag_fail:
+ link->link_enc->funcs->destroy(&link->link_enc);
+link_enc_create_fail:
+ dal_ddc_service_destroy(&link->ddc);
+ddc_create_fail:
+create_fail:
+
+ if (hpd_gpio != NULL) {
+ dal_gpio_destroy_irq(&hpd_gpio);
+ }
+
+ return false;
+}
+
+/*******************************************************************************
+ * Public functions
+ ******************************************************************************/
+struct core_link *link_create(const struct link_init_data *init_params)
+{
+ struct core_link *link =
+ dm_alloc(sizeof(*link));
+
+ if (NULL == link)
+ goto alloc_fail;
+
+ if (false == construct(link, init_params))
+ goto construct_fail;
+
+ return link;
+
+construct_fail:
+ dm_free(link);
+
+alloc_fail:
+ return NULL;
+}
+
+void link_destroy(struct core_link **link)
+{
+ destruct(*link);
+ dm_free(*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;
+
+ 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);
+ }
+ }
+ dm_logger_write(link->ctx->logger, LOG_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 void enable_stream_features(struct pipe_ctx *pipe_ctx)
+{
+ struct core_stream *stream = pipe_ctx->stream;
+ struct core_link *link = stream->sink->link;
+ union down_spread_ctrl downspread;
+
+ core_link_read_dpcd(link, DPCD_ADDRESS_DOWNSPREAD_CNTL,
+ &downspread.raw, sizeof(downspread));
+
+ downspread.bits.IGNORE_MSA_TIMING_PARAM =
+ (stream->public.ignore_msa_timing_param) ? 1 : 0;
+
+ core_link_write_dpcd(link, DPCD_ADDRESS_DOWNSPREAD_CNTL,
+ &downspread.raw, sizeof(downspread));
+}
+
+static enum dc_status enable_link_dp(struct pipe_ctx *pipe_ctx)
+{
+ struct core_stream *stream = pipe_ctx->stream;
+ enum dc_status status;
+ bool skip_video_pattern;
+ struct core_link *link = stream->sink->link;
+ struct dc_link_settings link_settings = {0};
+ enum dp_panel_mode panel_mode;
+ enum clocks_state cur_min_clock_state;
+ enum dc_link_rate max_link_rate = LINK_RATE_HIGH2;
+
+ /* get link settings for video mode timing */
+ decide_link_settings(stream, &link_settings);
+
+ /* raise clock state for HBR3 if required. Confirmed with HW DCE/DPCS
+ * logic for HBR3 still needs Nominal (0.8V) on VDDC rail
+ */
+ if (link->link_enc->features.flags.bits.IS_HBR3_CAPABLE)
+ max_link_rate = LINK_RATE_HIGH3;
+
+ if (link_settings.link_rate == max_link_rate) {
+ cur_min_clock_state = CLOCKS_STATE_INVALID;
+
+ if (dal_display_clock_get_min_clocks_state(
+ pipe_ctx->dis_clk, &cur_min_clock_state)) {
+ if (cur_min_clock_state < CLOCKS_STATE_NOMINAL)
+ dal_display_clock_set_min_clocks_state(
+ pipe_ctx->dis_clk,
+ CLOCKS_STATE_NOMINAL);
+ } else {
+ }
+ }
+
+ dp_enable_link_phy(
+ link,
+ pipe_ctx->stream->signal,
+ pipe_ctx->clock_source->id,
+ &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_with_retries(
+ link,
+ &link_settings,
+ skip_video_pattern,
+ LINK_TRAINING_ATTEMPTS)) {
+ link->public.cur_link_settings = link_settings;
+ status = DC_OK;
+ }
+ else
+ status = DC_ERROR_UNEXPECTED;
+
+ enable_stream_features(pipe_ctx);
+
+ return status;
+}
+
+static enum dc_status enable_link_dp_mst(struct pipe_ctx *pipe_ctx)
+{
+ struct core_link *link = pipe_ctx->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->public.cur_link_settings.lane_count != LANE_COUNT_UNKNOWN)
+ return DC_OK;
+
+ return enable_link_dp(pipe_ctx);
+}
+
+static void enable_link_hdmi(struct pipe_ctx *pipe_ctx)
+{
+ struct core_stream *stream = pipe_ctx->stream;
+ struct core_link *link = stream->sink->link;
+
+ if (dc_is_hdmi_signal(pipe_ctx->stream->signal))
+ dal_ddc_service_write_scdc_data(
+ stream->sink->link->ddc,
+ stream->phy_pix_clk,
+ stream->public.timing.flags.LTE_340MCSC_SCRAMBLE);
+
+ memset(&stream->sink->link->public.cur_link_settings, 0,
+ sizeof(struct dc_link_settings));
+
+ link->link_enc->funcs->enable_tmds_output(
+ link->link_enc,
+ pipe_ctx->clock_source->id,
+ stream->public.timing.display_color_depth,
+ pipe_ctx->stream->signal == SIGNAL_TYPE_HDMI_TYPE_A,
+ pipe_ctx->stream->signal == SIGNAL_TYPE_DVI_DUAL_LINK,
+ stream->phy_pix_clk);
+
+ if (pipe_ctx->stream->signal == SIGNAL_TYPE_HDMI_TYPE_A)
+ dal_ddc_service_read_scdc_data(link->ddc);
+}
+
+/****************************enable_link***********************************/
+static enum dc_status enable_link(struct pipe_ctx *pipe_ctx)
+{
+ enum dc_status status = DC_ERROR_UNEXPECTED;
+ switch (pipe_ctx->stream->signal) {
+ case SIGNAL_TYPE_DISPLAY_PORT:
+ case SIGNAL_TYPE_EDP:
+ status = enable_link_dp(pipe_ctx);
+ break;
+ case SIGNAL_TYPE_DISPLAY_PORT_MST:
+ status = enable_link_dp_mst(pipe_ctx);
+ msleep(200);
+ break;
+ case SIGNAL_TYPE_DVI_SINGLE_LINK:
+ case SIGNAL_TYPE_DVI_DUAL_LINK:
+ case SIGNAL_TYPE_HDMI_TYPE_A:
+ enable_link_hdmi(pipe_ctx);
+ status = DC_OK;
+ break;
+ case SIGNAL_TYPE_VIRTUAL:
+ status = DC_OK;
+ break;
+ default:
+ break;
+ }
+
+ if (pipe_ctx->audio && status == DC_OK) {
+ /* notify audio driver for audio modes of monitor */
+ pipe_ctx->audio->funcs->az_enable(pipe_ctx->audio);
+
+ /* un-mute audio */
+ /* TODO: audio should be per stream rather than per link */
+ pipe_ctx->stream_enc->funcs->audio_mute_control(
+ pipe_ctx->stream_enc, false);
+ }
+
+ return status;
+}
+
+static void disable_link(struct core_link *link, enum signal_type signal)
+{
+ /*
+ * TODO: implement call for dp_set_hw_test_pattern
+ * it is needed for compliance testing
+ */
+
+ /* 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(signal)) {
+ /* SST DP, eDP */
+ if (dc_is_dp_sst_signal(signal))
+ dp_disable_link_phy(link, signal);
+ else
+ dp_disable_link_phy_mst(link, signal);
+ } else
+ link->link_enc->funcs->disable_output(link->link_enc, signal);
+}
+
+enum dc_status dc_link_validate_mode_timing(
+ const struct core_stream *stream,
+ struct core_link *link,
+ const struct dc_crtc_timing *timing)
+{
+ uint32_t max_pix_clk = stream->sink->dongle_max_pix_clk;
+
+ /* A hack to avoid failing any modes for EDID override feature on
+ * topology change such as lower quality cable for DP or different dongle
+ */
+ if (link->public.remote_sinks[0])
+ return DC_OK;
+
+ if (0 != max_pix_clk && timing->pix_clk_khz > max_pix_clk)
+ return DC_EXCEED_DONGLE_MAX_CLK;
+
+ switch (stream->signal) {
+ case SIGNAL_TYPE_EDP:
+ 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 *dc_link, uint32_t level,
+ uint32_t frame_ramp, const struct dc_stream *stream)
+{
+ struct core_link *link = DC_LINK_TO_CORE(dc_link);
+ struct dc_context *ctx = link->ctx;
+ struct core_dc *core_dc = DC_TO_CORE(ctx->dc);
+ struct core_stream *core_stream = DC_STREAM_TO_CORE(stream);
+ unsigned int controller_id = 0;
+ int i;
+ uint32_t dmcu_status;
+
+ dm_logger_write(ctx->logger, LOG_BACKLIGHT,
+ "New Backlight level: %d (0x%X)\n", level, level);
+
+ dmcu_status = dm_read_reg(ctx, mmDMCU_STATUS);
+
+ /* If DMCU is in reset state, DMCU is uninitialized */
+ if (get_reg_field_value(dmcu_status, mmDMCU_STATUS, UC_IN_RESET)) {
+ link->link_enc->funcs->set_lcd_backlight_level(link->link_enc,
+ level);
+ } else {
+ for (i = 0; i < MAX_PIPES; i++) {
+ if (core_dc->current_context->res_ctx.pipe_ctx[i].stream
+ == core_stream)
+ /* dmcu -1 for all controller id values,
+ * therefore +1 here
+ */
+ controller_id = core_dc->current_context->res_ctx.
+ pipe_ctx[i].tg->inst + 1;
+ }
+
+ link->link_enc->funcs->set_dmcu_backlight_level
+ (link->link_enc, level,
+ frame_ramp, controller_id);
+ }
+ return true;
+}
+
+
+bool dc_link_init_dmcu_backlight_settings(const struct dc_link *dc_link)
+{
+ struct core_link *link = DC_LINK_TO_CORE(dc_link);
+
+ if (link->link_enc->funcs->init_dmcu_backlight_settings != NULL)
+ link->link_enc->funcs->
+ init_dmcu_backlight_settings(link->link_enc);
+
+ return true;
+}
+
+bool dc_link_set_abm_level(const struct dc_link *dc_link, uint32_t level)
+{
+ struct core_link *link = DC_LINK_TO_CORE(dc_link);
+ struct dc_context *ctx = link->ctx;
+
+ dm_logger_write(ctx->logger, LOG_BACKLIGHT,
+ "New abm level: %d (0x%X)\n", level, level);
+
+ link->link_enc->funcs->set_dmcu_abm_level(link->link_enc, level);
+ return true;
+}
+
+bool dc_link_set_psr_enable(const struct dc_link *dc_link, bool enable)
+{
+ struct core_link *link = DC_LINK_TO_CORE(dc_link);
+
+ if (dc_link != NULL && dc_link->psr_caps.psr_version > 0)
+ link->link_enc->funcs->set_dmcu_psr_enable(link->link_enc,
+ enable);
+ return true;
+}
+
+bool dc_link_setup_psr(const struct dc_link *dc_link,
+ const struct dc_stream *stream)
+{
+
+ struct core_link *link = DC_LINK_TO_CORE(dc_link);
+ struct dc_context *ctx = link->ctx;
+ struct core_dc *core_dc = DC_TO_CORE(ctx->dc);
+ struct core_stream *core_stream = DC_STREAM_TO_CORE(stream);
+ struct psr_dmcu_context psr_context = {0};
+ int i;
+
+ psr_context.controllerId = CONTROLLER_ID_UNDEFINED;
+
+
+ if (dc_link != NULL && dc_link->psr_caps.psr_version > 0) {
+ /* updateSinkPsrDpcdConfig*/
+ union dpcd_psr_configuration psr_configuration;
+
+ memset(&psr_configuration, 0, sizeof(psr_configuration));
+
+ psr_configuration.bits.ENABLE = 1;
+ psr_configuration.bits.CRC_VERIFICATION = 1;
+ psr_configuration.bits.FRAME_CAPTURE_INDICATION =
+ dc_link->psr_caps.psr_frame_capture_indication_req;
+
+ /* Check for PSR v2*/
+ if (dc_link->psr_caps.psr_version == 0x2) {
+ /* For PSR v2 selective update.
+ * Indicates whether sink should start capturing
+ * immediately following active scan line,
+ * or starting with the 2nd active scan line.
+ */
+ psr_configuration.bits.LINE_CAPTURE_INDICATION = 0;
+ /*For PSR v2, determines whether Sink should generate
+ * IRQ_HPD when CRC mismatch is detected.
+ */
+ psr_configuration.bits.IRQ_HPD_WITH_CRC_ERROR = 1;
+ }
+ dal_ddc_service_write_dpcd_data(
+ link->ddc,
+ 368,
+ &psr_configuration.raw,
+ sizeof(psr_configuration.raw));
+
+ psr_context.channel = link->ddc->ddc_pin->hw_info.ddc_channel;
+ if (psr_context.channel == 0)
+ psr_context.channel = 1;
+ psr_context.transmitterId = link->link_enc->transmitter;
+ psr_context.engineId = link->link_enc->preferred_engine;
+
+ for (i = 0; i < MAX_PIPES; i++) {
+ if (core_dc->current_context->res_ctx.pipe_ctx[i].stream
+ == core_stream) {
+ /* dmcu -1 for all controller id values,
+ * therefore +1 here
+ */
+ psr_context.controllerId =
+ core_dc->current_context->res_ctx.
+ pipe_ctx[i].tg->inst + 1;
+ break;
+ }
+ }
+
+ /* Hardcoded for now. Can be Pcie or Uniphy (or Unknown)*/
+ psr_context.phyType = PHY_TYPE_UNIPHY;
+ /*PhyId is associated with the transmitter id*/
+ psr_context.smuPhyId = link->link_enc->transmitter;
+
+ psr_context.crtcTimingVerticalTotal = stream->timing.v_total;
+ psr_context.vsyncRateHz = div64_u64(div64_u64((stream->
+ timing.pix_clk_khz * 1000),
+ stream->timing.v_total),
+ stream->timing.h_total);
+
+ psr_context.psrSupportedDisplayConfig =
+ (dc_link->psr_caps.psr_version > 0) ? true : false;
+ psr_context.psrExitLinkTrainingRequired =
+ dc_link->psr_caps.psr_exit_link_training_required;
+ psr_context.sdpTransmitLineNumDeadline =
+ dc_link->psr_caps.psr_sdp_transmit_line_num_deadline;
+ psr_context.psrFrameCaptureIndicationReq =
+ dc_link->psr_caps.psr_frame_capture_indication_req;
+
+ psr_context.skipPsrWaitForPllLock = 0; /* only = 1 in KV */
+
+ psr_context.numberOfControllers =
+ link->dc->res_pool->res_cap->num_timing_generator;
+
+ psr_context.rfb_update_auto_en = true;
+
+ /* 2 frames before enter PSR. */
+ psr_context.timehyst_frames = 2;
+ /* half a frame
+ * (units in 100 lines, i.e. a value of 1 represents 100 lines)
+ */
+ psr_context.hyst_lines = stream->timing.v_total / 2 / 100;
+ psr_context.aux_repeats = 10;
+
+ psr_context.psr_level.u32all = 0;
+
+ /* SMU will perform additional powerdown sequence.
+ * For unsupported ASICs, set psr_level flag to skip PSR
+ * static screen notification to SMU.
+ * (Always set for DAL2, did not check ASIC)
+ */
+ psr_context.psr_level.bits.SKIP_SMU_NOTIFICATION = 1;
+
+ /* Controls additional delay after remote frame capture before
+ * continuing power down, default = 0
+ */
+ psr_context.frame_delay = 0;
+
+ link->link_enc->funcs->setup_dmcu_psr
+ (link->link_enc, &psr_context);
+ return true;
+ } else
+ return false;
+
+}
+
+const struct dc_link_status *dc_link_get_status(const struct dc_link *dc_link)
+{
+ struct core_link *link = DC_LINK_TO_CORE(dc_link);
+
+ return &link->link_status;
+}
+
+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 dc_link_settings *link_settings =
+ &stream->sink->link->public.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(enum dc_color_depth color_depth)
+{
+ switch (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 pipe_ctx *pipe_ctx)
+{
+ uint32_t bpc;
+ uint64_t kbps;
+ struct fixed31_32 peak_kbps;
+ uint32_t numerator;
+ uint32_t denominator;
+
+ bpc = get_color_depth(pipe_ctx->pix_clk_params.color_depth);
+ kbps = pipe_ctx->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 stream_encoder *stream_enc,
+ 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_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 pipe_ctx *pipe_ctx)
+{
+ struct core_stream *stream = pipe_ctx->stream;
+ struct core_link *link = stream->sink->link;
+ struct link_encoder *link_encoder = link->link_enc;
+ struct stream_encoder *stream_encoder = pipe_ctx->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 */
+ if (dm_helpers_dp_mst_write_payload_allocation_table(
+ stream->ctx,
+ &stream->public,
+ &proposed_table,
+ true)) {
+ update_mst_stream_alloc_table(
+ link, pipe_ctx->stream_enc, &proposed_table);
+ }
+ else
+ dm_logger_write(link->ctx->logger, LOG_WARNING,
+ "Failed to update"
+ "MST allocation table for"
+ "pipe idx:%d\n",
+ pipe_ctx->pipe_idx);
+
+ dm_logger_write(link->ctx->logger, LOG_MST,
+ "%s "
+ "stream_count: %d: \n ",
+ __func__,
+ link->mst_stream_alloc_table.stream_count);
+
+ for (i = 0; i < MAX_CONTROLLER_NUM; i++) {
+ dm_logger_write(link->ctx->logger, LOG_MST,
+ "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);
+
+ /* 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(pipe_ctx);
+ 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 pipe_ctx *pipe_ctx)
+{
+ struct core_stream *stream = pipe_ctx->stream;
+ struct core_link *link = stream->sink->link;
+ struct link_encoder *link_encoder = link->link_enc;
+ struct stream_encoder *stream_encoder = pipe_ctx->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) {
+ if (dm_helpers_dp_mst_write_payload_allocation_table(
+ stream->ctx,
+ &stream->public,
+ &proposed_table,
+ false)) {
+
+ update_mst_stream_alloc_table(
+ link, pipe_ctx->stream_enc, &proposed_table);
+ }
+ else {
+ dm_logger_write(link->ctx->logger, LOG_WARNING,
+ "Failed to update"
+ "MST allocation table for"
+ "pipe idx:%d\n",
+ pipe_ctx->pipe_idx);
+ }
+ }
+
+ dm_logger_write(link->ctx->logger, LOG_MST,
+ "%s"
+ "stream_count: %d: ",
+ __func__,
+ link->mst_stream_alloc_table.stream_count);
+
+ for (i = 0; i < MAX_CONTROLLER_NUM; i++) {
+ dm_logger_write(link->ctx->logger, LOG_MST,
+ "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 pipe_ctx *pipe_ctx)
+{
+ struct core_dc *core_dc = DC_TO_CORE(pipe_ctx->stream->ctx->dc);
+
+ if (DC_OK != enable_link(pipe_ctx)) {
+ BREAK_TO_DEBUGGER();
+ return;
+ }
+
+ core_dc->hwss.enable_stream(pipe_ctx);
+
+ if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST)
+ allocate_mst_payload(pipe_ctx);
+}
+
+void core_link_disable_stream(struct pipe_ctx *pipe_ctx)
+{
+ struct core_dc *core_dc = DC_TO_CORE(pipe_ctx->stream->ctx->dc);
+
+ if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST)
+ deallocate_mst_payload(pipe_ctx);
+
+ core_dc->hwss.disable_stream(pipe_ctx);
+
+ disable_link(pipe_ctx->stream->sink->link, pipe_ctx->stream->signal);
+}
+
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_link_ddc.c b/drivers/gpu/drm/amd/display/dc/core/dc_link_ddc.c
new file mode 100644
index 000000000000..6379ccfdb06e
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_ddc.c
@@ -0,0 +1,1098 @@
+/*
+ * 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 "gpio_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 "core_types.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;
+};
+
+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(sizeof(struct i2c_payloads));
+
+ if (!payloads)
+ return NULL;
+
+ if (dal_vector_construct(
+ &payloads->payloads, ctx, count, sizeof(struct i2c_payload)))
+ return payloads;
+
+ dm_free(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);
+ *p = NULL;
+
+}
+
+struct aux_payloads *dal_ddc_aux_payloads_create(struct dc_context *ctx, uint32_t count)
+{
+ struct aux_payloads *payloads;
+
+ payloads = dm_alloc(sizeof(struct aux_payloads));
+
+ if (!payloads)
+ return NULL;
+
+ if (dal_vector_construct(
+ &payloads->payloads, ctx, count, sizeof(struct aux_payloads)))
+ return payloads;
+
+ dm_free(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);
+ *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);
+
+ struct gpio_service *gpio_service = init_data->ctx->gpio_service;
+ struct graphics_object_i2c_info i2c_info;
+ struct gpio_ddc_hw_info hw_info;
+ struct dc_bios *dcb = init_data->ctx->dc_bios;
+
+ ddc_service->link = init_data->link;
+ ddc_service->ctx = init_data->ctx;
+
+ if (BP_RESULT_OK != dcb->funcs->get_i2c_info(dcb, init_data->id, &i2c_info)) {
+ ddc_service->ddc_pin = NULL;
+ } else {
+ hw_info.ddc_channel = i2c_info.i2c_line;
+ hw_info.hw_supported = i2c_info.i2c_hw_assist;
+
+ ddc_service->ddc_pin = dal_gpio_create_ddc(
+ gpio_service,
+ i2c_info.gpio_info.clk_a_register_index,
+ 1 << i2c_info.gpio_info.clk_a_shift,
+ &hw_info);
+ }
+
+ ddc_service->flags.EDID_QUERY_DONE_ONCE = false;
+ ddc_service->flags.FORCE_READ_REPEATED_START = false;
+ ddc_service->flags.EDID_STRESS_READ = false;
+
+ ddc_service->flags.IS_INTERNAL_DISPLAY =
+ connector_id == CONNECTOR_ID_EDP ||
+ connector_id == CONNECTOR_ID_LVDS;
+
+ ddc_service->wa.raw = 0;
+ 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(sizeof(struct ddc_service));
+
+ if (!ddc_service)
+ return NULL;
+
+ if (construct(ddc_service, init_data))
+ return ddc_service;
+
+ dm_free(ddc_service);
+ return NULL;
+}
+
+static void destruct(struct ddc_service *ddc)
+{
+ if (ddc->ddc_pin)
+ dal_gpio_destroy_ddc(&ddc->ddc_pin);
+}
+
+void dal_ddc_service_destroy(struct ddc_service **ddc)
+{
+ if (!ddc || !*ddc) {
+ BREAK_TO_DEBUGGER();
+ return;
+ }
+ destruct(*ddc);
+ dm_free(*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 core_link *link = ddc->link;
+
+ if (link->dpcd_caps.branch_dev_id == DP_BRANCH_DEVICE_ID_4 &&
+ !memcmp(link->dpcd_caps.branch_dev_name,
+ DP_DVI_CONVERTER_ID_4,
+ sizeof(link->dpcd_caps.branch_dev_name)))
+ return defer_delay > I2C_OVER_AUX_DEFER_WA_DELAY ?
+ defer_delay : I2C_OVER_AUX_DEFER_WA_DELAY;
+
+ 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 = ddc->ctx->dc->caps.i2c_speed_in_khz };
+
+ return dm_helpers_submit_i2c(
+ ddc->ctx,
+ &ddc->link->public,
+ &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(
+ ddc->ctx->i2caux,
+ 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(
+ ddc->ctx->i2caux,
+ ddc->ddc_pin,
+ &cmd);
+
+ if (false == result)
+ dm_logger_write(
+ ddc->ctx->logger, LOG_ERROR,
+ "%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 = ddc->ctx->dc->caps.i2c_speed_in_khz };
+
+ 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 (dm_helpers_submit_i2c(
+ ddc->ctx,
+ &ddc->link->public,
+ &cmd)) {
+
+ cmd.payloads = &payloads[2];
+ cmd.number_of_payloads = 1;
+
+ ret = dm_helpers_submit_i2c(
+ ddc->ctx,
+ &ddc->link->public,
+ &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 = dm_helpers_submit_i2c(
+ ddc->ctx,
+ &ddc->link->public,
+ &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)) {
+ 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)
+{
+ 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))) {
+ *dongle = DISPLAY_DONGLE_DP_DVI_DONGLE;
+ sink_cap->max_hdmi_pixel_clock = DP_ADAPTOR_DVI_MAX_TMDS_CLK;
+
+ CONN_DATA_DETECT(ddc->link, type2_dongle_buf, sizeof(type2_dongle_buf),
+ "DP-DVI passive dongle %dMhz: ",
+ DP_ADAPTOR_DVI_MAX_TMDS_CLK / 1000);
+ 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) {
+ *dongle = DISPLAY_DONGLE_DP_DVI_DONGLE;
+
+ CONN_DATA_DETECT(ddc->link, type2_dongle_buf,
+ sizeof(type2_dongle_buf),
+ "DP-DVI passive dongle %dMhz: ",
+ DP_ADAPTOR_DVI_MAX_TMDS_CLK / 1000);
+ } else {
+ if (is_valid_hdmi_signature == true) {
+ *dongle = DISPLAY_DONGLE_DP_HDMI_DONGLE;
+
+ CONN_DATA_DETECT(ddc->link, type2_dongle_buf,
+ sizeof(type2_dongle_buf),
+ "Type 2 DP-HDMI passive dongle %dMhz: ",
+ max_tmds_clk);
+ } else {
+ *dongle = DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE;
+
+ CONN_DATA_DETECT(ddc->link, type2_dongle_buf,
+ sizeof(type2_dongle_buf),
+ "Type 2 DP-HDMI passive dongle (no signature) %dMhz: ",
+ 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) {
+ *dongle = DISPLAY_DONGLE_DP_HDMI_DONGLE;
+
+ CONN_DATA_DETECT(ddc->link, type2_dongle_buf,
+ sizeof(type2_dongle_buf),
+ "Type 1 DP-HDMI passive dongle %dMhz: ",
+ sink_cap->max_hdmi_pixel_clock / 1000);
+ } else {
+ *dongle = DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE;
+
+ CONN_DATA_DETECT(ddc->link, type2_dongle_buf,
+ sizeof(type2_dongle_buf),
+ "Type 1 DP-HDMI passive dongle (no signature) %dMhz: ",
+ sink_cap->max_hdmi_pixel_clock / 1000);
+ }
+ }
+
+ 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(
+ ddc->ctx->i2caux,
+ 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 = ddc->ctx->dc->caps.i2c_speed_in_khz };
+
+ 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 = dm_helpers_submit_i2c(
+ ddc->ctx,
+ &ddc->link->public,
+ &command);
+
+ dal_ddc_i2c_payloads_destroy(&payloads);
+ }
+
+ return ret;
+}
+
+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(
+ ddc->ctx->i2caux,
+ 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(
+ ddc->ctx->i2caux,
+ 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_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));
+ }
+}
+
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_link_dp.c b/drivers/gpu/drm/amd/display/dc/core/dc_link_dp.c
new file mode 100644
index 000000000000..2585ec332e58
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_dp.c
@@ -0,0 +1,2462 @@
+/* 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"
+
+#include "core_dc.h"
+
+/* maximum pre emphasis level allowed for each voltage swing level*/
+static const enum dc_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 dc_link_settings link_training_fallback_table[] = {
+/* 4320 Mbytes/sec*/
+{ LANE_COUNT_FOUR, LINK_RATE_HIGH3, LINK_SPREAD_DISABLED },
+/* 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 },
+/* 2160 Mbytes/sec*/
+{ LANE_COUNT_TWO, LINK_RATE_HIGH3, 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 },
+/* 1080 Mbytes/sec*/
+{ LANE_COUNT_ONE, LINK_RATE_HIGH3, 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)
+{
+ union training_aux_rd_interval 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,
+ (uint8_t *)&training_rd_interval,
+ sizeof(training_rd_interval));
+
+ if (training_rd_interval.bits.TRAINIG_AUX_RD_INTERVAL)
+ default_wait_in_micro_secs =
+ training_rd_interval.bits.TRAINIG_AUX_RD_INTERVAL * 4000;
+ }
+
+ udelay(default_wait_in_micro_secs);
+
+ dm_logger_write(link->ctx->logger, LOG_HW_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);
+
+ dm_logger_write(link->ctx->logger, LOG_HW_LINK_TRAINING,
+ "%s\n %x pattern = %x\n",
+ __func__,
+ DPCD_ADDRESS_TRAINING_PATTERN_SET,
+ dpcd_pattern.v1_4.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));
+
+ dm_logger_write(link->ctx->logger, LOG_HW_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;
+ case HW_DP_TRAINING_PATTERN_4:
+ dpcd_tr_pattern = DPCD_TRAINING_PATTERN_4;
+ break;
+ default:
+ ASSERT(0);
+ dm_logger_write(link->ctx->logger, LOG_HW_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.v1_4.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;
+
+ dm_logger_write(link->ctx->logger, LOG_HW_LINK_TRAINING,
+ "%s\n %x pattern = %x\n",
+ __func__,
+ DPCD_ADDRESS_TRAINING_PATTERN_SET,
+ dpcd_pattern.v1_4.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
+ memmove(
+ &dpcd_lt_buffer[DPCD_ADDRESS_LANE0_SET - dpcd_base_lt_offset],
+ dpcd_lane,
+ size_in_bytes);
+
+ dm_logger_write(link->ctx->logger, LOG_HW_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->public.cur_lane_setting = lt_settings->lane_settings[0];
+}
+
+static bool is_cr_done(enum dc_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 dc_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 dc_pre_emphasis get_max_pre_emphasis_for_voltage_swing(
+ enum dc_voltage_swing voltage)
+{
+ enum dc_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 dc_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;
+
+ 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];
+
+ dm_logger_write(link->ctx->logger, LOG_HW_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]);
+
+ dm_logger_write(link->ctx->logger, LOG_HW_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 dc_voltage_swing)(dpcd_lane_adjust[lane].bits.
+ VOLTAGE_SWING_LANE);
+ request_settings.lane_settings[lane].PRE_EMPHASIS =
+ (enum dc_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);
+ }
+ */
+
+ dm_logger_write(link->ctx->logger, LOG_HW_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->public.cur_lane_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 dc_link_dp_set_drive_settings(
+ struct dc_link *link,
+ struct link_training_settings *lt_settings)
+{
+ struct core_link *core_link = DC_LINK_TO_CORE(link);
+ /* program ASIC PHY settings*/
+ dp_set_hw_lane_settings(core_link, lt_settings);
+
+ /* Notify DP sink the PHY settings from source */
+ dpcd_set_lane_settings(core_link, lt_settings);
+}
+
+static bool perform_post_lt_adj_req_sequence(
+ struct core_link *link,
+ struct link_training_settings *lt_settings)
+{
+ enum dc_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);
+
+ dc_link_dp_set_drive_settings(&link->public,
+ lt_settings);
+ break;
+ }
+
+ msleep(1);
+ }
+
+ if (!req_drv_setting_changed) {
+ dm_logger_write(link->ctx->logger, LOG_WARNING,
+ "%s: Post Link Training Adjust Request Timed out\n",
+ __func__);
+
+ ASSERT(0);
+ return true;
+ }
+ }
+ dm_logger_write(link->ctx->logger, LOG_WARNING,
+ "%s: Post Link Training Adjust Request limit reached\n",
+ __func__);
+
+ ASSERT(0);
+ return true;
+
+}
+
+static enum hw_dp_training_pattern get_supported_tp(struct core_link *link)
+{
+ enum hw_dp_training_pattern highest_tp = HW_DP_TRAINING_PATTERN_2;
+ struct encoder_feature_support *features = &link->link_enc->features;
+ struct dpcd_caps *dpcd_caps = &link->dpcd_caps;
+
+ if (features->flags.bits.IS_TPS3_CAPABLE)
+ highest_tp = HW_DP_TRAINING_PATTERN_3;
+
+ if (features->flags.bits.IS_TPS4_CAPABLE)
+ highest_tp = HW_DP_TRAINING_PATTERN_4;
+
+ if (dpcd_caps->max_down_spread.bits.TPS4_SUPPORTED &&
+ highest_tp >= HW_DP_TRAINING_PATTERN_4)
+ return HW_DP_TRAINING_PATTERN_4;
+
+ if (dpcd_caps->max_ln_count.bits.TPS3_SUPPORTED &&
+ highest_tp >= HW_DP_TRAINING_PATTERN_3)
+ return HW_DP_TRAINING_PATTERN_3;
+
+ return HW_DP_TRAINING_PATTERN_2;
+}
+
+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 dc_lane_count lane_count = lt_settings->link_settings.lane_count;
+ union lane_align_status_updated dpcd_lane_status_updated = {{0}};
+ union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = {{{0}}};;
+
+ hw_tr_pattern = get_supported_tp(link);
+
+ 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 dc_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)) {
+
+ memset(&dpcd_lane_status, '\0', sizeof(dpcd_lane_status));
+ 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);
+ dm_logger_write(link->ctx->logger, LOG_ERROR,
+ "%s: Link Training Error, could not \
+ get CR after %d tries. \
+ Possibly voltage swing issue", __func__,
+ LINK_TRAINING_MAX_CR_RETRY);
+
+ }
+
+ return false;
+}
+
+static inline bool perform_link_training_int(
+ struct core_link *link,
+ struct link_training_settings *lt_settings,
+ bool status)
+{
+ union lane_count_set lane_count_set = { {0} };
+ union dpcd_training_pattern dpcd_pattern = { {0} };
+
+ /* 3. set training not in progress*/
+ dpcd_pattern.v1_4.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, NULL, 0);
+
+ /*
+ * 5. post training adjust if required
+ * If the upstream DPTX and downstream DPRX both support TPS4,
+ * TPS4 must be used instead of POST_LT_ADJ_REQ.
+ */
+ if (link->dpcd_caps.max_ln_count.bits.POST_LT_ADJ_REQ_SUPPORTED != 1 &&
+ get_supported_tp(link) == HW_DP_TRAINING_PATTERN_4)
+ return status;
+
+ if (status &&
+ perform_post_lt_adj_req_sequence(link, lt_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));
+
+ return status;
+}
+
+bool dc_link_dp_perform_link_training(
+ struct dc_link *link,
+ const struct dc_link_settings *link_setting,
+ bool skip_video_pattern)
+{
+ struct core_link *core_link = DC_LINK_TO_CORE(link);
+ bool status;
+
+ char *link_rate = "Unknown";
+ struct link_training_settings lt_settings;
+
+ status = false;
+ memset(&lt_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(core_link, &lt_settings);
+
+ /* 2. perform link training (set link training done
+ * to false is done as well)*/
+ if (perform_clock_recovery_sequence(core_link, &lt_settings)) {
+
+ if (perform_channel_equalization_sequence(core_link,
+ &lt_settings))
+ status = true;
+ }
+
+ if (status || !skip_video_pattern)
+ status = perform_link_training_int(core_link,
+ &lt_settings, status);
+
+ /* 6. print status message*/
+ switch (lt_settings.link_settings.link_rate) {
+
+ case LINK_RATE_LOW:
+ link_rate = "RBR";
+ break;
+ case LINK_RATE_HIGH:
+ link_rate = "HBR";
+ break;
+ case LINK_RATE_HIGH2:
+ link_rate = "HBR2";
+ break;
+ case LINK_RATE_RBR2:
+ link_rate = "RBR2";
+ break;
+ case LINK_RATE_HIGH3:
+ link_rate = "HBR3";
+ break;
+ default:
+ break;
+ }
+
+ /* Connectivity log: link training */
+ CONN_MSG_LT(core_link, "%sx%d %s VS=%d, PE=%d",
+ link_rate,
+ lt_settings.link_settings.lane_count,
+ status ? "pass" : "fail",
+ lt_settings.lane_settings[0].VOLTAGE_SWING,
+ lt_settings.lane_settings[0].PRE_EMPHASIS);
+
+ return status;
+}
+
+
+bool perform_link_training_with_retries(
+ struct core_link *link,
+ const struct dc_link_settings *link_setting,
+ bool skip_video_pattern,
+ int attempts)
+{
+ uint8_t j;
+ uint8_t delay_between_attempts = LINK_TRAINING_RETRY_DELAY;
+
+ for (j = 0; j < attempts; ++j) {
+
+ if (dc_link_dp_perform_link_training(
+ &link->public,
+ link_setting,
+ skip_video_pattern))
+ return true;
+
+ msleep(delay_between_attempts);
+ delay_between_attempts += LINK_TRAINING_RETRY_DELAY;
+ }
+
+ return false;
+}
+
+/*TODO add more check to see if link support request link configuration */
+static bool is_link_setting_supported(
+ const struct dc_link_settings *link_setting,
+ const struct dc_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 dc_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 dc_link_settings *link_setting,
+ const struct dc_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);
+}
+
+static struct dc_link_settings get_max_link_cap(struct core_link *link)
+{
+ /* Set Default link settings */
+ struct dc_link_settings max_link_cap = {LANE_COUNT_FOUR, LINK_RATE_HIGH,
+ LINK_SPREAD_05_DOWNSPREAD_30KHZ};
+
+ /* Higher link settings based on feature supported */
+ if (link->link_enc->features.flags.bits.IS_HBR2_CAPABLE)
+ max_link_cap.link_rate = LINK_RATE_HIGH2;
+
+ if (link->link_enc->features.flags.bits.IS_HBR3_CAPABLE)
+ max_link_cap.link_rate = LINK_RATE_HIGH3;
+
+ /* Lower link settings based on sink's link cap */
+ if (link->public.reported_link_cap.lane_count < max_link_cap.lane_count)
+ max_link_cap.lane_count =
+ link->public.reported_link_cap.lane_count;
+ if (link->public.reported_link_cap.link_rate < max_link_cap.link_rate)
+ max_link_cap.link_rate =
+ link->public.reported_link_cap.link_rate;
+ if (link->public.reported_link_cap.link_spread <
+ max_link_cap.link_spread)
+ max_link_cap.link_spread =
+ link->public.reported_link_cap.link_spread;
+ return max_link_cap;
+}
+
+bool dp_hbr_verify_link_cap(
+ struct core_link *link,
+ struct dc_link_settings *known_limit_link_setting)
+{
+ struct dc_link_settings max_link_cap = {0};
+ bool success;
+ bool skip_link_training;
+ const struct dc_link_settings *cur;
+ bool skip_video_pattern;
+ uint32_t i;
+ struct clock_source *dp_cs;
+ enum clock_source_id dp_cs_id = CLOCK_SOURCE_ID_EXTERNAL;
+
+ success = false;
+ skip_link_training = false;
+
+ max_link_cap = get_max_link_cap(link);
+
+ /* 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);
+
+ dp_cs = link->dc->res_pool->dp_clock_source;
+
+ if (dp_cs)
+ dp_cs_id = dp_cs->id;
+ else {
+ /*
+ * dp clock source is not initialized for some reason.
+ * Should not happen, CLOCK_SOURCE_ID_EXTERNAL will be used
+ */
+ ASSERT(dp_cs);
+ }
+
+ 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,
+ dp_cs_id,
+ cur);
+
+ if (skip_link_training)
+ success = true;
+ else {
+ success = dc_link_dp_perform_link_training(
+ &link->public,
+ cur,
+ skip_video_pattern);
+ }
+
+ if (success)
+ link->public.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->public.verified_link_cap.lane_count = LANE_COUNT_ONE;
+ link->public.verified_link_cap.link_rate = LINK_RATE_LOW;
+
+ link->public.verified_link_cap.link_spread =
+ LINK_SPREAD_DISABLED;
+ }
+
+ link->public.max_link_setting = link->public.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 dc_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 dc_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;
+
+ /* We always use verified link settings */
+ link_setting = &link->public.verified_link_cap;
+
+ /* TODO: DYNAMIC_VALIDATION needs to be implemented */
+ /*if (flags.DYNAMIC_VALIDATION == 1 &&
+ link->public.verified_link_cap.lane_count != LANE_COUNT_UNKNOWN)
+ link_setting = &link->public.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 dc_link_settings *link_setting)
+{
+
+ const struct dc_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->public.reported_link_cap.lane_count != LANE_COUNT_UNKNOWN) &&
+ (link->public.reported_link_cap.link_rate <=
+ link->public.verified_link_cap.link_rate)) {
+
+ link_bw = bandwidth_in_kbps_from_link_settings(
+ &link->public.reported_link_cap);
+
+ if (req_bw < link_bw) {
+ *link_setting = link->public.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->public.max_link_setting)) {
+ *link_setting = *cur_ls;
+ return;
+ }
+ }
+ }
+
+ BREAK_TO_DEBUGGER();
+ ASSERT(link->public.verified_link_cap.lane_count !=
+ LANE_COUNT_UNKNOWN);
+
+ *link_setting = link->public.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->public.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;
+ dm_logger_write(link->ctx->logger, LOG_HW_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->public.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) {
+
+ dm_logger_write(link->ctx->logger, LOG_HW_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->public.cur_link_settings.lane_count != LANE_COUNT_UNKNOWN) ||
+ (link->public.type == dc_connection_mst_branch) ||
+ is_dp_active_dongle(link))
+ return true;
+
+ return false;
+}
+
+static bool handle_hpd_irq_psr_sink(const struct core_link *link)
+{
+ union dpcd_psr_configuration psr_configuration;
+
+ if (link->public.psr_caps.psr_version == 0)
+ return false;
+
+ dal_ddc_service_read_dpcd_data(
+ link->ddc,
+ 368 /*DpcdAddress_PSR_Enable_Cfg*/,
+ &psr_configuration.raw,
+ sizeof(psr_configuration.raw));
+
+ if (psr_configuration.bits.ENABLE) {
+ unsigned char dpcdbuf[3] = {0};
+ union psr_error_status psr_error_status;
+ union psr_sink_psr_status psr_sink_psr_status;
+
+ dal_ddc_service_read_dpcd_data(
+ link->ddc,
+ 0x2006 /*DpcdAddress_PSR_Error_Status*/,
+ (unsigned char *) dpcdbuf,
+ sizeof(dpcdbuf));
+
+ /*DPCD 2006h ERROR STATUS*/
+ psr_error_status.raw = dpcdbuf[0];
+ /*DPCD 2008h SINK PANEL SELF REFRESH STATUS*/
+ psr_sink_psr_status.raw = dpcdbuf[2];
+
+ if (psr_error_status.bits.LINK_CRC_ERROR ||
+ psr_error_status.bits.RFB_STORAGE_ERROR) {
+ /* Acknowledge and clear error bits */
+ dal_ddc_service_write_dpcd_data(
+ link->ddc,
+ 8198 /*DpcdAddress_PSR_Error_Status*/,
+ &psr_error_status.raw,
+ sizeof(psr_error_status.raw));
+
+ /* PSR error, disable and re-enable PSR */
+ dc_link_set_psr_enable(&link->public, false);
+ dc_link_set_psr_enable(&link->public, true);
+
+ return true;
+ } else if (psr_sink_psr_status.bits.SINK_SELF_REFRESH_STATUS ==
+ PSR_SINK_STATE_ACTIVE_DISPLAY_FROM_SINK_RFB){
+ /* No error is detect, PSR is active.
+ * We should return with IRQ_HPD handled without
+ * checking for loss of sync since PSR would have
+ * powered down main link.
+ */
+ return true;
+ }
+ }
+ return false;
+}
+
+static void dp_test_send_link_training(struct core_link *link)
+{
+ struct dc_link_settings link_settings;
+
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_TEST_LANE_COUNT,
+ (unsigned char *)(&link_settings.lane_count),
+ 1);
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_TEST_LINK_RATE,
+ (unsigned char *)(&link_settings.link_rate),
+ 1);
+
+ /* Set preferred link settings */
+ link->public.verified_link_cap.lane_count = link_settings.lane_count;
+ link->public.verified_link_cap.link_rate = link_settings.link_rate;
+
+ dp_retrain_link(link);
+}
+
+static void dp_test_send_phy_test_pattern(struct core_link *link)
+{
+ union phy_test_pattern dpcd_test_pattern;
+ union lane_adjust dpcd_lane_adjustment[2];
+ unsigned char dpcd_post_cursor_2_adjustment = 0;
+ unsigned char test_80_bit_pattern[
+ (DPCD_ADDRESS_TEST_80BIT_CUSTOM_PATTERN_79_72 -
+ DPCD_ADDRESS_TEST_80BIT_CUSTOM_PATTERN_7_0)+1] = {0};
+ enum dp_test_pattern test_pattern;
+ struct dc_link_training_settings link_settings;
+ union lane_adjust dpcd_lane_adjust;
+ unsigned int lane;
+ struct link_training_settings link_training_settings;
+ int i = 0;
+
+ dpcd_test_pattern.raw = 0;
+ memset(dpcd_lane_adjustment, 0, sizeof(dpcd_lane_adjustment));
+ memset(&link_settings, 0, sizeof(link_settings));
+
+ /* get phy test pattern and pattern parameters from DP receiver */
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_TEST_PHY_PATTERN,
+ &dpcd_test_pattern.raw,
+ sizeof(dpcd_test_pattern));
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_ADJUST_REQUEST_LANE0_1,
+ &dpcd_lane_adjustment[0].raw,
+ sizeof(dpcd_lane_adjustment));
+
+ /*get post cursor 2 parameters
+ * For DP 1.1a or eariler, this DPCD register's value is 0
+ * For DP 1.2 or later:
+ * Bits 1:0 = POST_CURSOR2_LANE0; Bits 3:2 = POST_CURSOR2_LANE1
+ * Bits 5:4 = POST_CURSOR2_LANE2; Bits 7:6 = POST_CURSOR2_LANE3
+ */
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_ADJUST_REQUEST_POST_CURSOR2,
+ &dpcd_post_cursor_2_adjustment,
+ sizeof(dpcd_post_cursor_2_adjustment));
+
+ /* translate request */
+ switch (dpcd_test_pattern.bits.PATTERN) {
+ case PHY_TEST_PATTERN_D10_2:
+ test_pattern = DP_TEST_PATTERN_D102;
+ break;
+ case PHY_TEST_PATTERN_SYMBOL_ERROR:
+ test_pattern = DP_TEST_PATTERN_SYMBOL_ERROR;
+ break;
+ case PHY_TEST_PATTERN_PRBS7:
+ test_pattern = DP_TEST_PATTERN_PRBS7;
+ break;
+ case PHY_TEST_PATTERN_80BIT_CUSTOM:
+ test_pattern = DP_TEST_PATTERN_80BIT_CUSTOM;
+ break;
+ case PHY_TEST_PATTERN_HBR2_COMPLIANCE_EYE:
+ test_pattern = DP_TEST_PATTERN_HBR2_COMPLIANCE_EYE;
+ break;
+ default:
+ test_pattern = DP_TEST_PATTERN_VIDEO_MODE;
+ break;
+ }
+
+ if (test_pattern == DP_TEST_PATTERN_80BIT_CUSTOM)
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_TEST_80BIT_CUSTOM_PATTERN_7_0,
+ test_80_bit_pattern,
+ sizeof(test_80_bit_pattern));
+
+ /* prepare link training settings */
+ link_settings.link = link->public.cur_link_settings;
+
+ for (lane = 0; lane <
+ (unsigned int)(link->public.cur_link_settings.lane_count);
+ lane++) {
+ dpcd_lane_adjust.raw =
+ get_nibble_at_index(&dpcd_lane_adjustment[0].raw, lane);
+ link_settings.lane_settings[lane].VOLTAGE_SWING =
+ (enum dc_voltage_swing)
+ (dpcd_lane_adjust.bits.VOLTAGE_SWING_LANE);
+ link_settings.lane_settings[lane].PRE_EMPHASIS =
+ (enum dc_pre_emphasis)
+ (dpcd_lane_adjust.bits.PRE_EMPHASIS_LANE);
+ link_settings.lane_settings[lane].POST_CURSOR2 =
+ (enum dc_post_cursor2)
+ ((dpcd_post_cursor_2_adjustment >> (lane * 2)) & 0x03);
+ }
+
+ for (i = 0; i < 4; i++)
+ link_training_settings.lane_settings[i] =
+ link_settings.lane_settings[i];
+ link_training_settings.link_settings = link_settings.link;
+ link_training_settings.allow_invalid_msa_timing_param = false;
+ /*Usage: Measure DP physical lane signal
+ * by DP SI test equipment automatically.
+ * PHY test pattern request is generated by equipment via HPD interrupt.
+ * HPD needs to be active all the time. HPD should be active
+ * all the time. Do not touch it.
+ * forward request to DS
+ */
+ dc_link_dp_set_test_pattern(
+ &link->public,
+ test_pattern,
+ &link_training_settings,
+ test_80_bit_pattern,
+ (DPCD_ADDRESS_TEST_80BIT_CUSTOM_PATTERN_79_72 -
+ DPCD_ADDRESS_TEST_80BIT_CUSTOM_PATTERN_7_0)+1);
+}
+
+static void dp_test_send_link_test_pattern(struct core_link *link)
+{
+ union link_test_pattern dpcd_test_pattern;
+ union test_misc dpcd_test_params;
+ enum dp_test_pattern test_pattern;
+
+ memset(&dpcd_test_pattern, 0, sizeof(dpcd_test_pattern));
+ memset(&dpcd_test_params, 0, sizeof(dpcd_test_params));
+
+ /* get link test pattern and pattern parameters */
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_TEST_PATTERN,
+ &dpcd_test_pattern.raw,
+ sizeof(dpcd_test_pattern));
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_TEST_MISC1,
+ &dpcd_test_params.raw,
+ sizeof(dpcd_test_params));
+
+ switch (dpcd_test_pattern.bits.PATTERN) {
+ case LINK_TEST_PATTERN_COLOR_RAMP:
+ test_pattern = DP_TEST_PATTERN_COLOR_RAMP;
+ break;
+ case LINK_TEST_PATTERN_VERTICAL_BARS:
+ test_pattern = DP_TEST_PATTERN_VERTICAL_BARS;
+ break; /* black and white */
+ case LINK_TEST_PATTERN_COLOR_SQUARES:
+ test_pattern = (dpcd_test_params.bits.DYN_RANGE ==
+ TEST_DYN_RANGE_VESA ?
+ DP_TEST_PATTERN_COLOR_SQUARES :
+ DP_TEST_PATTERN_COLOR_SQUARES_CEA);
+ break;
+ default:
+ test_pattern = DP_TEST_PATTERN_VIDEO_MODE;
+ break;
+ }
+
+ dc_link_dp_set_test_pattern(
+ &link->public,
+ test_pattern,
+ NULL,
+ NULL,
+ 0);
+}
+
+static void handle_automated_test(struct core_link *link)
+{
+ union test_request test_request;
+ union test_response test_response;
+
+ memset(&test_request, 0, sizeof(test_request));
+ memset(&test_response, 0, sizeof(test_response));
+
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_TEST_REQUEST,
+ &test_request.raw,
+ sizeof(union test_request));
+ if (test_request.bits.LINK_TRAINING) {
+ /* ACK first to let DP RX test box monitor LT sequence */
+ test_response.bits.ACK = 1;
+ core_link_write_dpcd(
+ link,
+ DPCD_ADDRESS_TEST_RESPONSE,
+ &test_response.raw,
+ sizeof(test_response));
+ dp_test_send_link_training(link);
+ /* no acknowledge request is needed again */
+ test_response.bits.ACK = 0;
+ }
+ if (test_request.bits.LINK_TEST_PATTRN) {
+ dp_test_send_link_test_pattern(link);
+ link->public.compliance_test_state.bits.
+ SET_TEST_PATTERN_PENDING = 1;
+ }
+ if (test_request.bits.PHY_TEST_PATTERN) {
+ dp_test_send_phy_test_pattern(link);
+ test_response.bits.ACK = 1;
+ }
+ if (!test_request.raw)
+ /* no requests, revert all test signals
+ * TODO: revert all test signals
+ */
+ test_response.bits.ACK = 1;
+ /* send request acknowledgment */
+ if (test_response.bits.ACK)
+ core_link_write_dpcd(
+ link,
+ DPCD_ADDRESS_TEST_RESPONSE,
+ &test_response.raw,
+ sizeof(test_response));
+}
+
+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}}}};
+ union device_service_irq device_service_clear = {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*/
+
+ dm_logger_write(link->ctx->logger, LOG_HW_HPD_IRQ,
+ "%s: Got short pulse HPD on link %d\n",
+ __func__, link->public.link_index);
+
+ /* 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) {
+ dm_logger_write(link->ctx->logger, LOG_HW_HPD_IRQ,
+ "%s: DPCD read failed to obtain irq data\n",
+ __func__);
+ return false;
+ }
+
+ if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.AUTOMATED_TEST) {
+ device_service_clear.bits.AUTOMATED_TEST = 1;
+ core_link_write_dpcd(
+ link,
+ DPCD_ADDRESS_DEVICE_SERVICE_IRQ_VECTOR,
+ &device_service_clear.raw,
+ sizeof(device_service_clear.raw));
+ device_service_clear.raw = 0;
+ handle_automated_test(link);
+ return false;
+ }
+
+ if (!allow_hpd_rx_irq(link)) {
+ dm_logger_write(link->ctx->logger, LOG_HW_HPD_IRQ,
+ "%s: skipping HPD handling on %d\n",
+ __func__, link->public.link_index);
+ return false;
+ }
+
+ if (handle_hpd_irq_psr_sink(link))
+ /* PSR-related error was detected and handled */
+ return true;
+
+ /* If PSR-related error handled, Main link may be off,
+ * so do not handle as a normal sink status change interrupt.
+ */
+
+ /* 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)) {
+ /* Connectivity log: link loss */
+ CONN_DATA_LINK_LOSS(link,
+ hpd_irq_dpcd_data.raw,
+ sizeof(hpd_irq_dpcd_data),
+ "Status: ");
+
+ perform_link_training_with_retries(link,
+ &link->public.cur_link_settings,
+ true, LINK_TRAINING_ATTEMPTS);
+
+ status = false;
+ }
+
+ if (link->public.type == dc_connection_active_dongle &&
+ 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);
+
+ {
+ struct dp_device_vendor_id dp_id;
+
+ /* 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];
+
+ memmove(
+ link->dpcd_caps.branch_dev_name,
+ dp_id.ieee_device_id,
+ sizeof(dp_id.ieee_device_id));
+ }
+
+ {
+ struct dp_sink_hw_fw_revision dp_hw_fw_revision;
+
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_BRANCH_REVISION_START,
+ (uint8_t *)&dp_hw_fw_revision,
+ sizeof(dp_hw_fw_revision));
+
+ link->dpcd_caps.branch_hw_revision =
+ dp_hw_fw_revision.ieee_hw_rev;
+ }
+}
+
+static void dp_wa_power_up_0010FA(struct core_link *link, uint8_t *dpcd_data,
+ int length)
+{
+ int retry = 0;
+ 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];
+
+ 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_psr_link_cap(struct core_link *link,
+ enum edp_revision edp_revision)
+{
+ if (edp_revision >= EDP_REVISION_13) {
+ core_link_read_dpcd(link,
+ DPCD_ADDRESS_PSR_SUPPORT_VER,
+ (uint8_t *)(&link->public.psr_caps),
+ sizeof(link->public.psr_caps));
+ if (link->public.psr_caps.psr_version != 0) {
+ unsigned char psr_capability = 0;
+
+ core_link_read_dpcd(link,
+ DPCD_ADDRESS_PSR_CAPABILITY,
+ &psr_capability,
+ sizeof(psr_capability));
+ /* Bit 0 determines whether fast link training is
+ * required on PSR exit. If set to 0, link training
+ * is required. If set to 1, sink must lock within
+ * five Idle Patterns after Main Link is turned on.
+ */
+ link->public.psr_caps.psr_exit_link_training_required
+ = !(psr_capability & 0x1);
+
+ psr_capability = (psr_capability >> 1) & 0x7;
+ link->public.psr_caps.psr_rfb_setup_time =
+ 55 * (6 - psr_capability);
+ }
+ }
+}
+
+static void retrieve_link_cap(struct core_link *link)
+{
+ uint8_t dpcd_data[DPCD_ADDRESS_TRAINING_AUX_RD_INTERVAL - DPCD_ADDRESS_DPCD_REV + 1];
+
+ union down_stream_port_count down_strm_port_count;
+ union edp_configuration_cap edp_config_cap;
+ union dp_downstream_port_present ds_port = { 0 };
+
+ memset(dpcd_data, '\0', sizeof(dpcd_data));
+ memset(&down_strm_port_count,
+ '\0', sizeof(union down_stream_port_count));
+ memset(&edp_config_cap, '\0',
+ sizeof(union edp_configuration_cap));
+
+ 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];
+
+ {
+ union training_aux_rd_interval aux_rd_interval;
+
+ aux_rd_interval.raw =
+ dpcd_data[DPCD_ADDRESS_TRAINING_AUX_RD_INTERVAL];
+
+ if (aux_rd_interval.bits.EXT_RECIEVER_CAP_FIELD_PRESENT == 1) {
+ core_link_read_dpcd(
+ link,
+ DPCD_ADDRESS_DP13_DPCD_REV,
+ dpcd_data,
+ sizeof(dpcd_data));
+ }
+ }
+
+ 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];
+
+ link->dpcd_caps.max_down_spread.raw = dpcd_data[
+ DPCD_ADDRESS_MAX_DOWNSPREAD - DPCD_ADDRESS_DPCD_REV];
+
+ link->public.reported_link_cap.lane_count =
+ link->dpcd_caps.max_ln_count.bits.MAX_LANE_COUNT;
+ link->public.reported_link_cap.link_rate = dpcd_data[
+ DPCD_ADDRESS_MAX_LINK_RATE - DPCD_ADDRESS_DPCD_REV];
+ link->public.reported_link_cap.link_spread =
+ link->dpcd_caps.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;
+
+ link->public.test_pattern_enabled = false;
+ link->public.compliance_test_state.raw = 0;
+
+ link->public.psr_caps.psr_exit_link_training_required = false;
+ link->public.psr_caps.psr_frame_capture_indication_req = false;
+ link->public.psr_caps.psr_rfb_setup_time = 0;
+ link->public.psr_caps.psr_sdp_transmit_line_num_deadline = 0;
+ link->public.psr_caps.psr_version = 0;
+
+ /* 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));
+ }
+
+ /* Connectivity log: detection */
+ CONN_DATA_DETECT(link, dpcd_data, sizeof(dpcd_data), "Rx Caps: ");
+
+ /* TODO: Confirm if need retrieve_psr_link_cap */
+ retrieve_psr_link_cap(link, link->edp_revision);
+}
+
+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->public.verified_link_cap = link->public.reported_link_cap;
+ } else {
+ dp_hbr_verify_link_cap(link,
+ &link->public.reported_link_cap);
+ }
+ /* TODO save sink caps in link->sink */
+}
+
+void dc_link_dp_enable_hpd(const struct dc_link *link)
+{
+ struct core_link *core_link = DC_LINK_TO_CORE(link);
+ struct link_encoder *encoder = core_link->link_enc;
+
+ if (encoder != NULL && encoder->funcs->enable_hpd != NULL)
+ encoder->funcs->enable_hpd(encoder);
+}
+
+void dc_link_dp_disable_hpd(const struct dc_link *link)
+{
+ struct core_link *core_link = DC_LINK_TO_CORE(link);
+ struct link_encoder *encoder = core_link->link_enc;
+
+ if (encoder != NULL && encoder->funcs->enable_hpd != NULL)
+ encoder->funcs->disable_hpd(encoder);
+}
+
+static bool is_dp_phy_pattern(enum dp_test_pattern test_pattern)
+{
+ if (test_pattern == DP_TEST_PATTERN_D102 ||
+ test_pattern == DP_TEST_PATTERN_SYMBOL_ERROR ||
+ test_pattern == DP_TEST_PATTERN_PRBS7 ||
+ test_pattern == DP_TEST_PATTERN_80BIT_CUSTOM ||
+ test_pattern == DP_TEST_PATTERN_HBR2_COMPLIANCE_EYE ||
+ test_pattern == DP_TEST_PATTERN_TRAINING_PATTERN1 ||
+ test_pattern == DP_TEST_PATTERN_TRAINING_PATTERN2 ||
+ test_pattern == DP_TEST_PATTERN_TRAINING_PATTERN3 ||
+ test_pattern == DP_TEST_PATTERN_TRAINING_PATTERN4 ||
+ test_pattern == DP_TEST_PATTERN_VIDEO_MODE)
+ return true;
+ else
+ return false;
+}
+
+static void set_crtc_test_pattern(struct core_link *link,
+ struct pipe_ctx *pipe_ctx,
+ enum dp_test_pattern test_pattern)
+{
+ enum controller_dp_test_pattern controller_test_pattern;
+ enum dc_color_depth color_depth = pipe_ctx->
+ stream->public.timing.display_color_depth;
+ struct bit_depth_reduction_params params;
+
+ memset(&params, 0, sizeof(params));
+
+ switch (test_pattern) {
+ case DP_TEST_PATTERN_COLOR_SQUARES:
+ controller_test_pattern =
+ CONTROLLER_DP_TEST_PATTERN_COLORSQUARES;
+ break;
+ case DP_TEST_PATTERN_COLOR_SQUARES_CEA:
+ controller_test_pattern =
+ CONTROLLER_DP_TEST_PATTERN_COLORSQUARES_CEA;
+ break;
+ case DP_TEST_PATTERN_VERTICAL_BARS:
+ controller_test_pattern =
+ CONTROLLER_DP_TEST_PATTERN_VERTICALBARS;
+ break;
+ case DP_TEST_PATTERN_HORIZONTAL_BARS:
+ controller_test_pattern =
+ CONTROLLER_DP_TEST_PATTERN_HORIZONTALBARS;
+ break;
+ case DP_TEST_PATTERN_COLOR_RAMP:
+ controller_test_pattern =
+ CONTROLLER_DP_TEST_PATTERN_COLORRAMP;
+ break;
+ default:
+ controller_test_pattern =
+ CONTROLLER_DP_TEST_PATTERN_VIDEOMODE;
+ break;
+ }
+
+ switch (test_pattern) {
+ case DP_TEST_PATTERN_COLOR_SQUARES:
+ case DP_TEST_PATTERN_COLOR_SQUARES_CEA:
+ case DP_TEST_PATTERN_VERTICAL_BARS:
+ case DP_TEST_PATTERN_HORIZONTAL_BARS:
+ case DP_TEST_PATTERN_COLOR_RAMP:
+ {
+ /* disable bit depth reduction */
+ pipe_ctx->stream->bit_depth_params = params;
+ pipe_ctx->opp->funcs->
+ opp_program_bit_depth_reduction(pipe_ctx->opp, &params);
+
+ pipe_ctx->tg->funcs->set_test_pattern(pipe_ctx->tg,
+ controller_test_pattern, color_depth);
+ }
+ break;
+ case DP_TEST_PATTERN_VIDEO_MODE:
+ {
+ /* restore bitdepth reduction */
+ link->dc->current_context->res_ctx.pool->funcs->
+ build_bit_depth_reduction_params(pipe_ctx->stream,
+ &params);
+ pipe_ctx->stream->bit_depth_params = params;
+ pipe_ctx->opp->funcs->
+ opp_program_bit_depth_reduction(pipe_ctx->opp, &params);
+
+ pipe_ctx->tg->funcs->set_test_pattern(pipe_ctx->tg,
+ CONTROLLER_DP_TEST_PATTERN_VIDEOMODE,
+ color_depth);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+bool dc_link_dp_set_test_pattern(
+ const struct dc_link *link,
+ enum dp_test_pattern test_pattern,
+ const struct link_training_settings *p_link_settings,
+ const unsigned char *p_custom_pattern,
+ unsigned int cust_pattern_size)
+{
+ struct core_link *core_link = DC_LINK_TO_CORE(link);
+ struct pipe_ctx *pipes =
+ core_link->dc->current_context->res_ctx.pipe_ctx;
+ struct pipe_ctx pipe_ctx = pipes[0];
+ unsigned int lane;
+ unsigned int i;
+ unsigned char link_qual_pattern[LANE_COUNT_DP_MAX] = {0};
+ union dpcd_training_pattern training_pattern;
+ union test_response test_response;
+ enum dpcd_phy_test_patterns pattern;
+
+ memset(&training_pattern, 0, sizeof(training_pattern));
+ memset(&test_response, 0, sizeof(test_response));
+
+ for (i = 0; i < MAX_PIPES; i++) {
+ if (pipes[i].stream->sink->link == core_link) {
+ pipe_ctx = pipes[i];
+ break;
+ }
+ }
+
+ /* Reset CRTC Test Pattern if it is currently running and request
+ * is VideoMode Reset DP Phy Test Pattern if it is currently running
+ * and request is VideoMode
+ */
+ if (core_link->public.test_pattern_enabled && test_pattern ==
+ DP_TEST_PATTERN_VIDEO_MODE) {
+ /* Set CRTC Test Pattern */
+ set_crtc_test_pattern(core_link, &pipe_ctx, test_pattern);
+ dp_set_hw_test_pattern(core_link, test_pattern,
+ (uint8_t *)p_custom_pattern,
+ (uint32_t)cust_pattern_size);
+
+ /* Unblank Stream */
+ core_link->dc->hwss.unblank_stream(
+ &pipe_ctx,
+ &core_link->public.verified_link_cap);
+ /* TODO:m_pHwss->MuteAudioEndpoint
+ * (pPathMode->pDisplayPath, false);
+ */
+
+ /* Reset Test Pattern state */
+ core_link->public.test_pattern_enabled = false;
+
+ return true;
+ }
+
+ /* Check for PHY Test Patterns */
+ if (is_dp_phy_pattern(test_pattern)) {
+ /* Set DPCD Lane Settings before running test pattern */
+ if (p_link_settings != NULL) {
+ dp_set_hw_lane_settings(core_link, p_link_settings);
+ dpcd_set_lane_settings(core_link, p_link_settings);
+ }
+
+ /* Blank stream if running test pattern */
+ if (test_pattern != DP_TEST_PATTERN_VIDEO_MODE) {
+ /*TODO:
+ * m_pHwss->
+ * MuteAudioEndpoint(pPathMode->pDisplayPath, true);
+ */
+ /* Blank stream */
+ pipes->stream_enc->funcs->dp_blank(pipe_ctx.stream_enc);
+ }
+
+ dp_set_hw_test_pattern(core_link, test_pattern,
+ (uint8_t *)p_custom_pattern,
+ (uint32_t)cust_pattern_size);
+
+ if (test_pattern != DP_TEST_PATTERN_VIDEO_MODE) {
+ /* Set Test Pattern state */
+ core_link->public.test_pattern_enabled = true;
+ if (p_link_settings != NULL)
+ dpcd_set_link_settings(core_link,
+ p_link_settings);
+ }
+
+ switch (test_pattern) {
+ case DP_TEST_PATTERN_VIDEO_MODE:
+ pattern = PHY_TEST_PATTERN_NONE;
+ break;
+ case DP_TEST_PATTERN_D102:
+ pattern = PHY_TEST_PATTERN_D10_2;
+ break;
+ case DP_TEST_PATTERN_SYMBOL_ERROR:
+ pattern = PHY_TEST_PATTERN_SYMBOL_ERROR;
+ break;
+ case DP_TEST_PATTERN_PRBS7:
+ pattern = PHY_TEST_PATTERN_PRBS7;
+ break;
+ case DP_TEST_PATTERN_80BIT_CUSTOM:
+ pattern = PHY_TEST_PATTERN_80BIT_CUSTOM;
+ break;
+ case DP_TEST_PATTERN_HBR2_COMPLIANCE_EYE:
+ pattern = PHY_TEST_PATTERN_HBR2_COMPLIANCE_EYE;
+ break;
+ default:
+ return false;
+ }
+
+ if (test_pattern == DP_TEST_PATTERN_VIDEO_MODE
+ /*TODO:&& !pPathMode->pDisplayPath->IsTargetPoweredOn()*/)
+ return false;
+
+ if (core_link->dpcd_caps.dpcd_rev.raw >= DPCD_REV_12) {
+ /* tell receiver that we are sending qualification
+ * pattern DP 1.2 or later - DP receiver's link quality
+ * pattern is set using DPCD LINK_QUAL_LANEx_SET
+ * register (0x10B~0x10E)\
+ */
+ for (lane = 0; lane < LANE_COUNT_DP_MAX; lane++)
+ link_qual_pattern[lane] =
+ (unsigned char)(pattern);
+
+ core_link_write_dpcd(core_link,
+ DPCD_ADDRESS_LINK_QUAL_LANE0_SET,
+ link_qual_pattern,
+ sizeof(link_qual_pattern));
+ } else if (core_link->dpcd_caps.dpcd_rev.raw >= DPCD_REV_10 ||
+ core_link->dpcd_caps.dpcd_rev.raw == 0) {
+ /* tell receiver that we are sending qualification
+ * pattern DP 1.1a or earlier - DP receiver's link
+ * quality pattern is set using
+ * DPCD TRAINING_PATTERN_SET -> LINK_QUAL_PATTERN_SET
+ * register (0x102). We will use v_1.3 when we are
+ * setting test pattern for DP 1.1.
+ */
+ core_link_read_dpcd(core_link,
+ DPCD_ADDRESS_TRAINING_PATTERN_SET,
+ &training_pattern.raw,
+ sizeof(training_pattern));
+ training_pattern.v1_3.LINK_QUAL_PATTERN_SET = pattern;
+ core_link_write_dpcd(core_link,
+ DPCD_ADDRESS_TRAINING_PATTERN_SET,
+ &training_pattern.raw,
+ sizeof(training_pattern));
+ }
+ } else {
+ /* CRTC Patterns */
+ set_crtc_test_pattern(core_link, &pipe_ctx, test_pattern);
+ /* Set Test Pattern state */
+ core_link->public.test_pattern_enabled = true;
+
+ /* If this is called because of compliance test request,
+ * we respond ack here.
+ */
+ if (core_link->public.compliance_test_state.bits.
+ SET_TEST_PATTERN_PENDING == 1) {
+ core_link->public.compliance_test_state.bits.
+ SET_TEST_PATTERN_PENDING = 0;
+ test_response.bits.ACK = 1;
+ core_link_write_dpcd(core_link,
+ DPCD_ADDRESS_TEST_RESPONSE,
+ &test_response.raw,
+ sizeof(test_response));
+ }
+ }
+
+ return true;
+}
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_link_hwss.c b/drivers/gpu/drm/amd/display/dc/core/dc_link_hwss.c
new file mode 100644
index 000000000000..e89f5f176ec3
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_hwss.c
@@ -0,0 +1,222 @@
+/* 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_dp.h"
+#include "dc_link_ddc.h"
+#include "dm_helpers.h"
+#include "dce/dce_link_encoder.h"
+#include "dce/dce_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_helpers_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_helpers_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,
+ enum clock_source_id clock_source,
+ const struct dc_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);
+ } else {
+ link_enc->funcs->enable_dp_mst_output(
+ link_enc,
+ link_settings,
+ clock_source);
+ }
+
+ 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.*/
+ memset(&link->public.cur_link_settings, 0,
+ sizeof(link->public.cur_link_settings));
+}
+
+void dp_disable_link_phy_mst(struct core_link *link, enum signal_type signal)
+{
+ /* 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, 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;
+
+ 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;
+ case HW_DP_TRAINING_PATTERN_4:
+ test_pattern = DP_TEST_PATTERN_TRAINING_PATTERN4;
+ break;
+ default:
+ break;
+ }
+
+ dp_set_hw_test_pattern(link, test_pattern, NULL, 0);
+
+ 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,
+ uint8_t *custom_pattern,
+ uint32_t custom_pattern_size)
+{
+ 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 = custom_pattern;
+ pattern_param.custom_pattern_size = custom_pattern_size;
+ pattern_param.dp_panel_mode = dp_get_panel_mode(link);
+
+ encoder->funcs->dp_set_phy_pattern(encoder, &pattern_param);
+}
+
+
+void dp_retrain_link(struct core_link *link)
+{
+ struct pipe_ctx *pipes = link->dc->current_context->res_ctx.pipe_ctx;
+ unsigned int i;
+
+ for (i = 0; i < MAX_PIPES; i++) {
+ if (pipes[i].stream_enc != NULL) {
+ dm_delay_in_microseconds(link->ctx, 100);
+ pipes->stream_enc->funcs->dp_blank(pipes[i].stream_enc);
+ link->dc->hwss.disable_stream(&pipes[i]);
+ dc_link_dp_perform_link_training(
+ &link->public,
+ &link->public.verified_link_cap,
+ true);
+ link->dc->hwss.enable_stream(&pipes[i]);
+ link->dc->hwss.unblank_stream(&pipes[i],
+ &link->public.verified_link_cap);
+ }
+ }
+}
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c
new file mode 100644
index 000000000000..bd53d27e5414
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c
@@ -0,0 +1,1934 @@
+/*
+* 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 "timing_generator.h"
+#include "transform.h"
+#include "set_mode_types.h"
+
+#include "virtual/virtual_stream_encoder.h"
+
+#include "dce80/dce80_resource.h"
+#include "dce100/dce100_resource.h"
+#include "dce110/dce110_resource.h"
+#include "dce112/dce112_resource.h"
+
+enum dce_version resource_parse_asic_id(struct hw_asic_id asic_id)
+{
+ enum dce_version dc_version = DCE_VERSION_UNKNOWN;
+ switch (asic_id.chip_family) {
+
+ case FAMILY_CI:
+ case FAMILY_KV:
+ dc_version = DCE_VERSION_8_0;
+ break;
+ case FAMILY_CZ:
+ dc_version = DCE_VERSION_11_0;
+ break;
+
+ case FAMILY_VI:
+ if (ASIC_REV_IS_TONGA_P(asic_id.hw_internal_rev) ||
+ ASIC_REV_IS_FIJI_P(asic_id.hw_internal_rev)) {
+ dc_version = DCE_VERSION_10_0;
+ break;
+ }
+ if (ASIC_REV_IS_POLARIS10_P(asic_id.hw_internal_rev) ||
+ ASIC_REV_IS_POLARIS11_M(asic_id.hw_internal_rev)) {
+ dc_version = DCE_VERSION_11_2;
+ }
+ break;
+ default:
+ dc_version = DCE_VERSION_UNKNOWN;
+ break;
+ }
+ return dc_version;
+}
+
+struct resource_pool *dc_create_resource_pool(
+ struct core_dc *dc,
+ int num_virtual_links,
+ enum dce_version dc_version,
+ struct hw_asic_id asic_id)
+{
+
+ switch (dc_version) {
+ case DCE_VERSION_8_0:
+ return dce80_create_resource_pool(
+ num_virtual_links, dc);
+ case DCE_VERSION_10_0:
+ return dce100_create_resource_pool(
+ num_virtual_links, dc);
+ case DCE_VERSION_11_0:
+ return dce110_create_resource_pool(
+ num_virtual_links, dc, asic_id);
+ case DCE_VERSION_11_2:
+ return dce112_create_resource_pool(
+ num_virtual_links, dc);
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void dc_destroy_resource_pool(struct core_dc *dc)
+{
+ if (dc) {
+ if (dc->res_pool)
+ dc->res_pool->funcs->destroy(&dc->res_pool);
+
+ if (dc->hwseq)
+ dm_free(dc->hwseq);
+ }
+}
+
+static void update_num_audio(
+ const struct resource_straps *straps,
+ unsigned int *num_audio,
+ struct audio_support *aud_support)
+{
+ if (straps->hdmi_disable == 0) {
+ aud_support->hdmi_audio_native = true;
+ aud_support->hdmi_audio_on_dongle = true;
+ aud_support->dp_audio = true;
+ } else {
+ if (straps->dc_pinstraps_audio & 0x2) {
+ aud_support->hdmi_audio_on_dongle = true;
+ aud_support->dp_audio = true;
+ } else {
+ aud_support->dp_audio = true;
+ }
+ }
+
+ switch (straps->audio_stream_number) {
+ case 0: /* multi streams supported */
+ break;
+ case 1: /* multi streams not supported */
+ *num_audio = 1;
+ break;
+ default:
+ DC_ERR("DC: unexpected audio fuse!\n");
+ };
+}
+
+bool resource_construct(
+ unsigned int num_virtual_links,
+ struct core_dc *dc,
+ struct resource_pool *pool,
+ const struct resource_create_funcs *create_funcs)
+{
+ struct dc_context *ctx = dc->ctx;
+ const struct resource_caps *caps = pool->res_cap;
+ int i;
+ unsigned int num_audio = caps->num_audio;
+ struct resource_straps straps = {0};
+
+ if (create_funcs->read_dce_straps)
+ create_funcs->read_dce_straps(dc->ctx, &straps);
+
+ pool->audio_count = 0;
+ if (create_funcs->create_audio) {
+ /* find the total number of streams available via the
+ * AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_CONFIGURATION_DEFAULT
+ * registers (one for each pin) starting from pin 1
+ * up to the max number of audio pins.
+ * We stop on the first pin where
+ * PORT_CONNECTIVITY == 1 (as instructed by HW team).
+ */
+ update_num_audio(&straps, &num_audio, &pool->audio_support);
+ for (i = 0; i < pool->pipe_count && i < num_audio; i++) {
+ struct audio *aud = create_funcs->create_audio(ctx, i);
+
+ if (aud == NULL) {
+ DC_ERR("DC: failed to create audio!\n");
+ return false;
+ }
+
+ if (!aud->funcs->endpoint_valid(aud)) {
+ aud->funcs->destroy(&aud);
+ break;
+ }
+
+ pool->audios[i] = aud;
+ pool->audio_count++;
+ }
+ }
+
+ pool->stream_enc_count = 0;
+ if (create_funcs->create_stream_encoder) {
+ for (i = 0; i < caps->num_stream_encoder; i++) {
+ pool->stream_enc[i] = create_funcs->create_stream_encoder(i, ctx);
+ if (pool->stream_enc[i] == NULL)
+ DC_ERR("DC: failed to create stream_encoder!\n");
+ pool->stream_enc_count++;
+ }
+ }
+
+ for (i = 0; i < num_virtual_links; i++) {
+ pool->stream_enc[pool->stream_enc_count] =
+ virtual_stream_encoder_create(
+ ctx, ctx->dc_bios);
+ if (pool->stream_enc[pool->stream_enc_count] == NULL) {
+ DC_ERR("DC: failed to create stream_encoder!\n");
+ return false;
+ }
+ pool->stream_enc_count++;
+ }
+
+ dc->hwseq = create_funcs->create_hwseq(ctx);
+
+ return true;
+}
+
+
+void resource_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)
+ continue;
+
+ res_ctx->clock_source_ref_count[i]--;
+
+ if (res_ctx->clock_source_ref_count[i] == 0)
+ clock_source->funcs->cs_power_down(clock_source);
+
+ break;
+ }
+
+ if (res_ctx->pool->dp_clock_source == clock_source) {
+ res_ctx->dp_clock_source_ref_count--;
+
+ if (res_ctx->dp_clock_source_ref_count == 0)
+ clock_source->funcs->cs_power_down(clock_source);
+ }
+}
+
+void resource_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)
+ continue;
+
+ res_ctx->clock_source_ref_count[i]++;
+ break;
+ }
+
+ if (res_ctx->pool->dp_clock_source == clock_source)
+ res_ctx->dp_clock_source_ref_count++;
+}
+
+bool resource_are_streams_timing_synchronizable(
+ const struct core_stream *stream1,
+ const struct core_stream *stream2)
+{
+ if (stream1->public.timing.h_total != stream2->public.timing.h_total)
+ return false;
+
+ if (stream1->public.timing.v_total != stream2->public.timing.v_total)
+ return false;
+
+ if (stream1->public.timing.h_addressable
+ != stream2->public.timing.h_addressable)
+ return false;
+
+ if (stream1->public.timing.v_addressable
+ != stream2->public.timing.v_addressable)
+ return false;
+
+ if (stream1->public.timing.pix_clk_khz
+ != stream2->public.timing.pix_clk_khz)
+ return false;
+
+ if (stream1->phy_pix_clk != stream2->phy_pix_clk
+ && !dc_is_dp_signal(stream1->signal)
+ && !dc_is_dp_signal(stream2->signal))
+ return false;
+
+ return true;
+}
+
+static bool is_sharable_clk_src(
+ const struct pipe_ctx *pipe_with_clk_src,
+ const struct pipe_ctx *pipe)
+{
+ if (pipe_with_clk_src->clock_source == NULL)
+ return false;
+
+ if (pipe_with_clk_src->stream->signal == SIGNAL_TYPE_VIRTUAL)
+ return false;
+
+ if (dc_is_dp_signal(pipe_with_clk_src->stream->signal))
+ return false;
+
+ if (dc_is_hdmi_signal(pipe_with_clk_src->stream->signal)
+ && dc_is_dvi_signal(pipe->stream->signal))
+ return false;
+
+ if (dc_is_hdmi_signal(pipe->stream->signal)
+ && dc_is_dvi_signal(pipe_with_clk_src->stream->signal))
+ return false;
+
+ if (!resource_are_streams_timing_synchronizable(
+ pipe_with_clk_src->stream, pipe->stream))
+ return false;
+
+ return true;
+}
+
+struct clock_source *resource_find_used_clk_src_for_sharing(
+ struct resource_context *res_ctx,
+ struct pipe_ctx *pipe_ctx)
+{
+ int i;
+
+ for (i = 0; i < MAX_PIPES; i++) {
+ if (is_sharable_clk_src(&res_ctx->pipe_ctx[i], pipe_ctx))
+ return res_ctx->pipe_ctx[i].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_ABGR16161616F:
+ case SURFACE_PIXEL_FORMAT_GRPH_ARGB16161616F:
+ 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_GRPH_ARGB16161616:
+ default:
+ dal_pixel_format = PIXEL_FORMAT_UNKNOWN;
+ break;
+ }
+ return dal_pixel_format;
+}
+
+static void rect_swap_helper(struct rect *rect)
+{
+ uint32_t temp = 0;
+
+ temp = rect->height;
+ rect->height = rect->width;
+ rect->width = temp;
+
+ temp = rect->x;
+ rect->x = rect->y;
+ rect->y = temp;
+}
+
+static void calculate_viewport(
+ const struct dc_surface *surface,
+ struct pipe_ctx *pipe_ctx)
+{
+ struct rect stream_src = pipe_ctx->stream->public.src;
+ struct rect src = surface->src_rect;
+ struct rect dst = surface->dst_rect;
+ struct rect surface_clip = surface->clip_rect;
+ struct rect clip = {0};
+
+
+ if (surface->rotation == ROTATION_ANGLE_90 ||
+ surface->rotation == ROTATION_ANGLE_270) {
+ rect_swap_helper(&src);
+ rect_swap_helper(&dst);
+ rect_swap_helper(&surface_clip);
+ rect_swap_helper(&stream_src);
+ }
+
+ /* The actual clip is an intersection between stream
+ * source and surface clip
+ */
+ clip.x = stream_src.x > surface_clip.x ?
+ stream_src.x : surface_clip.x;
+
+ clip.width = stream_src.x + stream_src.width <
+ surface_clip.x + surface_clip.width ?
+ stream_src.x + stream_src.width - clip.x :
+ surface_clip.x + surface_clip.width - clip.x ;
+
+ clip.y = stream_src.y > surface_clip.y ?
+ stream_src.y : surface_clip.y;
+
+ clip.height = stream_src.y + stream_src.height <
+ surface_clip.y + surface_clip.height ?
+ stream_src.y + stream_src.height - clip.y :
+ surface_clip.y + surface_clip.height - clip.y ;
+
+ /* offset = src.ofs + (clip.ofs - dst.ofs) * scl_ratio
+ * num_pixels = clip.num_pix * scl_ratio
+ */
+ pipe_ctx->scl_data.viewport.x = src.x + (clip.x - dst.x) *
+ src.width / dst.width;
+ pipe_ctx->scl_data.viewport.width = clip.width *
+ src.width / dst.width;
+
+ pipe_ctx->scl_data.viewport.y = src.y + (clip.y - dst.y) *
+ src.height / dst.height;
+ pipe_ctx->scl_data.viewport.height = clip.height *
+ src.height / dst.height;
+
+ /* Minimum viewport such that 420/422 chroma vp is non 0 */
+ if (pipe_ctx->scl_data.viewport.width < 2)
+ pipe_ctx->scl_data.viewport.width = 2;
+ if (pipe_ctx->scl_data.viewport.height < 2)
+ pipe_ctx->scl_data.viewport.height = 2;
+}
+
+static void calculate_recout(
+ const struct dc_surface *surface,
+ struct pipe_ctx *pipe_ctx)
+{
+ struct core_stream *stream = pipe_ctx->stream;
+ struct rect clip = surface->clip_rect;
+
+ pipe_ctx->scl_data.recout.x = stream->public.dst.x;
+ if (stream->public.src.x < clip.x)
+ pipe_ctx->scl_data.recout.x += (clip.x
+ - stream->public.src.x) * stream->public.dst.width
+ / stream->public.src.width;
+
+ pipe_ctx->scl_data.recout.width = clip.width *
+ stream->public.dst.width / stream->public.src.width;
+ if (pipe_ctx->scl_data.recout.width + pipe_ctx->scl_data.recout.x >
+ stream->public.dst.x + stream->public.dst.width)
+ pipe_ctx->scl_data.recout.width =
+ stream->public.dst.x + stream->public.dst.width
+ - pipe_ctx->scl_data.recout.x;
+
+ pipe_ctx->scl_data.recout.y = stream->public.dst.y;
+ if (stream->public.src.y < clip.y)
+ pipe_ctx->scl_data.recout.y += (clip.y
+ - stream->public.src.y) * stream->public.dst.height
+ / stream->public.src.height;
+
+ pipe_ctx->scl_data.recout.height = clip.height *
+ stream->public.dst.height / stream->public.src.height;
+ if (pipe_ctx->scl_data.recout.height + pipe_ctx->scl_data.recout.y >
+ stream->public.dst.y + stream->public.dst.height)
+ pipe_ctx->scl_data.recout.height =
+ stream->public.dst.y + stream->public.dst.height
+ - pipe_ctx->scl_data.recout.y;
+}
+
+static void calculate_scaling_ratios(
+ const struct dc_surface *surface,
+ struct pipe_ctx *pipe_ctx)
+{
+ struct core_stream *stream = pipe_ctx->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;
+
+ pipe_ctx->scl_data.ratios.horz = dal_fixed31_32_from_fraction(
+ surface->src_rect.width,
+ surface->dst_rect.width);
+ pipe_ctx->scl_data.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)
+ pipe_ctx->scl_data.ratios.horz.value *= 2;
+ else if (surface->stereo_format == PLANE_STEREO_FORMAT_TOP_AND_BOTTOM)
+ pipe_ctx->scl_data.ratios.vert.value *= 2;
+
+ pipe_ctx->scl_data.ratios.vert.value = div64_s64(
+ pipe_ctx->scl_data.ratios.vert.value * in_h, out_h);
+ pipe_ctx->scl_data.ratios.horz.value = div64_s64(
+ pipe_ctx->scl_data.ratios.horz.value * in_w, out_w);
+
+ pipe_ctx->scl_data.ratios.horz_c = pipe_ctx->scl_data.ratios.horz;
+ pipe_ctx->scl_data.ratios.vert_c = pipe_ctx->scl_data.ratios.vert;
+
+ if (pipe_ctx->scl_data.format == PIXEL_FORMAT_420BPP12) {
+ pipe_ctx->scl_data.ratios.horz_c.value /= 2;
+ pipe_ctx->scl_data.ratios.vert_c.value /= 2;
+ }
+}
+
+bool resource_build_scaling_params(
+ const struct dc_surface *surface,
+ struct pipe_ctx *pipe_ctx)
+{
+ bool res;
+ struct dc_crtc_timing *timing = &pipe_ctx->stream->public.timing;
+ /* Important: scaling ratio calculation requires pixel format,
+ * lb depth calculation requires recout and taps require scaling ratios.
+ */
+ pipe_ctx->scl_data.format = convert_pixel_format_to_dalsurface(surface->format);
+
+ calculate_viewport(surface, pipe_ctx);
+
+ if (pipe_ctx->scl_data.viewport.height < 16 || pipe_ctx->scl_data.viewport.width < 16)
+ return false;
+
+ calculate_scaling_ratios(surface, pipe_ctx);
+
+ calculate_recout(surface, pipe_ctx);
+
+ /**
+ * Setting line buffer pixel depth to 24bpp yields banding
+ * on certain displays, such as the Sharp 4k
+ */
+ pipe_ctx->scl_data.lb_params.depth = LB_PIXEL_DEPTH_30BPP;
+
+ pipe_ctx->scl_data.h_active = timing->h_addressable;
+ pipe_ctx->scl_data.v_active = timing->v_addressable;
+
+ /* Taps calculations */
+ res = pipe_ctx->xfm->funcs->transform_get_optimal_number_of_taps(
+ pipe_ctx->xfm, &pipe_ctx->scl_data, &surface->scaling_quality);
+
+ if (!res) {
+ /* Try 24 bpp linebuffer */
+ pipe_ctx->scl_data.lb_params.depth = LB_PIXEL_DEPTH_24BPP;
+
+ res = pipe_ctx->xfm->funcs->transform_get_optimal_number_of_taps(
+ pipe_ctx->xfm, &pipe_ctx->scl_data, &surface->scaling_quality);
+ }
+
+ dm_logger_write(pipe_ctx->stream->ctx->logger, LOG_SCALER,
+ "%s: Viewport:\nheight:%d width:%d x:%d "
+ "y:%d\n dst_rect:\nheight:%d width:%d x:%d "
+ "y:%d\n",
+ __func__,
+ pipe_ctx->scl_data.viewport.height,
+ pipe_ctx->scl_data.viewport.width,
+ pipe_ctx->scl_data.viewport.x,
+ pipe_ctx->scl_data.viewport.y,
+ surface->dst_rect.height,
+ surface->dst_rect.width,
+ surface->dst_rect.x,
+ surface->dst_rect.y);
+
+ return res;
+}
+
+
+enum dc_status resource_build_scaling_params_for_context(
+ const struct core_dc *dc,
+ struct validate_context *context)
+{
+ int i;
+
+ for (i = 0; i < MAX_PIPES; i++) {
+ if (context->res_ctx.pipe_ctx[i].surface != NULL &&
+ context->res_ctx.pipe_ctx[i].stream != NULL)
+ if (!resource_build_scaling_params(
+ &context->res_ctx.pipe_ctx[i].surface->public,
+ &context->res_ctx.pipe_ctx[i]))
+ return DC_FAIL_BANDWIDTH_VALIDATE;
+ }
+
+ return DC_OK;
+}
+
+static void detach_surfaces_for_target(
+ struct validate_context *context,
+ const struct dc_target *dc_target)
+{
+ int i;
+ struct core_stream *stream = DC_STREAM_TO_CORE(dc_target->streams[0]);
+
+ for (i = 0; i < context->res_ctx.pool->pipe_count; i++) {
+ struct pipe_ctx *cur_pipe = &context->res_ctx.pipe_ctx[i];
+ if (cur_pipe->stream == stream) {
+ cur_pipe->surface = NULL;
+ cur_pipe->top_pipe = NULL;
+ cur_pipe->bottom_pipe = NULL;
+ }
+ }
+}
+
+struct pipe_ctx *find_idle_secondary_pipe(struct resource_context *res_ctx)
+{
+ int i;
+ struct pipe_ctx *secondary_pipe = NULL;
+
+ /*
+ * search backwards for the second pipe to keep pipe
+ * assignment more consistent
+ */
+
+ for (i = res_ctx->pool->pipe_count - 1; i >= 0; i--) {
+ if (res_ctx->pipe_ctx[i].stream == NULL) {
+ secondary_pipe = &res_ctx->pipe_ctx[i];
+ secondary_pipe->pipe_idx = i;
+ break;
+ }
+ }
+
+
+ return secondary_pipe;
+}
+
+struct pipe_ctx *resource_get_head_pipe_for_stream(
+ struct resource_context *res_ctx,
+ const struct core_stream *stream)
+{
+ int i;
+ for (i = 0; i < res_ctx->pool->pipe_count; i++) {
+ if (res_ctx->pipe_ctx[i].stream == stream &&
+ !res_ctx->pipe_ctx[i].top_pipe) {
+ return &res_ctx->pipe_ctx[i];
+ break;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * A free_pipe for a target is defined here as a pipe with a stream that belongs
+ * to the target but has no surface attached yet
+ */
+static struct pipe_ctx *acquire_free_pipe_for_target(
+ struct resource_context *res_ctx,
+ const struct dc_target *dc_target)
+{
+ int i;
+ struct core_stream *stream = DC_STREAM_TO_CORE(dc_target->streams[0]);
+
+ struct pipe_ctx *head_pipe = NULL;
+
+ /* Find head pipe, which has the back end set up*/
+
+ head_pipe = resource_get_head_pipe_for_stream(res_ctx, stream);
+
+ if (!head_pipe)
+ ASSERT(0);
+
+ if (!head_pipe->surface)
+ return head_pipe;
+
+ /* Re-use pipe already acquired for this stream if available*/
+ for (i = res_ctx->pool->pipe_count - 1; i >= 0; i--) {
+ if (res_ctx->pipe_ctx[i].stream == stream &&
+ !res_ctx->pipe_ctx[i].surface) {
+ return &res_ctx->pipe_ctx[i];
+ }
+ }
+
+ /*
+ * At this point we have no re-useable pipe for this stream and we need
+ * to acquire an idle one to satisfy the request
+ */
+
+ if(!res_ctx->pool->funcs->acquire_idle_pipe_for_layer)
+ return NULL;
+
+ return res_ctx->pool->funcs->acquire_idle_pipe_for_layer(res_ctx, stream);
+
+}
+
+static void release_free_pipes_for_target(
+ struct resource_context *res_ctx,
+ const struct dc_target *dc_target)
+{
+ int i;
+ struct core_stream *stream = DC_STREAM_TO_CORE(dc_target->streams[0]);
+
+ for (i = res_ctx->pool->pipe_count - 1; i >= 0; i--) {
+ if (res_ctx->pipe_ctx[i].stream == stream &&
+ !res_ctx->pipe_ctx[i].surface) {
+ res_ctx->pipe_ctx[i].stream = NULL;
+ }
+ }
+}
+
+bool resource_attach_surfaces_to_context(
+ const struct dc_surface * const *surfaces,
+ int surface_count,
+ const struct dc_target *dc_target,
+ struct validate_context *context)
+{
+ int i;
+ struct pipe_ctx *tail_pipe;
+ struct dc_target_status *target_status = NULL;
+
+
+ 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 < context->target_count; i++)
+ if (&context->targets[i]->public == dc_target) {
+ target_status = &context->target_status[i];
+ break;
+ }
+ if (target_status == NULL) {
+ dm_error("Existing target not found; failed to attach surfaces\n");
+ return false;
+ }
+
+ /* retain new surfaces */
+ for (i = 0; i < surface_count; i++)
+ dc_surface_retain(surfaces[i]);
+
+ detach_surfaces_for_target(context, dc_target);
+
+ /* release existing surfaces*/
+ for (i = 0; i < target_status->surface_count; i++)
+ dc_surface_release(target_status->surfaces[i]);
+
+ for (i = surface_count; i < target_status->surface_count; i++)
+ target_status->surfaces[i] = NULL;
+
+ target_status->surface_count = 0;
+
+ if (surface_count == 0)
+ return true;
+
+ tail_pipe = NULL;
+ for (i = 0; i < surface_count; i++) {
+ struct core_surface *surface = DC_SURFACE_TO_CORE(surfaces[i]);
+ struct pipe_ctx *free_pipe = acquire_free_pipe_for_target(
+ &context->res_ctx, dc_target);
+
+ if (!free_pipe) {
+ target_status->surfaces[i] = NULL;
+ return false;
+ }
+
+ free_pipe->surface = surface;
+
+ if (tail_pipe) {
+ free_pipe->top_pipe = tail_pipe;
+ tail_pipe->bottom_pipe = free_pipe;
+ }
+
+ tail_pipe = free_pipe;
+ }
+
+ release_free_pipes_for_target(&context->res_ctx, dc_target);
+
+ /* assign new surfaces*/
+ for (i = 0; i < surface_count; i++)
+ target_status->surfaces[i] = surfaces[i];
+
+ target_status->surface_count = surface_count;
+
+ return true;
+}
+
+
+static bool is_timing_changed(const struct core_stream *cur_stream,
+ const 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;
+
+ /* If output color space is changed, need to reprogram info frames */
+ if (cur_stream->public.output_color_space !=
+ new_stream->public.output_color_space)
+ return true;
+
+ return memcmp(
+ &cur_stream->public.timing,
+ &new_stream->public.timing,
+ sizeof(struct dc_crtc_timing)) != 0;
+}
+
+static bool are_stream_backends_same(
+ const struct core_stream *stream_a, const struct core_stream *stream_b)
+{
+ if (stream_a == stream_b)
+ return true;
+
+ if (stream_a == NULL || stream_b == NULL)
+ return false;
+
+ if (is_timing_changed(stream_a, stream_b))
+ return false;
+
+ return true;
+}
+
+bool is_target_unchanged(
+ const struct core_target *old_target, const struct core_target *target)
+{
+ int i;
+
+ if (old_target == target)
+ return true;
+ if (old_target->public.stream_count != target->public.stream_count)
+ return false;
+
+ for (i = 0; i < old_target->public.stream_count; i++) {
+ const struct core_stream *old_stream = DC_STREAM_TO_CORE(
+ old_target->public.streams[i]);
+ const struct core_stream *stream = DC_STREAM_TO_CORE(
+ target->public.streams[i]);
+
+ if (!are_stream_backends_same(old_stream, stream))
+ return false;
+ }
+
+ return true;
+}
+
+bool resource_validate_attach_surfaces(
+ const struct dc_validation_set set[],
+ int set_count,
+ const struct validate_context *old_context,
+ struct validate_context *context)
+{
+ int i, j;
+
+ for (i = 0; i < set_count; i++) {
+ for (j = 0; j < old_context->target_count; j++)
+ if (is_target_unchanged(
+ old_context->targets[j],
+ context->targets[i])) {
+ if (!resource_attach_surfaces_to_context(
+ old_context->target_status[j].surfaces,
+ old_context->target_status[j].surface_count,
+ &context->targets[i]->public,
+ context))
+ return false;
+ context->target_status[i] = old_context->target_status[j];
+ }
+ if (set[i].surface_count != 0)
+ if (!resource_attach_surfaces_to_context(
+ set[i].surfaces,
+ set[i].surface_count,
+ &context->targets[i]->public,
+ context))
+ return false;
+
+ }
+
+ return true;
+}
+
+/* Maximum TMDS single link pixel clock 165MHz */
+#define TMDS_MAX_PIXEL_CLOCK_IN_KHZ 165000
+
+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 int acquire_first_free_pipe(
+ struct resource_context *res_ctx,
+ struct core_stream *stream)
+{
+ int i;
+
+ for (i = 0; i < res_ctx->pool->pipe_count; i++) {
+ if (!res_ctx->pipe_ctx[i].stream) {
+ struct pipe_ctx *pipe_ctx = &res_ctx->pipe_ctx[i];
+
+ pipe_ctx->tg = res_ctx->pool->timing_generators[i];
+ pipe_ctx->mi = res_ctx->pool->mis[i];
+ pipe_ctx->ipp = res_ctx->pool->ipps[i];
+ pipe_ctx->xfm = res_ctx->pool->transforms[i];
+ pipe_ctx->opp = res_ctx->pool->opps[i];
+ pipe_ctx->dis_clk = res_ctx->pool->display_clock;
+ pipe_ctx->pipe_idx = i;
+
+ pipe_ctx->stream = stream;
+ return i;
+ }
+ }
+ return -1;
+}
+
+static struct stream_encoder *find_first_free_match_stream_enc_for_link(
+ struct resource_context *res_ctx,
+ struct core_stream *stream)
+{
+ int i;
+ int j = -1;
+ struct core_link *link = stream->sink->link;
+
+ 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.
+ */
+
+ if (j >= 0 && dc_is_dp_signal(stream->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 void update_stream_signal(struct core_stream *stream)
+{
+ const struct dc_sink *dc_sink = stream->public.sink;
+
+ stream->signal = dc_sink->sink_signal;
+ /* 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)
+ stream->signal = SIGNAL_TYPE_DVI_DUAL_LINK;
+ else
+ stream->signal = SIGNAL_TYPE_DVI_SINGLE_LINK;
+ }
+}
+
+bool resource_is_stream_unchanged(
+ const struct validate_context *old_context, struct core_stream *stream)
+{
+ int i, j;
+
+ for (i = 0; i < old_context->target_count; i++) {
+ struct core_target *old_target = old_context->targets[i];
+
+ for (j = 0; j < old_target->public.stream_count; j++) {
+ struct core_stream *old_stream =
+ DC_STREAM_TO_CORE(old_target->public.streams[j]);
+
+ if (are_stream_backends_same(old_stream, stream))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void copy_pipe_ctx(
+ const struct pipe_ctx *from_pipe_ctx, struct pipe_ctx *to_pipe_ctx)
+{
+ struct core_surface *surface = to_pipe_ctx->surface;
+ struct core_stream *stream = to_pipe_ctx->stream;
+
+ *to_pipe_ctx = *from_pipe_ctx;
+ to_pipe_ctx->stream = stream;
+ if (surface != NULL)
+ to_pipe_ctx->surface = surface;
+}
+
+static struct core_stream *find_pll_sharable_stream(
+ const struct core_stream *stream_needs_pll,
+ struct validate_context *context)
+{
+ int 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 *stream_has_pll =
+ DC_STREAM_TO_CORE(target->public.streams[j]);
+
+ /* We are looking for non dp, non virtual stream */
+ if (resource_are_streams_timing_synchronizable(
+ stream_needs_pll, stream_has_pll)
+ && !dc_is_dp_signal(stream_has_pll->signal)
+ && stream_has_pll->sink->link->public.connector_signal
+ != SIGNAL_TYPE_VIRTUAL)
+ return stream_has_pll;
+ }
+ }
+
+ return NULL;
+}
+
+static int get_norm_pix_clk(const struct dc_crtc_timing *timing)
+{
+ uint32_t pix_clk = timing->pix_clk_khz;
+ uint32_t normalized_pix_clk = pix_clk;
+
+ if (timing->pixel_encoding == PIXEL_ENCODING_YCBCR420)
+ pix_clk /= 2;
+
+ switch (timing->display_color_depth) {
+ case COLOR_DEPTH_888:
+ normalized_pix_clk = pix_clk;
+ break;
+ case COLOR_DEPTH_101010:
+ normalized_pix_clk = (pix_clk * 30) / 24;
+ break;
+ case COLOR_DEPTH_121212:
+ normalized_pix_clk = (pix_clk * 36) / 24;
+ break;
+ case COLOR_DEPTH_161616:
+ normalized_pix_clk = (pix_clk * 48) / 24;
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+
+ return normalized_pix_clk;
+}
+
+static void calculate_phy_pix_clks(
+ const struct core_dc *dc,
+ struct validate_context *context)
+{
+ int 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 *stream =
+ DC_STREAM_TO_CORE(target->public.streams[j]);
+
+ update_stream_signal(stream);
+
+ /* update actual pixel clock on all streams */
+ if (dc_is_hdmi_signal(stream->signal))
+ stream->phy_pix_clk = get_norm_pix_clk(
+ &stream->public.timing);
+ else
+ stream->phy_pix_clk =
+ stream->public.timing.pix_clk_khz;
+ }
+ }
+}
+
+enum dc_status resource_map_pool_resources(
+ const struct core_dc *dc,
+ struct validate_context *context)
+{
+ int i, j, k;
+
+ calculate_phy_pix_clks(dc, context);
+
+ 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 *stream =
+ DC_STREAM_TO_CORE(target->public.streams[j]);
+
+ if (!resource_is_stream_unchanged(dc->current_context, stream))
+ continue;
+
+ /* mark resources used for stream that is already active */
+ for (k = 0; k < MAX_PIPES; k++) {
+ struct pipe_ctx *pipe_ctx =
+ &context->res_ctx.pipe_ctx[k];
+ const struct pipe_ctx *old_pipe_ctx =
+ &dc->current_context->res_ctx.pipe_ctx[k];
+
+ if (!are_stream_backends_same(old_pipe_ctx->stream, stream))
+ continue;
+
+ pipe_ctx->stream = stream;
+ copy_pipe_ctx(old_pipe_ctx, pipe_ctx);
+
+ set_stream_engine_in_use(
+ &context->res_ctx,
+ pipe_ctx->stream_enc);
+
+ /* Switch to dp clock source only if there is
+ * no non dp stream that shares the same timing
+ * with the dp stream.
+ */
+ if (dc_is_dp_signal(pipe_ctx->stream->signal) &&
+ !find_pll_sharable_stream(stream, context))
+ pipe_ctx->clock_source =
+ context->res_ctx.pool->dp_clock_source;
+
+ resource_reference_clock_source(
+ &context->res_ctx,
+ pipe_ctx->clock_source);
+
+ set_audio_in_use(&context->res_ctx,
+ pipe_ctx->audio);
+ }
+ }
+ }
+
+ 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 *stream =
+ DC_STREAM_TO_CORE(target->public.streams[j]);
+ struct pipe_ctx *pipe_ctx = NULL;
+ int pipe_idx = -1;
+
+ if (resource_is_stream_unchanged(dc->current_context, stream))
+ continue;
+ /* acquire new resources */
+ pipe_idx = acquire_first_free_pipe(
+ &context->res_ctx, stream);
+ if (pipe_idx < 0)
+ return DC_NO_CONTROLLER_RESOURCE;
+
+
+ pipe_ctx = &context->res_ctx.pipe_ctx[pipe_idx];
+
+ pipe_ctx->stream_enc =
+ find_first_free_match_stream_enc_for_link(
+ &context->res_ctx, stream);
+
+ if (!pipe_ctx->stream_enc)
+ return DC_NO_STREAM_ENG_RESOURCE;
+
+ set_stream_engine_in_use(
+ &context->res_ctx,
+ pipe_ctx->stream_enc);
+
+ /* TODO: Add check if ASIC support and EDID audio */
+ if (!stream->sink->converter_disable_audio &&
+ dc_is_audio_capable_signal(pipe_ctx->stream->signal) &&
+ stream->public.audio_info.mode_count) {
+ pipe_ctx->audio = find_first_free_audio(
+ &context->res_ctx);
+
+ /*
+ * Audio assigned in order first come first get.
+ * There are asics which has number of audio
+ * resources less then number of pipes
+ */
+ if (pipe_ctx->audio)
+ set_audio_in_use(
+ &context->res_ctx,
+ pipe_ctx->audio);
+ }
+
+ if (j == 0) {
+ context->target_status[i].primary_otg_inst =
+ pipe_ctx->tg->inst;
+ }
+ }
+ }
+
+ return DC_OK;
+}
+
+/* first target in the context is used to populate the rest */
+void validate_guaranteed_copy_target(
+ struct validate_context *context,
+ int max_targets)
+{
+ int i;
+
+ for (i = 1; i < max_targets; i++) {
+ context->targets[i] = context->targets[0];
+
+ copy_pipe_ctx(&context->res_ctx.pipe_ctx[0],
+ &context->res_ctx.pipe_ctx[i]);
+ context->res_ctx.pipe_ctx[i].stream =
+ context->res_ctx.pipe_ctx[0].stream;
+
+ dc_target_retain(&context->targets[i]->public);
+ context->target_count++;
+ }
+}
+
+static void translate_info_frame(const struct hw_info_frame *hw_info_frame,
+ struct encoder_info_frame *encoder_info_frame)
+{
+ 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;
+
+ 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) {
+ memmove(
+ &encoder_info_frame->avi,
+ &hw_info_frame->avi_info_packet,
+ sizeof(struct hw_info_packet));
+ }
+
+ if (hw_info_frame->vendor_info_packet.valid) {
+ memmove(
+ &encoder_info_frame->vendor,
+ &hw_info_frame->vendor_info_packet,
+ sizeof(struct hw_info_packet));
+ }
+
+ if (hw_info_frame->spd_packet.valid) {
+ memmove(
+ &encoder_info_frame->spd,
+ &hw_info_frame->spd_packet,
+ sizeof(struct hw_info_packet));
+ }
+
+ if (hw_info_frame->vsc_packet.valid) {
+ 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 pipe_ctx *pipe_ctx)
+{
+ struct core_stream *stream = pipe_ctx->stream;
+ enum dc_color_space color_space = 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 = pipe_ctx->stream->public.output_color_space;
+
+ /* 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)
+ */
+
+ 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_YCBCR420:
+ pixel_encoding = 3;
+ 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 == COLOR_SPACE_YCBCR709)
+ info_frame.avi_info_packet.info_packet_hdmi.bits.C0_C1 =
+ COLORIMETRY_ITU709;
+ else if (color_space == 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 : We should handle YCC quantization */
+ /* but we do not have matrix calculation */
+ if (color_space == COLOR_SPACE_SRGB) {
+ info_frame.avi_info_packet.info_packet_hdmi.bits.Q0_Q1 =
+ RGB_QUANTIZATION_FULL_RANGE;
+ info_frame.avi_info_packet.info_packet_hdmi.bits.YQ0_YQ1 =
+ YYC_QUANTIZATION_FULL_RANGE;
+ } else if (color_space == COLOR_SPACE_SRGB_LIMITED) {
+ info_frame.avi_info_packet.info_packet_hdmi.bits.Q0_Q1 =
+ RGB_QUANTIZATION_LIMITED_RANGE;
+ info_frame.avi_info_packet.info_packet_hdmi.bits.YQ0_YQ1 =
+ YYC_QUANTIZATION_LIMITED_RANGE;
+ } else {
+ info_frame.avi_info_packet.info_packet_hdmi.bits.Q0_Q1 =
+ RGB_QUANTIZATION_DEFAULT_RANGE;
+ 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;
+}
+
+static void set_spd_info_packet(struct core_stream *stream,
+ struct hw_info_packet *info_packet)
+{
+ /* SPD info packet for FreeSync */
+
+ unsigned char checksum = 0;
+ unsigned int idx, payload_size = 0;
+
+ /* Check if Freesync is supported. Return if false. If true,
+ * set the corresponding bit in the info packet
+ */
+ if (stream->public.freesync_ctx.supported == false)
+ return;
+
+ if (dc_is_hdmi_signal(stream->signal)) {
+
+ /* HEADER */
+
+ /* HB0 = Packet Type = 0x83 (Source Product
+ * Descriptor InfoFrame)
+ */
+ info_packet->hb0 = 0x83;
+
+ /* HB1 = Version = 0x01 */
+ info_packet->hb1 = 0x01;
+
+ /* HB2 = [Bits 7:5 = 0] [Bits 4:0 = Length = 0x08] */
+ info_packet->hb2 = 0x08;
+
+ payload_size = 0x08;
+
+ } else if (dc_is_dp_signal(stream->signal)) {
+
+ /* HEADER */
+
+ /* HB0 = Secondary-data Packet ID = 0 - Only non-zero
+ * when used to associate audio related info packets
+ */
+ info_packet->hb0 = 0x00;
+
+ /* HB1 = Packet Type = 0x83 (Source Product
+ * Descriptor InfoFrame)
+ */
+ info_packet->hb1 = 0x83;
+
+ /* HB2 = [Bits 7:0 = Least significant eight bits -
+ * For INFOFRAME, the value must be 1Bh]
+ */
+ info_packet->hb2 = 0x1B;
+
+ /* HB3 = [Bits 7:2 = INFOFRAME SDP Version Number = 0x1]
+ * [Bits 1:0 = Most significant two bits = 0x00]
+ */
+ info_packet->hb3 = 0x04;
+
+ payload_size = 0x1B;
+ }
+
+ /* PB1 = 0x1A (24bit AMD IEEE OUI (0x00001A) - Byte 0) */
+ info_packet->sb[1] = 0x1A;
+
+ /* PB2 = 0x00 (24bit AMD IEEE OUI (0x00001A) - Byte 1) */
+ info_packet->sb[2] = 0x00;
+
+ /* PB3 = 0x00 (24bit AMD IEEE OUI (0x00001A) - Byte 2) */
+ info_packet->sb[3] = 0x00;
+
+ /* PB4 = Reserved */
+ info_packet->sb[4] = 0x00;
+
+ /* PB5 = Reserved */
+ info_packet->sb[5] = 0x00;
+
+ /* PB6 = [Bits 7:3 = Reserved] */
+ info_packet->sb[6] = 0x00;
+
+ if (stream->public.freesync_ctx.supported == true)
+ /* PB6 = [Bit 0 = FreeSync Supported] */
+ info_packet->sb[6] |= 0x01;
+
+ if (stream->public.freesync_ctx.enabled == true)
+ /* PB6 = [Bit 1 = FreeSync Enabled] */
+ info_packet->sb[6] |= 0x02;
+
+ if (stream->public.freesync_ctx.active == true)
+ /* PB6 = [Bit 2 = FreeSync Active] */
+ info_packet->sb[6] |= 0x04;
+
+ /* PB7 = FreeSync Minimum refresh rate (Hz) */
+ info_packet->sb[7] = (unsigned char) (stream->public.freesync_ctx.
+ min_refresh_in_micro_hz / 1000000);
+
+ /* PB8 = FreeSync Maximum refresh rate (Hz)
+ *
+ * Note: We do not use the maximum capable refresh rate
+ * of the panel, because we should never go above the field
+ * rate of the mode timing set.
+ */
+ info_packet->sb[8] = (unsigned char) (stream->public.freesync_ctx.
+ nominal_refresh_in_micro_hz / 1000000);
+
+ /* PB9 - PB27 = Reserved */
+ for (idx = 9; idx <= 27; idx++)
+ info_packet->sb[idx] = 0x00;
+
+ /* Calculate checksum */
+ checksum += info_packet->hb0;
+ checksum += info_packet->hb1;
+ checksum += info_packet->hb2;
+ checksum += info_packet->hb3;
+
+ for (idx = 1; idx <= payload_size; idx++)
+ checksum += info_packet->sb[idx];
+
+ /* PB0 = Checksum (one byte complement) */
+ info_packet->sb[0] = (unsigned char) (0x100 - checksum);
+
+ info_packet->valid = true;
+}
+
+static void set_vsc_info_packet(struct core_stream *stream,
+ struct hw_info_packet *info_packet)
+{
+ unsigned int vscPacketRevision = 0;
+ unsigned int i;
+
+ if (stream->sink->link->public.psr_caps.psr_version != 0) {
+ vscPacketRevision = 2;
+ }
+
+ /* VSC packet not needed based on the features
+ * supported by this DP display
+ */
+ if (vscPacketRevision == 0)
+ return;
+
+ if (vscPacketRevision == 0x2) {
+ /* Secondary-data Packet ID = 0*/
+ info_packet->hb0 = 0x00;
+ /* 07h - Packet Type Value indicating Video
+ * Stream Configuration packet
+ */
+ info_packet->hb1 = 0x07;
+ /* 02h = VSC SDP supporting 3D stereo and PSR
+ * (applies to eDP v1.3 or higher).
+ */
+ info_packet->hb2 = 0x02;
+ /* 08h = VSC packet supporting 3D stereo + PSR
+ * (HB2 = 02h).
+ */
+ info_packet->hb3 = 0x08;
+
+ for (i = 0; i < 28; i++)
+ info_packet->sb[i] = 0;
+
+ info_packet->valid = true;
+ }
+
+ /*TODO: stereo 3D support and extend pixel encoding colorimetry*/
+}
+
+void resource_validate_ctx_destruct(struct validate_context *context)
+{
+ int i, j;
+
+ for (i = 0; i < context->target_count; i++) {
+ for (j = 0; j < context->target_status[i].surface_count; j++)
+ dc_surface_release(
+ context->target_status[i].surfaces[j]);
+
+ context->target_status[i].surface_count = 0;
+ dc_target_release(&context->targets[i]->public);
+ }
+}
+
+/*
+ * Copy src_ctx into dst_ctx and retain all surfaces and targets referenced
+ * by the src_ctx
+ */
+void resource_validate_ctx_copy_construct(
+ const struct validate_context *src_ctx,
+ struct validate_context *dst_ctx)
+{
+ int i, j;
+
+ *dst_ctx = *src_ctx;
+
+ for (i = 0; i < dst_ctx->res_ctx.pool->pipe_count; i++) {
+ struct pipe_ctx *cur_pipe = &dst_ctx->res_ctx.pipe_ctx[i];
+
+ if (cur_pipe->top_pipe)
+ cur_pipe->top_pipe = &dst_ctx->res_ctx.pipe_ctx[cur_pipe->top_pipe->pipe_idx];
+
+ if (cur_pipe->bottom_pipe)
+ cur_pipe->bottom_pipe = &dst_ctx->res_ctx.pipe_ctx[cur_pipe->bottom_pipe->pipe_idx];
+
+ }
+
+ for (i = 0; i < dst_ctx->target_count; i++) {
+ dc_target_retain(&dst_ctx->targets[i]->public);
+ for (j = 0; j < dst_ctx->target_status[i].surface_count; j++)
+ dc_surface_retain(
+ dst_ctx->target_status[i].surfaces[j]);
+ }
+}
+
+struct clock_source *dc_resource_find_first_free_pll(
+ struct resource_context *res_ctx)
+{
+ int i;
+
+ for (i = 0; i < res_ctx->pool->clk_src_count; ++i) {
+ if (res_ctx->clock_source_ref_count[i] == 0)
+ return res_ctx->pool->clock_sources[i];
+ }
+
+ return NULL;
+}
+
+void resource_build_info_frame(struct pipe_ctx *pipe_ctx)
+{
+ 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 = pipe_ctx->stream->signal;
+
+ /* HDMi and DP have different info packets*/
+ if (dc_is_hdmi_signal(signal)) {
+ set_avi_info_frame(
+ &info_frame.avi_info_packet, pipe_ctx);
+ set_vendor_info_packet(
+ pipe_ctx->stream, &info_frame.vendor_info_packet);
+ set_spd_info_packet(pipe_ctx->stream, &info_frame.spd_packet);
+ }
+
+ else if (dc_is_dp_signal(signal))
+ set_vsc_info_packet(pipe_ctx->stream, &info_frame.vsc_packet);
+ set_spd_info_packet(pipe_ctx->stream, &info_frame.spd_packet);
+
+ translate_info_frame(&info_frame,
+ &pipe_ctx->encoder_info_frame);
+}
+
+enum dc_status resource_map_clock_resources(
+ const struct core_dc *dc,
+ struct validate_context *context)
+{
+ int i, j, k;
+
+ /* acquire new resources */
+ 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 *stream =
+ DC_STREAM_TO_CORE(target->public.streams[j]);
+
+ if (resource_is_stream_unchanged(dc->current_context, stream))
+ continue;
+
+ for (k = 0; k < MAX_PIPES; k++) {
+ struct pipe_ctx *pipe_ctx =
+ &context->res_ctx.pipe_ctx[k];
+
+ if (context->res_ctx.pipe_ctx[k].stream != stream)
+ continue;
+
+ if (dc_is_dp_signal(pipe_ctx->stream->signal)
+ || pipe_ctx->stream->signal == SIGNAL_TYPE_VIRTUAL)
+ pipe_ctx->clock_source =
+ context->res_ctx.pool->dp_clock_source;
+ else {
+ pipe_ctx->clock_source = NULL;
+
+ if (!dc->public.config.disable_disp_pll_sharing)
+ resource_find_used_clk_src_for_sharing(
+ &context->res_ctx,
+ pipe_ctx);
+
+ if (pipe_ctx->clock_source == NULL)
+ pipe_ctx->clock_source =
+ dc_resource_find_first_free_pll(&context->res_ctx);
+ }
+
+ if (pipe_ctx->clock_source == NULL)
+ return DC_NO_CLOCK_SOURCE_RESOURCE;
+
+ resource_reference_clock_source(
+ &context->res_ctx,
+ pipe_ctx->clock_source);
+
+ /* only one cs per stream regardless of mpo */
+ break;
+ }
+ }
+ }
+
+ return DC_OK;
+}
+
+/*
+ * Note: We need to disable output if clock sources change,
+ * since bios does optimization and doesn't apply if changing
+ * PHY when not already disabled.
+ */
+bool pipe_need_reprogram(
+ struct pipe_ctx *pipe_ctx_old,
+ struct pipe_ctx *pipe_ctx)
+{
+ if (pipe_ctx_old->stream->sink != pipe_ctx->stream->sink)
+ return true;
+
+ if (pipe_ctx_old->stream->signal != pipe_ctx->stream->signal)
+ return true;
+
+ if (pipe_ctx_old->audio != pipe_ctx->audio)
+ return true;
+
+ if (pipe_ctx_old->clock_source != pipe_ctx->clock_source
+ && pipe_ctx_old->stream != pipe_ctx->stream)
+ return true;
+
+ if (pipe_ctx_old->stream_enc != pipe_ctx->stream_enc)
+ return true;
+
+ if (is_timing_changed(pipe_ctx_old->stream, pipe_ctx->stream))
+ return true;
+
+
+ return false;
+}
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_sink.c b/drivers/gpu/drm/amd/display/dc/core/dc_sink.c
new file mode 100644
index 000000000000..67ae799b6f4f
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_sink.c
@@ -0,0 +1,113 @@
+/*
+ * 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 sink *sink = DC_SINK_TO_SINK(dc_sink);
+
+ --sink->ref_count;
+
+ if (sink->ref_count == 0) {
+ destruct(sink);
+ dm_free(sink);
+ }
+}
+
+struct dc_sink *dc_sink_create(const struct dc_sink_init_data *init_params)
+{
+ struct sink *sink = dm_alloc(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(sink);
+
+alloc_fail:
+ return NULL;
+}
+
+/*******************************************************************************
+ * Protected functions - visible only inside of DC (not visible in DM)
+ ******************************************************************************/
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c
new file mode 100644
index 000000000000..8d6aa607e1f5
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c
@@ -0,0 +1,141 @@
+/*
+ * 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 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);
+
+ /* 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;
+ 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;
+
+ stream->status.link = &stream->sink->link->public;
+
+ return true;
+}
+
+static void destruct(struct core_stream *stream)
+{
+ dc_sink_release(&stream->sink->public);
+}
+
+void dc_stream_retain(const struct dc_stream *dc_stream)
+{
+ struct stream *stream = DC_STREAM_TO_STREAM(dc_stream);
+ stream->ref_count++;
+}
+
+void dc_stream_release(const struct dc_stream *public)
+{
+ struct stream *stream = DC_STREAM_TO_STREAM(public);
+ struct core_stream *protected = DC_STREAM_TO_CORE(public);
+
+ if (public != NULL) {
+ stream->ref_count--;
+
+ if (stream->ref_count == 0) {
+ destruct(protected);
+ dm_free(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(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(stream);
+
+alloc_fail:
+ return NULL;
+}
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_surface.c b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c
new file mode 100644
index 000000000000..b89d3b5d0ba0
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c
@@ -0,0 +1,213 @@
+/*
+ * 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 "transform.h"
+
+/*******************************************************************************
+ * Private structures
+ ******************************************************************************/
+struct surface {
+ struct core_surface protected;
+ enum dc_irq_source irq_source;
+ int ref_count;
+};
+
+struct gamma {
+ struct core_gamma protected;
+ 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)
+
+#define DC_GAMMA_TO_GAMMA(dc_gamma) \
+ container_of(dc_gamma, struct gamma, protected.public)
+#define CORE_GAMMA_TO_GAMMA(core_gamma) \
+ container_of(core_gamma, struct gamma, protected)
+
+/*******************************************************************************
+ * Private functions
+ ******************************************************************************/
+static bool construct(struct dc_context *ctx, struct surface *surface)
+{
+ 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 core_dc *core_dc = DC_TO_CORE(dc);
+
+ struct surface *surface = dm_alloc(sizeof(*surface));
+
+ if (NULL == surface)
+ goto alloc_fail;
+
+ if (false == construct(core_dc->ctx, surface))
+ goto construct_fail;
+
+ dc_surface_retain(&surface->protected.public);
+
+ return &surface->protected.public;
+
+construct_fail:
+ dm_free(surface);
+
+alloc_fail:
+ return NULL;
+}
+
+const struct dc_surface_status *dc_surface_get_status(
+ const struct dc_surface *dc_surface)
+{
+ struct dc_surface_status *surface_status;
+ struct core_surface *core_surface;
+ struct core_dc *core_dc;
+ int i;
+
+ if (dc_surface == NULL)
+ return NULL;
+
+ core_surface = DC_SURFACE_TO_CORE(dc_surface);
+
+ if (core_surface == NULL || core_surface->ctx == NULL)
+ return NULL;
+
+ surface_status = &core_surface->status;
+
+ if (core_surface->ctx == NULL || core_surface->ctx->dc == NULL)
+ return NULL;
+
+ core_dc = DC_TO_CORE(core_surface->ctx->dc);
+
+
+ if (core_dc->current_context == NULL)
+ return NULL;
+
+ for (i = 0; i < core_dc->current_context->res_ctx.pool->pipe_count;
+ i++) {
+ struct pipe_ctx *pipe_ctx =
+ &core_dc->current_context->res_ctx.pipe_ctx[i];
+
+ if (pipe_ctx->surface !=
+ DC_SURFACE_TO_CORE(dc_surface))
+ continue;
+
+ core_dc->hwss.update_pending_status(pipe_ctx);
+ }
+
+ return surface_status;
+}
+
+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);
+ }
+}
+
+static bool construct_gamma(struct gamma *gamma)
+{
+ return true;
+}
+
+static void destruct_gamma(struct gamma *gamma)
+{
+
+}
+
+void dc_gamma_retain(const struct dc_gamma *dc_gamma)
+{
+ struct gamma *gamma = DC_GAMMA_TO_GAMMA(dc_gamma);
+
+ ++gamma->ref_count;
+}
+
+void dc_gamma_release(const struct dc_gamma *dc_gamma)
+{
+ struct gamma *gamma = DC_GAMMA_TO_GAMMA(dc_gamma);
+ --gamma->ref_count;
+
+ if (gamma->ref_count == 0) {
+ destruct_gamma(gamma);
+ dm_free(gamma);
+ }
+}
+
+struct dc_gamma *dc_create_gamma()
+{
+ struct gamma *gamma = dm_alloc(sizeof(*gamma));
+
+ if (gamma == NULL)
+ goto alloc_fail;
+
+ if (false == construct_gamma(gamma))
+ goto construct_fail;
+
+ dc_gamma_retain(&gamma->protected.public);
+
+ return &gamma->protected.public;
+
+construct_fail:
+ dm_free(gamma);
+
+alloc_fail:
+ return NULL;
+}
+
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_target.c b/drivers/gpu/drm/amd/display/dc/core/dc_target.c
new file mode 100644
index 000000000000..48eb7b0e0350
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_target.c
@@ -0,0 +1,334 @@
+/*
+ * 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"
+
+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->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(const struct dc_target *dc_target)
+{
+ struct target *target = DC_TARGET_TO_TARGET(dc_target);
+
+ target->ref_count++;
+}
+
+void dc_target_release(const 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(target);
+ }
+}
+
+const struct dc_target_status *dc_target_get_status(
+ const struct dc_target* dc_target)
+{
+ uint8_t i;
+ struct core_target* target = DC_TARGET_TO_CORE(dc_target);
+ struct core_dc *dc = DC_TO_CORE(target->ctx->dc);
+
+ for (i = 0; i < dc->current_context->target_count; i++)
+ if (target == dc->current_context->targets[i])
+ return &dc->current_context->target_status[i];
+
+ return NULL;
+}
+
+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(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;
+}
+
+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;
+}
+
+/**
+ * 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)
+{
+ uint8_t i, j;
+ struct core_target *target;
+ struct core_dc *core_dc;
+ struct resource_context *res_ctx;
+
+ if (NULL == dc_target) {
+ dm_error("DC: dc_target is NULL!\n");
+ return false;
+
+ }
+ if (NULL == attributes) {
+ dm_error("DC: attributes is NULL!\n");
+ return false;
+
+ }
+
+ target = DC_TARGET_TO_CORE(dc_target);
+ core_dc = DC_TO_CORE(target->ctx->dc);
+ res_ctx = &core_dc->current_context->res_ctx;
+
+ for (i = 0; i < target->public.stream_count; i++) {
+ for (j = 0; j < MAX_PIPES; j++) {
+ struct input_pixel_processor *ipp =
+ res_ctx->pipe_ctx[j].ipp;
+
+ if (res_ctx->pipe_ctx[j].stream !=
+ DC_STREAM_TO_CORE(target->public.streams[i]))
+ continue;
+
+ /* As of writing of this code cursor is on the top
+ * plane so we only need to set it on first pipe we
+ * find. May need to make this code dce specific later.
+ */
+ if (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)
+{
+ uint8_t i, j;
+ struct core_target *target;
+ struct core_dc *core_dc;
+ struct resource_context *res_ctx;
+
+ 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;
+ }
+
+ target = DC_TARGET_TO_CORE(dc_target);
+ core_dc = DC_TO_CORE(target->ctx->dc);
+ res_ctx = &core_dc->current_context->res_ctx;
+
+ for (i = 0; i < target->public.stream_count; i++) {
+ for (j = 0; j < MAX_PIPES; j++) {
+ struct input_pixel_processor *ipp =
+ res_ctx->pipe_ctx[j].ipp;
+
+ if (res_ctx->pipe_ctx[j].stream !=
+ DC_STREAM_TO_CORE(target->public.streams[i]))
+ continue;
+
+ /* As of writing of this code cursor is on the top
+ * plane so we only need to set it on first pipe we
+ * find. May need to make this code dce specific later.
+ */
+ ipp->funcs->ipp_cursor_set_position(ipp, position);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+uint32_t dc_target_get_vblank_counter(const struct dc_target *dc_target)
+{
+ uint8_t i, j;
+ struct core_target *target = DC_TARGET_TO_CORE(dc_target);
+ struct core_dc *core_dc = DC_TO_CORE(target->ctx->dc);
+ struct resource_context *res_ctx =
+ &core_dc->current_context->res_ctx;
+
+ for (i = 0; i < target->public.stream_count; i++) {
+ for (j = 0; j < MAX_PIPES; j++) {
+ struct timing_generator *tg = res_ctx->pipe_ctx[j].tg;
+
+ if (res_ctx->pipe_ctx[j].stream !=
+ DC_STREAM_TO_CORE(target->public.streams[i]))
+ continue;
+
+ return tg->funcs->get_frame_count(tg);
+ }
+ }
+
+ return 0;
+}
+
+uint32_t dc_target_get_scanoutpos(
+ const struct dc_target *dc_target,
+ uint32_t *vbl,
+ uint32_t *position)
+{
+ uint8_t i, j;
+ struct core_target *target = DC_TARGET_TO_CORE(dc_target);
+ struct core_dc *core_dc = DC_TO_CORE(target->ctx->dc);
+ struct resource_context *res_ctx =
+ &core_dc->current_context->res_ctx;
+
+ for (i = 0; i < target->public.stream_count; i++) {
+ for (j = 0; j < MAX_PIPES; j++) {
+ struct timing_generator *tg = res_ctx->pipe_ctx[j].tg;
+
+ if (res_ctx->pipe_ctx[j].stream !=
+ DC_STREAM_TO_CORE(target->public.streams[i]))
+ continue;
+
+ return tg->funcs->get_scanoutpos(tg, vbl, position);
+ }
+ }
+
+ return 0;
+}
+
+void dc_target_log(
+ const struct dc_target *dc_target,
+ struct dal_logger *dm_logger,
+ enum dc_log_type log_type)
+{
+ int i;
+
+ const struct core_target *core_target =
+ CONST_DC_TARGET_TO_CORE(dc_target);
+
+ dm_logger_write(dm_logger,
+ log_type,
+ "core_target 0x%x: stream_count=%d\n",
+ core_target,
+ 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]);
+
+ dm_logger_write(dm_logger,
+ log_type,
+ "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);
+ dm_logger_write(dm_logger,
+ log_type,
+ "\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);
+ dm_logger_write(dm_logger,
+ log_type,
+ "\tsink name: %s, serial: %d\n",
+ core_stream->sink->public.edid_caps.display_name,
+ core_stream->sink->public.edid_caps.serial_number);
+ dm_logger_write(dm_logger,
+ log_type,
+ "\tlink: %d\n",
+ core_stream->sink->link->public.link_index);
+ }
+}