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