diff options
Diffstat (limited to 'drivers/gpu/drm/amd/display/dc/core')
-rw-r--r-- | drivers/gpu/drm/amd/display/dc/core/dc.c | 1684 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/dc/core/dc_debug.c | 359 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c | 101 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/dc/core/dc_link.c | 2435 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/dc/core/dc_link_ddc.c | 775 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/dc/core/dc_link_dp.c | 2601 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/dc/core/dc_link_hwss.c | 331 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/dc/core/dc_resource.c | 2807 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/dc/core/dc_sink.c | 104 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/dc/core/dc_stream.c | 397 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/display/dc/core/dc_surface.c | 193 |
11 files changed, 11787 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..7240db2e6f09 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -0,0 +1,1684 @@ +/* + * 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 "bios_parser_interface.h" +#include "include/irq_service_interface.h" +#include "transform.h" +#include "dpp.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" +#include "hubp.h" + + +/******************************************************************************* + * Private functions + ******************************************************************************/ +static void destroy_links(struct dc *dc) +{ + uint32_t i; + + for (i = 0; i < dc->link_count; i++) { + if (NULL != dc->links[i]) + link_destroy(&dc->links[i]); + } +} + +static bool create_links( + struct dc *dc, + 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 dc_link *link; + + link_init_params.ctx = dc->ctx; + /* next BIOS object table connector */ + 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; + } + } + + for (i = 0; i < num_virtual_links; i++) { + struct dc_link *link = kzalloc(sizeof(*link), GFP_KERNEL); + struct encoder_init_data enc_init = {0}; + + if (link == NULL) { + BREAK_TO_DEBUGGER(); + goto failed_alloc; + } + + link->link_index = dc->link_count; + dc->links[dc->link_count] = link; + dc->link_count++; + + link->ctx = dc->ctx; + link->dc = dc; + link->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 = kzalloc(sizeof(*link->link_enc), GFP_KERNEL); + + if (!link->link_enc) { + BREAK_TO_DEBUGGER(); + goto failed_alloc; + } + + link->link_status.dpcd_caps = &link->dpcd_caps; + + 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); + } + + return true; + +failed_alloc: + return false; +} + +static bool stream_adjust_vmin_vmax(struct dc *dc, + struct dc_stream_state **streams, int num_streams, + int vmin, int vmax) +{ + /* TODO: Support multiple streams */ + struct dc_stream_state *stream = streams[0]; + int i = 0; + bool ret = false; + + for (i = 0; i < MAX_PIPES; i++) { + struct pipe_ctx *pipe = &dc->current_state->res_ctx.pipe_ctx[i]; + + if (pipe->stream == stream && pipe->stream_res.stream_enc) { + dc->hwss.set_drr(&pipe, 1, vmin, vmax); + + /* build and update the info frame */ + resource_build_info_frame(pipe); + dc->hwss.update_info_frame(pipe); + + ret = true; + } + } + return ret; +} + +static bool stream_get_crtc_position(struct dc *dc, + struct dc_stream_state **streams, int num_streams, + unsigned int *v_pos, unsigned int *nom_v_pos) +{ + /* TODO: Support multiple streams */ + struct dc_stream_state *stream = streams[0]; + int i = 0; + bool ret = false; + struct crtc_position position; + + for (i = 0; i < MAX_PIPES; i++) { + struct pipe_ctx *pipe = + &dc->current_state->res_ctx.pipe_ctx[i]; + + if (pipe->stream == stream && pipe->stream_res.stream_enc) { + dc->hwss.get_position(&pipe, 1, &position); + + *v_pos = position.vertical_count; + *nom_v_pos = position.nominal_vcount; + ret = true; + } + } + return ret; +} + +static bool set_gamut_remap(struct dc *dc, const struct dc_stream_state *stream) +{ + int i = 0; + bool ret = false; + struct pipe_ctx *pipes; + + for (i = 0; i < MAX_PIPES; i++) { + if (dc->current_state->res_ctx.pipe_ctx[i].stream == stream) { + pipes = &dc->current_state->res_ctx.pipe_ctx[i]; + dc->hwss.program_gamut_remap(pipes); + ret = true; + } + } + + return ret; +} + +static bool program_csc_matrix(struct dc *dc, struct dc_stream_state *stream) +{ + int i = 0; + bool ret = false; + struct pipe_ctx *pipes; + + for (i = 0; i < MAX_PIPES; i++) { + if (dc->current_state->res_ctx.pipe_ctx[i].stream + == stream) { + + pipes = &dc->current_state->res_ctx.pipe_ctx[i]; + dc->hwss.program_csc_matrix(pipes, + stream->output_color_space, + stream->csc_color_matrix.matrix); + ret = true; + } + } + + return ret; +} + +static void set_static_screen_events(struct dc *dc, + struct dc_stream_state **streams, + int num_streams, + const struct dc_static_screen_events *events) +{ + int i = 0; + int j = 0; + struct pipe_ctx *pipes_affected[MAX_PIPES]; + int num_pipes_affected = 0; + + for (i = 0; i < num_streams; i++) { + struct dc_stream_state *stream = streams[i]; + + for (j = 0; j < MAX_PIPES; j++) { + if (dc->current_state->res_ctx.pipe_ctx[j].stream + == stream) { + pipes_affected[num_pipes_affected++] = + &dc->current_state->res_ctx.pipe_ctx[j]; + } + } + } + + dc->hwss.set_static_screen_control(pipes_affected, num_pipes_affected, events); +} + +static void set_drive_settings(struct dc *dc, + struct link_training_settings *lt_settings, + const struct dc_link *link) +{ + + int i; + + for (i = 0; i < dc->link_count; i++) { + if (dc->links[i] == link) + break; + } + + if (i >= dc->link_count) + ASSERT_CRITICAL(false); + + dc_link_dp_set_drive_settings(dc->links[i], lt_settings); +} + +static void perform_link_training(struct dc *dc, + struct dc_link_settings *link_setting, + bool skip_video_pattern) +{ + int i; + + for (i = 0; i < dc->link_count; i++) + dc_link_dp_perform_link_training( + dc->links[i], + link_setting, + skip_video_pattern); +} + +static void set_preferred_link_settings(struct dc *dc, + struct dc_link_settings *link_setting, + struct dc_link *link) +{ + link->preferred_link_setting = *link_setting; + dp_retrain_link_dp_test(link, link_setting, false); +} + +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( + 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 set_dither_option(struct dc_stream_state *stream, + enum dc_dither_option option) +{ + struct bit_depth_reduction_params params; + struct dc_link *link = stream->status.link; + struct pipe_ctx *pipes = NULL; + int i; + + for (i = 0; i < MAX_PIPES; i++) { + if (link->dc->current_state->res_ctx.pipe_ctx[i].stream == + stream) { + pipes = &link->dc->current_state->res_ctx.pipe_ctx[i]; + break; + } + } + + memset(¶ms, 0, sizeof(params)); + if (!pipes) + return; + if (option > DITHER_OPTION_MAX) + return; + + stream->dither_option = option; + + resource_build_bit_depth_reduction_params(stream, + ¶ms); + stream->bit_depth_params = params; + pipes->stream_res.opp->funcs-> + opp_program_bit_depth_reduction(pipes->stream_res.opp, ¶ms); +} + +void set_dpms( + struct dc *dc, + struct dc_stream_state *stream, + bool dpms_off) +{ + struct pipe_ctx *pipe_ctx = NULL; + int i; + + for (i = 0; i < MAX_PIPES; i++) { + if (dc->current_state->res_ctx.pipe_ctx[i].stream == stream) { + pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i]; + break; + } + } + + if (!pipe_ctx) { + ASSERT(0); + return; + } + + if (stream->dpms_off != dpms_off) { + stream->dpms_off = dpms_off; + if (dpms_off) + core_link_disable_stream(pipe_ctx, + KEEP_ACQUIRED_RESOURCE); + else + core_link_enable_stream(dc->current_state, pipe_ctx); + } +} + +static void allocate_dc_stream_funcs(struct dc *dc) +{ + if (dc->hwss.set_drr != NULL) { + dc->stream_funcs.adjust_vmin_vmax = + stream_adjust_vmin_vmax; + } + + dc->stream_funcs.set_static_screen_events = + set_static_screen_events; + + dc->stream_funcs.get_crtc_position = + stream_get_crtc_position; + + dc->stream_funcs.set_gamut_remap = + set_gamut_remap; + + dc->stream_funcs.program_csc_matrix = + program_csc_matrix; + + dc->stream_funcs.set_dither_option = + set_dither_option; + + dc->stream_funcs.set_dpms = + set_dpms; + + dc->link_funcs.set_drive_settings = + set_drive_settings; + + dc->link_funcs.perform_link_training = + perform_link_training; + + dc->link_funcs.set_preferred_link_settings = + set_preferred_link_settings; + + dc->link_funcs.enable_hpd = + enable_hpd; + + dc->link_funcs.disable_hpd = + disable_hpd; + + dc->link_funcs.set_test_pattern = + set_test_pattern; +} + +static void destruct(struct dc *dc) +{ + dc_release_state(dc->current_state); + dc->current_state = 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); + + kfree(dc->ctx); + dc->ctx = NULL; + + kfree(dc->bw_vbios); + dc->bw_vbios = NULL; + + kfree(dc->bw_dceip); + dc->bw_dceip = NULL; + +#ifdef CONFIG_DRM_AMD_DC_DCN1_0 + kfree(dc->dcn_soc); + dc->dcn_soc = NULL; + + kfree(dc->dcn_ip); + dc->dcn_ip = NULL; + +#endif +} + +static bool construct(struct dc *dc, + const struct dc_init_data *init_params) +{ + struct dal_logger *logger; + struct dc_context *dc_ctx = kzalloc(sizeof(*dc_ctx), GFP_KERNEL); + struct bw_calcs_dceip *dc_dceip = kzalloc(sizeof(*dc_dceip), + GFP_KERNEL); + struct bw_calcs_vbios *dc_vbios = kzalloc(sizeof(*dc_vbios), + GFP_KERNEL); +#ifdef CONFIG_DRM_AMD_DC_DCN1_0 + struct dcn_soc_bounding_box *dcn_soc = kzalloc(sizeof(*dcn_soc), + GFP_KERNEL); + struct dcn_ip_params *dcn_ip = kzalloc(sizeof(*dcn_ip), GFP_KERNEL); +#endif + + enum dce_version dc_version = DCE_VERSION_UNKNOWN; + + if (!dc_dceip) { + dm_error("%s: failed to create dceip\n", __func__); + goto fail; + } + + dc->bw_dceip = dc_dceip; + + if (!dc_vbios) { + dm_error("%s: failed to create vbios\n", __func__); + goto fail; + } + + dc->bw_vbios = dc_vbios; +#ifdef CONFIG_DRM_AMD_DC_DCN1_0 + if (!dcn_soc) { + dm_error("%s: failed to create dcn_soc\n", __func__); + goto fail; + } + + dc->dcn_soc = dcn_soc; + + if (!dcn_ip) { + dm_error("%s: failed to create dcn_ip\n", __func__); + goto fail; + } + + dc->dcn_ip = dcn_ip; +#endif + + if (!dc_ctx) { + dm_error("%s: failed to create ctx\n", __func__); + goto fail; + } + + dc->current_state = dc_create_state(); + + if (!dc->current_state) { + dm_error("%s: failed to create validate ctx\n", __func__); + goto fail; + } + + dc_ctx->cgs_device = init_params->cgs_device; + dc_ctx->driver_context = init_params->driver; + dc_ctx->dc = dc; + dc_ctx->asic_id = init_params->asic_id; + + /* Create logger */ + logger = dal_logger_create(dc_ctx, init_params->log_mask); + + if (!logger) { + /* can *not* call logger. call base driver 'print error' */ + dm_error("%s: failed to create Logger!\n", __func__); + goto 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; +#if defined(CONFIG_DRM_AMD_DC_FBC) + dc->ctx->fbc_gpu_addr = init_params->fbc_gpu_addr; +#endif + /* 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 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 fail; + } + + /* 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 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 fail; + + dc_resource_state_construct(dc, dc->current_state); + + if (!create_links(dc, init_params->num_virtual_links)) + goto fail; + + allocate_dc_stream_funcs(dc); + + return true; + +fail: + + destruct(dc); + return false; +} + +static void disable_dangling_plane(struct dc *dc, struct dc_state *context) +{ + int i, j; + struct dc_state *dangling_context = dc_create_state(); + struct dc_state *current_ctx; + + if (dangling_context == NULL) + return; + + dc_resource_state_copy_construct(dc->current_state, dangling_context); + + for (i = 0; i < dc->res_pool->pipe_count; i++) { + struct dc_stream_state *old_stream = + dc->current_state->res_ctx.pipe_ctx[i].stream; + bool should_disable = true; + + for (j = 0; j < context->stream_count; j++) { + if (old_stream == context->streams[j]) { + should_disable = false; + break; + } + } + if (should_disable && old_stream) { + dc_rem_all_planes_for_stream(dc, old_stream, dangling_context); + dc->hwss.apply_ctx_for_surface(dc, old_stream, 0, dangling_context); + } + } + + current_ctx = dc->current_state; + dc->current_state = dangling_context; + dc_release_state(current_ctx); +} + +/******************************************************************************* + * Public functions + ******************************************************************************/ + +struct dc *dc_create(const struct dc_init_data *init_params) + { + struct dc *dc = kzalloc(sizeof(*dc), GFP_KERNEL); + unsigned int full_pipe_count; + + if (NULL == dc) + goto alloc_fail; + + if (false == construct(dc, init_params)) + goto construct_fail; + + /*TODO: separate HW and SW initialization*/ + dc->hwss.init_hw(dc); + + full_pipe_count = dc->res_pool->pipe_count; + if (dc->res_pool->underlay_pipe_index != NO_UNDERLAY_PIPE) + full_pipe_count--; + dc->caps.max_streams = min( + full_pipe_count, + dc->res_pool->stream_enc_count); + + dc->caps.max_links = dc->link_count; + dc->caps.max_audios = dc->res_pool->audio_count; + + dc->config = init_params->flags; + + dm_logger_write(dc->ctx->logger, LOG_DC, + "Display Core initialized\n"); + + + /* TODO: missing feature to be enabled */ + dc->debug.disable_dfs_bypass = true; + + return dc; + +construct_fail: + kfree(dc); + +alloc_fail: + return NULL; +} + +void dc_destroy(struct dc **dc) +{ + destruct(*dc); + kfree(*dc); + *dc = NULL; +} + +static void program_timing_sync( + struct dc *dc, + struct dc_state *ctx) +{ + int i, j; + int group_index = 0; + int pipe_count = dc->res_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]->stream_res.tg->funcs->is_blanked(pipe_set[j]->stream_res.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]->stream_res.tg->funcs->is_blanked(pipe_set[j]->stream_res.tg)) { + group_size--; + pipe_set[j] = pipe_set[group_size]; + j--; + } + } + + if (group_size > 1) { + dc->hwss.enable_timing_synchronization( + dc, group_index, group_size, pipe_set); + group_index++; + } + } +} + +static bool context_changed( + struct dc *dc, + struct dc_state *context) +{ + uint8_t i; + + if (context->stream_count != dc->current_state->stream_count) + return true; + + for (i = 0; i < dc->current_state->stream_count; i++) { + if (dc->current_state->streams[i] != context->streams[i]) + return true; + } + + return false; +} + +bool dc_enable_stereo( + struct dc *dc, + struct dc_state *context, + struct dc_stream_state *streams[], + uint8_t stream_count) +{ + bool ret = true; + int i, j; + struct pipe_ctx *pipe; + + for (i = 0; i < MAX_PIPES; i++) { + if (context != NULL) + pipe = &context->res_ctx.pipe_ctx[i]; + else + pipe = &dc->current_state->res_ctx.pipe_ctx[i]; + for (j = 0 ; pipe && j < stream_count; j++) { + if (streams[j] && streams[j] == pipe->stream && + dc->hwss.setup_stereo) + dc->hwss.setup_stereo(pipe, dc); + } + } + + return ret; +} + + +/* + * Applies given context to HW and copy it into current context. + * It's up to the user to release the src context afterwards. + */ +static enum dc_status dc_commit_state_no_check(struct dc *dc, struct dc_state *context) +{ + struct dc_bios *dcb = dc->ctx->dc_bios; + enum dc_status result = DC_ERROR_UNEXPECTED; + struct pipe_ctx *pipe; + int i, j, k, l; + struct dc_stream_state *dc_streams[MAX_STREAMS] = {0}; + + disable_dangling_plane(dc, context); + + for (i = 0; i < context->stream_count; i++) + dc_streams[i] = context->streams[i]; + + if (!dcb->funcs->is_accelerated_mode(dcb)) + dc->hwss.enable_accelerated_mode(dc); + + for (i = 0; i < context->stream_count; i++) { + const struct dc_sink *sink = context->streams[i]->sink; + + dc->hwss.apply_ctx_for_surface( + dc, context->streams[i], + context->stream_status[i].plane_count, + context); + + /* + * enable stereo + * TODO rework dc_enable_stereo call to work with validation sets? + */ + for (k = 0; k < MAX_PIPES; k++) { + pipe = &context->res_ctx.pipe_ctx[k]; + + for (l = 0 ; pipe && l < context->stream_count; l++) { + if (context->streams[l] && + context->streams[l] == pipe->stream && + dc->hwss.setup_stereo) + dc->hwss.setup_stereo(pipe, dc); + } + } + + CONN_MSG_MODE(sink->link, "{%dx%d, %dx%d@%dKhz}", + context->streams[i]->timing.h_addressable, + context->streams[i]->timing.v_addressable, + context->streams[i]->timing.h_total, + context->streams[i]->timing.v_total, + context->streams[i]->timing.pix_clk_khz); + } + + dc->hwss.ready_shared_resources(dc, context); + + for (i = 0; i < dc->res_pool->pipe_count; i++) { + pipe = &context->res_ctx.pipe_ctx[i]; + dc->hwss.wait_for_mpcc_disconnect(dc, dc->res_pool, pipe); + } + result = dc->hwss.apply_ctx_to_hw(dc, context); + + program_timing_sync(dc, context); + + dc_enable_stereo(dc, context, dc_streams, context->stream_count); + + for (i = 0; i < context->stream_count; i++) { + for (j = 0; j < MAX_PIPES; j++) { + pipe = &context->res_ctx.pipe_ctx[j]; + + if (!pipe->top_pipe && pipe->stream == context->streams[i]) + dc->hwss.pipe_control_lock(dc, pipe, false); + } + } + + dc_release_state(dc->current_state); + + dc->current_state = context; + + dc_retain_state(dc->current_state); + + dc->hwss.optimize_shared_resources(dc); + + return result; +} + +bool dc_commit_state(struct dc *dc, struct dc_state *context) +{ + enum dc_status result = DC_ERROR_UNEXPECTED; + int i; + + if (false == context_changed(dc, context)) + return DC_OK; + + dm_logger_write(dc->ctx->logger, LOG_DC, "%s: %d streams\n", + __func__, context->stream_count); + + for (i = 0; i < context->stream_count; i++) { + struct dc_stream_state *stream = context->streams[i]; + + dc_stream_log(stream, + dc->ctx->logger, + LOG_DC); + } + + result = dc_commit_state_no_check(dc, context); + + return (result == DC_OK); +} + + +bool dc_post_update_surfaces_to_stream(struct dc *dc) +{ + int i; + struct dc_state *context = dc->current_state; + + post_surface_trace(dc); + + for (i = 0; i < dc->res_pool->pipe_count; i++) + if (context->res_ctx.pipe_ctx[i].stream == NULL + || context->res_ctx.pipe_ctx[i].plane_state == NULL) + dc->hwss.power_down_front_end(dc, i); + + /* 3rd param should be true, temp w/a for RV*/ +#if defined(CONFIG_DRM_AMD_DC_DCN1_0) + dc->hwss.set_bandwidth(dc, context, dc->ctx->dce_version < DCN_VERSION_1_0); +#else + dc->hwss.set_bandwidth(dc, context, true); +#endif + return true; +} + +/* + * TODO this whole function needs to go + * + * dc_surface_update is needlessly complex. See if we can just replace this + * with a dc_plane_state and follow the atomic model a bit more closely here. + */ +bool dc_commit_planes_to_stream( + struct dc *dc, + struct dc_plane_state **plane_states, + uint8_t new_plane_count, + struct dc_stream_state *dc_stream, + struct dc_state *state) +{ + /* no need to dynamically allocate this. it's pretty small */ + struct dc_surface_update updates[MAX_SURFACES]; + struct dc_flip_addrs *flip_addr; + struct dc_plane_info *plane_info; + struct dc_scaling_info *scaling_info; + int i; + struct dc_stream_update *stream_update = + kzalloc(sizeof(struct dc_stream_update), GFP_KERNEL); + + if (!stream_update) { + BREAK_TO_DEBUGGER(); + return false; + } + + flip_addr = kcalloc(MAX_SURFACES, sizeof(struct dc_flip_addrs), + GFP_KERNEL); + plane_info = kcalloc(MAX_SURFACES, sizeof(struct dc_plane_info), + GFP_KERNEL); + scaling_info = kcalloc(MAX_SURFACES, sizeof(struct dc_scaling_info), + GFP_KERNEL); + + if (!flip_addr || !plane_info || !scaling_info) { + kfree(flip_addr); + kfree(plane_info); + kfree(scaling_info); + kfree(stream_update); + return false; + } + + memset(updates, 0, sizeof(updates)); + + stream_update->src = dc_stream->src; + stream_update->dst = dc_stream->dst; + stream_update->out_transfer_func = dc_stream->out_transfer_func; + + for (i = 0; i < new_plane_count; i++) { + updates[i].surface = plane_states[i]; + updates[i].gamma = + (struct dc_gamma *)plane_states[i]->gamma_correction; + updates[i].in_transfer_func = plane_states[i]->in_transfer_func; + flip_addr[i].address = plane_states[i]->address; + flip_addr[i].flip_immediate = plane_states[i]->flip_immediate; + plane_info[i].color_space = plane_states[i]->color_space; + plane_info[i].format = plane_states[i]->format; + plane_info[i].plane_size = plane_states[i]->plane_size; + plane_info[i].rotation = plane_states[i]->rotation; + plane_info[i].horizontal_mirror = plane_states[i]->horizontal_mirror; + plane_info[i].stereo_format = plane_states[i]->stereo_format; + plane_info[i].tiling_info = plane_states[i]->tiling_info; + plane_info[i].visible = plane_states[i]->visible; + plane_info[i].per_pixel_alpha = plane_states[i]->per_pixel_alpha; + plane_info[i].dcc = plane_states[i]->dcc; + scaling_info[i].scaling_quality = plane_states[i]->scaling_quality; + scaling_info[i].src_rect = plane_states[i]->src_rect; + scaling_info[i].dst_rect = plane_states[i]->dst_rect; + scaling_info[i].clip_rect = plane_states[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_commit_updates_for_stream( + dc, + updates, + new_plane_count, + dc_stream, stream_update, plane_states, state); + + kfree(flip_addr); + kfree(plane_info); + kfree(scaling_info); + kfree(stream_update); + return true; +} + +struct dc_state *dc_create_state(void) +{ + struct dc_state *context = kzalloc(sizeof(struct dc_state), + GFP_KERNEL); + + if (!context) + return NULL; + + kref_init(&context->refcount); + return context; +} + +void dc_retain_state(struct dc_state *context) +{ + kref_get(&context->refcount); +} + +static void dc_state_free(struct kref *kref) +{ + struct dc_state *context = container_of(kref, struct dc_state, refcount); + dc_resource_state_destruct(context); + kfree(context); +} + +void dc_release_state(struct dc_state *context) +{ + kref_put(&context->refcount, dc_state_free); +} + +static bool is_surface_in_context( + const struct dc_state *context, + const struct dc_plane_state *plane_state) +{ + int j; + + for (j = 0; j < MAX_PIPES; j++) { + const struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j]; + + if (plane_state == pipe_ctx->plane_state) { + return true; + } + } + + return false; +} + +static unsigned int pixel_format_to_bpp(enum surface_pixel_format format) +{ + switch (format) { + case SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr: + case SURFACE_PIXEL_FORMAT_VIDEO_420_YCrCb: + return 12; + case SURFACE_PIXEL_FORMAT_GRPH_ARGB1555: + case SURFACE_PIXEL_FORMAT_GRPH_RGB565: + case SURFACE_PIXEL_FORMAT_VIDEO_420_10bpc_YCbCr: + case SURFACE_PIXEL_FORMAT_VIDEO_420_10bpc_YCrCb: + return 16; + case SURFACE_PIXEL_FORMAT_GRPH_ARGB8888: + case SURFACE_PIXEL_FORMAT_GRPH_ABGR8888: + case SURFACE_PIXEL_FORMAT_GRPH_ARGB2101010: + case SURFACE_PIXEL_FORMAT_GRPH_ABGR2101010: + return 32; + case SURFACE_PIXEL_FORMAT_GRPH_ARGB16161616: + case SURFACE_PIXEL_FORMAT_GRPH_ARGB16161616F: + case SURFACE_PIXEL_FORMAT_GRPH_ABGR16161616F: + return 64; + default: + ASSERT_CRITICAL(false); + return -1; + } +} + +static enum surface_update_type get_plane_info_update_type( + const struct dc_surface_update *u, + int surface_index) +{ + struct dc_plane_info temp_plane_info; + memset(&temp_plane_info, 0, sizeof(temp_plane_info)); + + if (!u->plane_info) + return UPDATE_TYPE_FAST; + + temp_plane_info = *u->plane_info; + + /* Copy all parameters that will cause a full update + * from current surface, the rest of the parameters + * from provided plane configuration. + * Perform memory compare and special validation + * for those that can cause fast/medium updates + */ + + /* Full update parameters */ + temp_plane_info.color_space = u->surface->color_space; + temp_plane_info.dcc = u->surface->dcc; + temp_plane_info.horizontal_mirror = u->surface->horizontal_mirror; + temp_plane_info.plane_size = u->surface->plane_size; + temp_plane_info.rotation = u->surface->rotation; + temp_plane_info.stereo_format = u->surface->stereo_format; + + if (surface_index == 0) + temp_plane_info.visible = u->plane_info->visible; + else + temp_plane_info.visible = u->surface->visible; + + if (memcmp(u->plane_info, &temp_plane_info, + sizeof(struct dc_plane_info)) != 0) + return UPDATE_TYPE_FULL; + + if (pixel_format_to_bpp(u->plane_info->format) != + pixel_format_to_bpp(u->surface->format)) { + /* different bytes per element will require full bandwidth + * and DML calculation + */ + return UPDATE_TYPE_FULL; + } + + if (memcmp(&u->plane_info->tiling_info, &u->surface->tiling_info, + sizeof(union dc_tiling_info)) != 0) { + /* todo: below are HW dependent, we should add a hook to + * DCE/N resource and validated there. + */ + if (u->plane_info->tiling_info.gfx9.swizzle != DC_SW_LINEAR) { + /* swizzled mode requires RQ to be setup properly, + * thus need to run DML to calculate RQ settings + */ + return UPDATE_TYPE_FULL; + } + } + + return UPDATE_TYPE_MED; +} + +static enum surface_update_type get_scaling_info_update_type( + const struct dc_surface_update *u) +{ + if (!u->scaling_info) + return UPDATE_TYPE_FAST; + + if (u->scaling_info->src_rect.width != u->surface->src_rect.width + || u->scaling_info->src_rect.height != u->surface->src_rect.height + || u->scaling_info->clip_rect.width != u->surface->clip_rect.width + || u->scaling_info->clip_rect.height != u->surface->clip_rect.height + || u->scaling_info->dst_rect.width != u->surface->dst_rect.width + || u->scaling_info->dst_rect.height != u->surface->dst_rect.height) + return UPDATE_TYPE_FULL; + + if (u->scaling_info->src_rect.x != u->surface->src_rect.x + || u->scaling_info->src_rect.y != u->surface->src_rect.y + || u->scaling_info->clip_rect.x != u->surface->clip_rect.x + || u->scaling_info->clip_rect.y != u->surface->clip_rect.y + || u->scaling_info->dst_rect.x != u->surface->dst_rect.x + || u->scaling_info->dst_rect.y != u->surface->dst_rect.y) + return UPDATE_TYPE_MED; + + return UPDATE_TYPE_FAST; +} + +static enum surface_update_type det_surface_update( + const struct dc *dc, + const struct dc_surface_update *u, + int surface_index) +{ + const struct dc_state *context = dc->current_state; + enum surface_update_type type = UPDATE_TYPE_FAST; + enum surface_update_type overall_type = UPDATE_TYPE_FAST; + + if (!is_surface_in_context(context, u->surface)) + return UPDATE_TYPE_FULL; + + type = get_plane_info_update_type(u, surface_index); + if (overall_type < type) + overall_type = type; + + type = get_scaling_info_update_type(u); + if (overall_type < type) + overall_type = type; + + if (u->in_transfer_func || + u->hdr_static_metadata) { + if (overall_type < UPDATE_TYPE_MED) + overall_type = UPDATE_TYPE_MED; + } + + return overall_type; +} + +enum surface_update_type dc_check_update_surfaces_for_stream( + struct dc *dc, + struct dc_surface_update *updates, + int surface_count, + struct dc_stream_update *stream_update, + const struct dc_stream_status *stream_status) +{ + int i; + enum surface_update_type overall_type = UPDATE_TYPE_FAST; + + if (stream_status == NULL || stream_status->plane_count != surface_count) + return UPDATE_TYPE_FULL; + + if (stream_update) + return UPDATE_TYPE_FULL; + + for (i = 0 ; i < surface_count; i++) { + enum surface_update_type type = + det_surface_update(dc, &updates[i], i); + + if (type == UPDATE_TYPE_FULL) + return type; + + if (overall_type < type) + overall_type = type; + } + + return overall_type; +} + +static struct dc_stream_status *stream_get_status( + struct dc_state *ctx, + struct dc_stream_state *stream) +{ + uint8_t i; + + for (i = 0; i < ctx->stream_count; i++) { + if (stream == ctx->streams[i]) { + return &ctx->stream_status[i]; + } + } + + return NULL; +} + +static const enum surface_update_type update_surface_trace_level = UPDATE_TYPE_FULL; + + +static void commit_planes_for_stream(struct dc *dc, + struct dc_surface_update *srf_updates, + int surface_count, + struct dc_stream_state *stream, + struct dc_stream_update *stream_update, + enum surface_update_type update_type, + struct dc_state *context) +{ + int i, j; + + if (update_type == UPDATE_TYPE_FULL) { + dc->hwss.set_bandwidth(dc, context, false); + context_clock_trace(dc, context); + } + + if (update_type > UPDATE_TYPE_FAST) { + for (j = 0; j < dc->res_pool->pipe_count; j++) { + struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j]; + + dc->hwss.wait_for_mpcc_disconnect(dc, dc->res_pool, pipe_ctx); + } + } + + if (surface_count == 0) { + /* + * In case of turning off screen, no need to program front end a second time. + * just return after program front end. + */ + dc->hwss.apply_ctx_for_surface(dc, stream, surface_count, context); + return; + } + + /* Lock pipes for provided surfaces, or all active if full update*/ + for (i = 0; i < surface_count; i++) { + struct dc_plane_state *plane_state = srf_updates[i].surface; + + for (j = 0; j < dc->res_pool->pipe_count; j++) { + struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j]; + + if (update_type != UPDATE_TYPE_FULL && pipe_ctx->plane_state != plane_state) + continue; + if (!pipe_ctx->plane_state || pipe_ctx->top_pipe) + continue; + + dc->hwss.pipe_control_lock( + dc, + pipe_ctx, + true); + } + if (update_type == UPDATE_TYPE_FULL) + break; + } + + /* Full fe update*/ + for (j = 0; j < dc->res_pool->pipe_count; j++) { + struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j]; + + if (update_type != UPDATE_TYPE_FULL || !pipe_ctx->plane_state) + continue; + + if (!pipe_ctx->top_pipe && pipe_ctx->stream) { + struct dc_stream_status *stream_status = stream_get_status(context, pipe_ctx->stream); + + dc->hwss.apply_ctx_for_surface( + dc, pipe_ctx->stream, stream_status->plane_count, context); + } + } + + if (update_type > UPDATE_TYPE_FAST) + context_timing_trace(dc, &context->res_ctx); + + /* Perform requested Updates */ + for (i = 0; i < surface_count; i++) { + struct dc_plane_state *plane_state = srf_updates[i].surface; + + if (update_type == UPDATE_TYPE_MED) + dc->hwss.apply_ctx_for_surface( + dc, stream, surface_count, context); + + for (j = 0; j < dc->res_pool->pipe_count; j++) { + struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j]; + + if (pipe_ctx->plane_state != plane_state) + continue; + + if (srf_updates[i].flip_addr) + dc->hwss.update_plane_addr(dc, pipe_ctx); + + if (update_type == UPDATE_TYPE_FAST) + continue; + + /* work around to program degamma regs for split pipe after set mode. */ + if (srf_updates[i].in_transfer_func || (pipe_ctx->top_pipe && + pipe_ctx->top_pipe->plane_state == pipe_ctx->plane_state)) + dc->hwss.set_input_transfer_func( + pipe_ctx, pipe_ctx->plane_state); + + if (stream_update != NULL && + stream_update->out_transfer_func != NULL) { + dc->hwss.set_output_transfer_func( + pipe_ctx, pipe_ctx->stream); + } + + if (srf_updates[i].hdr_static_metadata) { + resource_build_info_frame(pipe_ctx); + dc->hwss.update_info_frame(pipe_ctx); + } + } + } + + /* Unlock pipes */ + for (i = dc->res_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 (update_type != UPDATE_TYPE_FULL && + srf_updates[j].surface != pipe_ctx->plane_state) + continue; + if (!pipe_ctx->plane_state || pipe_ctx->top_pipe) + continue; + + dc->hwss.pipe_control_lock( + dc, + pipe_ctx, + false); + + break; + } + } +} + +void dc_commit_updates_for_stream(struct dc *dc, + struct dc_surface_update *srf_updates, + int surface_count, + struct dc_stream_state *stream, + struct dc_stream_update *stream_update, + struct dc_plane_state **plane_states, + struct dc_state *state) +{ + const struct dc_stream_status *stream_status; + enum surface_update_type update_type; + struct dc_state *context; + struct dc_context *dc_ctx = dc->ctx; + int i, j; + + stream_status = dc_stream_get_status(stream); + context = dc->current_state; + + update_type = dc_check_update_surfaces_for_stream( + dc, srf_updates, surface_count, stream_update, stream_status); + + if (update_type >= update_surface_trace_level) + update_surface_trace(dc, srf_updates, surface_count); + + + if (update_type >= UPDATE_TYPE_FULL) { + + /* initialize scratch memory for building context */ + context = dc_create_state(); + if (context == NULL) { + DC_ERROR("Failed to allocate new validate context!\n"); + return; + } + + dc_resource_state_copy_construct(state, context); + } + + + for (i = 0; i < surface_count; i++) { + struct dc_plane_state *surface = srf_updates[i].surface; + + /* TODO: On flip we don't build the state, so it still has the + * old address. Which is why we are updating the address here + */ + if (srf_updates[i].flip_addr) { + surface->address = srf_updates[i].flip_addr->address; + surface->flip_immediate = srf_updates[i].flip_addr->flip_immediate; + + } + + if (update_type >= UPDATE_TYPE_MED) { + for (j = 0; j < dc->res_pool->pipe_count; j++) { + struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j]; + + if (pipe_ctx->plane_state != surface) + continue; + + resource_build_scaling_params(pipe_ctx); + } + } + } + + commit_planes_for_stream( + dc, + srf_updates, + surface_count, + stream, + stream_update, + update_type, + context); + + if (update_type >= UPDATE_TYPE_FULL) + dc_post_update_surfaces_to_stream(dc); + + if (dc->current_state != context) { + + struct dc_state *old = dc->current_state; + + dc->current_state = context; + dc_release_state(old); + + } + + return; + +} + +uint8_t dc_get_current_stream_count(struct dc *dc) +{ + return dc->current_state->stream_count; +} + +struct dc_stream_state *dc_get_stream_at_index(struct dc *dc, uint8_t i) +{ + if (i < dc->current_state->stream_count) + return dc->current_state->streams[i]; + return NULL; +} + +enum dc_irq_source dc_interrupt_to_irq_source( + struct dc *dc, + uint32_t src_id, + uint32_t ext_id) +{ + return dal_irq_service_to_irq_source(dc->res_pool->irqs, src_id, ext_id); +} + +void dc_interrupt_set(struct dc *dc, enum dc_irq_source src, bool enable) +{ + + if (dc == NULL) + return; + + dal_irq_service_set(dc->res_pool->irqs, src, enable); +} + +void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src) +{ + dal_irq_service_ack(dc->res_pool->irqs, src); +} + +void dc_set_power_state( + struct dc *dc, + enum dc_acpi_cm_power_state power_state) +{ + struct kref refcount; + + switch (power_state) { + case DC_ACPI_CM_POWER_STATE_D0: + dc_resource_state_construct(dc, dc->current_state); + + dc->hwss.init_hw(dc); + break; + default: + + dc->hwss.power_down(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. + */ + + /* Preserve refcount */ + refcount = dc->current_state->refcount; + dc_resource_state_destruct(dc->current_state); + memset(dc->current_state, 0, + sizeof(*dc->current_state)); + + dc->current_state->refcount = refcount; + + break; + } + +} + +void dc_resume(struct dc *dc) +{ + + uint32_t i; + + for (i = 0; i < dc->link_count; i++) + core_link_resume(dc->links[i]); +} + +bool dc_submit_i2c( + struct dc *dc, + uint32_t link_index, + struct i2c_command *cmd) +{ + + struct dc_link *link = 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 dc_link *dc_link, struct dc_sink *sink) +{ + 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( + 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; + + 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( + link, + dc_sink)) + goto fail_add_sink; + + edid_status = dm_helpers_parse_edid_caps( + 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_remove_remote_sink(struct dc_link *link, struct dc_sink *sink) +{ + int i; + + if (!link->sink_count) { + BREAK_TO_DEBUGGER(); + return; + } + + for (i = 0; i < link->sink_count; i++) { + if (link->remote_sinks[i] == sink) { + dc_sink_release(sink); + link->remote_sinks[i] = NULL; + + /* shrink array to remove empty place */ + while (i < link->sink_count - 1) { + link->remote_sinks[i] = link->remote_sinks[i+1]; + i++; + } + link->remote_sinks[i] = NULL; + link->sink_count--; + return; + } + } +} 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..6acee5426e4b --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/core/dc_debug.c @@ -0,0 +1,359 @@ +/* + * 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) + +#define TIMING_TRACE(...) do {\ + if (dc->debug.timing_trace) \ + dm_logger_write(logger, \ + LOG_SYNC, \ + ##__VA_ARGS__); \ +} while (0) + +#define CLOCK_TRACE(...) do {\ + if (dc->debug.clock_trace) \ + dm_logger_write(logger, \ + LOG_BANDWIDTH_CALCS, \ + ##__VA_ARGS__); \ +} while (0) + +void pre_surface_trace( + struct dc *dc, + const struct dc_plane_state *const *plane_states, + int surface_count) +{ + int i; + struct dc *core_dc = dc; + struct dal_logger *logger = core_dc->ctx->logger; + + for (i = 0; i < surface_count; i++) { + const struct dc_plane_state *plane_state = plane_states[i]; + + SURFACE_TRACE("Planes %d:\n", i); + + SURFACE_TRACE( + "plane_state->visible = %d;\n" + "plane_state->flip_immediate = %d;\n" + "plane_state->address.type = %d;\n" + "plane_state->address.grph.addr.quad_part = 0x%X;\n" + "plane_state->address.grph.meta_addr.quad_part = 0x%X;\n" + "plane_state->scaling_quality.h_taps = %d;\n" + "plane_state->scaling_quality.v_taps = %d;\n" + "plane_state->scaling_quality.h_taps_c = %d;\n" + "plane_state->scaling_quality.v_taps_c = %d;\n", + plane_state->visible, + plane_state->flip_immediate, + plane_state->address.type, + plane_state->address.grph.addr.quad_part, + plane_state->address.grph.meta_addr.quad_part, + plane_state->scaling_quality.h_taps, + plane_state->scaling_quality.v_taps, + plane_state->scaling_quality.h_taps_c, + plane_state->scaling_quality.v_taps_c); + + SURFACE_TRACE( + "plane_state->src_rect.x = %d;\n" + "plane_state->src_rect.y = %d;\n" + "plane_state->src_rect.width = %d;\n" + "plane_state->src_rect.height = %d;\n" + "plane_state->dst_rect.x = %d;\n" + "plane_state->dst_rect.y = %d;\n" + "plane_state->dst_rect.width = %d;\n" + "plane_state->dst_rect.height = %d;\n" + "plane_state->clip_rect.x = %d;\n" + "plane_state->clip_rect.y = %d;\n" + "plane_state->clip_rect.width = %d;\n" + "plane_state->clip_rect.height = %d;\n", + plane_state->src_rect.x, + plane_state->src_rect.y, + plane_state->src_rect.width, + plane_state->src_rect.height, + plane_state->dst_rect.x, + plane_state->dst_rect.y, + plane_state->dst_rect.width, + plane_state->dst_rect.height, + plane_state->clip_rect.x, + plane_state->clip_rect.y, + plane_state->clip_rect.width, + plane_state->clip_rect.height); + + SURFACE_TRACE( + "plane_state->plane_size.grph.surface_size.x = %d;\n" + "plane_state->plane_size.grph.surface_size.y = %d;\n" + "plane_state->plane_size.grph.surface_size.width = %d;\n" + "plane_state->plane_size.grph.surface_size.height = %d;\n" + "plane_state->plane_size.grph.surface_pitch = %d;\n", + plane_state->plane_size.grph.surface_size.x, + plane_state->plane_size.grph.surface_size.y, + plane_state->plane_size.grph.surface_size.width, + plane_state->plane_size.grph.surface_size.height, + plane_state->plane_size.grph.surface_pitch); + + + SURFACE_TRACE( + "plane_state->tiling_info.gfx8.num_banks = %d;\n" + "plane_state->tiling_info.gfx8.bank_width = %d;\n" + "plane_state->tiling_info.gfx8.bank_width_c = %d;\n" + "plane_state->tiling_info.gfx8.bank_height = %d;\n" + "plane_state->tiling_info.gfx8.bank_height_c = %d;\n" + "plane_state->tiling_info.gfx8.tile_aspect = %d;\n" + "plane_state->tiling_info.gfx8.tile_aspect_c = %d;\n" + "plane_state->tiling_info.gfx8.tile_split = %d;\n" + "plane_state->tiling_info.gfx8.tile_split_c = %d;\n" + "plane_state->tiling_info.gfx8.tile_mode = %d;\n" + "plane_state->tiling_info.gfx8.tile_mode_c = %d;\n", + plane_state->tiling_info.gfx8.num_banks, + plane_state->tiling_info.gfx8.bank_width, + plane_state->tiling_info.gfx8.bank_width_c, + plane_state->tiling_info.gfx8.bank_height, + plane_state->tiling_info.gfx8.bank_height_c, + plane_state->tiling_info.gfx8.tile_aspect, + plane_state->tiling_info.gfx8.tile_aspect_c, + plane_state->tiling_info.gfx8.tile_split, + plane_state->tiling_info.gfx8.tile_split_c, + plane_state->tiling_info.gfx8.tile_mode, + plane_state->tiling_info.gfx8.tile_mode_c); + + SURFACE_TRACE( + "plane_state->tiling_info.gfx8.pipe_config = %d;\n" + "plane_state->tiling_info.gfx8.array_mode = %d;\n" + "plane_state->color_space = %d;\n" + "plane_state->dcc.enable = %d;\n" + "plane_state->format = %d;\n" + "plane_state->rotation = %d;\n" + "plane_state->stereo_format = %d;\n", + plane_state->tiling_info.gfx8.pipe_config, + plane_state->tiling_info.gfx8.array_mode, + plane_state->color_space, + plane_state->dcc.enable, + plane_state->format, + plane_state->rotation, + plane_state->stereo_format); + + SURFACE_TRACE("plane_state->tiling_info.gfx9.swizzle = %d;\n", + plane_state->tiling_info.gfx9.swizzle); + + SURFACE_TRACE("\n"); + } + SURFACE_TRACE("\n"); +} + +void update_surface_trace( + struct dc *dc, + const struct dc_surface_update *updates, + int surface_count) +{ + int i; + struct dc *core_dc = 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.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.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" + "plane_info->per_pixel_alpha = %d;\n", + update->plane_info->tiling_info.gfx8.pipe_config, + update->plane_info->tiling_info.gfx8.array_mode, + update->plane_info->visible, + update->plane_info->per_pixel_alpha); + + SURFACE_TRACE("surface->tiling_info.gfx9.swizzle = %d;\n", + update->plane_info->tiling_info.gfx9.swizzle); + } + + 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(struct dc *dc) +{ + struct dc *core_dc = dc; + struct dal_logger *logger = core_dc->ctx->logger; + + SURFACE_TRACE("post surface process.\n"); + +} + +void context_timing_trace( + struct dc *dc, + struct resource_context *res_ctx) +{ + int i; + struct dc *core_dc = dc; + struct dal_logger *logger = core_dc->ctx->logger; + int h_pos[MAX_PIPES], v_pos[MAX_PIPES]; + struct crtc_position position; + unsigned int underlay_idx = core_dc->res_pool->underlay_pipe_index; + + + for (i = 0; i < core_dc->res_pool->pipe_count; i++) { + struct pipe_ctx *pipe_ctx = &res_ctx->pipe_ctx[i]; + /* get_position() returns CRTC vertical/horizontal counter + * hence not applicable for underlay pipe + */ + if (pipe_ctx->stream == NULL + || pipe_ctx->pipe_idx == underlay_idx) + continue; + + pipe_ctx->stream_res.tg->funcs->get_position(pipe_ctx->stream_res.tg, &position); + h_pos[i] = position.horizontal_count; + v_pos[i] = position.vertical_count; + } + for (i = 0; i < core_dc->res_pool->pipe_count; i++) { + struct pipe_ctx *pipe_ctx = &res_ctx->pipe_ctx[i]; + + if (pipe_ctx->stream == NULL) + continue; + + TIMING_TRACE("OTG_%d H_tot:%d V_tot:%d H_pos:%d V_pos:%d\n", + pipe_ctx->stream_res.tg->inst, + pipe_ctx->stream->timing.h_total, + pipe_ctx->stream->timing.v_total, + h_pos[i], v_pos[i]); + } +} + +void context_clock_trace( + struct dc *dc, + struct dc_state *context) +{ +#if defined(CONFIG_DRM_AMD_DC_DCN1_0) + struct dc *core_dc = dc; + struct dal_logger *logger = core_dc->ctx->logger; + + CLOCK_TRACE("Current: dispclk_khz:%d dppclk_div:%d dcfclk_khz:%d\n" + "dcfclk_deep_sleep_khz:%d fclk_khz:%d\n" + "dram_ccm_us:%d min_active_dram_ccm_us:%d\n", + context->bw.dcn.calc_clk.dispclk_khz, + context->bw.dcn.calc_clk.dppclk_div, + context->bw.dcn.calc_clk.dcfclk_khz, + context->bw.dcn.calc_clk.dcfclk_deep_sleep_khz, + context->bw.dcn.calc_clk.fclk_khz, + context->bw.dcn.calc_clk.dram_ccm_us, + context->bw.dcn.calc_clk.min_active_dram_ccm_us); + CLOCK_TRACE("Calculated: dispclk_khz:%d dppclk_div:%d dcfclk_khz:%d\n" + "dcfclk_deep_sleep_khz:%d fclk_khz:%d\n" + "dram_ccm_us:%d min_active_dram_ccm_us:%d\n", + context->bw.dcn.calc_clk.dispclk_khz, + context->bw.dcn.calc_clk.dppclk_div, + context->bw.dcn.calc_clk.dcfclk_khz, + context->bw.dcn.calc_clk.dcfclk_deep_sleep_khz, + context->bw.dcn.calc_clk.fclk_khz, + context->bw.dcn.calc_clk.dram_ccm_us, + context->bw.dcn.calc_clk.min_active_dram_ccm_us); +#endif +} 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..71993d5983bf --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c @@ -0,0 +1,101 @@ +/* + * 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 "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 dc *dc, + enum dc_color_space colorspace, + struct tg_color *black_color) +{ + switch (colorspace) { + 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; + } +} + +bool hwss_wait_for_blank_complete( + struct timing_generator *tg) +{ + int counter; + + for (counter = 0; counter < 100; counter++) { + if (tg->funcs->is_blanked(tg)) + break; + + msleep(1); + } + + if (counter == 100) { + dm_error("DC: failed to blank crtc!\n"); + return false; + } + + return true; +} 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..e27ed4a45265 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/core/dc_link.c @@ -0,0 +1,2435 @@ +/* + * 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 "atom.h" +#include "dm_helpers.h" +#include "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 "link_encoder.h" +#include "hw_sequencer.h" +#include "resource.h" +#include "abm.h" +#include "fixed31_32.h" +#include "dpcd_defs.h" +#include "dmcu.h" + +#include "dce/dce_11_0_d.h" +#include "dce/dce_11_0_enum.h" +#include "dce/dce_11_0_sh_mask.h" + +#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 dc_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->local_sink) + dc_sink_release(link->local_sink); + + for (i = 0; i < link->sink_count; ++i) + dc_sink_release(link->remote_sinks[i]); +} + +struct gpio *get_hpd_gpio(struct dc_bios *dcb, + struct graphics_object_id link_id, + struct gpio_service *gpio_service) +{ + enum bp_result bp_result; + struct graphics_object_hpd_info hpd_info; + struct gpio_pin_info pin_info; + + if (dcb->funcs->get_hpd_info(dcb, link_id, &hpd_info) != BP_RESULT_OK) + return NULL; + + bp_result = dcb->funcs->get_gpio_pin_info(dcb, + hpd_info.hpd_int_gpio_uid, &pin_info); + + if (bp_result != BP_RESULT_OK) { + ASSERT(bp_result == BP_RESULT_NORECORD); + return NULL; + } + + return dal_gpio_service_create_irq( + 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 dc_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->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->ctx->dc_bios, link->link_id, link->ctx->gpio_service); + + 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 dc_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->ctx->dc_bios, link->link_id, link->ctx->gpio_service); + 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; +} + +static 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 dc_link *link) +{ + enum gpio_result gpio_result; + uint32_t clock_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); + + present = (gpio_result == GPIO_RESULT_OK) && !clock_pin; + + dal_ddc_close(ddc); + + return present; +} + +/* + * @brief + * Detect output sink type + */ +static enum signal_type link_detect_sink( + struct dc_link *link, + enum dc_detect_reason reason) +{ + 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: { + /* DP HPD short pulse. Passive DP dongle will not + * have short pulse + */ + if (reason != DETECT_REASON_HPDRX) { + /* 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 dc_link *link) +{ + if (link->local_sink) { + dc_sink_release(link->local_sink); + link->local_sink = NULL; + } + + link->dpcd_sink_count = 0; +} + +static void detect_dp( + struct dc_link *link, + struct display_sink_capability *sink_caps, + bool *converter_disable_audio, + struct audio_support *audio_support, + enum dc_detect_reason reason) +{ + bool boot = false; + sink_caps->signal = link_detect_sink(link, reason); + 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); + + if (is_mst_supported(link)) { + sink_caps->signal = SIGNAL_TYPE_DISPLAY_PORT_MST; + link->type = dc_connection_mst_branch; + + /* + * 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 (reason == DETECT_REASON_BOOT) + boot = true; + + if (!dm_helpers_dp_mst_start_top_mgr( + link->ctx, + link, boot)) { + /* MST not supported */ + link->type = dc_connection_single; + sink_caps->signal = SIGNAL_TYPE_DISPLAY_PORT; + } + } + + if (link->type != dc_connection_mst_branch && + is_dp_active_dongle(link)) { + /* DP active dongles */ + link->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; + } + } else { + /* DP passive dongles */ + sink_caps->signal = dp_passive_dongle_detection(link->ddc, + sink_caps, + audio_support); + } +} + +bool dc_link_detect(struct dc_link *link, enum dc_detect_reason reason) +{ + 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 *sink = NULL; + enum dc_connection_type new_connection_type = dc_connection_none; + + if (link->connector_signal == SIGNAL_TYPE_VIRTUAL) + return false; + + if (false == detect_sink(link, &new_connection_type)) { + BREAK_TO_DEBUGGER(); + return false; + } + + if (link->connector_signal == SIGNAL_TYPE_EDP && + link->local_sink) + return true; + + link_disconnect_sink(link); + + if (new_connection_type != dc_connection_none) { + link->type = new_connection_type; + + /* From Disconnected-to-Connected. */ + switch (link->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_edp_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, reason); + + /* Active dongle downstream unplug */ + if (link->type == dc_connection_active_dongle + && link->dpcd_caps.sink_count. + bits.SINK_COUNT == 0) + return true; + + if (link->type == dc_connection_mst_branch) { + LINK_INFO("link=%d, mst branch is now Connected\n", + link->link_index); + /* Need to setup mst link_cap struct here + * otherwise dc_link_detect() will leave mst link_cap + * empty which leads to allocate_mst_payload() has "0" + * pbn_per_slot value leading to exception on dal_fixed31_32_div() + */ + link->verified_link_cap = link->reported_link_cap; + return false; + } + + break; + } + + default: + DC_ERROR("Invalid connector type! signal:%d\n", + link->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); + + link->aux_mode = dal_ddc_service_is_in_aux_transaction_mode( + link->ddc); + + sink_init_data.link = link; + sink_init_data.sink_signal = sink_caps.signal; + + sink = dc_sink_create(&sink_init_data); + if (!sink) { + DC_ERROR("Failed to create sink!\n"); + return false; + } + + sink->dongle_max_pix_clk = sink_caps.max_hdmi_pixel_clock; + sink->converter_disable_audio = converter_disable_audio; + + link->local_sink = sink; + + edid_status = dm_helpers_read_local_edid( + link->ctx, + 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; + } + + if (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT && + sink_caps.transaction_type == + DDC_TRANSACTION_TYPE_I2C_OVER_AUX) { + /* + * TODO debug why Dell 2413 doesn't like + * two link trainings + */ + + /* deal with non-mst cases */ + dp_hbr_verify_link_cap(link, &link->reported_link_cap); + } + + /* HDMI-DVI Dongle */ + if (sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A && + !sink->edid_caps.edid_hdmi) + sink->sink_signal = SIGNAL_TYPE_DVI_SINGLE_LINK; + + /* Connectivity log: detection */ + for (i = 0; i < sink->dc_edid.length / EDID_BLOCK_SIZE; i++) { + CONN_DATA_DETECT(link, + &sink->dc_edid.raw_edid[i * EDID_BLOCK_SIZE], + EDID_BLOCK_SIZE, + "%s: [Block %d] ", sink->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->edid_caps.manufacturer_id, + sink->edid_caps.product_id, + sink->edid_caps.serial_number, + sink->edid_caps.manufacture_week, + sink->edid_caps.manufacture_year, + sink->edid_caps.display_name, + sink->edid_caps.speaker_flags, + sink->edid_caps.audio_mode_count); + + for (i = 0; i < sink->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->edid_caps.audio_modes[i].format_code, + sink->edid_caps.audio_modes[i].channel_count, + sink->edid_caps.audio_modes[i].sample_rate, + sink->edid_caps.audio_modes[i].sample_size); + } + + } else { + /* From Connected-to-Disconnected. */ + if (link->type == dc_connection_mst_branch) { + LINK_INFO("link=%d, mst branch is now Disconnected\n", + link->link_index); + + dm_helpers_dp_mst_stop_top_mgr(link->ctx, link); + + 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->type = dc_connection_none; + sink_caps.signal = SIGNAL_TYPE_NONE; + } + + LINK_INFO("link=%d, dc_sink_in=%p is now %s\n", + link->link_index, sink, + (sink_caps.signal == SIGNAL_TYPE_NONE ? + "Disconnected":"Connected")); + + return true; +} + +static enum hpd_source_id get_hpd_line( + struct dc_link *link) +{ + struct gpio *hpd; + enum hpd_source_id hpd_id = HPD_SOURCEID_UNKNOWN; + + hpd = get_hpd_gpio(link->ctx->dc_bios, link->link_id, link->ctx->gpio_service); + + 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 dc_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 dc_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->irq_source_hpd = DC_IRQ_SOURCE_INVALID; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_INVALID; + + link->link_status.dpcd_caps = &link->dpcd_caps; + + link->dc = init_params->dc; + link->ctx = dc_ctx; + link->link_index = init_params->link_index; + + 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->ctx->dc_bios, link->link_id, link->ctx->gpio_service); + + if (hpd_gpio != NULL) + link->irq_source_hpd = dal_irq_get_source(hpd_gpio); + + switch (link->link_id.id) { + case CONNECTOR_ID_HDMI_TYPE_A: + link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A; + + break; + case CONNECTOR_ID_SINGLE_LINK_DVID: + case CONNECTOR_ID_SINGLE_LINK_DVII: + link->connector_signal = SIGNAL_TYPE_DVI_SINGLE_LINK; + break; + case CONNECTOR_ID_DUAL_LINK_DVID: + case CONNECTOR_ID_DUAL_LINK_DVII: + link->connector_signal = SIGNAL_TYPE_DVI_DUAL_LINK; + break; + case CONNECTOR_ID_DISPLAY_PORT: + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + if (hpd_gpio != NULL) + link->irq_source_hpd_rx = + dal_irq_get_rx_source(hpd_gpio); + + break; + case CONNECTOR_ID_EDP: + link->connector_signal = SIGNAL_TYPE_EDP; + + if (hpd_gpio != NULL) { + link->irq_source_hpd = DC_IRQ_SOURCE_INVALID; + link->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->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 (link->ddc == NULL) { + DC_ERROR("Failed to create ddc_service!\n"); + goto ddc_create_fail; + } + + link->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); + + link->hpd_src = enc_init_data.hpd_source; + + enc_init_data.transmitter = + translate_encoder_to_transmitter(enc_init_data.encoder); + link->link_enc = link->dc->res_pool->funcs->link_enc_create( + &enc_init_data); + + if( link->link_enc == NULL) { + DC_ERROR("Failed to create link encoder!\n"); + goto link_enc_create_fail; + } + + link->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->connector_signal != SIGNAL_TYPE_RGB) + continue; + if (link->device_tag.dev_id.device_type == DEVICE_TYPE_LCD + && link->connector_signal == SIGNAL_TYPE_RGB) + 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) { + + if (link->device_tag.acpi_device != 0 + && path->device_acpi_enum == link->device_tag.acpi_device) { + link->ddi_channel_mapping = path->channel_mapping; + link->chip_caps = path->caps; + } else if (path->device_tag == + link->device_tag.dev_id.raw_device_tag) { + link->ddi_channel_mapping = path->channel_mapping; + link->chip_caps = path->caps; + } + 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 dc_link *link_create(const struct link_init_data *init_params) +{ + struct dc_link *link = + kzalloc(sizeof(*link), GFP_KERNEL); + + if (NULL == link) + goto alloc_fail; + + if (false == construct(link, init_params)) + goto construct_fail; + + return link; + +construct_fail: + kfree(link); + +alloc_fail: + return NULL; +} + +void link_destroy(struct dc_link **link) +{ + destruct(*link); + kfree(*link); + *link = NULL; +} + +static void dpcd_configure_panel_mode( + struct dc_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, + DP_EDP_CONFIGURATION_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, + DP_EDP_CONFIGURATION_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->link_index, + link->dpcd_caps.panel_mode_edp, + panel_mode_edp); +} + +static void enable_stream_features(struct pipe_ctx *pipe_ctx) +{ + struct dc_stream_state *stream = pipe_ctx->stream; + struct dc_link *link = stream->sink->link; + union down_spread_ctrl downspread; + + core_link_read_dpcd(link, DP_DOWNSPREAD_CTRL, + &downspread.raw, sizeof(downspread)); + + downspread.bits.IGNORE_MSA_TIMING_PARAM = + (stream->ignore_msa_timing_param) ? 1 : 0; + + core_link_write_dpcd(link, DP_DOWNSPREAD_CTRL, + &downspread.raw, sizeof(downspread)); +} + +static enum dc_status enable_link_dp( + struct dc_state *state, + struct pipe_ctx *pipe_ctx) +{ + struct dc_stream_state *stream = pipe_ctx->stream; + enum dc_status status; + bool skip_video_pattern; + struct dc_link *link = stream->sink->link; + struct dc_link_settings link_settings = {0}; + enum dp_panel_mode panel_mode; + 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) { + if (state->dis_clk->funcs->set_min_clocks_state) { + if (state->dis_clk->cur_min_clks_state < DM_PP_CLOCKS_STATE_NOMINAL) + state->dis_clk->funcs->set_min_clocks_state( + state->dis_clk, DM_PP_CLOCKS_STATE_NOMINAL); + } else { + uint32_t dp_phyclk_in_khz; + const struct clocks_value clocks_value = + state->dis_clk->cur_clocks_value; + + /* 27mhz = 27000000hz= 27000khz */ + dp_phyclk_in_khz = link_settings.link_rate * 27000; + + if (((clocks_value.max_non_dp_phyclk_in_khz != 0) && + (dp_phyclk_in_khz > clocks_value.max_non_dp_phyclk_in_khz)) || + (dp_phyclk_in_khz > clocks_value.max_dp_phyclk_in_khz)) { + state->dis_clk->funcs->apply_clock_voltage_request( + state->dis_clk, + DM_PP_CLOCK_TYPE_DISPLAYPHYCLK, + dp_phyclk_in_khz, + false, + true); + } + } + } + + 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->cur_link_settings = link_settings; + status = DC_OK; + } + else + status = DC_FAIL_DP_LINK_TRAINING; + + enable_stream_features(pipe_ctx); + + return status; +} + +static enum dc_status enable_link_dp_mst( + struct dc_state *state, + struct pipe_ctx *pipe_ctx) +{ + struct dc_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->cur_link_settings.lane_count != LANE_COUNT_UNKNOWN) + return DC_OK; + + /* set the sink to MST mode before enabling the link */ + dp_enable_mst_on_sink(link, true); + + return enable_link_dp(state, pipe_ctx); +} + +static bool get_ext_hdmi_settings(struct pipe_ctx *pipe_ctx, + enum engine_id eng_id, + struct ext_hdmi_settings *settings) +{ + bool result = false; + int i = 0; + struct integrated_info *integrated_info = + pipe_ctx->stream->ctx->dc_bios->integrated_info; + + if (integrated_info == NULL) + return false; + + /* + * Get retimer settings from sbios for passing SI eye test for DCE11 + * The setting values are varied based on board revision and port id + * Therefore the setting values of each ports is passed by sbios. + */ + + // Check if current bios contains ext Hdmi settings + if (integrated_info->gpu_cap_info & 0x20) { + switch (eng_id) { + case ENGINE_ID_DIGA: + settings->slv_addr = integrated_info->dp0_ext_hdmi_slv_addr; + settings->reg_num = integrated_info->dp0_ext_hdmi_6g_reg_num; + settings->reg_num_6g = integrated_info->dp0_ext_hdmi_6g_reg_num; + memmove(settings->reg_settings, + integrated_info->dp0_ext_hdmi_reg_settings, + sizeof(integrated_info->dp0_ext_hdmi_reg_settings)); + memmove(settings->reg_settings_6g, + integrated_info->dp0_ext_hdmi_6g_reg_settings, + sizeof(integrated_info->dp0_ext_hdmi_6g_reg_settings)); + result = true; + break; + case ENGINE_ID_DIGB: + settings->slv_addr = integrated_info->dp1_ext_hdmi_slv_addr; + settings->reg_num = integrated_info->dp1_ext_hdmi_6g_reg_num; + settings->reg_num_6g = integrated_info->dp1_ext_hdmi_6g_reg_num; + memmove(settings->reg_settings, + integrated_info->dp1_ext_hdmi_reg_settings, + sizeof(integrated_info->dp1_ext_hdmi_reg_settings)); + memmove(settings->reg_settings_6g, + integrated_info->dp1_ext_hdmi_6g_reg_settings, + sizeof(integrated_info->dp1_ext_hdmi_6g_reg_settings)); + result = true; + break; + case ENGINE_ID_DIGC: + settings->slv_addr = integrated_info->dp2_ext_hdmi_slv_addr; + settings->reg_num = integrated_info->dp2_ext_hdmi_6g_reg_num; + settings->reg_num_6g = integrated_info->dp2_ext_hdmi_6g_reg_num; + memmove(settings->reg_settings, + integrated_info->dp2_ext_hdmi_reg_settings, + sizeof(integrated_info->dp2_ext_hdmi_reg_settings)); + memmove(settings->reg_settings_6g, + integrated_info->dp2_ext_hdmi_6g_reg_settings, + sizeof(integrated_info->dp2_ext_hdmi_6g_reg_settings)); + result = true; + break; + case ENGINE_ID_DIGD: + settings->slv_addr = integrated_info->dp3_ext_hdmi_slv_addr; + settings->reg_num = integrated_info->dp3_ext_hdmi_6g_reg_num; + settings->reg_num_6g = integrated_info->dp3_ext_hdmi_6g_reg_num; + memmove(settings->reg_settings, + integrated_info->dp3_ext_hdmi_reg_settings, + sizeof(integrated_info->dp3_ext_hdmi_reg_settings)); + memmove(settings->reg_settings_6g, + integrated_info->dp3_ext_hdmi_6g_reg_settings, + sizeof(integrated_info->dp3_ext_hdmi_6g_reg_settings)); + result = true; + break; + default: + break; + } + + if (result == true) { + // Validate settings from bios integrated info table + if (settings->slv_addr == 0) + return false; + if (settings->reg_num > 9) + return false; + if (settings->reg_num_6g > 3) + return false; + + for (i = 0; i < settings->reg_num; i++) { + if (settings->reg_settings[i].i2c_reg_index > 0x20) + return false; + } + + for (i = 0; i < settings->reg_num_6g; i++) { + if (settings->reg_settings_6g[i].i2c_reg_index > 0x20) + return false; + } + } + } + + return result; +} + +static bool i2c_write(struct pipe_ctx *pipe_ctx, + uint8_t address, uint8_t *buffer, uint32_t length) +{ + struct i2c_command cmd = {0}; + struct i2c_payload payload = {0}; + + memset(&payload, 0, sizeof(payload)); + memset(&cmd, 0, sizeof(cmd)); + + cmd.number_of_payloads = 1; + cmd.engine = I2C_COMMAND_ENGINE_DEFAULT; + cmd.speed = pipe_ctx->stream->ctx->dc->caps.i2c_speed_in_khz; + + payload.address = address; + payload.data = buffer; + payload.length = length; + payload.write = true; + cmd.payloads = &payload; + + if (dc_submit_i2c(pipe_ctx->stream->ctx->dc, + pipe_ctx->stream->sink->link->link_index, &cmd)) + return true; + + return false; +} + +static void write_i2c_retimer_setting( + struct pipe_ctx *pipe_ctx, + bool is_vga_mode, + bool is_over_340mhz, + struct ext_hdmi_settings *settings) +{ + uint8_t slave_address = (settings->slv_addr >> 1); + uint8_t buffer[2]; + const uint8_t apply_rx_tx_change = 0x4; + uint8_t offset = 0xA; + uint8_t value = 0; + int i = 0; + bool i2c_success = false; + + memset(&buffer, 0, sizeof(buffer)); + + /* Start Ext-Hdmi programming*/ + + for (i = 0; i < settings->reg_num; i++) { + /* Apply 3G settings */ + if (settings->reg_settings[i].i2c_reg_index <= 0x20) { + + buffer[0] = settings->reg_settings[i].i2c_reg_index; + buffer[1] = settings->reg_settings[i].i2c_reg_val; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + /* Based on DP159 specs, APPLY_RX_TX_CHANGE bit in 0x0A + * needs to be set to 1 on every 0xA-0xC write. + */ + if (settings->reg_settings[i].i2c_reg_index == 0xA || + settings->reg_settings[i].i2c_reg_index == 0xB || + settings->reg_settings[i].i2c_reg_index == 0xC) { + + /* Query current value from offset 0xA */ + if (settings->reg_settings[i].i2c_reg_index == 0xA) + value = settings->reg_settings[i].i2c_reg_val; + else { + i2c_success = + dal_ddc_service_query_ddc_data( + pipe_ctx->stream->sink->link->ddc, + slave_address, &offset, 1, &value, 1); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + } + + buffer[0] = offset; + /* Set APPLY_RX_TX_CHANGE bit to 1 */ + buffer[1] = value | apply_rx_tx_change; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + } + } + } + + /* Apply 3G settings */ + if (is_over_340mhz) { + for (i = 0; i < settings->reg_num_6g; i++) { + /* Apply 3G settings */ + if (settings->reg_settings[i].i2c_reg_index <= 0x20) { + + buffer[0] = settings->reg_settings_6g[i].i2c_reg_index; + buffer[1] = settings->reg_settings_6g[i].i2c_reg_val; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + /* Based on DP159 specs, APPLY_RX_TX_CHANGE bit in 0x0A + * needs to be set to 1 on every 0xA-0xC write. + */ + if (settings->reg_settings_6g[i].i2c_reg_index == 0xA || + settings->reg_settings_6g[i].i2c_reg_index == 0xB || + settings->reg_settings_6g[i].i2c_reg_index == 0xC) { + + /* Query current value from offset 0xA */ + if (settings->reg_settings_6g[i].i2c_reg_index == 0xA) + value = settings->reg_settings_6g[i].i2c_reg_val; + else { + i2c_success = + dal_ddc_service_query_ddc_data( + pipe_ctx->stream->sink->link->ddc, + slave_address, &offset, 1, &value, 1); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + } + + buffer[0] = offset; + /* Set APPLY_RX_TX_CHANGE bit to 1 */ + buffer[1] = value | apply_rx_tx_change; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + } + } + } + } + + if (is_vga_mode) { + /* Program additional settings if using 640x480 resolution */ + + /* Write offset 0xFF to 0x01 */ + buffer[0] = 0xff; + buffer[1] = 0x01; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + /* Write offset 0x00 to 0x23 */ + buffer[0] = 0x00; + buffer[1] = 0x23; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + /* Write offset 0xff to 0x00 */ + buffer[0] = 0xff; + buffer[1] = 0x00; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + } +} + +static void write_i2c_default_retimer_setting( + struct pipe_ctx *pipe_ctx, + bool is_vga_mode, + bool is_over_340mhz) +{ + uint8_t slave_address = (0xBA >> 1); + uint8_t buffer[2]; + bool i2c_success = false; + + memset(&buffer, 0, sizeof(buffer)); + + /* Program Slave Address for tuning single integrity */ + /* Write offset 0x0A to 0x13 */ + buffer[0] = 0x0A; + buffer[1] = 0x13; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + /* Write offset 0x0A to 0x17 */ + buffer[0] = 0x0A; + buffer[1] = 0x17; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + /* Write offset 0x0B to 0xDA or 0xD8 */ + buffer[0] = 0x0B; + buffer[1] = is_over_340mhz ? 0xDA : 0xD8; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + /* Write offset 0x0A to 0x17 */ + buffer[0] = 0x0A; + buffer[1] = 0x17; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + /* Write offset 0x0C to 0x1D or 0x91 */ + buffer[0] = 0x0C; + buffer[1] = is_over_340mhz ? 0x1D : 0x91; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + /* Write offset 0x0A to 0x17 */ + buffer[0] = 0x0A; + buffer[1] = 0x17; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + + if (is_vga_mode) { + /* Program additional settings if using 640x480 resolution */ + + /* Write offset 0xFF to 0x01 */ + buffer[0] = 0xff; + buffer[1] = 0x01; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + /* Write offset 0x00 to 0x23 */ + buffer[0] = 0x00; + buffer[1] = 0x23; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + + /* Write offset 0xff to 0x00 */ + buffer[0] = 0xff; + buffer[1] = 0x00; + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); + } +} + +static void write_i2c_redriver_setting( + struct pipe_ctx *pipe_ctx, + bool is_over_340mhz) +{ + uint8_t slave_address = (0xF0 >> 1); + uint8_t buffer[16]; + bool i2c_success = false; + + memset(&buffer, 0, sizeof(buffer)); + + // Program Slave Address for tuning single integrity + buffer[3] = 0x4E; + buffer[4] = 0x4E; + buffer[5] = 0x4E; + buffer[6] = is_over_340mhz ? 0x4E : 0x4A; + + i2c_success = i2c_write(pipe_ctx, slave_address, + buffer, sizeof(buffer)); + + if (!i2c_success) + /* Write failure */ + ASSERT(i2c_success); +} + +static void enable_link_hdmi(struct pipe_ctx *pipe_ctx) +{ + struct dc_stream_state *stream = pipe_ctx->stream; + struct dc_link *link = stream->sink->link; + enum dc_color_depth display_color_depth; + enum engine_id eng_id; + struct ext_hdmi_settings settings = {0}; + bool is_over_340mhz = false; + bool is_vga_mode = (stream->timing.h_addressable == 640) + && (stream->timing.v_addressable == 480); + + if (stream->phy_pix_clk > 340000) + is_over_340mhz = true; + + if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) { + unsigned short masked_chip_caps = pipe_ctx->stream->sink->link->chip_caps & + EXT_DISPLAY_PATH_CAPS__EXT_CHIP_MASK; + if (masked_chip_caps == EXT_DISPLAY_PATH_CAPS__HDMI20_TISN65DP159RSBT) { + /* DP159, Retimer settings */ + eng_id = pipe_ctx->stream_res.stream_enc->id; + + if (get_ext_hdmi_settings(pipe_ctx, eng_id, &settings)) { + write_i2c_retimer_setting(pipe_ctx, + is_vga_mode, is_over_340mhz, &settings); + } else { + write_i2c_default_retimer_setting(pipe_ctx, + is_vga_mode, is_over_340mhz); + } + } else if (masked_chip_caps == EXT_DISPLAY_PATH_CAPS__HDMI20_PI3EQX1204) { + /* PI3EQX1204, Redriver settings */ + write_i2c_redriver_setting(pipe_ctx, is_over_340mhz); + } + } + + if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) + dal_ddc_service_write_scdc_data( + stream->sink->link->ddc, + stream->phy_pix_clk, + stream->timing.flags.LTE_340MCSC_SCRAMBLE); + + memset(&stream->sink->link->cur_link_settings, 0, + sizeof(struct dc_link_settings)); + + display_color_depth = stream->timing.display_color_depth; + if (stream->timing.pixel_encoding == PIXEL_ENCODING_YCBCR422) + display_color_depth = COLOR_DEPTH_888; + + link->link_enc->funcs->enable_tmds_output( + link->link_enc, + pipe_ctx->clock_source->id, + 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 dc_state *state, + 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(state, pipe_ctx); + break; + case SIGNAL_TYPE_DISPLAY_PORT_MST: + status = enable_link_dp_mst(state, 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->stream_res.audio && status == DC_OK) { + /* notify audio driver for audio modes of monitor */ + pipe_ctx->stream_res.audio->funcs->az_enable(pipe_ctx->stream_res.audio); + + /* un-mute audio */ + /* TODO: audio should be per stream rather than per link */ + pipe_ctx->stream_res.stream_enc->funcs->audio_mute_control( + pipe_ctx->stream_res.stream_enc, false); + } + + return status; +} + +static void disable_link(struct dc_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, link); +} + +bool dp_active_dongle_validate_timing( + const struct dc_crtc_timing *timing, + const struct dc_dongle_caps *dongle_caps) +{ + unsigned int required_pix_clk = timing->pix_clk_khz; + + if (dongle_caps->dongle_type != DISPLAY_DONGLE_DP_HDMI_CONVERTER || + dongle_caps->extendedCapValid == false) + return true; + + /* Check Pixel Encoding */ + switch (timing->pixel_encoding) { + case PIXEL_ENCODING_RGB: + case PIXEL_ENCODING_YCBCR444: + break; + case PIXEL_ENCODING_YCBCR422: + if (!dongle_caps->is_dp_hdmi_ycbcr422_pass_through) + return false; + break; + case PIXEL_ENCODING_YCBCR420: + if (!dongle_caps->is_dp_hdmi_ycbcr420_pass_through) + return false; + break; + default: + /* Invalid Pixel Encoding*/ + return false; + } + + + /* Check Color Depth and Pixel Clock */ + if (timing->pixel_encoding == PIXEL_ENCODING_YCBCR420) + required_pix_clk /= 2; + + switch (timing->display_color_depth) { + case COLOR_DEPTH_666: + case COLOR_DEPTH_888: + /*888 and 666 should always be supported*/ + break; + case COLOR_DEPTH_101010: + if (dongle_caps->dp_hdmi_max_bpc < 10) + return false; + required_pix_clk = required_pix_clk * 10 / 8; + break; + case COLOR_DEPTH_121212: + if (dongle_caps->dp_hdmi_max_bpc < 12) + return false; + required_pix_clk = required_pix_clk * 12 / 8; + break; + + case COLOR_DEPTH_141414: + case COLOR_DEPTH_161616: + default: + /* These color depths are currently not supported */ + return false; + } + + if (required_pix_clk > dongle_caps->dp_hdmi_max_pixel_clk) + return false; + + return true; +} + +enum dc_status dc_link_validate_mode_timing( + const struct dc_stream_state *stream, + struct dc_link *link, + const struct dc_crtc_timing *timing) +{ + uint32_t max_pix_clk = stream->sink->dongle_max_pix_clk; + struct dc_dongle_caps *dongle_caps = &link->link_status.dpcd_caps->dongle_caps; + + /* 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->remote_sinks[0]) + return DC_OK; + + /* Passive Dongle */ + if (0 != max_pix_clk && timing->pix_clk_khz > max_pix_clk) + return DC_EXCEED_DONGLE_CAP; + + /* Active Dongle*/ + if (!dp_active_dongle_validate_timing(timing, dongle_caps)) + return DC_EXCEED_DONGLE_CAP; + + 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 *link, uint32_t level, + uint32_t frame_ramp, const struct dc_stream_state *stream) +{ + struct dc *core_dc = link->ctx->dc; + struct abm *abm = core_dc->res_pool->abm; + unsigned int controller_id = 0; + int i; + + if ((abm == NULL) || (abm->funcs->set_backlight_level == NULL)) + return false; + + dm_logger_write(link->ctx->logger, LOG_BACKLIGHT, + "New Backlight level: %d (0x%X)\n", level, level); + + if (dc_is_embedded_signal(link->connector_signal)) { + if (stream != NULL) { + for (i = 0; i < MAX_PIPES; i++) { + if (core_dc->current_state->res_ctx. + pipe_ctx[i].stream + == stream) + /* DMCU -1 for all controller id values, + * therefore +1 here + */ + controller_id = + core_dc->current_state-> + res_ctx.pipe_ctx[i].stream_res.tg->inst + + 1; + } + } + abm->funcs->set_backlight_level( + abm, + level, + frame_ramp, + controller_id); + } + + return true; +} + +bool dc_link_set_psr_enable(const struct dc_link *link, bool enable, bool wait) +{ + struct dc *core_dc = link->ctx->dc; + struct dmcu *dmcu = core_dc->res_pool->dmcu; + + if (dmcu != NULL && link->psr_enabled) + dmcu->funcs->set_psr_enable(dmcu, enable, wait); + + return true; +} + +bool dc_link_get_psr_state(const struct dc_link *link, uint32_t *psr_state) +{ + struct dc *core_dc = link->ctx->dc; + struct dmcu *dmcu = core_dc->res_pool->dmcu; + + if (dmcu != NULL && link->psr_enabled) + dmcu->funcs->get_psr_state(dmcu, psr_state); + + return true; +} + +bool dc_link_setup_psr(struct dc_link *link, + const struct dc_stream_state *stream, struct psr_config *psr_config, + struct psr_context *psr_context) +{ + struct dc *core_dc = link->ctx->dc; + struct dmcu *dmcu = core_dc->res_pool->dmcu; + int i; + + psr_context->controllerId = CONTROLLER_ID_UNDEFINED; + + if (link != NULL && + dmcu != NULL) { + /* 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 = + psr_config->psr_frame_capture_indication_req; + + /* Check for PSR v2*/ + if (psr_config->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; + } + + dm_helpers_dp_write_dpcd( + link->ctx, + link, + 368, + &psr_configuration.raw, + sizeof(psr_configuration.raw)); + + psr_context->channel = link->ddc->ddc_pin->hw_info.ddc_channel; + 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_state->res_ctx.pipe_ctx[i].stream + == stream) { + /* dmcu -1 for all controller id values, + * therefore +1 here + */ + psr_context->controllerId = + core_dc->current_state->res_ctx. + pipe_ctx[i].stream_res.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 = true; + psr_context->psrExitLinkTrainingRequired = + psr_config->psr_exit_link_training_required; + psr_context->sdpTransmitLineNumDeadline = + psr_config->psr_sdp_transmit_line_num_deadline; + psr_context->psrFrameCaptureIndicationReq = + psr_config->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; + +#if defined(CONFIG_DRM_AMD_DC_DCN1_0) + /*skip power down the single pipe since it blocks the cstate*/ + if (ASIC_REV_IS_RAVEN(link->ctx->asic_id.hw_internal_rev)) + psr_context->psr_level.bits.SKIP_CRTC_DISABLE = true; +#endif + + /* 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; + + /* Complete PSR entry before aborting to prevent intermittent + * freezes on certain eDPs + */ + psr_context->psr_level.bits.DISABLE_PSR_ENTRY_ABORT = 1; + + /* Controls additional delay after remote frame capture before + * continuing power down, default = 0 + */ + psr_context->frame_delay = 0; + + link->psr_enabled = true; + dmcu->funcs->setup_psr(dmcu, link, psr_context); + return true; + } else + return false; + +} + +const struct dc_link_status *dc_link_get_status(const struct dc_link *link) +{ + return &link->link_status; +} + +void core_link_resume(struct dc_link *link) +{ + if (link->connector_signal != SIGNAL_TYPE_VIRTUAL) + program_hpd_filter(link); +} + +static struct fixed31_32 get_pbn_per_slot(struct dc_stream_state *stream) +{ + struct dc_link_settings *link_settings = + &stream->sink->link->cur_link_settings; + uint32_t link_rate_in_mbps = + link_settings->link_rate * LINK_RATE_REF_FREQ_IN_MHZ; + struct fixed31_32 mbps = dal_fixed31_32_from_int( + link_rate_in_mbps * link_settings->lane_count); + + return dal_fixed31_32_div_int(mbps, 54); +} + +static int get_color_depth(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->stream_res.pix_clk_params.color_depth); + kbps = pipe_ctx->stream_res.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 dc_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 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 dc_stream_state *stream = pipe_ctx->stream; + struct dc_link *link = stream->sink->link; + struct link_encoder *link_encoder = link->link_enc; + struct stream_encoder *stream_encoder = pipe_ctx->stream_res.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, + &proposed_table, + true)) { + update_mst_stream_alloc_table( + link, pipe_ctx->stream_res.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); + + dm_helpers_dp_mst_send_payload_allocation( + stream->ctx, + stream, + 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 dc_stream_state *stream = pipe_ctx->stream; + struct dc_link *link = stream->sink->link; + struct link_encoder *link_encoder = link->link_enc; + struct stream_encoder *stream_encoder = pipe_ctx->stream_res.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->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, + &proposed_table, + false)) { + + update_mst_stream_alloc_table( + link, pipe_ctx->stream_res.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); + + dm_helpers_dp_mst_send_payload_allocation( + stream->ctx, + stream, + false); + } + + return DC_OK; +} + +void core_link_enable_stream( + struct dc_state *state, + struct pipe_ctx *pipe_ctx) +{ + struct dc *core_dc = pipe_ctx->stream->ctx->dc; + + enum dc_status status = enable_link(state, pipe_ctx); + + if (status != DC_OK) { + dm_logger_write(pipe_ctx->stream->ctx->logger, + LOG_WARNING, "enabling link %u failed: %d\n", + pipe_ctx->stream->sink->link->link_index, + status); + + /* Abort stream enable *unless* the failure was due to + * DP link training - some DP monitors will recover and + * show the stream anyway. But MST displays can't proceed + * without link training. + */ + if (status != DC_FAIL_DP_LINK_TRAINING || + pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) { + BREAK_TO_DEBUGGER(); + return; + } + } + + /* turn off otg test pattern if enable */ + pipe_ctx->stream_res.tg->funcs->set_test_pattern(pipe_ctx->stream_res.tg, + CONTROLLER_DP_TEST_PATTERN_VIDEOMODE, + COLOR_DEPTH_UNDEFINED); + + core_dc->hwss.enable_stream(pipe_ctx); + + if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) + allocate_mst_payload(pipe_ctx); + + if (dc_is_dp_signal(pipe_ctx->stream->signal)) + core_dc->hwss.unblank_stream(pipe_ctx, + &pipe_ctx->stream->sink->link->cur_link_settings); +} + +void core_link_disable_stream(struct pipe_ctx *pipe_ctx, int option) +{ + struct dc *core_dc = 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, option); + + disable_link(pipe_ctx->stream->sink->link, pipe_ctx->stream->signal); +} + +void core_link_set_avmute(struct pipe_ctx *pipe_ctx, bool enable) +{ + struct dc *core_dc = pipe_ctx->stream->ctx->dc; + + if (pipe_ctx->stream->signal != SIGNAL_TYPE_HDMI_TYPE_A) + return; + + core_dc->hwss.set_avmute(pipe_ctx, enable); +} + 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..d5294798b0a5 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_ddc.c @@ -0,0 +1,775 @@ +/* + * 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' */ +}; + +/* 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; +}; + +static struct i2c_payloads *dal_ddc_i2c_payloads_create(struct dc_context *ctx, uint32_t count) +{ + struct i2c_payloads *payloads; + + payloads = kzalloc(sizeof(struct i2c_payloads), GFP_KERNEL); + + if (!payloads) + return NULL; + + if (dal_vector_construct( + &payloads->payloads, ctx, count, sizeof(struct i2c_payload))) + return payloads; + + kfree(payloads); + return NULL; + +} + +static struct i2c_payload *dal_ddc_i2c_payloads_get(struct i2c_payloads *p) +{ + return (struct i2c_payload *)p->payloads.container; +} + +static uint32_t dal_ddc_i2c_payloads_get_count(struct i2c_payloads *p) +{ + return p->payloads.count; +} + +static void dal_ddc_i2c_payloads_destroy(struct i2c_payloads **p) +{ + if (!p || !*p) + return; + dal_vector_destruct(&(*p)->payloads); + kfree(*p); + *p = NULL; + +} + +static struct aux_payloads *dal_ddc_aux_payloads_create(struct dc_context *ctx, uint32_t count) +{ + struct aux_payloads *payloads; + + payloads = kzalloc(sizeof(struct aux_payloads), GFP_KERNEL); + + if (!payloads) + return NULL; + + if (dal_vector_construct( + &payloads->payloads, ctx, count, sizeof(struct aux_payload))) + return payloads; + + kfree(payloads); + return NULL; +} + +static struct aux_payload *dal_ddc_aux_payloads_get(struct aux_payloads *p) +{ + return (struct aux_payload *)p->payloads.container; +} + +static uint32_t dal_ddc_aux_payloads_get_count(struct aux_payloads *p) +{ + return p->payloads.count; +} + +static void dal_ddc_aux_payloads_destroy(struct aux_payloads **p) +{ + if (!p || !*p) + return; + + dal_vector_destruct(&(*p)->payloads); + kfree(*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 void 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; +} + +struct ddc_service *dal_ddc_service_create( + struct ddc_service_init_data *init_data) +{ + struct ddc_service *ddc_service; + + ddc_service = kzalloc(sizeof(struct ddc_service), GFP_KERNEL); + + if (!ddc_service) + return NULL; + + construct(ddc_service, init_data); + return ddc_service; +} + +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); + kfree(*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 dc_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 + +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, + &command); +} + +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 = + DP_EDP_CONFIGURATION_CAP - DP_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, + &command); + + dal_ddc_i2c_payloads_destroy(&payloads); + } + + return ret; +} + +enum ddc_result dal_ddc_service_read_dpcd_data( + struct ddc_service *ddc, + bool i2c, + enum i2c_mot_mode mot, + uint32_t address, + uint8_t *data, + uint32_t len) +{ + struct aux_payload read_payload = { + .i2c_over_aux = i2c, + .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, + .mot = mot + }; + + 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, + bool i2c, + enum i2c_mot_mode mot, + uint32_t address, + const uint8_t *data, + uint32_t len) +{ + struct aux_payload write_payload = { + .i2c_over_aux = i2c, + .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, + .mot = mot + }; + + 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..e6bf05d76a94 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_dp.c @@ -0,0 +1,2601 @@ +/* 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 "resource.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 bool decide_fallback_link_setting( + struct dc_link_settings initial_link_settings, + struct dc_link_settings *current_link_setting, + enum link_training_result training_result); +static struct dc_link_settings get_common_supported_link_settings ( + struct dc_link_settings link_setting_a, + struct dc_link_settings link_setting_b); + +static void wait_for_training_aux_rd_interval( + struct dc_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, + DP_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 dc_link *link, + union dpcd_training_pattern dpcd_pattern) +{ + core_link_write_dpcd( + link, + DP_TRAINING_PATTERN_SET, + &dpcd_pattern.raw, + 1); + + dm_logger_write(link->ctx->logger, LOG_HW_LINK_TRAINING, + "%s\n %x pattern = %x\n", + __func__, + DP_TRAINING_PATTERN_SET, + dpcd_pattern.v1_4.TRAINING_PATTERN_SET); +} + +static void dpcd_set_link_settings( + struct dc_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, DP_LINK_BW_SET, + link_set_buffer, 2); + core_link_write_dpcd(link, DP_DOWNSPREAD_CTRL, + &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__, + DP_LINK_BW_SET, + lt_settings->link_settings.link_rate, + DP_LANE_COUNT_SET, + lt_settings->link_settings.lane_count, + DP_DOWNSPREAD_CTRL, + lt_settings->link_settings.link_spread); + +} + +static enum dpcd_training_patterns + hw_training_pattern_to_dpcd_training_pattern( + struct dc_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 dc_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 = + DP_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[DP_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__, + DP_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[DP_TRAINING_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__, + DP_TRAINING_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, + DP_TRAINING_PATTERN_SET, + &dpcd_pattern.raw, + sizeof(dpcd_pattern.raw) ); + + core_link_write_dpcd( + link, + DP_TRAINING_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->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 dc_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, + DP_LANE0_1_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__, + DP_LANE0_1_STATUS, dpcd_buf[0], + DP_LANE2_3_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__, + DP_ADJUST_REQUEST_LANE0_1, + dpcd_buf[4], + DP_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 dc_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, + DP_TRAINING_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__, + DP_TRAINING_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->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) +{ + /* program ASIC PHY settings*/ + dp_set_hw_lane_settings(link, lt_settings); + + /* Notify DP sink the PHY settings from source */ + dpcd_set_lane_settings(link, lt_settings); +} + +static bool perform_post_lt_adj_req_sequence( + struct dc_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, + 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 dc_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 enum link_training_result perform_channel_equalization_sequence( + struct dc_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 LINK_TRAINING_EQ_FAIL_CR; + + /* 6. check CHEQ done*/ + if (is_ch_eq_done(lane_count, + dpcd_lane_status, + &dpcd_lane_status_updated)) + return LINK_TRAINING_SUCCESS; + + /* 7. update VS/PE/PC2 in lt_settings*/ + update_drive_settings(lt_settings, req_settings); + } + + return LINK_TRAINING_EQ_FAIL_EQ; + +} + +static bool perform_clock_recovery_sequence( + struct dc_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 dc_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, + DP_LANE_COUNT_SET, + &lane_count_set.raw, + sizeof(lane_count_set)); + + return status; +} + +enum link_training_result dc_link_dp_perform_link_training( + struct dc_link *link, + const struct dc_link_settings *link_setting, + bool skip_video_pattern) +{ + enum link_training_result status = LINK_TRAINING_SUCCESS; + + char *link_rate = "Unknown"; + struct link_training_settings lt_settings; + + memset(<_settings, '\0', sizeof(lt_settings)); + + lt_settings.link_settings.link_rate = link_setting->link_rate; + lt_settings.link_settings.lane_count = link_setting->lane_count; + + /*@todo[vdevulap] move SS to LS, should not be handled by displaypath*/ + + /* TODO hard coded to SS for now + * lt_settings.link_settings.link_spread = + * dal_display_path_is_ss_supported( + * path_mode->display_path) ? + * LINK_SPREAD_05_DOWNSPREAD_30KHZ : + * LINK_SPREAD_DISABLED; + */ + lt_settings.link_settings.link_spread = LINK_SPREAD_05_DOWNSPREAD_30KHZ; + + /* 1. set link rate, lane count and spread*/ + dpcd_set_link_settings(link, <_settings); + + /* 2. perform link training (set link training done + * to false is done as well)*/ + if (!perform_clock_recovery_sequence(link, <_settings)) { + status = LINK_TRAINING_CR_FAIL; + } else { + status = perform_channel_equalization_sequence(link, + <_settings); + } + + if ((status == LINK_TRAINING_SUCCESS) || !skip_video_pattern) { + if (!perform_link_training_int(link, + <_settings, + status == LINK_TRAINING_SUCCESS)) { + /* the next link training setting in this case + * would be the same as CR failure case. + */ + status = LINK_TRAINING_CR_FAIL; + } + } + + /* 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(link, "%sx%d %s VS=%d, PE=%d", + link_rate, + lt_settings.link_settings.lane_count, + (status == LINK_TRAINING_SUCCESS) ? "pass" : + ((status == LINK_TRAINING_CR_FAIL) ? "CR failed" : + "EQ failed"), + lt_settings.lane_settings[0].VOLTAGE_SWING, + lt_settings.lane_settings[0].PRE_EMPHASIS); + + return status; +} + + +bool perform_link_training_with_retries( + struct dc_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, + link_setting, + skip_video_pattern) == LINK_TRAINING_SUCCESS) + return true; + + msleep(delay_between_attempts); + delay_between_attempts += LINK_TRAINING_RETRY_DELAY; + } + + return false; +} + +static struct dc_link_settings get_max_link_cap(struct dc_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->reported_link_cap.lane_count < max_link_cap.lane_count) + max_link_cap.lane_count = + link->reported_link_cap.lane_count; + if (link->reported_link_cap.link_rate < max_link_cap.link_rate) + max_link_cap.link_rate = + link->reported_link_cap.link_rate; + if (link->reported_link_cap.link_spread < + max_link_cap.link_spread) + max_link_cap.link_spread = + link->reported_link_cap.link_spread; + return max_link_cap; +} + +bool dp_hbr_verify_link_cap( + struct dc_link *link, + struct dc_link_settings *known_limit_link_setting) +{ + struct dc_link_settings max_link_cap = {0}; + struct dc_link_settings cur_link_setting = {0}; + struct dc_link_settings *cur = &cur_link_setting; + struct dc_link_settings initial_link_settings = {0}; + bool success; + bool skip_link_training; + bool skip_video_pattern; + struct clock_source *dp_cs; + enum clock_source_id dp_cs_id = CLOCK_SOURCE_ID_EXTERNAL; + enum link_training_result status; + + 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->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); + } + + /* link training starts with the maximum common settings + * supported by both sink and ASIC. + */ + initial_link_settings = get_common_supported_link_settings( + *known_limit_link_setting, + max_link_cap); + cur_link_setting = initial_link_settings; + do { + skip_video_pattern = true; + + if (cur->link_rate == LINK_RATE_LOW) + skip_video_pattern = false; + + dp_enable_link_phy( + link, + link->connector_signal, + dp_cs_id, + cur); + + if (skip_link_training) + success = true; + else { + status = dc_link_dp_perform_link_training( + link, + cur, + skip_video_pattern); + if (status == LINK_TRAINING_SUCCESS) + success = true; + } + + if (success) + link->verified_link_cap = *cur; + + /* always disable the link before trying another + * setting or before returning we'll enable it later + * based on the actual mode we're driving + */ + dp_disable_link_phy(link, link->connector_signal); + } while (!success && decide_fallback_link_setting( + initial_link_settings, cur, status)); + + /* Link Training failed for all Link Settings + * (Lane Count is still unknown) + */ + if (!success) { + /* If all LT fails for all settings, + * set verified = failed safe (1 lane low) + */ + link->verified_link_cap.lane_count = LANE_COUNT_ONE; + link->verified_link_cap.link_rate = LINK_RATE_LOW; + + link->verified_link_cap.link_spread = + LINK_SPREAD_DISABLED; + } + + + return success; +} + +static struct dc_link_settings get_common_supported_link_settings ( + struct dc_link_settings link_setting_a, + struct dc_link_settings link_setting_b) +{ + struct dc_link_settings link_settings = {0}; + + link_settings.lane_count = + (link_setting_a.lane_count <= + link_setting_b.lane_count) ? + link_setting_a.lane_count : + link_setting_b.lane_count; + link_settings.link_rate = + (link_setting_a.link_rate <= + link_setting_b.link_rate) ? + link_setting_a.link_rate : + link_setting_b.link_rate; + link_settings.link_spread = LINK_SPREAD_DISABLED; + + /* in DP compliance test, DPR-120 may have + * a random value in its MAX_LINK_BW dpcd field. + * We map it to the maximum supported link rate that + * is smaller than MAX_LINK_BW in this case. + */ + if (link_settings.link_rate > LINK_RATE_HIGH3) { + link_settings.link_rate = LINK_RATE_HIGH3; + } else if (link_settings.link_rate < LINK_RATE_HIGH3 + && link_settings.link_rate > LINK_RATE_HIGH2) { + link_settings.link_rate = LINK_RATE_HIGH2; + } else if (link_settings.link_rate < LINK_RATE_HIGH2 + && link_settings.link_rate > LINK_RATE_HIGH) { + link_settings.link_rate = LINK_RATE_HIGH; + } else if (link_settings.link_rate < LINK_RATE_HIGH + && link_settings.link_rate > LINK_RATE_LOW) { + link_settings.link_rate = LINK_RATE_LOW; + } else if (link_settings.link_rate < LINK_RATE_LOW) { + link_settings.link_rate = LINK_RATE_UNKNOWN; + } + + return link_settings; +} + +static inline bool reached_minimum_lane_count(enum dc_lane_count lane_count) +{ + return lane_count <= LANE_COUNT_ONE; +} + +static inline bool reached_minimum_link_rate(enum dc_link_rate link_rate) +{ + return link_rate <= LINK_RATE_LOW; +} + +static enum dc_lane_count reduce_lane_count(enum dc_lane_count lane_count) +{ + switch (lane_count) { + case LANE_COUNT_FOUR: + return LANE_COUNT_TWO; + case LANE_COUNT_TWO: + return LANE_COUNT_ONE; + case LANE_COUNT_ONE: + return LANE_COUNT_UNKNOWN; + default: + return LANE_COUNT_UNKNOWN; + } +} + +static enum dc_link_rate reduce_link_rate(enum dc_link_rate link_rate) +{ + switch (link_rate) { + case LINK_RATE_HIGH3: + return LINK_RATE_HIGH2; + case LINK_RATE_HIGH2: + return LINK_RATE_HIGH; + case LINK_RATE_HIGH: + return LINK_RATE_LOW; + case LINK_RATE_LOW: + return LINK_RATE_UNKNOWN; + default: + return LINK_RATE_UNKNOWN; + } +} + +static enum dc_lane_count increase_lane_count(enum dc_lane_count lane_count) +{ + switch (lane_count) { + case LANE_COUNT_ONE: + return LANE_COUNT_TWO; + case LANE_COUNT_TWO: + return LANE_COUNT_FOUR; + default: + return LANE_COUNT_UNKNOWN; + } +} + +static enum dc_link_rate increase_link_rate(enum dc_link_rate link_rate) +{ + switch (link_rate) { + case LINK_RATE_LOW: + return LINK_RATE_HIGH; + case LINK_RATE_HIGH: + return LINK_RATE_HIGH2; + case LINK_RATE_HIGH2: + return LINK_RATE_HIGH3; + default: + return LINK_RATE_UNKNOWN; + } +} + +/* + * function: set link rate and lane count fallback based + * on current link setting and last link training result + * return value: + * true - link setting could be set + * false - has reached minimum setting + * and no further fallback could be done + */ +static bool decide_fallback_link_setting( + struct dc_link_settings initial_link_settings, + struct dc_link_settings *current_link_setting, + enum link_training_result training_result) +{ + if (!current_link_setting) + return false; + + switch (training_result) { + case LINK_TRAINING_CR_FAIL: + { + if (!reached_minimum_link_rate + (current_link_setting->link_rate)) { + current_link_setting->link_rate = + reduce_link_rate( + current_link_setting->link_rate); + } else if (!reached_minimum_lane_count + (current_link_setting->lane_count)) { + current_link_setting->link_rate = + initial_link_settings.link_rate; + current_link_setting->lane_count = + reduce_lane_count( + current_link_setting->lane_count); + } else { + return false; + } + break; + } + case LINK_TRAINING_EQ_FAIL_EQ: + { + if (!reached_minimum_lane_count + (current_link_setting->lane_count)) { + current_link_setting->lane_count = + reduce_lane_count( + current_link_setting->lane_count); + } else if (!reached_minimum_link_rate + (current_link_setting->link_rate)) { + current_link_setting->link_rate = + reduce_link_rate( + current_link_setting->link_rate); + } else { + return false; + } + break; + } + case LINK_TRAINING_EQ_FAIL_CR: + { + if (!reached_minimum_link_rate + (current_link_setting->link_rate)) { + current_link_setting->link_rate = + reduce_link_rate( + current_link_setting->link_rate); + } else { + return false; + } + break; + } + default: + return false; + } + return true; +} + +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 dc_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->verified_link_cap; + + /* TODO: DYNAMIC_VALIDATION needs to be implemented */ + /*if (flags.DYNAMIC_VALIDATION == 1 && + link->verified_link_cap.lane_count != LANE_COUNT_UNKNOWN) + link_setting = &link->verified_link_cap; + */ + + req_bw = bandwidth_in_kbps_from_timing(timing); + max_bw = bandwidth_in_kbps_from_link_settings(link_setting); + + if (req_bw <= max_bw) { + /* remember the biggest mode here, during + * initial link training (to get + * verified_link_cap), LS sends event about + * cannot train at reported cap to upper + * layer and upper layer will re-enumerate modes. + * this is not necessary if the lower + * verified_link_cap is enough to drive + * all the modes */ + + /* TODO: DYNAMIC_VALIDATION needs to be implemented */ + /* if (flags.DYNAMIC_VALIDATION == 1) + dpsst->max_req_bw_for_verified_linkcap = dal_max( + dpsst->max_req_bw_for_verified_linkcap, req_bw); */ + return true; + } else + return false; +} + +void decide_link_settings(struct dc_stream_state *stream, + struct dc_link_settings *link_setting) +{ + + struct dc_link_settings initial_link_setting = { + LANE_COUNT_ONE, LINK_RATE_LOW, LINK_SPREAD_DISABLED}; + struct dc_link_settings current_link_setting = + initial_link_setting; + struct dc_link *link; + uint32_t req_bw; + uint32_t link_bw; + + req_bw = bandwidth_in_kbps_from_timing(&stream->timing); + + link = stream->sink->link; + + /* if preferred is specified through AMDDP, use it, if it's enough + * to drive the mode + */ + if (link->preferred_link_setting.lane_count != + LANE_COUNT_UNKNOWN && + link->preferred_link_setting.link_rate != + LINK_RATE_UNKNOWN) { + *link_setting = link->preferred_link_setting; + return; + } + + /* MST doesn't perform link training for now + * TODO: add MST specific link training routine + */ + if (is_mst_supported(link)) { + *link_setting = link->verified_link_cap; + return; + } + + /* search for the minimum link setting that: + * 1. is supported according to the link training result + * 2. could support the b/w requested by the timing + */ + while (current_link_setting.link_rate <= + link->verified_link_cap.link_rate) { + link_bw = bandwidth_in_kbps_from_link_settings( + ¤t_link_setting); + if (req_bw <= link_bw) { + *link_setting = current_link_setting; + return; + } + + if (current_link_setting.lane_count < + link->verified_link_cap.lane_count) { + current_link_setting.lane_count = + increase_lane_count( + current_link_setting.lane_count); + } else { + current_link_setting.link_rate = + increase_link_rate( + current_link_setting.link_rate); + current_link_setting.lane_count = + initial_link_setting.lane_count; + } + } + + BREAK_TO_DEBUGGER(); + ASSERT(link->verified_link_cap.lane_count != LANE_COUNT_UNKNOWN); + + *link_setting = link->verified_link_cap; +} + +/*************************Short Pulse IRQ***************************/ + +static bool hpd_rx_irq_check_link_loss_status( + struct dc_link *link, + union hpd_irq_data *hpd_irq_dpcd_data) +{ + uint8_t irq_reg_rx_power_state = 0; + enum dc_status dpcd_result = DC_ERROR_UNEXPECTED; + union lane_status lane_status; + uint32_t lane; + bool sink_status_changed; + bool return_code; + + sink_status_changed = false; + return_code = false; + + if (link->cur_link_settings.lane_count == 0) + return return_code; + + /*1. Check that Link Status changed, before re-training.*/ + + /*parse lane status*/ + for (lane = 0; lane < link->cur_link_settings.lane_count; lane++) { + /* check status of lanes 0,1 + * changed DpcdAddress_Lane01Status (0x202) + */ + lane_status.raw = get_nibble_at_index( + &hpd_irq_dpcd_data->bytes.lane01_status.raw, + lane); + + if (!lane_status.bits.CHANNEL_EQ_DONE_0 || + !lane_status.bits.CR_DONE_0 || + !lane_status.bits.SYMBOL_LOCKED_0) { + /* if one of the channel equalization, clock + * recovery or symbol lock is dropped + * consider it as (link has been + * dropped) dp sink status has changed + */ + sink_status_changed = true; + break; + } + } + + /* Check interlane align.*/ + if (sink_status_changed || + !hpd_irq_dpcd_data->bytes.lane_status_updated.bits.INTERLANE_ALIGN_DONE) { + + dm_logger_write(link->ctx->logger, LOG_HW_HPD_IRQ, + "%s: Link Status changed.\n", __func__); + + return_code = true; + + /*2. Check that we can handle interrupt: Not in FS DOS, + * Not in "Display Timeout" state, Link is trained. + */ + dpcd_result = core_link_read_dpcd(link, + DP_SET_POWER, + &irq_reg_rx_power_state, + sizeof(irq_reg_rx_power_state)); + + if (dpcd_result != DC_OK) { + dm_logger_write(link->ctx->logger, LOG_HW_HPD_IRQ, + "%s: DPCD read failed to obtain power state.\n", + __func__); + } else { + if (irq_reg_rx_power_state != DP_SET_POWER_D0) + return_code = false; + } + } + + return return_code; +} + +static enum dc_status read_hpd_rx_irq_data( + struct dc_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, + DP_SINK_COUNT, + irq_data->raw, + sizeof(union hpd_irq_data)); +} + +static bool allow_hpd_rx_irq(const struct dc_link *link) +{ + /* + * Don't handle RX IRQ unless one of following is met: + * 1) The link is established (cur_link_settings != unknown) + * 2) We kicked off MST detection + * 3) We know we're dealing with an active dongle + */ + + if ((link->cur_link_settings.lane_count != LANE_COUNT_UNKNOWN) || + (link->type == dc_connection_mst_branch) || + is_dp_active_dongle(link)) + return true; + + return false; +} + +static bool handle_hpd_irq_psr_sink(const struct dc_link *link) +{ + union dpcd_psr_configuration psr_configuration; + + if (!link->psr_enabled) + return false; + + dm_helpers_dp_read_dpcd( + link->ctx, + link, + 368,/*DpcdAddress_PSR_Enable_Cfg*/ + &psr_configuration.raw, + sizeof(psr_configuration.raw)); + + + if (psr_configuration.bits.ENABLE) { + unsigned char dpcdbuf[3] = {0}; + union psr_error_status psr_error_status; + union psr_sink_psr_status psr_sink_psr_status; + + dm_helpers_dp_read_dpcd( + link->ctx, + link, + 0x2006, /*DpcdAddress_PSR_Error_Status*/ + (unsigned char *) dpcdbuf, + sizeof(dpcdbuf)); + + /*DPCD 2006h ERROR STATUS*/ + psr_error_status.raw = dpcdbuf[0]; + /*DPCD 2008h SINK PANEL SELF REFRESH STATUS*/ + psr_sink_psr_status.raw = dpcdbuf[2]; + + if (psr_error_status.bits.LINK_CRC_ERROR || + psr_error_status.bits.RFB_STORAGE_ERROR) { + /* Acknowledge and clear error bits */ + dm_helpers_dp_write_dpcd( + link->ctx, + link, + 8198,/*DpcdAddress_PSR_Error_Status*/ + &psr_error_status.raw, + sizeof(psr_error_status.raw)); + + /* PSR error, disable and re-enable PSR */ + dc_link_set_psr_enable(link, false, true); + dc_link_set_psr_enable(link, true, 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 dc_link *link) +{ + struct dc_link_settings link_settings = {0}; + + core_link_read_dpcd( + link, + DP_TEST_LANE_COUNT, + (unsigned char *)(&link_settings.lane_count), + 1); + core_link_read_dpcd( + link, + DP_TEST_LINK_RATE, + (unsigned char *)(&link_settings.link_rate), + 1); + + /* Set preferred link settings */ + link->verified_link_cap.lane_count = link_settings.lane_count; + link->verified_link_cap.link_rate = link_settings.link_rate; + + dp_retrain_link_dp_test(link, &link_settings, false); +} + +/* TODO hbr2 compliance eye output is unstable + * (toggling on and off) with debugger break + * This caueses intermittent PHY automation failure + * Need to look into the root cause */ +static uint8_t force_tps4_for_cp2520 = 1; + +static void dp_test_send_phy_test_pattern(struct dc_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[ + (DP_TEST_80BIT_CUSTOM_PATTERN_79_72 - + DP_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, + DP_TEST_PHY_PATTERN, + &dpcd_test_pattern.raw, + sizeof(dpcd_test_pattern)); + core_link_read_dpcd( + link, + DP_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, + DP_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_CP2520_1: + /* CP2520 pattern is unstable, temporarily use TPS4 instead */ + test_pattern = (force_tps4_for_cp2520 == 1) ? + DP_TEST_PATTERN_TRAINING_PATTERN4 : + DP_TEST_PATTERN_HBR2_COMPLIANCE_EYE; + break; + case PHY_TEST_PATTERN_CP2520_2: + /* CP2520 pattern is unstable, temporarily use TPS4 instead */ + test_pattern = (force_tps4_for_cp2520 == 1) ? + DP_TEST_PATTERN_TRAINING_PATTERN4 : + DP_TEST_PATTERN_HBR2_COMPLIANCE_EYE; + break; + case PHY_TEST_PATTERN_CP2520_3: + test_pattern = DP_TEST_PATTERN_TRAINING_PATTERN4; + break; + default: + test_pattern = DP_TEST_PATTERN_VIDEO_MODE; + break; + } + + if (test_pattern == DP_TEST_PATTERN_80BIT_CUSTOM) + core_link_read_dpcd( + link, + DP_TEST_80BIT_CUSTOM_PATTERN_7_0, + test_80_bit_pattern, + sizeof(test_80_bit_pattern)); + + /* prepare link training settings */ + link_settings.link = link->cur_link_settings; + + for (lane = 0; lane < + (unsigned int)(link->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, + test_pattern, + &link_training_settings, + test_80_bit_pattern, + (DP_TEST_80BIT_CUSTOM_PATTERN_79_72 - + DP_TEST_80BIT_CUSTOM_PATTERN_7_0)+1); +} + +static void dp_test_send_link_test_pattern(struct dc_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, + DP_TEST_PATTERN, + &dpcd_test_pattern.raw, + sizeof(dpcd_test_pattern)); + core_link_read_dpcd( + link, + DP_TEST_MISC0, + &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, + test_pattern, + NULL, + NULL, + 0); +} + +static void handle_automated_test(struct dc_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, + DP_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, + DP_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); + test_response.bits.ACK = 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, + DP_TEST_RESPONSE, + &test_response.raw, + sizeof(test_response)); +} + +bool dc_link_handle_hpd_rx_irq(struct dc_link *link, union hpd_irq_data *out_hpd_irq_dpcd_data) +{ + 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->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 (out_hpd_irq_dpcd_data) + *out_hpd_irq_dpcd_data = 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, + DP_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->link_index); + return false; + } + + if (handle_hpd_irq_psr_sink(link)) + /* PSR-related error was detected and handled */ + return true; + + /* If PSR-related error handled, Main link may be off, + * so do not handle as a normal sink status change interrupt. + */ + + if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY) + return true; + + /* check if we have MST msg and return since we poll for it */ + if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) + 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->cur_link_settings, + true, LINK_TRAINING_ATTEMPTS); + + status = false; + } + + if (link->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 dc_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, DP_DPCD_REV, &rev.raw, + sizeof(rev)); + + if (st == DC_OK && rev.raw >= DPCD_REV_12) { + + st = core_link_read_dpcd(link, DP_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 dc_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 int translate_dpcd_max_bpc(enum dpcd_downstream_port_max_bpc bpc) +{ + switch (bpc) { + case DOWN_STREAM_MAX_8BPC: + return 8; + case DOWN_STREAM_MAX_10BPC: + return 10; + case DOWN_STREAM_MAX_12BPC: + return 12; + case DOWN_STREAM_MAX_16BPC: + return 16; + default: + break; + } + + return -1; +} + +static void get_active_converter_info( + uint8_t data, struct dc_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 >= 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, DP_DOWNSTREAM_PORT_0, + 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; + + link->dpcd_caps.dongle_caps.dongle_type = link->dpcd_caps.dongle_type; + if (ds_port.fields.DETAILED_CAPS) { + + union dwnstream_port_caps_byte3_hdmi + hdmi_caps = {.raw = det_caps[3] }; + union dwnstream_port_caps_byte1 + hdmi_color_caps = {.raw = det_caps[2] }; + link->dpcd_caps.dongle_caps.dp_hdmi_max_pixel_clk = + det_caps[1] * 25000; + + link->dpcd_caps.dongle_caps.is_dp_hdmi_s3d_converter = + hdmi_caps.bits.FRAME_SEQ_TO_FRAME_PACK; + link->dpcd_caps.dongle_caps.is_dp_hdmi_ycbcr422_pass_through = + hdmi_caps.bits.YCrCr422_PASS_THROUGH; + link->dpcd_caps.dongle_caps.is_dp_hdmi_ycbcr420_pass_through = + hdmi_caps.bits.YCrCr420_PASS_THROUGH; + link->dpcd_caps.dongle_caps.is_dp_hdmi_ycbcr422_converter = + hdmi_caps.bits.YCrCr422_CONVERSION; + link->dpcd_caps.dongle_caps.is_dp_hdmi_ycbcr420_converter = + hdmi_caps.bits.YCrCr420_CONVERSION; + + link->dpcd_caps.dongle_caps.dp_hdmi_max_bpc = + translate_dpcd_max_bpc( + hdmi_color_caps.bits.MAX_BITS_PER_COLOR_COMPONENT); + + link->dpcd_caps.dongle_caps.extendedCapValid = true; + } + + 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, + DP_BRANCH_OUI, + (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, + DP_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 dc_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, DP_DPCD_REV, + dpcd_data, length); + link->dpcd_caps.dpcd_rev.raw = dpcd_data[ + DP_DPCD_REV - + DP_DPCD_REV]; + } while (retry++ < 4 && !link->dpcd_caps.dpcd_rev.raw); + } + + ds_port.byte = dpcd_data[DP_DOWNSTREAMPORT_PRESENT - + DP_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_link_cap(struct dc_link *link) +{ + uint8_t dpcd_data[DP_TRAINING_AUX_RD_INTERVAL - DP_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, + DP_DPCD_REV, + dpcd_data, + sizeof(dpcd_data)); + + { + union training_aux_rd_interval aux_rd_interval; + + aux_rd_interval.raw = + dpcd_data[DP_TRAINING_AUX_RD_INTERVAL]; + + if (aux_rd_interval.bits.EXT_RECIEVER_CAP_FIELD_PRESENT == 1) { + core_link_read_dpcd( + link, + DP_DP13_DPCD_REV, + dpcd_data, + sizeof(dpcd_data)); + } + } + + link->dpcd_caps.dpcd_rev.raw = + dpcd_data[DP_DPCD_REV - DP_DPCD_REV]; + + ds_port.byte = dpcd_data[DP_DOWNSTREAMPORT_PRESENT - + DP_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[ + DP_MAX_LANE_COUNT - DP_DPCD_REV]; + + link->dpcd_caps.max_down_spread.raw = dpcd_data[ + DP_MAX_DOWNSPREAD - DP_DPCD_REV]; + + link->reported_link_cap.lane_count = + link->dpcd_caps.max_ln_count.bits.MAX_LANE_COUNT; + link->reported_link_cap.link_rate = dpcd_data[ + DP_MAX_LINK_RATE - DP_DPCD_REV]; + link->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[ + DP_EDP_CONFIGURATION_CAP - DP_DPCD_REV]; + link->dpcd_caps.panel_mode_edp = + edp_config_cap.bits.ALT_SCRAMBLER_RESET; + link->dpcd_caps.dpcd_display_control_capable = + edp_config_cap.bits.DPCD_DISPLAY_CONTROL_CAPABLE; + + link->test_pattern_enabled = false; + link->compliance_test_state.raw = 0; + + /* read sink count */ + core_link_read_dpcd(link, + DP_SINK_COUNT, + &link->dpcd_caps.sink_count.raw, + sizeof(link->dpcd_caps.sink_count.raw)); + + /* Connectivity log: detection */ + CONN_DATA_DETECT(link, dpcd_data, sizeof(dpcd_data), "Rx Caps: "); +} + +void detect_dp_sink_caps(struct dc_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. + */ + /* TODO save sink caps in link->sink */ +} + +void detect_edp_sink_caps(struct dc_link *link) +{ + retrieve_link_cap(link); + link->verified_link_cap = link->reported_link_cap; +} + +void dc_link_dp_enable_hpd(const struct dc_link *link) +{ + struct link_encoder *encoder = 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 link_encoder *encoder = 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 ((DP_TEST_PATTERN_PHY_PATTERN_BEGIN <= test_pattern && + test_pattern <= DP_TEST_PATTERN_PHY_PATTERN_END) || + test_pattern == DP_TEST_PATTERN_VIDEO_MODE) + return true; + else + return false; +} + +static void set_crtc_test_pattern(struct dc_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->timing.display_color_depth; + struct bit_depth_reduction_params params; + + memset(¶ms, 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->stream_res.opp->funcs-> + opp_program_bit_depth_reduction(pipe_ctx->stream_res.opp, ¶ms); + + pipe_ctx->stream_res.tg->funcs->set_test_pattern(pipe_ctx->stream_res.tg, + controller_test_pattern, color_depth); + } + break; + case DP_TEST_PATTERN_VIDEO_MODE: + { + /* restore bitdepth reduction */ + resource_build_bit_depth_reduction_params(pipe_ctx->stream, + ¶ms); + pipe_ctx->stream->bit_depth_params = params; + pipe_ctx->stream_res.opp->funcs-> + opp_program_bit_depth_reduction(pipe_ctx->stream_res.opp, ¶ms); + + pipe_ctx->stream_res.tg->funcs->set_test_pattern(pipe_ctx->stream_res.tg, + CONTROLLER_DP_TEST_PATTERN_VIDEOMODE, + color_depth); + } + break; + + default: + break; + } +} + +bool dc_link_dp_set_test_pattern( + 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 pipe_ctx *pipes = link->dc->current_state->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; + enum dpcd_phy_test_patterns pattern; + + memset(&training_pattern, 0, sizeof(training_pattern)); + + for (i = 0; i < MAX_PIPES; i++) { + if (pipes[i].stream->sink->link == 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 (link->test_pattern_enabled && test_pattern == + DP_TEST_PATTERN_VIDEO_MODE) { + /* Set CRTC Test Pattern */ + set_crtc_test_pattern(link, pipe_ctx, test_pattern); + dp_set_hw_test_pattern(link, test_pattern, + (uint8_t *)p_custom_pattern, + (uint32_t)cust_pattern_size); + + /* Unblank Stream */ + link->dc->hwss.unblank_stream( + pipe_ctx, + &link->verified_link_cap); + /* TODO:m_pHwss->MuteAudioEndpoint + * (pPathMode->pDisplayPath, false); + */ + + /* Reset Test Pattern state */ + link->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(link, p_link_settings); + dpcd_set_lane_settings(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_res.stream_enc->funcs->dp_blank(pipe_ctx->stream_res.stream_enc); + } + + dp_set_hw_test_pattern(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 */ + link->test_pattern_enabled = true; + if (p_link_settings != NULL) + dpcd_set_link_settings(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_CP2520_1: + pattern = PHY_TEST_PATTERN_CP2520_1; + break; + case DP_TEST_PATTERN_CP2520_2: + pattern = PHY_TEST_PATTERN_CP2520_2; + break; + case DP_TEST_PATTERN_CP2520_3: + pattern = PHY_TEST_PATTERN_CP2520_3; + break; + default: + return false; + } + + if (test_pattern == DP_TEST_PATTERN_VIDEO_MODE + /*TODO:&& !pPathMode->pDisplayPath->IsTargetPoweredOn()*/) + return false; + + if (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(link, + DP_LINK_QUAL_LANE0_SET, + link_qual_pattern, + sizeof(link_qual_pattern)); + } else if (link->dpcd_caps.dpcd_rev.raw >= DPCD_REV_10 || + 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(link, DP_TRAINING_PATTERN_SET, + &training_pattern.raw, + sizeof(training_pattern)); + training_pattern.v1_3.LINK_QUAL_PATTERN_SET = pattern; + core_link_write_dpcd(link, DP_TRAINING_PATTERN_SET, + &training_pattern.raw, + sizeof(training_pattern)); + } + } else { + /* CRTC Patterns */ + set_crtc_test_pattern(link, pipe_ctx, test_pattern); + /* Set Test Pattern state */ + link->test_pattern_enabled = true; + } + + return true; +} + +void dp_enable_mst_on_sink(struct dc_link *link, bool enable) +{ + unsigned char mstmCntl; + + core_link_read_dpcd(link, DP_MSTM_CTRL, &mstmCntl, 1); + if (enable) + mstmCntl |= DP_MST_EN; + else + mstmCntl &= (~DP_MST_EN); + + core_link_write_dpcd(link, DP_MSTM_CTRL, &mstmCntl, 1); +} 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..9a33b471270a --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_hwss.c @@ -0,0 +1,331 @@ +/* Copyright 2015 Advanced Micro Devices, Inc. */ + + +#include "dm_services.h" +#include "dc.h" +#include "inc/core_types.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" +#include "dpcd_defs.h" + +enum dc_status core_link_read_dpcd( + struct dc_link *link, + uint32_t address, + uint8_t *data, + uint32_t size) +{ + if (!dm_helpers_dp_read_dpcd(link->ctx, + link, + address, data, size)) + return DC_ERROR_UNEXPECTED; + + return DC_OK; +} + +enum dc_status core_link_write_dpcd( + struct dc_link *link, + uint32_t address, + const uint8_t *data, + uint32_t size) +{ + if (!dm_helpers_dp_write_dpcd(link->ctx, + link, + address, data, size)) + return DC_ERROR_UNEXPECTED; + + return DC_OK; +} + +void dp_receiver_power_ctrl(struct dc_link *link, bool on) +{ + uint8_t state; + + state = on ? DP_POWER_STATE_D0 : DP_POWER_STATE_D3; + + core_link_write_dpcd(link, DP_SET_POWER, &state, + sizeof(state)); +} + +void dp_enable_link_phy( + struct dc_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; + + struct pipe_ctx *pipes = + link->dc->current_state->res_ctx.pipe_ctx; + struct clock_source *dp_cs = + link->dc->res_pool->dp_clock_source; + unsigned int i; + /* If the current pixel clock source is not DTO(happens after + * switching from HDMI passive dongle to DP on the same connector), + * switch the pixel clock source to DTO. + */ + for (i = 0; i < MAX_PIPES; i++) { + if (pipes[i].stream != NULL && + pipes[i].stream->sink != NULL && + pipes[i].stream->sink->link == link) { + if (pipes[i].clock_source != NULL && + pipes[i].clock_source->id != CLOCK_SOURCE_ID_DP_DTO) { + pipes[i].clock_source = dp_cs; + pipes[i].stream_res.pix_clk_params.requested_pix_clk = + pipes[i].stream->timing.pix_clk_khz; + pipes[i].clock_source->funcs->program_pix_clk( + pipes[i].clock_source, + &pipes[i].stream_res.pix_clk_params, + &pipes[i].pll_settings); + } + } + } + + if (dc_is_dp_sst_signal(signal)) { + if (signal == SIGNAL_TYPE_EDP) { + link->dc->hwss.edp_power_control(link->link_enc, true); + link_enc->funcs->enable_dp_output( + link_enc, + link_settings, + clock_source); + link->dc->hwss.edp_backlight_control(link, true); + } else + 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); +} + +static bool edp_receiver_ready_T9(struct dc_link *link) +{ + unsigned int tries = 0; + unsigned char sinkstatus = 0; + unsigned char edpRev = 0; + enum dc_status result = DC_OK; + result = core_link_read_dpcd(link, DP_EDP_DPCD_REV, &edpRev, sizeof(edpRev)); + if (edpRev < DP_EDP_12) + return true; + /* start from eDP version 1.2, SINK_STAUS indicate the sink is ready.*/ + do { + sinkstatus = 1; + result = core_link_read_dpcd(link, DP_SINK_STATUS, &sinkstatus, sizeof(sinkstatus)); + if (sinkstatus == 0) + break; + if (result != DC_OK) + break; + udelay(100); //MAx T9 + } while (++tries < 50); + return result; +} + +void dp_disable_link_phy(struct dc_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->dc->hwss.edp_backlight_control(link, false); + edp_receiver_ready_T9(link); + link->link_enc->funcs->disable_output(link->link_enc, signal, link); + link->dc->hwss.edp_power_control(link->link_enc, false); + } else + link->link_enc->funcs->disable_output(link->link_enc, signal, link); + + /* Clear current link setting.*/ + memset(&link->cur_link_settings, 0, + sizeof(link->cur_link_settings)); +} + +void dp_disable_link_phy_mst(struct dc_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); + + /* set the sink to SST mode after disabling the link */ + dp_enable_mst_on_sink(link, false); +} + +bool dp_set_hw_training_pattern( + struct dc_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 dc_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 dc_link *link) +{ + /* We need to explicitly check that connector + * is not DP. Some Travis_VGA get reported + * by video bios as DP. + */ + if (link->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 dc_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_dp_test(struct dc_link *link, + struct dc_link_settings *link_setting, + bool skip_video_pattern) +{ + struct pipe_ctx *pipes = + &link->dc->current_state->res_ctx.pipe_ctx[0]; + unsigned int i; + + for (i = 0; i < MAX_PIPES; i++) { + if (pipes[i].stream != NULL && + pipes[i].stream->sink != NULL && + pipes[i].stream->sink->link != NULL && + pipes[i].stream_res.stream_enc != NULL && + pipes[i].stream->sink->link == link) { + udelay(100); + + pipes[i].stream_res.stream_enc->funcs->dp_blank( + pipes[i].stream_res.stream_enc); + + /* disable any test pattern that might be active */ + dp_set_hw_test_pattern(link, + DP_TEST_PATTERN_VIDEO_MODE, NULL, 0); + + dp_receiver_power_ctrl(link, false); + + link->dc->hwss.disable_stream(&pipes[i], KEEP_ACQUIRED_RESOURCE); + + link->link_enc->funcs->disable_output( + link->link_enc, + SIGNAL_TYPE_DISPLAY_PORT, + link); + + /* Clear current link setting. */ + memset(&link->cur_link_settings, 0, + sizeof(link->cur_link_settings)); + + link->link_enc->funcs->enable_dp_output( + link->link_enc, + link_setting, + pipes[i].clock_source->id); + + dp_receiver_power_ctrl(link, true); + + perform_link_training_with_retries( + link, + link_setting, + skip_video_pattern, + LINK_TRAINING_ATTEMPTS); + + link->cur_link_settings = *link_setting; + + link->dc->hwss.enable_stream(&pipes[i]); + + link->dc->hwss.unblank_stream(&pipes[i], + link_setting); + + if (pipes[i].stream_res.audio) { + /* notify audio driver for + * audio modes of monitor */ + pipes[i].stream_res.audio->funcs->az_enable( + pipes[i].stream_res.audio); + + /* un-mute audio */ + /* TODO: audio should be per stream rather than + * per link */ + pipes[i].stream_res.stream_enc->funcs-> + audio_mute_control( + pipes[i].stream_res.stream_enc, false); + } + } + } +} 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..b7422d3b71ef --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c @@ -0,0 +1,2807 @@ +/* +* 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 "dpp.h" +#include "core_types.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" +#if defined(CONFIG_DRM_AMD_DC_DCN1_0) +#include "dcn10/dcn10_resource.h" +#endif +#include "dce120/dce120_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: + dc_version = DCE_VERSION_8_0; + break; + case FAMILY_KV: + if (ASIC_REV_IS_KALINDI(asic_id.hw_internal_rev) || + ASIC_REV_IS_BHAVANI(asic_id.hw_internal_rev) || + ASIC_REV_IS_GODAVARI(asic_id.hw_internal_rev)) + dc_version = DCE_VERSION_8_3; + else + dc_version = DCE_VERSION_8_1; + 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) || + ASIC_REV_IS_POLARIS12_V(asic_id.hw_internal_rev)) { + dc_version = DCE_VERSION_11_2; + } + break; + case FAMILY_AI: + dc_version = DCE_VERSION_12_0; + break; +#if defined(CONFIG_DRM_AMD_DC_DCN1_0) + case FAMILY_RV: + dc_version = DCN_VERSION_1_0; + break; +#endif + default: + dc_version = DCE_VERSION_UNKNOWN; + break; + } + return dc_version; +} + +struct resource_pool *dc_create_resource_pool( + struct dc *dc, + int num_virtual_links, + enum dce_version dc_version, + struct hw_asic_id asic_id) +{ + struct resource_pool *res_pool = NULL; + + switch (dc_version) { + case DCE_VERSION_8_0: + res_pool = dce80_create_resource_pool( + num_virtual_links, dc); + break; + case DCE_VERSION_8_1: + res_pool = dce81_create_resource_pool( + num_virtual_links, dc); + break; + case DCE_VERSION_8_3: + res_pool = dce83_create_resource_pool( + num_virtual_links, dc); + break; + case DCE_VERSION_10_0: + res_pool = dce100_create_resource_pool( + num_virtual_links, dc); + break; + case DCE_VERSION_11_0: + res_pool = dce110_create_resource_pool( + num_virtual_links, dc, asic_id); + break; + case DCE_VERSION_11_2: + res_pool = dce112_create_resource_pool( + num_virtual_links, dc); + break; + case DCE_VERSION_12_0: + res_pool = dce120_create_resource_pool( + num_virtual_links, dc); + break; + +#if defined(CONFIG_DRM_AMD_DC_DCN1_0) + case DCN_VERSION_1_0: + res_pool = dcn10_create_resource_pool( + num_virtual_links, dc); + break; +#endif + + + default: + break; + } + if (res_pool != NULL) { + struct dc_firmware_info fw_info = { { 0 } }; + + if (dc->ctx->dc_bios->funcs->get_firmware_info( + dc->ctx->dc_bios, &fw_info) == BP_RESULT_OK) { + res_pool->ref_clock_inKhz = fw_info.pll_info.crystal_frequency; + } else + ASSERT_CRITICAL(false); + } + + return res_pool; +} + +void dc_destroy_resource_pool(struct dc *dc) +{ + if (dc) { + if (dc->res_pool) + dc->res_pool->funcs->destroy(&dc->res_pool); + + kfree(dc->hwseq); + } +} + +static void update_num_audio( + const struct resource_straps *straps, + unsigned int *num_audio, + struct audio_support *aud_support) +{ + aud_support->dp_audio = true; + aud_support->hdmi_audio_native = false; + aud_support->hdmi_audio_on_dongle = false; + + if (straps->hdmi_disable == 0) { + if (straps->dc_pinstraps_audio & 0x2) { + aud_support->hdmi_audio_on_dongle = true; + aud_support->hdmi_audio_native = 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 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++; + } + } + dc->caps.dynamic_audio = false; + if (pool->audio_count < pool->stream_enc_count) { + dc->caps.dynamic_audio = true; + } + 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, + const struct resource_pool *pool, + struct clock_source *clock_source) +{ + int i; + + for (i = 0; i < pool->clk_src_count; i++) { + if (pool->clock_sources[i] != clock_source) + continue; + + res_ctx->clock_source_ref_count[i]--; + + break; + } + + if (pool->dp_clock_source == clock_source) + res_ctx->dp_clock_source_ref_count--; +} + +void resource_reference_clock_source( + struct resource_context *res_ctx, + const struct resource_pool *pool, + struct clock_source *clock_source) +{ + int i; + for (i = 0; i < pool->clk_src_count; i++) { + if (pool->clock_sources[i] != clock_source) + continue; + + res_ctx->clock_source_ref_count[i]++; + break; + } + + if (pool->dp_clock_source == clock_source) + res_ctx->dp_clock_source_ref_count++; +} + +bool resource_are_streams_timing_synchronizable( + struct dc_stream_state *stream1, + struct dc_stream_state *stream2) +{ + if (stream1->timing.h_total != stream2->timing.h_total) + return false; + + if (stream1->timing.v_total != stream2->timing.v_total) + return false; + + if (stream1->timing.h_addressable + != stream2->timing.h_addressable) + return false; + + if (stream1->timing.v_addressable + != stream2->timing.v_addressable) + return false; + + if (stream1->timing.pix_clk_khz + != stream2->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_ABGR8888: + 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: + case SURFACE_PIXEL_FORMAT_VIDEO_420_YCrCb: + dal_pixel_format = PIXEL_FORMAT_420BPP8; + break; + case SURFACE_PIXEL_FORMAT_VIDEO_420_10bpc_YCbCr: + case SURFACE_PIXEL_FORMAT_VIDEO_420_10bpc_YCrCb: + dal_pixel_format = PIXEL_FORMAT_420BPP10; + 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(struct pipe_ctx *pipe_ctx) +{ + const struct dc_plane_state *plane_state = pipe_ctx->plane_state; + const struct dc_stream_state *stream = pipe_ctx->stream; + struct scaler_data *data = &pipe_ctx->plane_res.scl_data; + struct rect surf_src = plane_state->src_rect; + struct rect clip = { 0 }; + int vpc_div = (data->format == PIXEL_FORMAT_420BPP8 + || data->format == PIXEL_FORMAT_420BPP10) ? 2 : 1; + bool pri_split = pipe_ctx->bottom_pipe && + pipe_ctx->bottom_pipe->plane_state == pipe_ctx->plane_state; + bool sec_split = pipe_ctx->top_pipe && + pipe_ctx->top_pipe->plane_state == pipe_ctx->plane_state; + + if (stream->view_format == VIEW_3D_FORMAT_SIDE_BY_SIDE || + stream->view_format == VIEW_3D_FORMAT_TOP_AND_BOTTOM) { + pri_split = false; + sec_split = false; + } + + if (pipe_ctx->plane_state->rotation == ROTATION_ANGLE_90 || + pipe_ctx->plane_state->rotation == ROTATION_ANGLE_270) + rect_swap_helper(&surf_src); + + /* The actual clip is an intersection between stream + * source and surface clip + */ + clip.x = stream->src.x > plane_state->clip_rect.x ? + stream->src.x : plane_state->clip_rect.x; + + clip.width = stream->src.x + stream->src.width < + plane_state->clip_rect.x + plane_state->clip_rect.width ? + stream->src.x + stream->src.width - clip.x : + plane_state->clip_rect.x + plane_state->clip_rect.width - clip.x ; + + clip.y = stream->src.y > plane_state->clip_rect.y ? + stream->src.y : plane_state->clip_rect.y; + + clip.height = stream->src.y + stream->src.height < + plane_state->clip_rect.y + plane_state->clip_rect.height ? + stream->src.y + stream->src.height - clip.y : + plane_state->clip_rect.y + plane_state->clip_rect.height - clip.y ; + + /* offset = surf_src.ofs + (clip.ofs - surface->dst_rect.ofs) * scl_ratio + * num_pixels = clip.num_pix * scl_ratio + */ + data->viewport.x = surf_src.x + (clip.x - plane_state->dst_rect.x) * + surf_src.width / plane_state->dst_rect.width; + data->viewport.width = clip.width * + surf_src.width / plane_state->dst_rect.width; + + data->viewport.y = surf_src.y + (clip.y - plane_state->dst_rect.y) * + surf_src.height / plane_state->dst_rect.height; + data->viewport.height = clip.height * + surf_src.height / plane_state->dst_rect.height; + + /* Round down, compensate in init */ + data->viewport_c.x = data->viewport.x / vpc_div; + data->viewport_c.y = data->viewport.y / vpc_div; + data->inits.h_c = (data->viewport.x % vpc_div) != 0 ? + dal_fixed31_32_half : dal_fixed31_32_zero; + data->inits.v_c = (data->viewport.y % vpc_div) != 0 ? + dal_fixed31_32_half : dal_fixed31_32_zero; + /* Round up, assume original video size always even dimensions */ + data->viewport_c.width = (data->viewport.width + vpc_div - 1) / vpc_div; + data->viewport_c.height = (data->viewport.height + vpc_div - 1) / vpc_div; + + /* Handle hsplit */ + if (pri_split || sec_split) { + /* HMirror XOR Secondary_pipe XOR Rotation_180 */ + bool right_view = (sec_split != plane_state->horizontal_mirror) != + (plane_state->rotation == ROTATION_ANGLE_180); + + if (plane_state->rotation == ROTATION_ANGLE_90 + || plane_state->rotation == ROTATION_ANGLE_270) + /* Secondary_pipe XOR Rotation_270 */ + right_view = (plane_state->rotation == ROTATION_ANGLE_270) != sec_split; + + if (right_view) { + data->viewport.x += data->viewport.width / 2; + data->viewport_c.x += data->viewport_c.width / 2; + /* Ceil offset pipe */ + data->viewport.width = (data->viewport.width + 1) / 2; + data->viewport_c.width = (data->viewport_c.width + 1) / 2; + } else { + data->viewport.width /= 2; + data->viewport_c.width /= 2; + } + } + + if (plane_state->rotation == ROTATION_ANGLE_90 || + plane_state->rotation == ROTATION_ANGLE_270) { + rect_swap_helper(&data->viewport_c); + rect_swap_helper(&data->viewport); + } +} + +static void calculate_recout(struct pipe_ctx *pipe_ctx, struct view *recout_skip) +{ + const struct dc_plane_state *plane_state = pipe_ctx->plane_state; + const struct dc_stream_state *stream = pipe_ctx->stream; + struct rect surf_src = plane_state->src_rect; + struct rect surf_clip = plane_state->clip_rect; + int recout_full_x, recout_full_y; + + if (pipe_ctx->plane_state->rotation == ROTATION_ANGLE_90 || + pipe_ctx->plane_state->rotation == ROTATION_ANGLE_270) + rect_swap_helper(&surf_src); + + pipe_ctx->plane_res.scl_data.recout.x = stream->dst.x; + if (stream->src.x < surf_clip.x) + pipe_ctx->plane_res.scl_data.recout.x += (surf_clip.x + - stream->src.x) * stream->dst.width + / stream->src.width; + + pipe_ctx->plane_res.scl_data.recout.width = surf_clip.width * + stream->dst.width / stream->src.width; + if (pipe_ctx->plane_res.scl_data.recout.width + pipe_ctx->plane_res.scl_data.recout.x > + stream->dst.x + stream->dst.width) + pipe_ctx->plane_res.scl_data.recout.width = + stream->dst.x + stream->dst.width + - pipe_ctx->plane_res.scl_data.recout.x; + + pipe_ctx->plane_res.scl_data.recout.y = stream->dst.y; + if (stream->src.y < surf_clip.y) + pipe_ctx->plane_res.scl_data.recout.y += (surf_clip.y + - stream->src.y) * stream->dst.height + / stream->src.height; + + pipe_ctx->plane_res.scl_data.recout.height = surf_clip.height * + stream->dst.height / stream->src.height; + if (pipe_ctx->plane_res.scl_data.recout.height + pipe_ctx->plane_res.scl_data.recout.y > + stream->dst.y + stream->dst.height) + pipe_ctx->plane_res.scl_data.recout.height = + stream->dst.y + stream->dst.height + - pipe_ctx->plane_res.scl_data.recout.y; + + /* Handle h & vsplit */ + if (pipe_ctx->top_pipe && pipe_ctx->top_pipe->plane_state == + pipe_ctx->plane_state) { + if (stream->view_format == VIEW_3D_FORMAT_TOP_AND_BOTTOM) { + pipe_ctx->plane_res.scl_data.recout.y += pipe_ctx->plane_res.scl_data.recout.height / 2; + /* Floor primary pipe, ceil 2ndary pipe */ + pipe_ctx->plane_res.scl_data.recout.height = (pipe_ctx->plane_res.scl_data.recout.height + 1) / 2; + } else { + pipe_ctx->plane_res.scl_data.recout.x += pipe_ctx->plane_res.scl_data.recout.width / 2; + pipe_ctx->plane_res.scl_data.recout.width = (pipe_ctx->plane_res.scl_data.recout.width + 1) / 2; + } + } else if (pipe_ctx->bottom_pipe && + pipe_ctx->bottom_pipe->plane_state == pipe_ctx->plane_state) { + if (stream->view_format == VIEW_3D_FORMAT_TOP_AND_BOTTOM) + pipe_ctx->plane_res.scl_data.recout.height /= 2; + else + pipe_ctx->plane_res.scl_data.recout.width /= 2; + } + + /* Unclipped recout offset = stream dst offset + ((surf dst offset - stream surf_src offset) + * * 1/ stream scaling ratio) - (surf surf_src offset * 1/ full scl + * ratio) + */ + recout_full_x = stream->dst.x + (plane_state->dst_rect.x - stream->src.x) + * stream->dst.width / stream->src.width - + surf_src.x * plane_state->dst_rect.width / surf_src.width + * stream->dst.width / stream->src.width; + recout_full_y = stream->dst.y + (plane_state->dst_rect.y - stream->src.y) + * stream->dst.height / stream->src.height - + surf_src.y * plane_state->dst_rect.height / surf_src.height + * stream->dst.height / stream->src.height; + + recout_skip->width = pipe_ctx->plane_res.scl_data.recout.x - recout_full_x; + recout_skip->height = pipe_ctx->plane_res.scl_data.recout.y - recout_full_y; +} + +static void calculate_scaling_ratios(struct pipe_ctx *pipe_ctx) +{ + const struct dc_plane_state *plane_state = pipe_ctx->plane_state; + const struct dc_stream_state *stream = pipe_ctx->stream; + struct rect surf_src = plane_state->src_rect; + const int in_w = stream->src.width; + const int in_h = stream->src.height; + const int out_w = stream->dst.width; + const int out_h = stream->dst.height; + + if (pipe_ctx->plane_state->rotation == ROTATION_ANGLE_90 || + pipe_ctx->plane_state->rotation == ROTATION_ANGLE_270) + rect_swap_helper(&surf_src); + + pipe_ctx->plane_res.scl_data.ratios.horz = dal_fixed31_32_from_fraction( + surf_src.width, + plane_state->dst_rect.width); + pipe_ctx->plane_res.scl_data.ratios.vert = dal_fixed31_32_from_fraction( + surf_src.height, + plane_state->dst_rect.height); + + if (stream->view_format == VIEW_3D_FORMAT_SIDE_BY_SIDE) + pipe_ctx->plane_res.scl_data.ratios.horz.value *= 2; + else if (stream->view_format == VIEW_3D_FORMAT_TOP_AND_BOTTOM) + pipe_ctx->plane_res.scl_data.ratios.vert.value *= 2; + + pipe_ctx->plane_res.scl_data.ratios.vert.value = div64_s64( + pipe_ctx->plane_res.scl_data.ratios.vert.value * in_h, out_h); + pipe_ctx->plane_res.scl_data.ratios.horz.value = div64_s64( + pipe_ctx->plane_res.scl_data.ratios.horz.value * in_w, out_w); + + pipe_ctx->plane_res.scl_data.ratios.horz_c = pipe_ctx->plane_res.scl_data.ratios.horz; + pipe_ctx->plane_res.scl_data.ratios.vert_c = pipe_ctx->plane_res.scl_data.ratios.vert; + + if (pipe_ctx->plane_res.scl_data.format == PIXEL_FORMAT_420BPP8 + || pipe_ctx->plane_res.scl_data.format == PIXEL_FORMAT_420BPP10) { + pipe_ctx->plane_res.scl_data.ratios.horz_c.value /= 2; + pipe_ctx->plane_res.scl_data.ratios.vert_c.value /= 2; + } +} + +static void calculate_inits_and_adj_vp(struct pipe_ctx *pipe_ctx, struct view *recout_skip) +{ + struct scaler_data *data = &pipe_ctx->plane_res.scl_data; + struct rect src = pipe_ctx->plane_state->src_rect; + int vpc_div = (data->format == PIXEL_FORMAT_420BPP8 + || data->format == PIXEL_FORMAT_420BPP10) ? 2 : 1; + + + if (pipe_ctx->plane_state->rotation == ROTATION_ANGLE_90 || + pipe_ctx->plane_state->rotation == ROTATION_ANGLE_270) { + rect_swap_helper(&src); + rect_swap_helper(&data->viewport_c); + rect_swap_helper(&data->viewport); + } + + /* + * Init calculated according to formula: + * init = (scaling_ratio + number_of_taps + 1) / 2 + * init_bot = init + scaling_ratio + * init_c = init + truncated_vp_c_offset(from calculate viewport) + */ + data->inits.h = dal_fixed31_32_div_int( + dal_fixed31_32_add_int(data->ratios.horz, data->taps.h_taps + 1), 2); + + data->inits.h_c = dal_fixed31_32_add(data->inits.h_c, dal_fixed31_32_div_int( + dal_fixed31_32_add_int(data->ratios.horz_c, data->taps.h_taps_c + 1), 2)); + + data->inits.v = dal_fixed31_32_div_int( + dal_fixed31_32_add_int(data->ratios.vert, data->taps.v_taps + 1), 2); + + data->inits.v_c = dal_fixed31_32_add(data->inits.v_c, dal_fixed31_32_div_int( + dal_fixed31_32_add_int(data->ratios.vert_c, data->taps.v_taps_c + 1), 2)); + + + /* Adjust for viewport end clip-off */ + if ((data->viewport.x + data->viewport.width) < (src.x + src.width)) { + int vp_clip = src.x + src.width - data->viewport.width - data->viewport.x; + int int_part = dal_fixed31_32_floor( + dal_fixed31_32_sub(data->inits.h, data->ratios.horz)); + + int_part = int_part > 0 ? int_part : 0; + data->viewport.width += int_part < vp_clip ? int_part : vp_clip; + } + if ((data->viewport.y + data->viewport.height) < (src.y + src.height)) { + int vp_clip = src.y + src.height - data->viewport.height - data->viewport.y; + int int_part = dal_fixed31_32_floor( + dal_fixed31_32_sub(data->inits.v, data->ratios.vert)); + + int_part = int_part > 0 ? int_part : 0; + data->viewport.height += int_part < vp_clip ? int_part : vp_clip; + } + if ((data->viewport_c.x + data->viewport_c.width) < (src.x + src.width) / vpc_div) { + int vp_clip = (src.x + src.width) / vpc_div - + data->viewport_c.width - data->viewport_c.x; + int int_part = dal_fixed31_32_floor( + dal_fixed31_32_sub(data->inits.h_c, data->ratios.horz_c)); + + int_part = int_part > 0 ? int_part : 0; + data->viewport_c.width += int_part < vp_clip ? int_part : vp_clip; + } + if ((data->viewport_c.y + data->viewport_c.height) < (src.y + src.height) / vpc_div) { + int vp_clip = (src.y + src.height) / vpc_div - + data->viewport_c.height - data->viewport_c.y; + int int_part = dal_fixed31_32_floor( + dal_fixed31_32_sub(data->inits.v_c, data->ratios.vert_c)); + + int_part = int_part > 0 ? int_part : 0; + data->viewport_c.height += int_part < vp_clip ? int_part : vp_clip; + } + + /* Adjust for non-0 viewport offset */ + if (data->viewport.x) { + int int_part; + + data->inits.h = dal_fixed31_32_add(data->inits.h, dal_fixed31_32_mul_int( + data->ratios.horz, recout_skip->width)); + int_part = dal_fixed31_32_floor(data->inits.h) - data->viewport.x; + if (int_part < data->taps.h_taps) { + int int_adj = data->viewport.x >= (data->taps.h_taps - int_part) ? + (data->taps.h_taps - int_part) : data->viewport.x; + data->viewport.x -= int_adj; + data->viewport.width += int_adj; + int_part += int_adj; + } else if (int_part > data->taps.h_taps) { + data->viewport.x += int_part - data->taps.h_taps; + data->viewport.width -= int_part - data->taps.h_taps; + int_part = data->taps.h_taps; + } + data->inits.h.value &= 0xffffffff; + data->inits.h = dal_fixed31_32_add_int(data->inits.h, int_part); + } + + if (data->viewport_c.x) { + int int_part; + + data->inits.h_c = dal_fixed31_32_add(data->inits.h_c, dal_fixed31_32_mul_int( + data->ratios.horz_c, recout_skip->width)); + int_part = dal_fixed31_32_floor(data->inits.h_c) - data->viewport_c.x; + if (int_part < data->taps.h_taps_c) { + int int_adj = data->viewport_c.x >= (data->taps.h_taps_c - int_part) ? + (data->taps.h_taps_c - int_part) : data->viewport_c.x; + data->viewport_c.x -= int_adj; + data->viewport_c.width += int_adj; + int_part += int_adj; + } else if (int_part > data->taps.h_taps_c) { + data->viewport_c.x += int_part - data->taps.h_taps_c; + data->viewport_c.width -= int_part - data->taps.h_taps_c; + int_part = data->taps.h_taps_c; + } + data->inits.h_c.value &= 0xffffffff; + data->inits.h_c = dal_fixed31_32_add_int(data->inits.h_c, int_part); + } + + if (data->viewport.y) { + int int_part; + + data->inits.v = dal_fixed31_32_add(data->inits.v, dal_fixed31_32_mul_int( + data->ratios.vert, recout_skip->height)); + int_part = dal_fixed31_32_floor(data->inits.v) - data->viewport.y; + if (int_part < data->taps.v_taps) { + int int_adj = data->viewport.y >= (data->taps.v_taps - int_part) ? + (data->taps.v_taps - int_part) : data->viewport.y; + data->viewport.y -= int_adj; + data->viewport.height += int_adj; + int_part += int_adj; + } else if (int_part > data->taps.v_taps) { + data->viewport.y += int_part - data->taps.v_taps; + data->viewport.height -= int_part - data->taps.v_taps; + int_part = data->taps.v_taps; + } + data->inits.v.value &= 0xffffffff; + data->inits.v = dal_fixed31_32_add_int(data->inits.v, int_part); + } + + if (data->viewport_c.y) { + int int_part; + + data->inits.v_c = dal_fixed31_32_add(data->inits.v_c, dal_fixed31_32_mul_int( + data->ratios.vert_c, recout_skip->height)); + int_part = dal_fixed31_32_floor(data->inits.v_c) - data->viewport_c.y; + if (int_part < data->taps.v_taps_c) { + int int_adj = data->viewport_c.y >= (data->taps.v_taps_c - int_part) ? + (data->taps.v_taps_c - int_part) : data->viewport_c.y; + data->viewport_c.y -= int_adj; + data->viewport_c.height += int_adj; + int_part += int_adj; + } else if (int_part > data->taps.v_taps_c) { + data->viewport_c.y += int_part - data->taps.v_taps_c; + data->viewport_c.height -= int_part - data->taps.v_taps_c; + int_part = data->taps.v_taps_c; + } + data->inits.v_c.value &= 0xffffffff; + data->inits.v_c = dal_fixed31_32_add_int(data->inits.v_c, int_part); + } + + /* Interlaced inits based on final vert inits */ + data->inits.v_bot = dal_fixed31_32_add(data->inits.v, data->ratios.vert); + data->inits.v_c_bot = dal_fixed31_32_add(data->inits.v_c, data->ratios.vert_c); + + if (pipe_ctx->plane_state->rotation == ROTATION_ANGLE_90 || + pipe_ctx->plane_state->rotation == ROTATION_ANGLE_270) { + rect_swap_helper(&data->viewport_c); + rect_swap_helper(&data->viewport); + } +} + +bool resource_build_scaling_params(struct pipe_ctx *pipe_ctx) +{ + const struct dc_plane_state *plane_state = pipe_ctx->plane_state; + struct dc_crtc_timing *timing = &pipe_ctx->stream->timing; + struct view recout_skip = { 0 }; + bool res = false; + + /* Important: scaling ratio calculation requires pixel format, + * lb depth calculation requires recout and taps require scaling ratios. + * Inits require viewport, taps, ratios and recout of split pipe + */ + pipe_ctx->plane_res.scl_data.format = convert_pixel_format_to_dalsurface( + pipe_ctx->plane_state->format); + + calculate_scaling_ratios(pipe_ctx); + + calculate_viewport(pipe_ctx); + + if (pipe_ctx->plane_res.scl_data.viewport.height < 16 || pipe_ctx->plane_res.scl_data.viewport.width < 16) + return false; + + calculate_recout(pipe_ctx, &recout_skip); + + /** + * Setting line buffer pixel depth to 24bpp yields banding + * on certain displays, such as the Sharp 4k + */ + pipe_ctx->plane_res.scl_data.lb_params.depth = LB_PIXEL_DEPTH_30BPP; + + pipe_ctx->plane_res.scl_data.recout.x += timing->h_border_left; + pipe_ctx->plane_res.scl_data.recout.y += timing->v_border_top; + + pipe_ctx->plane_res.scl_data.h_active = timing->h_addressable + timing->h_border_left + timing->h_border_right; + pipe_ctx->plane_res.scl_data.v_active = timing->v_addressable + timing->v_border_top + timing->v_border_bottom; + + + /* Taps calculations */ + if (pipe_ctx->plane_res.xfm != NULL) + res = pipe_ctx->plane_res.xfm->funcs->transform_get_optimal_number_of_taps( + pipe_ctx->plane_res.xfm, &pipe_ctx->plane_res.scl_data, &plane_state->scaling_quality); + + if (pipe_ctx->plane_res.dpp != NULL) + res = pipe_ctx->plane_res.dpp->funcs->dpp_get_optimal_number_of_taps( + pipe_ctx->plane_res.dpp, &pipe_ctx->plane_res.scl_data, &plane_state->scaling_quality); + if (!res) { + /* Try 24 bpp linebuffer */ + pipe_ctx->plane_res.scl_data.lb_params.depth = LB_PIXEL_DEPTH_24BPP; + + if (pipe_ctx->plane_res.xfm != NULL) + res = pipe_ctx->plane_res.xfm->funcs->transform_get_optimal_number_of_taps( + pipe_ctx->plane_res.xfm, + &pipe_ctx->plane_res.scl_data, + &plane_state->scaling_quality); + + if (pipe_ctx->plane_res.dpp != NULL) + res = pipe_ctx->plane_res.dpp->funcs->dpp_get_optimal_number_of_taps( + pipe_ctx->plane_res.dpp, + &pipe_ctx->plane_res.scl_data, + &plane_state->scaling_quality); + } + + if (res) + /* May need to re-check lb size after this in some obscure scenario */ + calculate_inits_and_adj_vp(pipe_ctx, &recout_skip); + + 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->plane_res.scl_data.viewport.height, + pipe_ctx->plane_res.scl_data.viewport.width, + pipe_ctx->plane_res.scl_data.viewport.x, + pipe_ctx->plane_res.scl_data.viewport.y, + plane_state->dst_rect.height, + plane_state->dst_rect.width, + plane_state->dst_rect.x, + plane_state->dst_rect.y); + + return res; +} + + +enum dc_status resource_build_scaling_params_for_context( + const struct dc *dc, + struct dc_state *context) +{ + int i; + + for (i = 0; i < MAX_PIPES; i++) { + if (context->res_ctx.pipe_ctx[i].plane_state != NULL && + context->res_ctx.pipe_ctx[i].stream != NULL) + if (!resource_build_scaling_params(&context->res_ctx.pipe_ctx[i])) + return DC_FAIL_SCALING; + } + + return DC_OK; +} + +struct pipe_ctx *find_idle_secondary_pipe( + struct resource_context *res_ctx, + const struct resource_pool *pool) +{ + int i; + struct pipe_ctx *secondary_pipe = NULL; + + /* + * search backwards for the second pipe to keep pipe + * assignment more consistent + */ + + for (i = 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, + struct dc_stream_state *stream) +{ + int i; + for (i = 0; i < MAX_PIPES; 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; +} + +static struct pipe_ctx *resource_get_tail_pipe_for_stream( + struct resource_context *res_ctx, + struct dc_stream_state *stream) +{ + struct pipe_ctx *head_pipe, *tail_pipe; + head_pipe = resource_get_head_pipe_for_stream(res_ctx, stream); + + if (!head_pipe) + return NULL; + + tail_pipe = head_pipe->bottom_pipe; + + while (tail_pipe) { + head_pipe = tail_pipe; + tail_pipe = tail_pipe->bottom_pipe; + } + + return head_pipe; +} + +/* + * A free_pipe for a stream is defined here as a pipe + * that has no surface attached yet + */ +static struct pipe_ctx *acquire_free_pipe_for_stream( + struct dc_state *context, + const struct resource_pool *pool, + struct dc_stream_state *stream) +{ + int i; + struct resource_context *res_ctx = &context->res_ctx; + + 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); + return NULL; + } + + if (!head_pipe->plane_state) + return head_pipe; + + /* Re-use pipe already acquired for this stream if available*/ + for (i = pool->pipe_count - 1; i >= 0; i--) { + if (res_ctx->pipe_ctx[i].stream == stream && + !res_ctx->pipe_ctx[i].plane_state) { + 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 (!pool->funcs->acquire_idle_pipe_for_layer) + return NULL; + + return pool->funcs->acquire_idle_pipe_for_layer(context, pool, stream); + +} + +#if defined(CONFIG_DRM_AMD_DC_DCN1_0) +static int acquire_first_split_pipe( + struct resource_context *res_ctx, + const struct resource_pool *pool, + struct dc_stream_state *stream) +{ + int i; + + for (i = 0; i < pool->pipe_count; i++) { + struct pipe_ctx *pipe_ctx = &res_ctx->pipe_ctx[i]; + + if (pipe_ctx->top_pipe && + pipe_ctx->top_pipe->plane_state == pipe_ctx->plane_state) { + pipe_ctx->top_pipe->bottom_pipe = pipe_ctx->bottom_pipe; + if (pipe_ctx->bottom_pipe) + pipe_ctx->bottom_pipe->top_pipe = pipe_ctx->top_pipe; + + memset(pipe_ctx, 0, sizeof(*pipe_ctx)); + pipe_ctx->stream_res.tg = pool->timing_generators[i]; + pipe_ctx->plane_res.hubp = pool->hubps[i]; + pipe_ctx->plane_res.ipp = pool->ipps[i]; + pipe_ctx->plane_res.dpp = pool->dpps[i]; + pipe_ctx->stream_res.opp = pool->opps[i]; + pipe_ctx->pipe_idx = i; + + pipe_ctx->stream = stream; + return i; + } + } + return -1; +} +#endif + +bool dc_add_plane_to_context( + const struct dc *dc, + struct dc_stream_state *stream, + struct dc_plane_state *plane_state, + struct dc_state *context) +{ + int i; + struct resource_pool *pool = dc->res_pool; + struct pipe_ctx *head_pipe, *tail_pipe, *free_pipe; + struct dc_stream_status *stream_status = NULL; + + for (i = 0; i < context->stream_count; i++) + if (context->streams[i] == stream) { + stream_status = &context->stream_status[i]; + break; + } + if (stream_status == NULL) { + dm_error("Existing stream not found; failed to attach surface!\n"); + return false; + } + + + if (stream_status->plane_count == MAX_SURFACE_NUM) { + dm_error("Surface: can not attach plane_state %p! Maximum is: %d\n", + plane_state, MAX_SURFACE_NUM); + return false; + } + + head_pipe = resource_get_head_pipe_for_stream(&context->res_ctx, stream); + + if (!head_pipe) { + dm_error("Head pipe not found for stream_state %p !\n", stream); + return false; + } + + free_pipe = acquire_free_pipe_for_stream(context, pool, stream); + +#if defined(CONFIG_DRM_AMD_DC_DCN1_0) + if (!free_pipe) { + int pipe_idx = acquire_first_split_pipe(&context->res_ctx, pool, stream); + if (pipe_idx >= 0) + free_pipe = &context->res_ctx.pipe_ctx[pipe_idx]; + } +#endif + if (!free_pipe) + return false; + + /* retain new surfaces */ + dc_plane_state_retain(plane_state); + free_pipe->plane_state = plane_state; + + if (head_pipe != free_pipe) { + + tail_pipe = resource_get_tail_pipe_for_stream(&context->res_ctx, stream); + ASSERT(tail_pipe); + + free_pipe->stream_res.tg = tail_pipe->stream_res.tg; + free_pipe->stream_res.opp = tail_pipe->stream_res.opp; + free_pipe->stream_res.stream_enc = tail_pipe->stream_res.stream_enc; + free_pipe->stream_res.audio = tail_pipe->stream_res.audio; + free_pipe->clock_source = tail_pipe->clock_source; + free_pipe->top_pipe = tail_pipe; + tail_pipe->bottom_pipe = free_pipe; + } + + /* assign new surfaces*/ + stream_status->plane_states[stream_status->plane_count] = plane_state; + + stream_status->plane_count++; + + return true; +} + +bool dc_remove_plane_from_context( + const struct dc *dc, + struct dc_stream_state *stream, + struct dc_plane_state *plane_state, + struct dc_state *context) +{ + int i; + struct dc_stream_status *stream_status = NULL; + struct resource_pool *pool = dc->res_pool; + + for (i = 0; i < context->stream_count; i++) + if (context->streams[i] == stream) { + stream_status = &context->stream_status[i]; + break; + } + + if (stream_status == NULL) { + dm_error("Existing stream not found; failed to remove plane.\n"); + return false; + } + + /* release pipe for plane*/ + for (i = pool->pipe_count - 1; i >= 0; i--) { + struct pipe_ctx *pipe_ctx; + + if (context->res_ctx.pipe_ctx[i].plane_state == plane_state) { + pipe_ctx = &context->res_ctx.pipe_ctx[i]; + + if (pipe_ctx->top_pipe) + pipe_ctx->top_pipe->bottom_pipe = pipe_ctx->bottom_pipe; + + /* Second condition is to avoid setting NULL to top pipe + * of tail pipe making it look like head pipe in subsequent + * deletes + */ + if (pipe_ctx->bottom_pipe && pipe_ctx->top_pipe) + pipe_ctx->bottom_pipe->top_pipe = pipe_ctx->top_pipe; + + /* + * For head pipe detach surfaces from pipe for tail + * pipe just zero it out + */ + if (!pipe_ctx->top_pipe) { + pipe_ctx->plane_state = NULL; + pipe_ctx->bottom_pipe = NULL; + } else { + memset(pipe_ctx, 0, sizeof(*pipe_ctx)); + } + } + } + + + for (i = 0; i < stream_status->plane_count; i++) { + if (stream_status->plane_states[i] == plane_state) { + + dc_plane_state_release(stream_status->plane_states[i]); + break; + } + } + + if (i == stream_status->plane_count) { + dm_error("Existing plane_state not found; failed to detach it!\n"); + return false; + } + + stream_status->plane_count--; + + /* Start at the plane we've just released, and move all the planes one index forward to "trim" the array */ + for (; i < stream_status->plane_count; i++) + stream_status->plane_states[i] = stream_status->plane_states[i + 1]; + + stream_status->plane_states[stream_status->plane_count] = NULL; + + return true; +} + +bool dc_rem_all_planes_for_stream( + const struct dc *dc, + struct dc_stream_state *stream, + struct dc_state *context) +{ + int i, old_plane_count; + struct dc_stream_status *stream_status = NULL; + struct dc_plane_state *del_planes[MAX_SURFACE_NUM] = { 0 }; + + for (i = 0; i < context->stream_count; i++) + if (context->streams[i] == stream) { + stream_status = &context->stream_status[i]; + break; + } + + if (stream_status == NULL) { + dm_error("Existing stream %p not found!\n", stream); + return false; + } + + old_plane_count = stream_status->plane_count; + + for (i = 0; i < old_plane_count; i++) + del_planes[i] = stream_status->plane_states[i]; + + for (i = 0; i < old_plane_count; i++) + if (!dc_remove_plane_from_context(dc, stream, del_planes[i], context)) + return false; + + return true; +} + +static bool add_all_planes_for_stream( + const struct dc *dc, + struct dc_stream_state *stream, + const struct dc_validation_set set[], + int set_count, + struct dc_state *context) +{ + int i, j; + + for (i = 0; i < set_count; i++) + if (set[i].stream == stream) + break; + + if (i == set_count) { + dm_error("Stream %p not found in set!\n", stream); + return false; + } + + for (j = 0; j < set[i].plane_count; j++) + if (!dc_add_plane_to_context(dc, stream, set[i].plane_states[j], context)) + return false; + + return true; +} + +bool dc_add_all_planes_for_stream( + const struct dc *dc, + struct dc_stream_state *stream, + struct dc_plane_state * const *plane_states, + int plane_count, + struct dc_state *context) +{ + struct dc_validation_set set; + int i; + + set.stream = stream; + set.plane_count = plane_count; + + for (i = 0; i < plane_count; i++) + set.plane_states[i] = plane_states[i]; + + return add_all_planes_for_stream(dc, stream, &set, 1, context); +} + + + +static bool is_timing_changed(struct dc_stream_state *cur_stream, + struct dc_stream_state *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->output_color_space != new_stream->output_color_space) + return true; + + return memcmp( + &cur_stream->timing, + &new_stream->timing, + sizeof(struct dc_crtc_timing)) != 0; +} + +static bool are_stream_backends_same( + struct dc_stream_state *stream_a, struct dc_stream_state *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 dc_is_stream_unchanged( + struct dc_stream_state *old_stream, struct dc_stream_state *stream) +{ + + if (!are_stream_backends_same(old_stream, stream)) + return false; + + return true; +} + +bool dc_is_stream_scaling_unchanged( + struct dc_stream_state *old_stream, struct dc_stream_state *stream) +{ + if (old_stream == stream) + return true; + + if (old_stream == NULL || stream == NULL) + return false; + + if (memcmp(&old_stream->src, + &stream->src, + sizeof(struct rect)) != 0) + return false; + + if (memcmp(&old_stream->dst, + &stream->dst, + sizeof(struct rect)) != 0) + return false; + + return true; +} + +/* Maximum TMDS single link pixel clock 165MHz */ +#define TMDS_MAX_PIXEL_CLOCK_IN_KHZ 165000 + +static void update_stream_engine_usage( + struct resource_context *res_ctx, + const struct resource_pool *pool, + struct stream_encoder *stream_enc, + bool acquired) +{ + int i; + + for (i = 0; i < pool->stream_enc_count; i++) { + if (pool->stream_enc[i] == stream_enc) + res_ctx->is_stream_enc_acquired[i] = acquired; + } +} + +/* TODO: release audio object */ +void update_audio_usage( + struct resource_context *res_ctx, + const struct resource_pool *pool, + struct audio *audio, + bool acquired) +{ + int i; + for (i = 0; i < pool->audio_count; i++) { + if (pool->audios[i] == audio) + res_ctx->is_audio_acquired[i] = acquired; + } +} + +static int acquire_first_free_pipe( + struct resource_context *res_ctx, + const struct resource_pool *pool, + struct dc_stream_state *stream) +{ + int i; + + for (i = 0; i < pool->pipe_count; i++) { + if (!res_ctx->pipe_ctx[i].stream) { + struct pipe_ctx *pipe_ctx = &res_ctx->pipe_ctx[i]; + + pipe_ctx->stream_res.tg = pool->timing_generators[i]; + pipe_ctx->plane_res.mi = pool->mis[i]; + pipe_ctx->plane_res.hubp = pool->hubps[i]; + pipe_ctx->plane_res.ipp = pool->ipps[i]; + pipe_ctx->plane_res.xfm = pool->transforms[i]; + pipe_ctx->plane_res.dpp = pool->dpps[i]; + pipe_ctx->stream_res.opp = pool->opps[i]; + 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, + const struct resource_pool *pool, + struct dc_stream_state *stream) +{ + int i; + int j = -1; + struct dc_link *link = stream->sink->link; + + for (i = 0; i < pool->stream_enc_count; i++) { + if (!res_ctx->is_stream_enc_acquired[i] && + pool->stream_enc[i]) { + /* Store first available for MST second display + * in daisy chain use case */ + j = i; + if (pool->stream_enc[i]->id == + link->link_enc->preferred_engine) + return 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 pool->stream_enc[j]; + + return NULL; +} + +static struct audio *find_first_free_audio( + struct resource_context *res_ctx, + const struct resource_pool *pool, + enum engine_id id) +{ + int i; + for (i = 0; i < pool->audio_count; i++) { + if ((res_ctx->is_audio_acquired[i] == false) && (res_ctx->is_stream_enc_acquired[i] == true)) { + /*we have enough audio endpoint, find the matching inst*/ + if (id != i) + continue; + + return pool->audios[i]; + } + } + /*not found the matching one, first come first serve*/ + for (i = 0; i < pool->audio_count; i++) { + if (res_ctx->is_audio_acquired[i] == false) { + return pool->audios[i]; + } + } + return 0; +} + +bool resource_is_stream_unchanged( + struct dc_state *old_context, struct dc_stream_state *stream) +{ + int i; + + for (i = 0; i < old_context->stream_count; i++) { + struct dc_stream_state *old_stream = old_context->streams[i]; + + if (are_stream_backends_same(old_stream, stream)) + return true; + } + + return false; +} + +enum dc_status dc_add_stream_to_ctx( + struct dc *dc, + struct dc_state *new_ctx, + struct dc_stream_state *stream) +{ + struct dc_context *dc_ctx = dc->ctx; + enum dc_status res; + + if (new_ctx->stream_count >= dc->res_pool->pipe_count) { + DC_ERROR("Max streams reached, can add stream %p !\n", stream); + return DC_ERROR_UNEXPECTED; + } + + new_ctx->streams[new_ctx->stream_count] = stream; + dc_stream_retain(stream); + new_ctx->stream_count++; + + res = dc->res_pool->funcs->add_stream_to_ctx(dc, new_ctx, stream); + if (res != DC_OK) + DC_ERROR("Adding stream %p to context failed with err %d!\n", stream, res); + + return res; +} + +enum dc_status dc_remove_stream_from_ctx( + struct dc *dc, + struct dc_state *new_ctx, + struct dc_stream_state *stream) +{ + int i; + struct dc_context *dc_ctx = dc->ctx; + struct pipe_ctx *del_pipe = NULL; + + /* Release primary pipe */ + for (i = 0; i < MAX_PIPES; i++) { + if (new_ctx->res_ctx.pipe_ctx[i].stream == stream && + !new_ctx->res_ctx.pipe_ctx[i].top_pipe) { + del_pipe = &new_ctx->res_ctx.pipe_ctx[i]; + + ASSERT(del_pipe->stream_res.stream_enc); + update_stream_engine_usage( + &new_ctx->res_ctx, + dc->res_pool, + del_pipe->stream_res.stream_enc, + false); + + if (del_pipe->stream_res.audio) + update_audio_usage( + &new_ctx->res_ctx, + dc->res_pool, + del_pipe->stream_res.audio, + false); + + resource_unreference_clock_source(&new_ctx->res_ctx, + dc->res_pool, + del_pipe->clock_source); + + memset(del_pipe, 0, sizeof(*del_pipe)); + + break; + } + } + + if (!del_pipe) { + DC_ERROR("Pipe not found for stream %p !\n", stream); + return DC_ERROR_UNEXPECTED; + } + + for (i = 0; i < new_ctx->stream_count; i++) + if (new_ctx->streams[i] == stream) + break; + + if (new_ctx->streams[i] != stream) { + DC_ERROR("Context doesn't have stream %p !\n", stream); + return DC_ERROR_UNEXPECTED; + } + + dc_stream_release(new_ctx->streams[i]); + new_ctx->stream_count--; + + /* Trim back arrays */ + for (; i < new_ctx->stream_count; i++) { + new_ctx->streams[i] = new_ctx->streams[i + 1]; + new_ctx->stream_status[i] = new_ctx->stream_status[i + 1]; + } + + new_ctx->streams[new_ctx->stream_count] = NULL; + memset( + &new_ctx->stream_status[new_ctx->stream_count], + 0, + sizeof(new_ctx->stream_status[0])); + + return DC_OK; +} + +static void copy_pipe_ctx( + const struct pipe_ctx *from_pipe_ctx, struct pipe_ctx *to_pipe_ctx) +{ + struct dc_plane_state *plane_state = to_pipe_ctx->plane_state; + struct dc_stream_state *stream = to_pipe_ctx->stream; + + *to_pipe_ctx = *from_pipe_ctx; + to_pipe_ctx->stream = stream; + if (plane_state != NULL) + to_pipe_ctx->plane_state = plane_state; +} + +static struct dc_stream_state *find_pll_sharable_stream( + struct dc_stream_state *stream_needs_pll, + struct dc_state *context) +{ + int i; + + for (i = 0; i < context->stream_count; i++) { + struct dc_stream_state *stream_has_pll = context->streams[i]; + + /* 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->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; + if (timing->pixel_encoding != PIXEL_ENCODING_YCBCR422) { + 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(struct dc_stream_state *stream) +{ + /* update actual pixel clock on all streams */ + if (dc_is_hdmi_signal(stream->signal)) + stream->phy_pix_clk = get_norm_pix_clk( + &stream->timing); + else + stream->phy_pix_clk = + stream->timing.pix_clk_khz; +} + +enum dc_status resource_map_pool_resources( + const struct dc *dc, + struct dc_state *context, + struct dc_stream_state *stream) +{ + const struct resource_pool *pool = dc->res_pool; + int i; + struct dc_context *dc_ctx = dc->ctx; + struct pipe_ctx *pipe_ctx = NULL; + int pipe_idx = -1; + + /* TODO Check if this is needed */ + /*if (!resource_is_stream_unchanged(old_context, stream)) { + if (stream != NULL && old_context->streams[i] != NULL) { + stream->bit_depth_params = + old_context->streams[i]->bit_depth_params; + stream->clamping = old_context->streams[i]->clamping; + continue; + } + } + */ + + /* acquire new resources */ + pipe_idx = acquire_first_free_pipe(&context->res_ctx, pool, stream); + +#ifdef CONFIG_DRM_AMD_DC_DCN1_0 + if (pipe_idx < 0) + pipe_idx = acquire_first_split_pipe(&context->res_ctx, pool, stream); +#endif + + if (pipe_idx < 0) + return DC_NO_CONTROLLER_RESOURCE; + + pipe_ctx = &context->res_ctx.pipe_ctx[pipe_idx]; + + pipe_ctx->stream_res.stream_enc = + find_first_free_match_stream_enc_for_link( + &context->res_ctx, pool, stream); + + if (!pipe_ctx->stream_res.stream_enc) + return DC_NO_STREAM_ENG_RESOURCE; + + update_stream_engine_usage( + &context->res_ctx, pool, + pipe_ctx->stream_res.stream_enc, + true); + + /* 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->audio_info.mode_count) { + pipe_ctx->stream_res.audio = find_first_free_audio( + &context->res_ctx, pool, pipe_ctx->stream_res.stream_enc->id); + + /* + * 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->stream_res.audio) + update_audio_usage(&context->res_ctx, pool, + pipe_ctx->stream_res.audio, true); + } + + for (i = 0; i < context->stream_count; i++) + if (context->streams[i] == stream) { + context->stream_status[i].primary_otg_inst = pipe_ctx->stream_res.tg->inst; + context->stream_status[i].stream_enc_inst = pipe_ctx->stream_res.stream_enc->id; + return DC_OK; + } + + DC_ERROR("Stream %p not found in new ctx!\n", stream); + return DC_ERROR_UNEXPECTED; +} + +/* first stream in the context is used to populate the rest */ +void validate_guaranteed_copy_streams( + struct dc_state *context, + int max_streams) +{ + int i; + + for (i = 1; i < max_streams; i++) { + context->streams[i] = context->streams[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_stream_retain(context->streams[i]); + context->stream_count++; + } +} + +void dc_resource_state_copy_construct_current( + const struct dc *dc, + struct dc_state *dst_ctx) +{ + dc_resource_state_copy_construct(dc->current_state, dst_ctx); +} + + +void dc_resource_state_construct( + const struct dc *dc, + struct dc_state *dst_ctx) +{ + dst_ctx->dis_clk = dc->res_pool->display_clock; +} + +enum dc_status dc_validate_global_state( + struct dc *dc, + struct dc_state *new_ctx) +{ + enum dc_status result = DC_ERROR_UNEXPECTED; + int i, j; + + if (!new_ctx) + return DC_ERROR_UNEXPECTED; + + if (dc->res_pool->funcs->validate_global) { + result = dc->res_pool->funcs->validate_global(dc, new_ctx); + if (result != DC_OK) + return result; + } + + for (i = 0; i < new_ctx->stream_count; i++) { + struct dc_stream_state *stream = new_ctx->streams[i]; + + for (j = 0; j < dc->res_pool->pipe_count; j++) { + struct pipe_ctx *pipe_ctx = &new_ctx->res_ctx.pipe_ctx[j]; + + if (pipe_ctx->stream != stream) + continue; + + /* 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, new_ctx)) { + + resource_unreference_clock_source( + &new_ctx->res_ctx, + dc->res_pool, + pipe_ctx->clock_source); + + pipe_ctx->clock_source = dc->res_pool->dp_clock_source; + resource_reference_clock_source( + &new_ctx->res_ctx, + dc->res_pool, + pipe_ctx->clock_source); + } + } + } + + result = resource_build_scaling_params_for_context(dc, new_ctx); + + if (result == DC_OK) + if (!dc->res_pool->funcs->validate_bandwidth(dc, new_ctx)) + result = DC_FAIL_BANDWIDTH_VALIDATE; + + return result; +} + +static void patch_gamut_packet_checksum( + struct encoder_info_packet *gamut_packet) +{ + /* For gamut we recalc checksum */ + if (gamut_packet->valid) { + uint8_t chk_sum = 0; + uint8_t *ptr; + uint8_t i; + + /*start of the Gamut data. */ + ptr = &gamut_packet->sb[3]; + + for (i = 0; i <= gamut_packet->sb[1]; i++) + chk_sum += ptr[i]; + + gamut_packet->sb[2] = (uint8_t) (0x100 - chk_sum); + } +} + +static void set_avi_info_frame( + struct encoder_info_packet *info_packet, + struct pipe_ctx *pipe_ctx) +{ + struct dc_stream_state *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 itc_value = 0; + uint8_t cn0_cn1 = 0; + unsigned int cn0_cn1_value = 0; + uint8_t *check_sum = NULL; + uint8_t byte_index = 0; + union hdmi_info_packet *hdmi_info = &info_frame.avi_info_packet.info_packet_hdmi; + union display_content_support support = {0}; + unsigned int vic = pipe_ctx->stream->timing.vic; + enum dc_timing_3d_format format; + + color_space = pipe_ctx->stream->output_color_space; + if (color_space == COLOR_SPACE_UNKNOWN) + color_space = (stream->timing.pixel_encoding == PIXEL_ENCODING_RGB) ? + COLOR_SPACE_SRGB:COLOR_SPACE_YCBCR709; + + /* Initialize header */ + hdmi_info->bits.header.info_frame_type = HDMI_INFOFRAME_TYPE_AVI; + /* InfoFrameVersion_3 is defined by CEA861F (Section 6.4), but shall + * not be used in HDMI 2.0 (Section 10.1) */ + hdmi_info->bits.header.version = 2; + hdmi_info->bits.header.length = HDMI_AVI_INFOFRAME_SIZE; + + /* + * 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->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 */ + hdmi_info->bits.Y0_Y1_Y2 = pixel_encoding; + + /* A0 = 1 Active Format Information valid */ + hdmi_info->bits.A0 = ACTIVE_FORMAT_VALID; + + /* B0, B1 = 3; Bar info data is valid */ + hdmi_info->bits.B0_B1 = BAR_INFO_BOTH_VALID; + + hdmi_info->bits.SC0_SC1 = PICTURE_SCALING_UNIFORM; + + /* S0, S1 : Underscan / Overscan */ + /* TODO: un-hardcode scan type */ + scan_type = SCANNING_TYPE_UNDERSCAN; + hdmi_info->bits.S0_S1 = scan_type; + + /* C0, C1 : Colorimetry */ + if (color_space == COLOR_SPACE_YCBCR709 || + color_space == COLOR_SPACE_YCBCR709_LIMITED) + hdmi_info->bits.C0_C1 = COLORIMETRY_ITU709; + else if (color_space == COLOR_SPACE_YCBCR601 || + color_space == COLOR_SPACE_YCBCR601_LIMITED) + hdmi_info->bits.C0_C1 = COLORIMETRY_ITU601; + else { + hdmi_info->bits.C0_C1 = COLORIMETRY_NO_DATA; + } + if (color_space == COLOR_SPACE_2020_RGB_FULLRANGE || + color_space == COLOR_SPACE_2020_RGB_LIMITEDRANGE || + color_space == COLOR_SPACE_2020_YCBCR) { + hdmi_info->bits.EC0_EC2 = COLORIMETRYEX_BT2020RGBYCBCR; + hdmi_info->bits.C0_C1 = COLORIMETRY_EXTENDED; + } else if (color_space == COLOR_SPACE_ADOBERGB) { + hdmi_info->bits.EC0_EC2 = COLORIMETRYEX_ADOBERGB; + hdmi_info->bits.C0_C1 = COLORIMETRY_EXTENDED; + } + + /* TODO: un-hardcode aspect ratio */ + aspect = stream->timing.aspect_ratio; + + switch (aspect) { + case ASPECT_RATIO_4_3: + case ASPECT_RATIO_16_9: + hdmi_info->bits.M0_M1 = aspect; + break; + + case ASPECT_RATIO_NO_DATA: + case ASPECT_RATIO_64_27: + case ASPECT_RATIO_256_135: + default: + hdmi_info->bits.M0_M1 = 0; + } + + /* Active Format Aspect ratio - same as Picture Aspect Ratio. */ + hdmi_info->bits.R0_R3 = ACTIVE_FORMAT_ASPECT_RATIO_SAME_AS_PICTURE; + + /* TODO: un-hardcode cn0_cn1 and itc */ + + cn0_cn1 = 0; + cn0_cn1_value = 0; + + itc = true; + itc_value = 1; + + support = stream->sink->edid_caps.content_support; + + if (itc) { + if (!support.bits.valid_content_type) { + cn0_cn1_value = 0; + } else { + if (cn0_cn1 == DISPLAY_CONTENT_TYPE_GRAPHICS) { + if (support.bits.graphics_content == 1) { + cn0_cn1_value = 0; + } + } else if (cn0_cn1 == DISPLAY_CONTENT_TYPE_PHOTO) { + if (support.bits.photo_content == 1) { + cn0_cn1_value = 1; + } else { + cn0_cn1_value = 0; + itc_value = 0; + } + } else if (cn0_cn1 == DISPLAY_CONTENT_TYPE_CINEMA) { + if (support.bits.cinema_content == 1) { + cn0_cn1_value = 2; + } else { + cn0_cn1_value = 0; + itc_value = 0; + } + } else if (cn0_cn1 == DISPLAY_CONTENT_TYPE_GAME) { + if (support.bits.game_content == 1) { + cn0_cn1_value = 3; + } else { + cn0_cn1_value = 0; + itc_value = 0; + } + } + } + hdmi_info->bits.CN0_CN1 = cn0_cn1_value; + hdmi_info->bits.ITC = itc_value; + } + + /* TODO : We should handle YCC quantization */ + /* but we do not have matrix calculation */ + if (stream->sink->edid_caps.qs_bit == 1 && + stream->sink->edid_caps.qy_bit == 1) { + if (color_space == COLOR_SPACE_SRGB || + color_space == COLOR_SPACE_2020_RGB_FULLRANGE) { + hdmi_info->bits.Q0_Q1 = RGB_QUANTIZATION_FULL_RANGE; + hdmi_info->bits.YQ0_YQ1 = YYC_QUANTIZATION_FULL_RANGE; + } else if (color_space == COLOR_SPACE_SRGB_LIMITED || + color_space == COLOR_SPACE_2020_RGB_LIMITEDRANGE) { + hdmi_info->bits.Q0_Q1 = RGB_QUANTIZATION_LIMITED_RANGE; + hdmi_info->bits.YQ0_YQ1 = YYC_QUANTIZATION_LIMITED_RANGE; + } else { + hdmi_info->bits.Q0_Q1 = RGB_QUANTIZATION_DEFAULT_RANGE; + hdmi_info->bits.YQ0_YQ1 = YYC_QUANTIZATION_LIMITED_RANGE; + } + } else { + hdmi_info->bits.Q0_Q1 = RGB_QUANTIZATION_DEFAULT_RANGE; + hdmi_info->bits.YQ0_YQ1 = YYC_QUANTIZATION_LIMITED_RANGE; + } + + ///VIC + format = stream->timing.timing_3d_format; + /*todo, add 3DStereo support*/ + if (format != TIMING_3D_FORMAT_NONE) { + // Based on HDMI specs hdmi vic needs to be converted to cea vic when 3D is enabled + switch (pipe_ctx->stream->timing.hdmi_vic) { + case 1: + vic = 95; + break; + case 2: + vic = 94; + break; + case 3: + vic = 93; + break; + case 4: + vic = 98; + break; + default: + break; + } + } + hdmi_info->bits.VIC0_VIC7 = vic; + + /* pixel repetition + * PR0 - PR3 start from 0 whereas pHwPathMode->mode.timing.flags.pixel + * repetition start from 1 */ + hdmi_info->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. */ + hdmi_info->bits.bar_top = stream->timing.v_border_top; + hdmi_info->bits.bar_bottom = (stream->timing.v_total + - stream->timing.v_border_bottom + 1); + hdmi_info->bits.bar_left = stream->timing.h_border_left; + hdmi_info->bits.bar_right = (stream->timing.h_total + - stream->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 = HDMI_INFOFRAME_TYPE_AVI + HDMI_AVI_INFOFRAME_SIZE + 2; + + for (byte_index = 1; byte_index <= HDMI_AVI_INFOFRAME_SIZE; byte_index++) + *check_sum += hdmi_info->packet_raw_data.sb[byte_index]; + + /* one byte complement */ + *check_sum = (uint8_t) (0x100 - *check_sum); + + /* Store in hw_path_mode */ + info_packet->hb0 = hdmi_info->packet_raw_data.hb0; + info_packet->hb1 = hdmi_info->packet_raw_data.hb1; + info_packet->hb2 = hdmi_info->packet_raw_data.hb2; + + for (byte_index = 0; byte_index < sizeof(info_frame.avi_info_packet. + info_packet_hdmi.packet_raw_data.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 encoder_info_packet *info_packet, + struct dc_stream_state *stream) +{ + uint32_t length = 0; + bool hdmi_vic_mode = false; + uint8_t checksum = 0; + uint32_t i = 0; + enum dc_timing_3d_format format; + // Can be different depending on packet content /*todo*/ + // unsigned int length = pPathMode->dolbyVision ? 24 : 5; + + info_packet->valid = false; + + format = stream->timing.timing_3d_format; + if (stream->view_format == VIEW_3D_FORMAT_NONE) + format = TIMING_3D_FORMAT_NONE; + + /* Can be different depending on packet content */ + length = 5; + + if (stream->timing.hdmi_vic != 0 + && stream->timing.h_total >= 3840 + && stream->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->timing.hdmi_vic; + + /* Header */ + info_packet->hb0 = HDMI_INFOFRAME_TYPE_VENDOR; /* 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 encoder_info_packet *info_packet, + struct dc_stream_state *stream) +{ + /* 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->freesync_ctx.supported == false) + return; + + if (dc_is_hdmi_signal(stream->signal)) { + + /* HEADER */ + + /* HB0 = Packet Type = 0x83 (Source Product + * Descriptor InfoFrame) + */ + info_packet->hb0 = HDMI_INFOFRAME_TYPE_SPD; + + /* 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 = HDMI_INFOFRAME_TYPE_SPD; + + /* 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->freesync_ctx.supported == true) + /* PB6 = [Bit 0 = FreeSync Supported] */ + info_packet->sb[6] |= 0x01; + + if (stream->freesync_ctx.enabled == true) + /* PB6 = [Bit 1 = FreeSync Enabled] */ + info_packet->sb[6] |= 0x02; + + if (stream->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->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->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_hdr_static_info_packet( + struct encoder_info_packet *info_packet, + struct dc_plane_state *plane_state, + struct dc_stream_state *stream) +{ + uint16_t i = 0; + enum signal_type signal = stream->signal; + struct dc_hdr_static_metadata hdr_metadata; + uint32_t data; + + if (!plane_state) + return; + + hdr_metadata = plane_state->hdr_static_ctx; + + if (!hdr_metadata.hdr_supported) + return; + + if (dc_is_hdmi_signal(signal)) { + info_packet->valid = true; + + info_packet->hb0 = 0x87; + info_packet->hb1 = 0x01; + info_packet->hb2 = 0x1A; + i = 1; + } else if (dc_is_dp_signal(signal)) { + info_packet->valid = true; + + info_packet->hb0 = 0x00; + info_packet->hb1 = 0x87; + info_packet->hb2 = 0x1D; + info_packet->hb3 = (0x13 << 2); + i = 2; + } + + data = hdr_metadata.is_hdr; + info_packet->sb[i++] = data ? 0x02 : 0x00; + info_packet->sb[i++] = 0x00; + + data = hdr_metadata.chromaticity_green_x / 2; + info_packet->sb[i++] = data & 0xFF; + info_packet->sb[i++] = (data & 0xFF00) >> 8; + + data = hdr_metadata.chromaticity_green_y / 2; + info_packet->sb[i++] = data & 0xFF; + info_packet->sb[i++] = (data & 0xFF00) >> 8; + + data = hdr_metadata.chromaticity_blue_x / 2; + info_packet->sb[i++] = data & 0xFF; + info_packet->sb[i++] = (data & 0xFF00) >> 8; + + data = hdr_metadata.chromaticity_blue_y / 2; + info_packet->sb[i++] = data & 0xFF; + info_packet->sb[i++] = (data & 0xFF00) >> 8; + + data = hdr_metadata.chromaticity_red_x / 2; + info_packet->sb[i++] = data & 0xFF; + info_packet->sb[i++] = (data & 0xFF00) >> 8; + + data = hdr_metadata.chromaticity_red_y / 2; + info_packet->sb[i++] = data & 0xFF; + info_packet->sb[i++] = (data & 0xFF00) >> 8; + + data = hdr_metadata.chromaticity_white_point_x / 2; + info_packet->sb[i++] = data & 0xFF; + info_packet->sb[i++] = (data & 0xFF00) >> 8; + + data = hdr_metadata.chromaticity_white_point_y / 2; + info_packet->sb[i++] = data & 0xFF; + info_packet->sb[i++] = (data & 0xFF00) >> 8; + + data = hdr_metadata.max_luminance; + info_packet->sb[i++] = data & 0xFF; + info_packet->sb[i++] = (data & 0xFF00) >> 8; + + data = hdr_metadata.min_luminance; + info_packet->sb[i++] = data & 0xFF; + info_packet->sb[i++] = (data & 0xFF00) >> 8; + + data = hdr_metadata.maximum_content_light_level; + info_packet->sb[i++] = data & 0xFF; + info_packet->sb[i++] = (data & 0xFF00) >> 8; + + data = hdr_metadata.maximum_frame_average_light_level; + info_packet->sb[i++] = data & 0xFF; + info_packet->sb[i++] = (data & 0xFF00) >> 8; + + if (dc_is_hdmi_signal(signal)) { + uint32_t checksum = 0; + + checksum += info_packet->hb0; + checksum += info_packet->hb1; + checksum += info_packet->hb2; + + for (i = 1; i <= info_packet->hb2; i++) + checksum += info_packet->sb[i]; + + info_packet->sb[0] = 0x100 - checksum; + } else if (dc_is_dp_signal(signal)) { + info_packet->sb[0] = 0x01; + info_packet->sb[1] = 0x1A; + } +} + +static void set_vsc_info_packet( + struct encoder_info_packet *info_packet, + struct dc_stream_state *stream) +{ + unsigned int vscPacketRevision = 0; + unsigned int i; + + if (stream->sink->link->psr_enabled) { + 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 dc_resource_state_destruct(struct dc_state *context) +{ + int i, j; + + for (i = 0; i < context->stream_count; i++) { + for (j = 0; j < context->stream_status[i].plane_count; j++) + dc_plane_state_release( + context->stream_status[i].plane_states[j]); + + context->stream_status[i].plane_count = 0; + dc_stream_release(context->streams[i]); + context->streams[i] = NULL; + } +} + +/* + * Copy src_ctx into dst_ctx and retain all surfaces and streams referenced + * by the src_ctx + */ +void dc_resource_state_copy_construct( + const struct dc_state *src_ctx, + struct dc_state *dst_ctx) +{ + int i, j; + struct kref refcount = dst_ctx->refcount; + + *dst_ctx = *src_ctx; + + for (i = 0; i < MAX_PIPES; 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->stream_count; i++) { + dc_stream_retain(dst_ctx->streams[i]); + for (j = 0; j < dst_ctx->stream_status[i].plane_count; j++) + dc_plane_state_retain( + dst_ctx->stream_status[i].plane_states[j]); + } + + /* context refcount should not be overridden */ + dst_ctx->refcount = refcount; + +} + +struct clock_source *dc_resource_find_first_free_pll( + struct resource_context *res_ctx, + const struct resource_pool *pool) +{ + int i; + + for (i = 0; i < pool->clk_src_count; ++i) { + if (res_ctx->clock_source_ref_count[i] == 0) + return pool->clock_sources[i]; + } + + return NULL; +} + +void resource_build_info_frame(struct pipe_ctx *pipe_ctx) +{ + enum signal_type signal = SIGNAL_TYPE_NONE; + struct encoder_info_frame *info = &pipe_ctx->stream_res.encoder_info_frame; + + /* default all packets to invalid */ + info->avi.valid = false; + info->gamut.valid = false; + info->vendor.valid = false; + info->spd.valid = false; + info->hdrsmd.valid = false; + info->vsc.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->avi, pipe_ctx); + + set_vendor_info_packet(&info->vendor, pipe_ctx->stream); + + set_spd_info_packet(&info->spd, pipe_ctx->stream); + + set_hdr_static_info_packet(&info->hdrsmd, + pipe_ctx->plane_state, pipe_ctx->stream); + + } else if (dc_is_dp_signal(signal)) { + set_vsc_info_packet(&info->vsc, pipe_ctx->stream); + + set_spd_info_packet(&info->spd, pipe_ctx->stream); + + set_hdr_static_info_packet(&info->hdrsmd, + pipe_ctx->plane_state, pipe_ctx->stream); + } + + patch_gamut_packet_checksum(&info->gamut); +} + +enum dc_status resource_map_clock_resources( + const struct dc *dc, + struct dc_state *context, + struct dc_stream_state *stream) +{ + /* acquire new resources */ + const struct resource_pool *pool = dc->res_pool; + struct pipe_ctx *pipe_ctx = resource_get_head_pipe_for_stream( + &context->res_ctx, stream); + + if (!pipe_ctx) + return DC_ERROR_UNEXPECTED; + + if (dc_is_dp_signal(pipe_ctx->stream->signal) + || pipe_ctx->stream->signal == SIGNAL_TYPE_VIRTUAL) + pipe_ctx->clock_source = pool->dp_clock_source; + else { + pipe_ctx->clock_source = NULL; + + if (!dc->config.disable_disp_pll_sharing) + pipe_ctx->clock_source = 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, + pool); + } + + if (pipe_ctx->clock_source == NULL) + return DC_NO_CLOCK_SOURCE_RESOURCE; + + resource_reference_clock_source( + &context->res_ctx, pool, + pipe_ctx->clock_source); + + 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) + return false; + + 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->stream_res.audio != pipe_ctx->stream_res.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_res.stream_enc != pipe_ctx->stream_res.stream_enc) + return true; + + if (is_timing_changed(pipe_ctx_old->stream, pipe_ctx->stream)) + return true; + + + return false; +} + +void resource_build_bit_depth_reduction_params(struct dc_stream_state *stream, + struct bit_depth_reduction_params *fmt_bit_depth) +{ + enum dc_dither_option option = stream->dither_option; + enum dc_pixel_encoding pixel_encoding = + stream->timing.pixel_encoding; + + memset(fmt_bit_depth, 0, sizeof(*fmt_bit_depth)); + + if (option == DITHER_OPTION_DEFAULT) { + switch (stream->timing.display_color_depth) { + case COLOR_DEPTH_666: + option = DITHER_OPTION_SPATIAL6; + break; + case COLOR_DEPTH_888: + option = DITHER_OPTION_SPATIAL8; + break; + case COLOR_DEPTH_101010: + option = DITHER_OPTION_SPATIAL10; + break; + default: + option = DITHER_OPTION_DISABLE; + } + } + + if (option == DITHER_OPTION_DISABLE) + return; + + if (option == DITHER_OPTION_TRUN6) { + fmt_bit_depth->flags.TRUNCATE_ENABLED = 1; + fmt_bit_depth->flags.TRUNCATE_DEPTH = 0; + } else if (option == DITHER_OPTION_TRUN8 || + option == DITHER_OPTION_TRUN8_SPATIAL6 || + option == DITHER_OPTION_TRUN8_FM6) { + fmt_bit_depth->flags.TRUNCATE_ENABLED = 1; + fmt_bit_depth->flags.TRUNCATE_DEPTH = 1; + } else if (option == DITHER_OPTION_TRUN10 || + option == DITHER_OPTION_TRUN10_SPATIAL6 || + option == DITHER_OPTION_TRUN10_SPATIAL8 || + option == DITHER_OPTION_TRUN10_FM8 || + option == DITHER_OPTION_TRUN10_FM6 || + option == DITHER_OPTION_TRUN10_SPATIAL8_FM6) { + fmt_bit_depth->flags.TRUNCATE_ENABLED = 1; + fmt_bit_depth->flags.TRUNCATE_DEPTH = 2; + } + + /* special case - Formatter can only reduce by 4 bits at most. + * When reducing from 12 to 6 bits, + * HW recommends we use trunc with round mode + * (if we did nothing, trunc to 10 bits would be used) + * note that any 12->10 bit reduction is ignored prior to DCE8, + * as the input was 10 bits. + */ + if (option == DITHER_OPTION_SPATIAL6_FRAME_RANDOM || + option == DITHER_OPTION_SPATIAL6 || + option == DITHER_OPTION_FM6) { + fmt_bit_depth->flags.TRUNCATE_ENABLED = 1; + fmt_bit_depth->flags.TRUNCATE_DEPTH = 2; + fmt_bit_depth->flags.TRUNCATE_MODE = 1; + } + + /* spatial dither + * note that spatial modes 1-3 are never used + */ + if (option == DITHER_OPTION_SPATIAL6_FRAME_RANDOM || + option == DITHER_OPTION_SPATIAL6 || + option == DITHER_OPTION_TRUN10_SPATIAL6 || + option == DITHER_OPTION_TRUN8_SPATIAL6) { + fmt_bit_depth->flags.SPATIAL_DITHER_ENABLED = 1; + fmt_bit_depth->flags.SPATIAL_DITHER_DEPTH = 0; + fmt_bit_depth->flags.HIGHPASS_RANDOM = 1; + fmt_bit_depth->flags.RGB_RANDOM = + (pixel_encoding == PIXEL_ENCODING_RGB) ? 1 : 0; + } else if (option == DITHER_OPTION_SPATIAL8_FRAME_RANDOM || + option == DITHER_OPTION_SPATIAL8 || + option == DITHER_OPTION_SPATIAL8_FM6 || + option == DITHER_OPTION_TRUN10_SPATIAL8 || + option == DITHER_OPTION_TRUN10_SPATIAL8_FM6) { + fmt_bit_depth->flags.SPATIAL_DITHER_ENABLED = 1; + fmt_bit_depth->flags.SPATIAL_DITHER_DEPTH = 1; + fmt_bit_depth->flags.HIGHPASS_RANDOM = 1; + fmt_bit_depth->flags.RGB_RANDOM = + (pixel_encoding == PIXEL_ENCODING_RGB) ? 1 : 0; + } else if (option == DITHER_OPTION_SPATIAL10_FRAME_RANDOM || + option == DITHER_OPTION_SPATIAL10 || + option == DITHER_OPTION_SPATIAL10_FM8 || + option == DITHER_OPTION_SPATIAL10_FM6) { + fmt_bit_depth->flags.SPATIAL_DITHER_ENABLED = 1; + fmt_bit_depth->flags.SPATIAL_DITHER_DEPTH = 2; + fmt_bit_depth->flags.HIGHPASS_RANDOM = 1; + fmt_bit_depth->flags.RGB_RANDOM = + (pixel_encoding == PIXEL_ENCODING_RGB) ? 1 : 0; + } + + if (option == DITHER_OPTION_SPATIAL6 || + option == DITHER_OPTION_SPATIAL8 || + option == DITHER_OPTION_SPATIAL10) { + fmt_bit_depth->flags.FRAME_RANDOM = 0; + } else { + fmt_bit_depth->flags.FRAME_RANDOM = 1; + } + + ////////////////////// + //// temporal dither + ////////////////////// + if (option == DITHER_OPTION_FM6 || + option == DITHER_OPTION_SPATIAL8_FM6 || + option == DITHER_OPTION_SPATIAL10_FM6 || + option == DITHER_OPTION_TRUN10_FM6 || + option == DITHER_OPTION_TRUN8_FM6 || + option == DITHER_OPTION_TRUN10_SPATIAL8_FM6) { + fmt_bit_depth->flags.FRAME_MODULATION_ENABLED = 1; + fmt_bit_depth->flags.FRAME_MODULATION_DEPTH = 0; + } else if (option == DITHER_OPTION_FM8 || + option == DITHER_OPTION_SPATIAL10_FM8 || + option == DITHER_OPTION_TRUN10_FM8) { + fmt_bit_depth->flags.FRAME_MODULATION_ENABLED = 1; + fmt_bit_depth->flags.FRAME_MODULATION_DEPTH = 1; + } else if (option == DITHER_OPTION_FM10) { + fmt_bit_depth->flags.FRAME_MODULATION_ENABLED = 1; + fmt_bit_depth->flags.FRAME_MODULATION_DEPTH = 2; + } + + fmt_bit_depth->pixel_encoding = pixel_encoding; +} + +enum dc_status dc_validate_stream(struct dc *dc, struct dc_stream_state *stream) +{ + struct dc *core_dc = dc; + struct dc_link *link = stream->sink->link; + struct timing_generator *tg = core_dc->res_pool->timing_generators[0]; + enum dc_status res = DC_OK; + + calculate_phy_pix_clks(stream); + + if (!tg->funcs->validate_timing(tg, &stream->timing)) + res = DC_FAIL_CONTROLLER_VALIDATE; + + if (res == DC_OK) + if (!link->link_enc->funcs->validate_output_with_stream( + link->link_enc, stream)) + res = DC_FAIL_ENC_VALIDATE; + + /* TODO: validate audio ASIC caps, encoder */ + + if (res == DC_OK) + res = dc_link_validate_mode_timing(stream, + link, + &stream->timing); + + return res; +} + +enum dc_status dc_validate_plane(struct dc *dc, const struct dc_plane_state *plane_state) +{ + enum dc_status res = DC_OK; + + /* TODO For now validates pixel format only */ + if (dc->res_pool->funcs->validate_plane) + return dc->res_pool->funcs->validate_plane(plane_state, &dc->caps); + + return res; +} 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..25fae38409ab --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/core/dc_sink.c @@ -0,0 +1,104 @@ +/* + * 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 functions + ******************************************************************************/ + +static void destruct(struct dc_sink *sink) +{ + if (sink->dc_container_id) { + kfree(sink->dc_container_id); + sink->dc_container_id = NULL; + } +} + +static bool construct(struct dc_sink *sink, const struct dc_sink_init_data *init_params) +{ + + struct dc_link *link = init_params->link; + + if (!link) + return false; + + sink->sink_signal = init_params->sink_signal; + sink->link = link; + sink->ctx = link->ctx; + sink->dongle_max_pix_clk = init_params->dongle_max_pix_clk; + sink->converter_disable_audio = init_params->converter_disable_audio; + sink->dc_container_id = NULL; + + return true; +} + +/******************************************************************************* + * Public functions + ******************************************************************************/ + +void dc_sink_retain(struct dc_sink *sink) +{ + kref_get(&sink->refcount); +} + +static void dc_sink_free(struct kref *kref) +{ + struct dc_sink *sink = container_of(kref, struct dc_sink, refcount); + destruct(sink); + kfree(sink); +} + +void dc_sink_release(struct dc_sink *sink) +{ + kref_put(&sink->refcount, dc_sink_free); +} + +struct dc_sink *dc_sink_create(const struct dc_sink_init_data *init_params) +{ + struct dc_sink *sink = kzalloc(sizeof(*sink), GFP_KERNEL); + + if (NULL == sink) + goto alloc_fail; + + if (false == construct(sink, init_params)) + goto construct_fail; + + kref_init(&sink->refcount); + + return sink; + +construct_fail: + kfree(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..e230cc44a0a7 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c @@ -0,0 +1,397 @@ +/* + * 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" +#include "ipp.h" +#include "timing_generator.h" + +/******************************************************************************* + * Private functions + ******************************************************************************/ +#define TMDS_MAX_PIXEL_CLOCK_IN_KHZ_UPMOST 297000 +static void update_stream_signal(struct dc_stream_state *stream) +{ + if (stream->output_signal == SIGNAL_TYPE_NONE) { + struct dc_sink *dc_sink = stream->sink; + + if (dc_sink->sink_signal == SIGNAL_TYPE_NONE) + stream->signal = stream->sink->link->connector_signal; + else + stream->signal = dc_sink->sink_signal; + } else { + stream->signal = stream->output_signal; + } + + if (dc_is_dvi_signal(stream->signal)) { + if (stream->timing.pix_clk_khz > TMDS_MAX_PIXEL_CLOCK_IN_KHZ_UPMOST && + stream->sink->sink_signal != SIGNAL_TYPE_DVI_SINGLE_LINK) + stream->signal = SIGNAL_TYPE_DVI_DUAL_LINK; + else + stream->signal = SIGNAL_TYPE_DVI_SINGLE_LINK; + } +} + +static void construct(struct dc_stream_state *stream, + struct dc_sink *dc_sink_data) +{ + uint32_t i = 0; + + stream->sink = dc_sink_data; + stream->ctx = stream->sink->ctx; + + 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->audio_info.modes[i].channel_count = dc_sink_data->edid_caps.audio_modes[i].channel_count; + stream->audio_info.modes[i].format_code = dc_sink_data->edid_caps.audio_modes[i].format_code; + stream->audio_info.modes[i].sample_rates.all = dc_sink_data->edid_caps.audio_modes[i].sample_rate; + stream->audio_info.modes[i].sample_size = dc_sink_data->edid_caps.audio_modes[i].sample_size; + } + stream->audio_info.mode_count = dc_sink_data->edid_caps.audio_mode_count; + stream->audio_info.audio_latency = dc_sink_data->edid_caps.audio_latency; + stream->audio_info.video_latency = dc_sink_data->edid_caps.video_latency; + memmove( + stream->audio_info.display_name, + dc_sink_data->edid_caps.display_name, + AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS); + stream->audio_info.manufacture_id = dc_sink_data->edid_caps.manufacturer_id; + stream->audio_info.product_id = dc_sink_data->edid_caps.product_id; + stream->audio_info.flags.all = dc_sink_data->edid_caps.speaker_flags; + + if (dc_sink_data->dc_container_id != NULL) { + struct dc_container_id *dc_container_id = dc_sink_data->dc_container_id; + + stream->audio_info.port_id[0] = dc_container_id->portId[0]; + stream->audio_info.port_id[1] = dc_container_id->portId[1]; + } else { + /* TODO - WindowDM has implemented, + other DMs need Unhardcode port_id */ + stream->audio_info.port_id[0] = 0x5558859e; + stream->audio_info.port_id[1] = 0xd989449; + } + + /* EDID CAP translation for HDMI 2.0 */ + stream->timing.flags.LTE_340MCSC_SCRAMBLE = dc_sink_data->edid_caps.lte_340mcsc_scramble; + + stream->status.link = stream->sink->link; + + update_stream_signal(stream); +} + +static void destruct(struct dc_stream_state *stream) +{ + dc_sink_release(stream->sink); + if (stream->out_transfer_func != NULL) { + dc_transfer_func_release( + stream->out_transfer_func); + stream->out_transfer_func = NULL; + } +} + +void dc_stream_retain(struct dc_stream_state *stream) +{ + kref_get(&stream->refcount); +} + +static void dc_stream_free(struct kref *kref) +{ + struct dc_stream_state *stream = container_of(kref, struct dc_stream_state, refcount); + + destruct(stream); + kfree(stream); +} + +void dc_stream_release(struct dc_stream_state *stream) +{ + if (stream != NULL) { + kref_put(&stream->refcount, dc_stream_free); + } +} + +struct dc_stream_state *dc_create_stream_for_sink( + struct dc_sink *sink) +{ + struct dc_stream_state *stream; + + if (sink == NULL) + return NULL; + + stream = kzalloc(sizeof(struct dc_stream_state), GFP_KERNEL); + if (stream == NULL) + return NULL; + + construct(stream, sink); + + kref_init(&stream->refcount); + + return stream; +} + +struct dc_stream_status *dc_stream_get_status( + struct dc_stream_state *stream) +{ + uint8_t i; + struct dc *dc = stream->ctx->dc; + + for (i = 0; i < dc->current_state->stream_count; i++) { + if (stream == dc->current_state->streams[i]) + return &dc->current_state->stream_status[i]; + } + + return NULL; +} + +/** + * Update the cursor attributes and set cursor surface address + */ +bool dc_stream_set_cursor_attributes( + struct dc_stream_state *stream, + const struct dc_cursor_attributes *attributes) +{ + int i; + struct dc *core_dc; + struct resource_context *res_ctx; + + if (NULL == stream) { + dm_error("DC: dc_stream is NULL!\n"); + return false; + } + if (NULL == attributes) { + dm_error("DC: attributes is NULL!\n"); + return false; + } + + if (attributes->address.quad_part == 0) { + dm_output_to_console("DC: Cursor address is 0!\n"); + return false; + } + + core_dc = stream->ctx->dc; + res_ctx = &core_dc->current_state->res_ctx; + + for (i = 0; i < MAX_PIPES; i++) { + struct pipe_ctx *pipe_ctx = &res_ctx->pipe_ctx[i]; + + if (pipe_ctx->stream != stream || (!pipe_ctx->plane_res.xfm && !pipe_ctx->plane_res.dpp)) + continue; + if (pipe_ctx->top_pipe && pipe_ctx->plane_state != pipe_ctx->top_pipe->plane_state) + continue; + + + if (pipe_ctx->plane_res.ipp->funcs->ipp_cursor_set_attributes != NULL) + pipe_ctx->plane_res.ipp->funcs->ipp_cursor_set_attributes( + pipe_ctx->plane_res.ipp, attributes); + + if (pipe_ctx->plane_res.hubp != NULL && + pipe_ctx->plane_res.hubp->funcs->set_cursor_attributes != NULL) + pipe_ctx->plane_res.hubp->funcs->set_cursor_attributes( + pipe_ctx->plane_res.hubp, attributes); + + if (pipe_ctx->plane_res.mi != NULL && + pipe_ctx->plane_res.mi->funcs->set_cursor_attributes != NULL) + pipe_ctx->plane_res.mi->funcs->set_cursor_attributes( + pipe_ctx->plane_res.mi, attributes); + + + if (pipe_ctx->plane_res.xfm != NULL && + pipe_ctx->plane_res.xfm->funcs->set_cursor_attributes != NULL) + pipe_ctx->plane_res.xfm->funcs->set_cursor_attributes( + pipe_ctx->plane_res.xfm, attributes); + + if (pipe_ctx->plane_res.dpp != NULL && + pipe_ctx->plane_res.dpp->funcs->set_cursor_attributes != NULL) + pipe_ctx->plane_res.dpp->funcs->set_cursor_attributes( + pipe_ctx->plane_res.dpp, attributes); + } + + stream->cursor_attributes = *attributes; + + return true; +} + +bool dc_stream_set_cursor_position( + struct dc_stream_state *stream, + const struct dc_cursor_position *position) +{ + int i; + struct dc *core_dc; + struct resource_context *res_ctx; + + if (NULL == stream) { + dm_error("DC: dc_stream is NULL!\n"); + return false; + } + + if (NULL == position) { + dm_error("DC: cursor position is NULL!\n"); + return false; + } + + core_dc = stream->ctx->dc; + res_ctx = &core_dc->current_state->res_ctx; + + for (i = 0; i < MAX_PIPES; i++) { + struct pipe_ctx *pipe_ctx = &res_ctx->pipe_ctx[i]; + struct input_pixel_processor *ipp = pipe_ctx->plane_res.ipp; + struct mem_input *mi = pipe_ctx->plane_res.mi; + struct hubp *hubp = pipe_ctx->plane_res.hubp; + struct dpp *dpp = pipe_ctx->plane_res.dpp; + struct dc_cursor_position pos_cpy = *position; + struct dc_cursor_mi_param param = { + .pixel_clk_khz = stream->timing.pix_clk_khz, + .ref_clk_khz = core_dc->res_pool->ref_clock_inKhz, + .viewport_x_start = pipe_ctx->plane_res.scl_data.viewport.x, + .viewport_width = pipe_ctx->plane_res.scl_data.viewport.width, + .h_scale_ratio = pipe_ctx->plane_res.scl_data.ratios.horz + }; + + if (pipe_ctx->stream != stream || + (!pipe_ctx->plane_res.mi && !pipe_ctx->plane_res.hubp) || + !pipe_ctx->plane_state || + (!pipe_ctx->plane_res.xfm && !pipe_ctx->plane_res.dpp)) + continue; + + if (pipe_ctx->plane_state->address.type + == PLN_ADDR_TYPE_VIDEO_PROGRESSIVE) + pos_cpy.enable = false; + + if (pipe_ctx->top_pipe && pipe_ctx->plane_state != pipe_ctx->top_pipe->plane_state) + pos_cpy.enable = false; + + + if (ipp != NULL && ipp->funcs->ipp_cursor_set_position != NULL) + ipp->funcs->ipp_cursor_set_position(ipp, &pos_cpy, ¶m); + + if (mi != NULL && mi->funcs->set_cursor_position != NULL) + mi->funcs->set_cursor_position(mi, &pos_cpy, ¶m); + + if (!hubp) + continue; + + if (hubp->funcs->set_cursor_position != NULL) + hubp->funcs->set_cursor_position(hubp, &pos_cpy, ¶m); + + if (dpp != NULL && dpp->funcs->set_cursor_position != NULL) + dpp->funcs->set_cursor_position(dpp, &pos_cpy, ¶m, hubp->curs_attr.width); + + } + + return true; +} + +uint32_t dc_stream_get_vblank_counter(const struct dc_stream_state *stream) +{ + uint8_t i; + struct dc *core_dc = stream->ctx->dc; + struct resource_context *res_ctx = + &core_dc->current_state->res_ctx; + + for (i = 0; i < MAX_PIPES; i++) { + struct timing_generator *tg = res_ctx->pipe_ctx[i].stream_res.tg; + + if (res_ctx->pipe_ctx[i].stream != stream) + continue; + + return tg->funcs->get_frame_count(tg); + } + + return 0; +} + +bool dc_stream_get_scanoutpos(const struct dc_stream_state *stream, + uint32_t *v_blank_start, + uint32_t *v_blank_end, + uint32_t *h_position, + uint32_t *v_position) +{ + uint8_t i; + bool ret = false; + struct dc *core_dc = stream->ctx->dc; + struct resource_context *res_ctx = + &core_dc->current_state->res_ctx; + + for (i = 0; i < MAX_PIPES; i++) { + struct timing_generator *tg = res_ctx->pipe_ctx[i].stream_res.tg; + + if (res_ctx->pipe_ctx[i].stream != stream) + continue; + + tg->funcs->get_scanoutpos(tg, + v_blank_start, + v_blank_end, + h_position, + v_position); + + ret = true; + break; + } + + return ret; +} + + +void dc_stream_log( + const struct dc_stream_state *stream, + struct dal_logger *dm_logger, + enum dc_log_type log_type) +{ + + dm_logger_write(dm_logger, + log_type, + "core_stream 0x%x: src: %d, %d, %d, %d; dst: %d, %d, %d, %d, colorSpace:%d\n", + stream, + stream->src.x, + stream->src.y, + stream->src.width, + stream->src.height, + stream->dst.x, + stream->dst.y, + stream->dst.width, + stream->dst.height, + stream->output_color_space); + dm_logger_write(dm_logger, + log_type, + "\tpix_clk_khz: %d, h_total: %d, v_total: %d, pixelencoder:%d, displaycolorDepth:%d\n", + stream->timing.pix_clk_khz, + stream->timing.h_total, + stream->timing.v_total, + stream->timing.pixel_encoding, + stream->timing.display_color_depth); + dm_logger_write(dm_logger, + log_type, + "\tsink name: %s, serial: %d\n", + stream->sink->edid_caps.display_name, + stream->sink->edid_caps.serial_number); + dm_logger_write(dm_logger, + log_type, + "\tlink: %d\n", + stream->sink->link->link_index); +} 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..ade5b8ee9c3c --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c @@ -0,0 +1,193 @@ +/* + * 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_types.h" +#include "transform.h" +#include "dpp.h" + +/******************************************************************************* + * Private functions + ******************************************************************************/ +static void construct(struct dc_context *ctx, struct dc_plane_state *plane_state) +{ + plane_state->ctx = ctx; +} + +static void destruct(struct dc_plane_state *plane_state) +{ + if (plane_state->gamma_correction != NULL) { + dc_gamma_release(&plane_state->gamma_correction); + } + if (plane_state->in_transfer_func != NULL) { + dc_transfer_func_release( + plane_state->in_transfer_func); + plane_state->in_transfer_func = NULL; + } +} + +/******************************************************************************* + * Public functions + ******************************************************************************/ +void enable_surface_flip_reporting(struct dc_plane_state *plane_state, + uint32_t controller_id) +{ + plane_state->irq_source = controller_id + DC_IRQ_SOURCE_PFLIP1 - 1; + /*register_flip_interrupt(surface);*/ +} + +struct dc_plane_state *dc_create_plane_state(struct dc *dc) +{ + struct dc *core_dc = dc; + + struct dc_plane_state *plane_state = kzalloc(sizeof(*plane_state), + GFP_KERNEL); + + if (NULL == plane_state) + return NULL; + + kref_init(&plane_state->refcount); + construct(core_dc->ctx, plane_state); + + return plane_state; +} + +const struct dc_plane_status *dc_plane_get_status( + const struct dc_plane_state *plane_state) +{ + const struct dc_plane_status *plane_status; + struct dc *core_dc; + int i; + + if (!plane_state || + !plane_state->ctx || + !plane_state->ctx->dc) { + ASSERT(0); + return NULL; /* remove this if above assert never hit */ + } + + plane_status = &plane_state->status; + core_dc = plane_state->ctx->dc; + + if (core_dc->current_state == NULL) + return NULL; + + for (i = 0; i < core_dc->res_pool->pipe_count; i++) { + struct pipe_ctx *pipe_ctx = + &core_dc->current_state->res_ctx.pipe_ctx[i]; + + if (pipe_ctx->plane_state != plane_state) + continue; + + core_dc->hwss.update_pending_status(pipe_ctx); + } + + return plane_status; +} + +void dc_plane_state_retain(struct dc_plane_state *plane_state) +{ + kref_get(&plane_state->refcount); +} + +static void dc_plane_state_free(struct kref *kref) +{ + struct dc_plane_state *plane_state = container_of(kref, struct dc_plane_state, refcount); + destruct(plane_state); + kfree(plane_state); +} + +void dc_plane_state_release(struct dc_plane_state *plane_state) +{ + kref_put(&plane_state->refcount, dc_plane_state_free); +} + +void dc_gamma_retain(struct dc_gamma *gamma) +{ + kref_get(&gamma->refcount); +} + +static void dc_gamma_free(struct kref *kref) +{ + struct dc_gamma *gamma = container_of(kref, struct dc_gamma, refcount); + kfree(gamma); +} + +void dc_gamma_release(struct dc_gamma **gamma) +{ + kref_put(&(*gamma)->refcount, dc_gamma_free); + *gamma = NULL; +} + +struct dc_gamma *dc_create_gamma(void) +{ + struct dc_gamma *gamma = kzalloc(sizeof(*gamma), GFP_KERNEL); + + if (gamma == NULL) + goto alloc_fail; + + kref_init(&gamma->refcount); + return gamma; + +alloc_fail: + return NULL; +} + +void dc_transfer_func_retain(struct dc_transfer_func *tf) +{ + kref_get(&tf->refcount); +} + +static void dc_transfer_func_free(struct kref *kref) +{ + struct dc_transfer_func *tf = container_of(kref, struct dc_transfer_func, refcount); + kfree(tf); +} + +void dc_transfer_func_release(struct dc_transfer_func *tf) +{ + kref_put(&tf->refcount, dc_transfer_func_free); +} + +struct dc_transfer_func *dc_create_transfer_func(void) +{ + struct dc_transfer_func *tf = kzalloc(sizeof(*tf), GFP_KERNEL); + + if (tf == NULL) + goto alloc_fail; + + kref_init(&tf->refcount); + + return tf; + +alloc_fail: + return NULL; +} + + |