diff options
Diffstat (limited to 'common/recipes-kernel/linux/files/0511-drm-amd-dal-Adding-amdgpu_dm-for-dal-v2.patch')
-rw-r--r-- | common/recipes-kernel/linux/files/0511-drm-amd-dal-Adding-amdgpu_dm-for-dal-v2.patch | 6016 |
1 files changed, 6016 insertions, 0 deletions
diff --git a/common/recipes-kernel/linux/files/0511-drm-amd-dal-Adding-amdgpu_dm-for-dal-v2.patch b/common/recipes-kernel/linux/files/0511-drm-amd-dal-Adding-amdgpu_dm-for-dal-v2.patch new file mode 100644 index 00000000..c709f6e8 --- /dev/null +++ b/common/recipes-kernel/linux/files/0511-drm-amd-dal-Adding-amdgpu_dm-for-dal-v2.patch @@ -0,0 +1,6016 @@ +From a04ef2511da8e6d563253dd70f193979ca0ebb81 Mon Sep 17 00:00:00 2001 +From: Harry Wentland <harry.wentland@amd.com> +Date: Wed, 25 Nov 2015 14:48:16 -0500 +Subject: [PATCH 0511/1110] drm/amd/dal: Adding amdgpu_dm for dal (v2) + +v2: agd: fix for API changes in kernel 4.6 + +Signed-off-by: Harry Wentland <harry.wentland@amd.com> +Acked-by: Alex Deucher <alexander.deucher@amd.com> +--- + drivers/gpu/drm/amd/dal/amdgpu_dm/Makefile | 17 + + .../drm/amd/dal/amdgpu_dm/amdgpu_dal_services.c | 251 ++ + .../gpu/drm/amd/dal/amdgpu_dm/amdgpu_dc_helpers.c | 350 +++ + drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.c | 1318 +++++++++++ + drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.h | 166 ++ + drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.c | 814 +++++++ + drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.h | 122 + + .../drm/amd/dal/amdgpu_dm/amdgpu_dm_mst_types.c | 353 +++ + .../drm/amd/dal/amdgpu_dm/amdgpu_dm_mst_types.h | 36 + + .../gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.c | 2390 ++++++++++++++++++++ + .../gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.h | 96 + + 11 files changed, 5913 insertions(+) + create mode 100644 drivers/gpu/drm/amd/dal/amdgpu_dm/Makefile + create mode 100644 drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dal_services.c + create mode 100644 drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dc_helpers.c + create mode 100644 drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.c + create mode 100644 drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.h + create mode 100644 drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.c + create mode 100644 drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.h + create mode 100644 drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_mst_types.c + create mode 100644 drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_mst_types.h + create mode 100644 drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.c + create mode 100644 drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.h + +diff --git a/drivers/gpu/drm/amd/dal/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/dal/amdgpu_dm/Makefile +new file mode 100644 +index 0000000..65ad370 +--- /dev/null ++++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/Makefile +@@ -0,0 +1,17 @@ ++# ++# Makefile for the 'dm' sub-component of DAL. ++# It provides the control and status of dm blocks. ++ ++ ++ ++AMDGPUDM = amdgpu_dm_types.o amdgpu_dm.o amdgpu_dm_irq.o amdgpu_dm_mst_types.o ++ ++ifneq ($(CONFIG_DRM_AMD_DAL),) ++AMDGPUDM += amdgpu_dal_services.o amdgpu_dc_helpers.o ++endif ++ ++subdir-ccflags-y += -I$(FULL_AMD_DAL_PATH)/dc ++ ++AMDGPU_DM = $(addprefix $(AMDDALPATH)/amdgpu_dm/,$(AMDGPUDM)) ++ ++AMD_DAL_FILES += $(AMDGPU_DM) +diff --git a/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dal_services.c b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dal_services.c +new file mode 100644 +index 0000000..a497093 +--- /dev/null ++++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dal_services.c +@@ -0,0 +1,251 @@ ++/* ++ * 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 <linux/string.h> ++#include <linux/acpi.h> ++ ++#include <drm/drmP.h> ++#include <drm/drm_crtc_helper.h> ++#include <drm/amdgpu_drm.h> ++ ++#include "amdgpu.h" ++#include "dal_services.h" ++#include "amdgpu_dm.h" ++#include "amdgpu_dm_irq.h" ++#include "amdgpu_dm_types.h" ++#include "amdgpu_pm.h" ++ ++/* ++#include "logger_interface.h" ++#include "acpimethod_atif.h" ++#include "amdgpu_powerplay.h" ++#include "amdgpu_notifications.h" ++*/ ++ ++/* if the pointer is not NULL, the allocated memory is zeroed */ ++void *dc_service_alloc(struct dc_context *ctx, uint32_t size) ++{ ++ return kzalloc(size, GFP_KERNEL); ++} ++ ++/* Reallocate memory. The contents will remain unchanged.*/ ++void *dc_service_realloc(struct dc_context *ctx, const void *ptr, uint32_t size) ++{ ++ return krealloc(ptr, size, GFP_KERNEL); ++} ++ ++void dc_service_memmove(void *dst, const void *src, uint32_t size) ++{ ++ memmove(dst, src, size); ++} ++ ++void dc_service_free(struct dc_context *ctx, void *p) ++{ ++ kfree(p); ++} ++ ++void dc_service_memset(void *p, int32_t c, uint32_t count) ++{ ++ memset(p, c, count); ++} ++ ++int32_t dal_memcmp(const void *p1, const void *p2, uint32_t count) ++{ ++ return memcmp(p1, p2, count); ++} ++ ++int32_t dal_strncmp(const int8_t *p1, const int8_t *p2, uint32_t count) ++{ ++ return strncmp(p1, p2, count); ++} ++ ++void dc_service_sleep_in_milliseconds(struct dc_context *ctx, uint32_t milliseconds) ++{ ++ if (milliseconds >= 20) ++ msleep(milliseconds); ++ else ++ usleep_range(milliseconds*1000, milliseconds*1000+1); ++} ++ ++void dal_delay_in_nanoseconds(uint32_t nanoseconds) ++{ ++ ndelay(nanoseconds); ++} ++ ++void dc_service_delay_in_microseconds(struct dc_context *ctx, uint32_t microseconds) ++{ ++ udelay(microseconds); ++} ++ ++/****************************************************************************** ++ * IRQ Interfaces. ++ *****************************************************************************/ ++ ++void dal_register_timer_interrupt( ++ struct dc_context *ctx, ++ struct dc_timer_interrupt_params *int_params, ++ interrupt_handler ih, ++ void *args) ++{ ++ struct amdgpu_device *adev = ctx->driver_context; ++ ++ if (!adev || !int_params) { ++ DRM_ERROR("DM_IRQ: invalid input!\n"); ++ return; ++ } ++ ++ if (int_params->int_context != INTERRUPT_LOW_IRQ_CONTEXT) { ++ /* only low irq ctx is supported. */ ++ DRM_ERROR("DM_IRQ: invalid context: %d!\n", ++ int_params->int_context); ++ return; ++ } ++ ++ amdgpu_dm_irq_register_timer(adev, int_params, ih, args); ++} ++ ++void dal_isr_acquire_lock(struct dc_context *ctx) ++{ ++ /*TODO*/ ++} ++ ++void dal_isr_release_lock(struct dc_context *ctx) ++{ ++ /*TODO*/ ++} ++ ++/****************************************************************************** ++ * End-of-IRQ Interfaces. ++ *****************************************************************************/ ++ ++bool dal_get_platform_info(struct dc_context *ctx, ++ struct platform_info_params *params) ++{ ++ /*TODO*/ ++ return false; ++} ++ ++/* Next calls are to power component */ ++bool dc_service_pp_pre_dce_clock_change(struct dc_context *ctx, ++ struct dal_to_power_info *input, ++ struct power_to_dal_info *output) ++{ ++ /*TODO*/ ++ return false; ++} ++ ++bool dc_service_pp_post_dce_clock_change(struct dc_context *ctx, ++ const struct dc_pp_display_configuration *pp_display_cfg) ++{ ++#ifdef CONFIG_DRM_AMD_POWERPLAY ++ struct amdgpu_device *adev = ctx->driver_context; ++ ++ if (adev->pm.dpm_enabled) { ++ ++ memset(&adev->pm.pm_display_cfg, 0, ++ sizeof(adev->pm.pm_display_cfg)); ++ ++ adev->pm.pm_display_cfg.cpu_cc6_disable = ++ pp_display_cfg->cpu_cc6_disable; ++ ++ adev->pm.pm_display_cfg.cpu_pstate_disable = ++ pp_display_cfg->cpu_pstate_disable; ++ ++ adev->pm.pm_display_cfg.cpu_pstate_separation_time = ++ pp_display_cfg->cpu_pstate_separation_time; ++ ++ adev->pm.pm_display_cfg.nb_pstate_switch_disable = ++ pp_display_cfg->nb_pstate_switch_disable; ++ ++ amd_powerplay_display_configuration_change( ++ adev->powerplay.pp_handle, ++ &adev->pm.pm_display_cfg); ++ ++ /* TODO: replace by a separate call to 'apply display cfg'? */ ++ amdgpu_pm_compute_clocks(adev); ++ } ++ return true; ++#else ++ return false; ++#endif ++} ++ ++bool dc_service_get_system_clocks_range(struct dc_context *ctx, ++ struct dal_system_clock_range *sys_clks) ++{ ++ struct amdgpu_device *adev = ctx->driver_context; ++ ++ /* Default values, in case PPLib is not compiled-in. */ ++ sys_clks->max_mclk = 80000; ++ sys_clks->min_mclk = 80000; ++ ++ sys_clks->max_sclk = 60000; ++ sys_clks->min_sclk = 30000; ++ ++#ifdef CONFIG_DRM_AMD_POWERPLAY ++ if (adev->pm.dpm_enabled) { ++ sys_clks->max_mclk = amdgpu_dpm_get_mclk(adev, false); ++ sys_clks->min_mclk = amdgpu_dpm_get_mclk(adev, true); ++ ++ sys_clks->max_sclk = amdgpu_dpm_get_sclk(adev, false); ++ sys_clks->min_sclk = amdgpu_dpm_get_sclk(adev, true); ++ } ++#endif ++ ++ return true; ++} ++ ++ ++bool dc_service_pp_set_display_clock(struct dc_context *ctx, ++ struct dal_to_power_dclk *dclk) ++{ ++ /* TODO: need power component to provide appropriate interface */ ++ return false; ++} ++ ++/* end of calls to power component */ ++ ++/* Calls to notification */ ++ ++void dal_notify_setmode_complete(struct dc_context *ctx, ++ uint32_t h_total, ++ uint32_t v_total, ++ uint32_t h_active, ++ uint32_t v_active, ++ uint32_t pix_clk_in_khz) ++{ ++ /*TODO*/ ++} ++/* End of calls to notification */ ++ ++long dal_get_pid(void) ++{ ++ return current->pid; ++} ++ ++long dal_get_tgid(void) ++{ ++ return current->tgid; ++} +diff --git a/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dc_helpers.c b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dc_helpers.c +new file mode 100644 +index 0000000..beaef70 +--- /dev/null ++++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dc_helpers.c +@@ -0,0 +1,350 @@ ++/* ++ * 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 <linux/string.h> ++#include <linux/acpi.h> ++ ++#include <drm/drmP.h> ++#include <drm/drm_crtc_helper.h> ++#include <drm/amdgpu_drm.h> ++#include <drm/drm_edid.h> ++ ++#include "dc_types.h" ++#include "amdgpu.h" ++#include "dc.h" ++#include "dc_services.h" ++ ++#include "amdgpu_dm.h" ++#include "amdgpu_dm_irq.h" ++#include "amdgpu_dm_types.h" ++ ++/* dc_helpers_parse_edid_caps ++ * ++ * Parse edid caps ++ * ++ * @edid: [in] pointer to edid ++ * edid_caps: [in] pointer to edid caps ++ * @return ++ * void ++ * */ ++enum dc_edid_status dc_helpers_parse_edid_caps( ++ struct dc_context *ctx, ++ const struct dc_edid *edid, ++ struct dc_edid_caps *edid_caps) ++{ ++ struct edid *edid_buf = (struct edid *) edid->raw_edid; ++ struct cea_sad *sads; ++ int sad_count = -1; ++ int sadb_count = -1; ++ int i = 0; ++ int j = 0; ++ uint8_t *sadb = NULL; ++ ++ enum dc_edid_status result = EDID_OK; ++ ++ if (!edid_caps || !edid) ++ return EDID_BAD_INPUT; ++ ++ if (!drm_edid_is_valid(edid_buf)) ++ result = EDID_BAD_CHECKSUM; ++ ++ edid_caps->manufacturer_id = (uint16_t) edid_buf->mfg_id[0] | ++ ((uint16_t) edid_buf->mfg_id[1])<<8; ++ edid_caps->product_id = (uint16_t) edid_buf->prod_code[0] | ++ ((uint16_t) edid_buf->prod_code[1])<<8; ++ edid_caps->serial_number = edid_buf->serial; ++ edid_caps->manufacture_week = edid_buf->mfg_week; ++ edid_caps->manufacture_year = edid_buf->mfg_year; ++ ++ /* One of the four detailed_timings stores the monitor name. It's ++ * stored in an array of length 13. */ ++ for (i = 0; i < 4; i++) { ++ if (edid_buf->detailed_timings[i].data.other_data.type == 0xfc) { ++ while (edid_buf->detailed_timings[i].data.other_data.data.str.str[j] && j < 13) { ++ if (edid_buf->detailed_timings[i].data.other_data.data.str.str[j] == '\n') ++ break; ++ ++ edid_caps->display_name[j] = ++ edid_buf->detailed_timings[i].data.other_data.data.str.str[j]; ++ j++; ++ } ++ } ++ } ++ ++ sad_count = drm_edid_to_sad((struct edid *) edid->raw_edid, &sads); ++ if (sad_count <= 0) { ++ DRM_INFO("SADs count is: %d, don't need to read it\n", ++ sad_count); ++ return result; ++ } ++ ++ edid_caps->audio_mode_count = sad_count < DC_MAX_AUDIO_DESC_COUNT ? sad_count : DC_MAX_AUDIO_DESC_COUNT; ++ for (i = 0; i < edid_caps->audio_mode_count; ++i) { ++ struct cea_sad *sad = &sads[i]; ++ ++ edid_caps->audio_modes[i].format_code = sad->format; ++ edid_caps->audio_modes[i].channel_count = sad->channels; ++ edid_caps->audio_modes[i].sample_rate = sad->freq; ++ edid_caps->audio_modes[i].sample_size = sad->byte2; ++ } ++ ++ sadb_count = drm_edid_to_speaker_allocation((struct edid *) edid->raw_edid, &sadb); ++ ++ if (sadb_count < 0) { ++ DRM_ERROR("Couldn't read Speaker Allocation Data Block: %d\n", sadb_count); ++ sadb_count = 0; ++ } ++ ++ if (sadb_count) ++ edid_caps->speaker_flags = sadb[0]; ++ else ++ edid_caps->speaker_flags = DEFAULT_SPEAKER_LOCATION; ++ ++ kfree(sads); ++ kfree(sadb); ++ ++ return result; ++} ++ ++ ++static struct amdgpu_connector *get_connector_for_sink( ++ struct drm_device *dev, ++ const struct dc_sink *sink) ++{ ++ struct drm_connector *connector; ++ struct amdgpu_connector *aconnector = NULL; ++ ++ list_for_each_entry(connector, &dev->mode_config.connector_list, head) { ++ aconnector = to_amdgpu_connector(connector); ++ if (aconnector->dc_sink == sink) ++ break; ++ } ++ ++ return aconnector; ++} ++ ++static struct amdgpu_connector *get_connector_for_link( ++ struct drm_device *dev, ++ const struct dc_link *link) ++{ ++ struct drm_connector *connector; ++ struct amdgpu_connector *aconnector = NULL; ++ ++ list_for_each_entry(connector, &dev->mode_config.connector_list, head) { ++ aconnector = to_amdgpu_connector(connector); ++ if (aconnector->dc_link == link) ++ break; ++ } ++ ++ return aconnector; ++} ++ ++/* ++ * Writes payload allocation table in immediate downstream device. ++ */ ++bool dc_helpers_dp_mst_write_payload_allocation_table( ++ struct dc_context *ctx, ++ const struct dc_sink *sink, ++ struct dp_mst_stream_allocation *alloc_entity, ++ bool enable) ++{ ++ struct amdgpu_device *adev = ctx->driver_context; ++ struct drm_device *dev = adev->ddev; ++ struct amdgpu_connector *aconnector; ++ struct drm_crtc *crtc; ++ struct drm_dp_mst_topology_mgr *mst_mgr; ++ struct drm_dp_mst_port *mst_port; ++ int slots = 0; ++ bool ret; ++ int clock; ++ int bpp; ++ int pbn = 0; ++ ++ aconnector = get_connector_for_sink(dev, sink); ++ crtc = aconnector->base.state->crtc; ++ ++ if (!aconnector->mst_port) ++ return false; ++ ++ mst_mgr = &aconnector->mst_port->mst_mgr; ++ mst_port = aconnector->port; ++ ++ if (enable) { ++ clock = crtc->state->mode.clock; ++ /* TODO remove following hardcode value */ ++ bpp = 30; ++ ++ /* TODO need to know link rate */ ++ ++ pbn = drm_dp_calc_pbn_mode(clock, bpp); ++ ++ ret = drm_dp_mst_allocate_vcpi(mst_mgr, mst_port, pbn, &slots); ++ ++ if (!ret) ++ return false; ++ ++ } else { ++ drm_dp_mst_reset_vcpi_slots(mst_mgr, mst_port); ++ } ++ ++ alloc_entity->slot_count = slots; ++ alloc_entity->pbn = pbn; ++ alloc_entity->pbn_per_slot = mst_mgr->pbn_div; ++ ++ ret = drm_dp_update_payload_part1(mst_mgr); ++ if (ret) ++ return false; ++ ++ return true; ++} ++ ++/* ++ * Polls for ACT (allocation change trigger) handled and sends ++ * ALLOCATE_PAYLOAD message. ++ */ ++bool dc_helpers_dp_mst_poll_for_allocation_change_trigger( ++ struct dc_context *ctx, ++ const struct dc_sink *sink) ++{ ++ struct amdgpu_device *adev = ctx->driver_context; ++ struct drm_device *dev = adev->ddev; ++ struct amdgpu_connector *aconnector; ++ struct drm_dp_mst_topology_mgr *mst_mgr; ++ int ret; ++ ++ aconnector = get_connector_for_sink(dev, sink); ++ ++ if (!aconnector->mst_port) ++ return false; ++ ++ mst_mgr = &aconnector->mst_port->mst_mgr; ++ ++ ret = drm_dp_check_act_status(mst_mgr); ++ ++ if (ret) ++ return false; ++ ++ return true; ++} ++ ++bool dc_helpers_dp_mst_send_payload_allocation( ++ struct dc_context *ctx, ++ const struct dc_sink *sink, ++ bool enable) ++{ ++ struct amdgpu_device *adev = ctx->driver_context; ++ struct drm_device *dev = adev->ddev; ++ struct amdgpu_connector *aconnector; ++ struct drm_dp_mst_topology_mgr *mst_mgr; ++ struct drm_dp_mst_port *mst_port; ++ int ret; ++ ++ aconnector = get_connector_for_sink(dev, sink); ++ ++ mst_port = aconnector->port; ++ ++ if (!aconnector->mst_port) ++ return false; ++ ++ mst_mgr = &aconnector->mst_port->mst_mgr; ++ ++ ret = drm_dp_update_payload_part2(mst_mgr); ++ ++ if (ret) ++ return false; ++ ++ if (!enable) ++ drm_dp_mst_deallocate_vcpi(mst_mgr, mst_port); ++ ++ return true; ++} ++ ++void dc_helpers_dp_mst_handle_mst_hpd_rx_irq(void *param) ++{ ++ uint8_t esi[8] = { 0 }; ++ uint8_t dret; ++ bool new_irq_handled = true; ++ struct amdgpu_connector *aconnector = (struct amdgpu_connector *)param; ++ ++ /* DPCD 0x2002 - 0x2008 for down stream IRQ from MST, eDP etc. */ ++ dret = drm_dp_dpcd_read( ++ &aconnector->dm_dp_aux.aux, ++ DP_SINK_COUNT_ESI, esi, 8); ++ ++ while ((dret == 8) && new_irq_handled) { ++ uint8_t retry; ++ ++ DRM_DEBUG_KMS("ESI %02x %02x %02x\n", esi[0], esi[1], esi[2]); ++ ++ /* handle HPD short pulse irq */ ++ drm_dp_mst_hpd_irq(&aconnector->mst_mgr, esi, &new_irq_handled); ++ ++ if (new_irq_handled) { ++ /* ACK at DPCD to notify down stream */ ++ for (retry = 0; retry < 3; retry++) { ++ uint8_t wret; ++ ++ wret = drm_dp_dpcd_write( ++ &aconnector->dm_dp_aux.aux, ++ DP_SINK_COUNT_ESI + 1, ++ &esi[1], ++ 3); ++ if (wret == 3) ++ break; ++ } ++ ++ /* check if there is new irq to be handle */ ++ dret = drm_dp_dpcd_read( ++ &aconnector->dm_dp_aux.aux, ++ DP_SINK_COUNT_ESI, esi, 8); ++ } ++ } ++} ++ ++bool dc_helpers_dp_mst_start_top_mgr( ++ struct dc_context *ctx, ++ const struct dc_link *link) ++{ ++ struct amdgpu_device *adev = ctx->driver_context; ++ struct drm_device *dev = adev->ddev; ++ struct amdgpu_connector *aconnector = get_connector_for_link(dev, link); ++ ++ if (aconnector) ++ drm_dp_mst_topology_mgr_set_mst(&aconnector->mst_mgr, true); ++ ++ return true; ++} ++ ++void dc_helpers_dp_mst_stop_top_mgr( ++ struct dc_context *ctx, ++ const struct dc_link *link) ++{ ++ struct amdgpu_device *adev = ctx->driver_context; ++ struct drm_device *dev = adev->ddev; ++ struct amdgpu_connector *aconnector = get_connector_for_link(dev, link); ++ ++ if (aconnector) ++ drm_dp_mst_topology_mgr_set_mst(&aconnector->mst_mgr, false); ++} +diff --git a/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.c +new file mode 100644 +index 0000000..37810ff +--- /dev/null ++++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.c +@@ -0,0 +1,1318 @@ ++/* ++ * 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 "dal_services_types.h" ++#include "dc.h" ++ ++#include "vid.h" ++#include "amdgpu.h" ++#include "atom.h" ++#include "amdgpu_dm.h" ++#include "amdgpu_dm_types.h" ++ ++#include "amd_shared.h" ++#include "amdgpu_dm_irq.h" ++#include "dc_helpers.h" ++ ++#include "dce/dce_11_0_d.h" ++#include "dce/dce_11_0_sh_mask.h" ++#include "dce/dce_11_0_enum.h" ++#include "ivsrcid/ivsrcid_vislands30.h" ++ ++#include "oss/oss_3_0_d.h" ++#include "oss/oss_3_0_sh_mask.h" ++#include "gmc/gmc_8_1_d.h" ++#include "gmc/gmc_8_1_sh_mask.h" ++ ++#include <linux/module.h> ++#include <linux/moduleparam.h> ++ ++#include <drm/drm_atomic.h> ++#include <drm/drm_atomic_helper.h> ++#include <drm/drm_dp_mst_helper.h> ++ ++/* TODO: Remove when mc access work around is removed */ ++static const u32 crtc_offsets[] = ++{ ++ CRTC0_REGISTER_OFFSET, ++ CRTC1_REGISTER_OFFSET, ++ CRTC2_REGISTER_OFFSET, ++ CRTC3_REGISTER_OFFSET, ++ CRTC4_REGISTER_OFFSET, ++ CRTC5_REGISTER_OFFSET, ++ CRTC6_REGISTER_OFFSET ++}; ++/* TODO: End of when Remove mc access work around is removed */ ++ ++/* Define variables here ++ * These values will be passed to DAL for feature enable purpose ++ * Disable ALL for HDMI light up ++ * TODO: follow up if need this mechanism*/ ++struct dal_override_parameters display_param = { ++ .bool_param_enable_mask = 0, ++ .bool_param_values = 0, ++ .int_param_values[DAL_PARAM_MAX_COFUNC_NON_DP_DISPLAYS] = DAL_PARAM_INVALID_INT, ++ .int_param_values[DAL_PARAM_DRR_SUPPORT] = DAL_PARAM_INVALID_INT, ++}; ++ ++/* Debug facilities */ ++#define AMDGPU_DM_NOT_IMPL(fmt, ...) \ ++ DRM_INFO("DM_NOT_IMPL: " fmt, ##__VA_ARGS__) ++ ++/* ++ * dm_vblank_get_counter ++ * ++ * @brief ++ * Get counter for number of vertical blanks ++ * ++ * @param ++ * struct amdgpu_device *adev - [in] desired amdgpu device ++ * int disp_idx - [in] which CRTC to get the counter from ++ * ++ * @return ++ * Counter for vertical blanks ++ */ ++static u32 dm_vblank_get_counter(struct amdgpu_device *adev, int crtc) ++{ ++ if (crtc >= adev->mode_info.num_crtc) ++ return 0; ++ else { ++ struct amdgpu_crtc *acrtc = adev->mode_info.crtcs[crtc]; ++ ++ if (NULL == acrtc->target) { ++ DRM_ERROR("dc_target is NULL for crtc '%d'!\n", crtc); ++ return 0; ++ } ++ ++ return dc_target_get_vblank_counter(acrtc->target); ++ } ++} ++ ++static int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, ++ u32 *vbl, u32 *position) ++{ ++ if ((crtc < 0) || (crtc >= adev->mode_info.num_crtc)) ++ return -EINVAL; ++ ++/* TODO: #DAL3 Implement scanoutpos ++ dal_get_crtc_scanoutpos(adev->dm.dal, crtc, vbl, position); ++*/ ++ return 0; ++} ++ ++static u32 dm_hpd_get_gpio_reg(struct amdgpu_device *adev) ++{ ++ return mmDC_GPIO_HPD_A; ++} ++ ++ ++static bool dm_is_display_hung(struct amdgpu_device *adev) ++{ ++ /* TODO: #DAL3 need to replace ++ u32 crtc_hung = 0; ++ u32 i, j, tmp; ++ ++ crtc_hung = dal_get_connected_targets_vector(adev->dm.dal); ++ ++ for (j = 0; j < 10; j++) { ++ for (i = 0; i < adev->mode_info.num_crtc; i++) { ++ if (crtc_hung & (1 << i)) { ++ int32_t vpos1, hpos1; ++ int32_t vpos2, hpos2; ++ ++ tmp = dal_get_crtc_scanoutpos( ++ adev->dm.dal, ++ i, ++ &vpos1, ++ &hpos1); ++ udelay(10); ++ tmp = dal_get_crtc_scanoutpos( ++ adev->dm.dal, ++ i, ++ &vpos2, ++ &hpos2); ++ ++ if (hpos1 != hpos2 && vpos1 != vpos2) ++ crtc_hung &= ~(1 << i); ++ } ++ } ++ ++ if (crtc_hung == 0) ++ return false; ++ } ++*/ ++ return true; ++} ++ ++/* TODO: Remove mc access work around*/ ++static void dm_stop_mc_access(struct amdgpu_device *adev, ++ struct amdgpu_mode_mc_save *save) ++{ ++ ++ u32 crtc_enabled, tmp; ++ int i; ++ ++ save->vga_render_control = RREG32(mmVGA_RENDER_CONTROL); ++ save->vga_hdp_control = RREG32(mmVGA_HDP_CONTROL); ++ ++ /* disable VGA render */ ++ tmp = RREG32(mmVGA_RENDER_CONTROL); ++ tmp = REG_SET_FIELD(tmp, VGA_RENDER_CONTROL, VGA_VSTATUS_CNTL, 0); ++ WREG32(mmVGA_RENDER_CONTROL, tmp); ++ ++ /* blank the display controllers */ ++ for (i = 0; i < adev->mode_info.num_crtc; i++) { ++ crtc_enabled = REG_GET_FIELD(RREG32(mmCRTC_CONTROL + crtc_offsets[i]), ++ CRTC_CONTROL, CRTC_MASTER_EN); ++ if (crtc_enabled) { ++#if 0 ++ u32 frame_count; ++ int j; ++ ++ save->crtc_enabled[i] = true; ++ tmp = RREG32(mmCRTC_BLANK_CONTROL + crtc_offsets[i]); ++ if (REG_GET_FIELD(tmp, CRTC_BLANK_CONTROL, CRTC_BLANK_DATA_EN) == 0) { ++ amdgpu_display_vblank_wait(adev, i); ++ WREG32(mmCRTC_UPDATE_LOCK + crtc_offsets[i], 1); ++ tmp = REG_SET_FIELD(tmp, CRTC_BLANK_CONTROL, CRTC_BLANK_DATA_EN, 1); ++ WREG32(mmCRTC_BLANK_CONTROL + crtc_offsets[i], tmp); ++ WREG32(mmCRTC_UPDATE_LOCK + crtc_offsets[i], 0); ++ } ++ /* wait for the next frame */ ++ frame_count = amdgpu_display_vblank_get_counter(adev, i); ++ for (j = 0; j < adev->usec_timeout; j++) { ++ if (amdgpu_display_vblank_get_counter(adev, i) != frame_count) ++ break; ++ udelay(1); ++ } ++ tmp = RREG32(mmGRPH_UPDATE + crtc_offsets[i]); ++ if (REG_GET_FIELD(tmp, GRPH_UPDATE, GRPH_UPDATE_LOCK) == 0) { ++ tmp = REG_SET_FIELD(tmp, GRPH_UPDATE, GRPH_UPDATE_LOCK, 1); ++ WREG32(mmGRPH_UPDATE + crtc_offsets[i], tmp); ++ } ++ tmp = RREG32(mmCRTC_MASTER_UPDATE_LOCK + crtc_offsets[i]); ++ if (REG_GET_FIELD(tmp, CRTC_MASTER_UPDATE_LOCK, MASTER_UPDATE_LOCK) == 0) { ++ tmp = REG_SET_FIELD(tmp, CRTC_MASTER_UPDATE_LOCK, MASTER_UPDATE_LOCK, 1); ++ WREG32(mmCRTC_MASTER_UPDATE_LOCK + crtc_offsets[i], tmp); ++ } ++#else ++ /* XXX this is a hack to avoid strange behavior with EFI on certain systems */ ++ WREG32(mmCRTC_UPDATE_LOCK + crtc_offsets[i], 1); ++ tmp = RREG32(mmCRTC_CONTROL + crtc_offsets[i]); ++ tmp = REG_SET_FIELD(tmp, CRTC_CONTROL, CRTC_MASTER_EN, 0); ++ WREG32(mmCRTC_CONTROL + crtc_offsets[i], tmp); ++ WREG32(mmCRTC_UPDATE_LOCK + crtc_offsets[i], 0); ++ save->crtc_enabled[i] = false; ++ /* ***** */ ++#endif ++ } else { ++ save->crtc_enabled[i] = false; ++ } ++ } ++} ++ ++ ++static void dm_resume_mc_access(struct amdgpu_device *adev, ++ struct amdgpu_mode_mc_save *save) ++{ ++ ++ u32 tmp, frame_count; ++ int i, j; ++ ++ /* update crtc base addresses */ ++ for (i = 0; i < adev->mode_info.num_crtc; i++) { ++ WREG32(mmGRPH_PRIMARY_SURFACE_ADDRESS_HIGH + crtc_offsets[i], ++ upper_32_bits(adev->mc.vram_start)); ++ WREG32(mmGRPH_SECONDARY_SURFACE_ADDRESS_HIGH + crtc_offsets[i], ++ upper_32_bits(adev->mc.vram_start)); ++ WREG32(mmGRPH_PRIMARY_SURFACE_ADDRESS + crtc_offsets[i], ++ (u32)adev->mc.vram_start); ++ WREG32(mmGRPH_SECONDARY_SURFACE_ADDRESS + crtc_offsets[i], ++ (u32)adev->mc.vram_start); ++ ++ if (save->crtc_enabled[i]) { ++ tmp = RREG32(mmCRTC_MASTER_UPDATE_MODE + crtc_offsets[i]); ++ if (REG_GET_FIELD(tmp, CRTC_MASTER_UPDATE_MODE, MASTER_UPDATE_MODE) != 3) { ++ tmp = REG_SET_FIELD(tmp, CRTC_MASTER_UPDATE_MODE, MASTER_UPDATE_MODE, 3); ++ WREG32(mmCRTC_MASTER_UPDATE_MODE + crtc_offsets[i], tmp); ++ } ++ tmp = RREG32(mmGRPH_UPDATE + crtc_offsets[i]); ++ if (REG_GET_FIELD(tmp, GRPH_UPDATE, GRPH_UPDATE_LOCK)) { ++ tmp = REG_SET_FIELD(tmp, GRPH_UPDATE, GRPH_UPDATE_LOCK, 0); ++ WREG32(mmGRPH_UPDATE + crtc_offsets[i], tmp); ++ } ++ tmp = RREG32(mmCRTC_MASTER_UPDATE_LOCK + crtc_offsets[i]); ++ if (REG_GET_FIELD(tmp, CRTC_MASTER_UPDATE_LOCK, MASTER_UPDATE_LOCK)) { ++ tmp = REG_SET_FIELD(tmp, CRTC_MASTER_UPDATE_LOCK, MASTER_UPDATE_LOCK, 0); ++ WREG32(mmCRTC_MASTER_UPDATE_LOCK + crtc_offsets[i], tmp); ++ } ++ for (j = 0; j < adev->usec_timeout; j++) { ++ tmp = RREG32(mmGRPH_UPDATE + crtc_offsets[i]); ++ if (REG_GET_FIELD(tmp, GRPH_UPDATE, GRPH_SURFACE_UPDATE_PENDING) == 0) ++ break; ++ udelay(1); ++ } ++ tmp = RREG32(mmCRTC_BLANK_CONTROL + crtc_offsets[i]); ++ tmp = REG_SET_FIELD(tmp, CRTC_BLANK_CONTROL, CRTC_BLANK_DATA_EN, 0); ++ WREG32(mmCRTC_UPDATE_LOCK + crtc_offsets[i], 1); ++ WREG32(mmCRTC_BLANK_CONTROL + crtc_offsets[i], tmp); ++ WREG32(mmCRTC_UPDATE_LOCK + crtc_offsets[i], 0); ++ /* wait for the next frame */ ++ frame_count = amdgpu_display_vblank_get_counter(adev, i); ++ for (j = 0; j < adev->usec_timeout; j++) { ++ if (amdgpu_display_vblank_get_counter(adev, i) != frame_count) ++ break; ++ udelay(1); ++ } ++ } ++ } ++ ++ WREG32(mmVGA_MEMORY_BASE_ADDRESS_HIGH, upper_32_bits(adev->mc.vram_start)); ++ WREG32(mmVGA_MEMORY_BASE_ADDRESS, lower_32_bits(adev->mc.vram_start)); ++ ++ /* Unlock vga access */ ++ WREG32(mmVGA_HDP_CONTROL, save->vga_hdp_control); ++ mdelay(1); ++ WREG32(mmVGA_RENDER_CONTROL, save->vga_render_control); ++} ++ ++/* End of TODO: Remove mc access work around*/ ++ ++static bool dm_is_idle(void *handle) ++{ ++ /* XXX todo */ ++ return true; ++} ++ ++static int dm_wait_for_idle(void *handle) ++{ ++ /* XXX todo */ ++ return 0; ++} ++ ++static void dm_print_status(void *handle) ++{ ++ struct amdgpu_device *adev = (struct amdgpu_device *)handle; ++ dev_info(adev->dev, "DCE 10.x registers\n"); ++ /* XXX todo */ ++} ++ ++static int dm_soft_reset(void *handle) ++{ ++ struct amdgpu_device *adev = (struct amdgpu_device *)handle; ++ u32 srbm_soft_reset = 0, tmp; ++ ++ if (dm_is_display_hung(adev)) ++ srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_DC_MASK; ++ ++ if (srbm_soft_reset) { ++ dm_print_status(adev); ++ ++ tmp = RREG32(mmSRBM_SOFT_RESET); ++ tmp |= srbm_soft_reset; ++ dev_info(adev->dev, "SRBM_SOFT_RESET=0x%08X\n", tmp); ++ WREG32(mmSRBM_SOFT_RESET, tmp); ++ tmp = RREG32(mmSRBM_SOFT_RESET); ++ ++ udelay(50); ++ ++ tmp &= ~srbm_soft_reset; ++ WREG32(mmSRBM_SOFT_RESET, tmp); ++ tmp = RREG32(mmSRBM_SOFT_RESET); ++ ++ /* Wait a little for things to settle down */ ++ udelay(50); ++ dm_print_status(adev); ++ } ++ return 0; ++} ++ ++ ++ ++static void dm_pflip_high_irq(void *interrupt_params) ++{ ++ struct amdgpu_flip_work *works; ++ struct amdgpu_crtc *amdgpu_crtc; ++ struct common_irq_params *irq_params = interrupt_params; ++ struct amdgpu_device *adev = irq_params->adev; ++ unsigned long flags; ++ const struct dc *dc = irq_params->adev->dm.dc; ++ const struct dc_target *dc_target = ++ dc_get_target_on_irq_source(dc, irq_params->irq_src); ++ uint8_t link_index = 0; ++ ++ /* TODO: #flip address all tags together*/ ++ if (dc_target != NULL) ++ link_index = dc_target_get_link_index(dc_target); ++ ++ amdgpu_crtc= adev->mode_info.crtcs[link_index]; ++ ++ /* IRQ could occur when in initial stage */ ++ if(amdgpu_crtc == NULL) ++ return; ++ ++ spin_lock_irqsave(&adev->ddev->event_lock, flags); ++ works = amdgpu_crtc->pflip_works; ++ if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_SUBMITTED){ ++ DRM_DEBUG_DRIVER("amdgpu_crtc->pflip_status = %d != " ++ "AMDGPU_FLIP_SUBMITTED(%d)\n", ++ amdgpu_crtc->pflip_status, ++ AMDGPU_FLIP_SUBMITTED); ++ spin_unlock_irqrestore(&adev->ddev->event_lock, flags); ++ return; ++ } ++ ++ /* page flip completed. clean up */ ++ amdgpu_crtc->pflip_status = AMDGPU_FLIP_NONE; ++ amdgpu_crtc->pflip_works = NULL; ++ ++ /* wakeup usersapce */ ++ if(works->event) ++ drm_send_vblank_event( ++ adev->ddev, ++ amdgpu_crtc->crtc_id, ++ works->event); ++ ++ spin_unlock_irqrestore(&adev->ddev->event_lock, flags); ++ ++ drm_crtc_vblank_put(&amdgpu_crtc->base); ++ schedule_work(&works->unpin_work); ++} ++ ++static void dm_crtc_high_irq(void *interrupt_params) ++{ ++ struct common_irq_params *irq_params = interrupt_params; ++ struct amdgpu_device *adev = irq_params->adev; ++ const struct dc *dc = irq_params->adev->dm.dc; ++ const struct dc_target *dc_target = ++ dc_get_target_on_irq_source(dc, irq_params->irq_src); ++ uint8_t link_index = 0; ++ ++ /* TODO: #flip fix all tags together*/ ++ if (dc_target != NULL) ++ link_index = dc_target_get_link_index(dc_target); ++ ++ drm_handle_vblank(adev->ddev, link_index); ++ ++} ++ ++static int dm_set_clockgating_state(void *handle, ++ enum amd_clockgating_state state) ++{ ++ return 0; ++} ++ ++static int dm_set_powergating_state(void *handle, ++ enum amd_powergating_state state) ++{ ++ return 0; ++} ++ ++/* Prototypes of private functions */ ++static int dm_early_init(void* handle); ++ ++static void detect_on_all_dc_links(struct amdgpu_display_manager *dm) ++{ ++ uint32_t i; ++ const struct dc_link *dc_link; ++ struct dc_caps caps = { 0 }; ++ ++ dc_get_caps(dm->dc, &caps); ++ ++ for (i = 0; i < caps.max_links; i++) { ++ dc_link = dc_get_link_at_index(dm->dc, i); ++ dc_link_detect(dc_link); ++ } ++} ++ ++/* Init display KMS ++ * ++ * Returns 0 on success ++ */ ++int amdgpu_dm_init(struct amdgpu_device *adev) ++{ ++ struct dal_init_data init_data; ++ struct drm_device *ddev = adev->ddev; ++ adev->dm.ddev = adev->ddev; ++ adev->dm.adev = adev; ++ ++ /* Zero all the fields */ ++ memset(&init_data, 0, sizeof(init_data)); ++ ++ /* initialize DAL's lock (for SYNC context use) */ ++ spin_lock_init(&adev->dm.dal_lock); ++ ++ /* initialize DAL's mutex */ ++ mutex_init(&adev->dm.dal_mutex); ++ ++ if(amdgpu_dm_irq_init(adev)) { ++ DRM_ERROR("amdgpu: failed to initialize DM IRQ support.\n"); ++ goto error; ++ } ++ ++ if (ddev->pdev) { ++ init_data.bdf_info.DEVICE_NUMBER = PCI_SLOT(ddev->pdev->devfn); ++ init_data.bdf_info.FUNCTION_NUMBER = ++ PCI_FUNC(ddev->pdev->devfn); ++ if (ddev->pdev->bus) ++ init_data.bdf_info.BUS_NUMBER = ddev->pdev->bus->number; ++ } ++ ++ init_data.display_param = display_param; ++ ++ init_data.asic_id.chip_family = adev->family; ++ ++ init_data.asic_id.pci_revision_id = adev->rev_id; ++ init_data.asic_id.hw_internal_rev = adev->external_rev_id; ++ ++ init_data.asic_id.vram_width = adev->mc.vram_width; ++ /* TODO: initialize init_data.asic_id.vram_type here!!!! */ ++ init_data.asic_id.atombios_base_address = ++ adev->mode_info.atom_context->bios; ++ init_data.asic_id.runtime_flags.flags.bits.SKIP_POWER_DOWN_ON_RESUME = 1; ++ ++ if (adev->asic_type == CHIP_CARRIZO) ++ init_data.asic_id.runtime_flags.flags.bits.GNB_WAKEUP_SUPPORTED = 1; ++ ++ init_data.driver = adev; ++ ++ adev->dm.cgs_device = amdgpu_cgs_create_device(adev); ++ ++ if (!adev->dm.cgs_device) { ++ DRM_ERROR("amdgpu: failed to create cgs device.\n"); ++ goto error; ++ } ++ ++ init_data.cgs_device = adev->dm.cgs_device; ++ ++ adev->dm.dal = NULL; ++ ++ /* enable gpu scaling in DAL */ ++ init_data.display_param.bool_param_enable_mask |= ++ 1 << DAL_PARAM_ENABLE_GPU_SCALING; ++ init_data.display_param.bool_param_values |= ++ 1 << DAL_PARAM_ENABLE_GPU_SCALING; ++ ++ /* Display Core create. */ ++ adev->dm.dc = dc_create(&init_data); ++ ++ if (amdgpu_dm_initialize_drm_device(adev)) { ++ DRM_ERROR( ++ "amdgpu: failed to initialize sw for display support.\n"); ++ goto error; ++ } ++ ++ /* Update the actual used number of crtc */ ++ adev->mode_info.num_crtc = adev->dm.display_indexes_num; ++ ++ /* TODO: Add_display_info? */ ++ ++ /* TODO use dynamic cursor width */ ++ adev->ddev->mode_config.cursor_width = 128; ++ adev->ddev->mode_config.cursor_height = 128; ++ ++ if (drm_vblank_init(adev->ddev, adev->dm.display_indexes_num)) { ++ DRM_ERROR( ++ "amdgpu: failed to initialize sw for display support.\n"); ++ goto error; ++ } ++ ++ DRM_INFO("KMS initialized.\n"); ++ ++ return 0; ++error: ++ amdgpu_dm_fini(adev); ++ ++ return -1; ++} ++ ++void amdgpu_dm_fini(struct amdgpu_device *adev) ++{ ++ /* ++ * TODO: pageflip, vlank interrupt ++ * ++ * amdgpu_dm_destroy_drm_device(&adev->dm); ++ * amdgpu_dm_irq_fini(adev); ++ */ ++ ++ if (adev->dm.cgs_device) { ++ amdgpu_cgs_destroy_device(adev->dm.cgs_device); ++ adev->dm.cgs_device = NULL; ++ } ++ ++ /* DC Destroy TODO: Replace destroy DAL */ ++ { ++ dc_destroy(&adev->dm.dc); ++ } ++ return; ++} ++ ++/* moved from amdgpu_dm_kms.c */ ++void amdgpu_dm_destroy() ++{ ++} ++ ++static int dm_sw_init(void *handle) ++{ ++ return 0; ++} ++ ++static int dm_sw_fini(void *handle) ++{ ++ return 0; ++} ++ ++static int dm_hw_init(void *handle) ++{ ++ struct amdgpu_device *adev = (struct amdgpu_device *)handle; ++ /* Create DAL display manager */ ++ amdgpu_dm_init(adev); ++ ++ amdgpu_dm_hpd_init(adev); ++ ++ return 0; ++} ++ ++static int dm_hw_fini(void *handle) ++{ ++ struct amdgpu_device *adev = (struct amdgpu_device *)handle; ++ ++ amdgpu_dm_hpd_fini(adev); ++ ++ amdgpu_dm_irq_fini(adev); ++ ++ return 0; ++} ++ ++static int dm_suspend(void *handle) ++{ ++ struct amdgpu_device *adev = handle; ++ struct amdgpu_display_manager *dm = &adev->dm; ++ struct drm_crtc *crtc; ++ ++ dc_set_power_state( ++ dm->dc, ++ DC_ACPI_CM_POWER_STATE_D3, ++ DC_VIDEO_POWER_SUSPEND); ++ ++ amdgpu_dm_irq_suspend(adev); ++ ++ list_for_each_entry(crtc, &dm->ddev->mode_config.crtc_list, head) { ++ crtc->mode.clock = 0; ++ } ++ ++ return 0; ++} ++ ++static int dm_resume(void *handle) ++{ ++ int ret = 0; ++ struct amdgpu_device *adev = handle; ++ struct amdgpu_display_manager *dm = &adev->dm; ++ ++ dc_set_power_state( ++ dm->dc, ++ DC_ACPI_CM_POWER_STATE_D0, ++ DC_VIDEO_POWER_ON); ++ ++ amdgpu_dm_irq_resume(adev); ++ ++ dc_resume(dm->dc); ++ ++ detect_on_all_dc_links(dm); ++ ++ drm_mode_config_reset(adev->ddev); ++ ++ return ret; ++} ++const struct amd_ip_funcs amdgpu_dm_funcs = { ++ .early_init = dm_early_init, ++ .late_init = NULL, ++ .sw_init = dm_sw_init, ++ .sw_fini = dm_sw_fini, ++ .hw_init = dm_hw_init, ++ .hw_fini = dm_hw_fini, ++ .suspend = dm_suspend, ++ .resume = dm_resume, ++ .is_idle = dm_is_idle, ++ .wait_for_idle = dm_wait_for_idle, ++ .soft_reset = dm_soft_reset, ++ .print_status = dm_print_status, ++ .set_clockgating_state = dm_set_clockgating_state, ++ .set_powergating_state = dm_set_powergating_state, ++}; ++ ++/* TODO: it is temporary non-const, should fixed later */ ++static struct drm_mode_config_funcs amdgpu_dm_mode_funcs = { ++ .atomic_check = amdgpu_dm_atomic_check, ++ .atomic_commit = amdgpu_dm_atomic_commit ++}; ++ ++static bool dm_get_sink_from_link(const struct dc_link *link, ++ struct amdgpu_connector *aconnector, ++ const struct dc_sink **sink) ++{ ++ int i; ++ *sink = NULL; ++ ++ if (!link->sink_count) { ++ DRM_INFO("No sinks on link!\n"); ++ return true; ++ } else if (link->sink_count > 1 && !aconnector) { ++ DRM_ERROR("Multi sink link but no connector given!\n"); ++ return false; ++ } ++ ++ if (link->sink_count == 1) { ++ *sink = link->sink[0]; ++ return true; ++ } ++ ++ for (i = 0; i < link->sink_count; i++) ++ if (aconnector->dc_sink == link->sink[i]) ++ *sink = aconnector->dc_sink; ++ ++ return true; ++} ++ ++void amdgpu_dm_update_connector_after_detect( ++ struct amdgpu_connector *aconnector) ++{ ++ struct drm_connector *connector = &aconnector->base; ++ struct drm_device *dev = connector->dev; ++ const struct dc_link *dc_link = aconnector->dc_link; ++ const struct dc_sink *sink; ++ ++ /* MST handled by drm_mst framework */ ++ if (aconnector->mst_mgr.mst_state) ++ return; ++ if (!dm_get_sink_from_link(dc_link, aconnector, &sink)) { ++ return; ++ } ++ ++ if (aconnector->dc_sink == sink) { ++ /* We got a DP short pulse (Link Loss, DP CTS, etc...). ++ * Do nothing!! */ ++ DRM_INFO("DCHPD: connector_id=%d: dc_sink didn't change.\n", ++ aconnector->connector_id); ++ return; ++ } ++ ++ DRM_INFO("DCHPD: connector_id=%d: Old sink=%p New sink=%p\n", ++ aconnector->connector_id, aconnector->dc_sink, sink); ++ ++ mutex_lock(&dev->mode_config.mutex); ++ ++ /* 1. Update status of the drm connector ++ * 2. Send an event and let userspace tell us what to do */ ++ if (sink) { ++ /* TODO: check if we still need the S3 mode update workaround. ++ * If yes, put it here. */ ++ ++ aconnector->dc_sink = sink; ++ if (sink->dc_edid.length == 0) ++ aconnector->edid = NULL; ++ else { ++ aconnector->edid = ++ (struct edid *) sink->dc_edid.raw_edid; ++ drm_mode_connector_update_edid_property(connector, ++ aconnector->edid); ++ amdgpu_dm_connector_get_modes(&aconnector->base); ++ } ++ } else { ++ drm_mode_connector_update_edid_property(connector, NULL); ++ aconnector->num_modes = 0; ++ aconnector->dc_sink = NULL; ++ } ++ ++ mutex_unlock(&dev->mode_config.mutex); ++} ++ ++static void handle_hpd_irq(void *param) ++{ ++ struct amdgpu_connector *aconnector = (struct amdgpu_connector *)param; ++ struct drm_connector *connector = &aconnector->base; ++ struct drm_device *dev = connector->dev; ++ ++ dc_link_detect(aconnector->dc_link); ++ amdgpu_dm_update_connector_after_detect(aconnector); ++ drm_helper_hpd_irq_event(dev); ++} ++ ++static void handle_hpd_rx_irq(void *param) ++{ ++ struct amdgpu_connector *aconnector = (struct amdgpu_connector *)param; ++ struct drm_connector *connector = &aconnector->base; ++ struct drm_device *dev = connector->dev; ++ ++ if (dc_link_handle_hpd_rx_irq(aconnector->dc_link) && ++ !aconnector->mst_mgr.mst_state) { ++ /* Downstream Port status changed. */ ++ dc_link_detect(aconnector->dc_link); ++ amdgpu_dm_update_connector_after_detect(aconnector); ++ drm_helper_hpd_irq_event(dev); ++ } ++ ++ if (aconnector->mst_mgr.mst_state) ++ dc_helpers_dp_mst_handle_mst_hpd_rx_irq(param); ++} ++ ++static void register_hpd_handlers(struct amdgpu_device *adev) ++{ ++ struct drm_device *dev = adev->ddev; ++ struct drm_connector *connector; ++ struct amdgpu_connector *aconnector; ++ const struct dc_link *dc_link; ++ struct dc_interrupt_params int_params = {0}; ++ ++ int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; ++ int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; ++ ++ list_for_each_entry(connector, ++ &dev->mode_config.connector_list, head) { ++ ++ aconnector = to_amdgpu_connector(connector); ++ dc_link = aconnector->dc_link; ++ ++ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; ++ int_params.irq_source = dc_link->irq_source_hpd; ++ ++ amdgpu_dm_irq_register_interrupt(adev, &int_params, ++ handle_hpd_irq, ++ (void *) aconnector); ++ ++ if (DC_IRQ_SOURCE_INVALID != dc_link->irq_source_hpd_rx) { ++ ++ /* Also register for DP short pulse (hpd_rx). */ ++ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; ++ int_params.irq_source = dc_link->irq_source_hpd_rx; ++ ++ amdgpu_dm_irq_register_interrupt(adev, &int_params, ++ handle_hpd_rx_irq, ++ (void *) aconnector); ++ } ++ } ++} ++ ++/* Register IRQ sources and initialize IRQ callbacks */ ++static int dce110_register_irq_handlers(struct amdgpu_device *adev) ++{ ++ struct dc *dc = adev->dm.dc; ++ struct common_irq_params *c_irq_params; ++ struct dc_interrupt_params int_params = {0}; ++ int r; ++ int i; ++ struct dc_caps caps = { 0 }; ++ ++ dc_get_caps(dc, &caps); ++ ++ int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; ++ int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; ++ ++ /* Actions of amdgpu_irq_add_id(): ++ * 1. Register a set() function with base driver. ++ * Base driver will call set() function to enable/disable an ++ * interrupt in DC hardware. ++ * 2. Register amdgpu_dm_irq_handler(). ++ * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts ++ * coming from DC hardware. ++ * amdgpu_dm_irq_handler() will re-direct the interrupt to DC ++ * for acknowledging and handling. */ ++ ++ for (i = VISLANDS30_IV_SRCID_D1_V_UPDATE_INT; ++ i <= VISLANDS30_IV_SRCID_D6_V_UPDATE_INT; i += 2) { ++ r = amdgpu_irq_add_id(adev, i, &adev->crtc_irq); ++ if (r) { ++ DRM_ERROR("Failed to add crtc irq id!\n"); ++ return r; ++ } ++ ++ int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; ++ int_params.irq_source = ++ dc_interrupt_to_irq_source(dc, i, 0); ++ ++ c_irq_params = &adev->dm.vupdate_params[int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1]; ++ ++ c_irq_params->adev = adev; ++ c_irq_params->irq_src = int_params.irq_source; ++ ++ amdgpu_dm_irq_register_interrupt(adev, &int_params, ++ dm_crtc_high_irq, c_irq_params); ++ } ++ ++ for (i = VISLANDS30_IV_SRCID_D1_GRPH_PFLIP; ++ i <= VISLANDS30_IV_SRCID_D6_GRPH_PFLIP; i += 2) { ++ r = amdgpu_irq_add_id(adev, i, &adev->pageflip_irq); ++ if (r) { ++ DRM_ERROR("Failed to add page flip irq id!\n"); ++ return r; ++ } ++ ++ int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; ++ int_params.irq_source = ++ dc_interrupt_to_irq_source(dc, i, 0); ++ ++ c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; ++ ++ c_irq_params->adev = adev; ++ c_irq_params->irq_src = int_params.irq_source; ++ ++ amdgpu_dm_irq_register_interrupt(adev, &int_params, ++ dm_pflip_high_irq, c_irq_params); ++ ++ } ++ ++ /* HPD */ ++ r = amdgpu_irq_add_id(adev, VISLANDS30_IV_SRCID_HOTPLUG_DETECT_A, ++ &adev->hpd_irq); ++ if (r) { ++ DRM_ERROR("Failed to add hpd irq id!\n"); ++ return r; ++ } ++ ++ register_hpd_handlers(adev); ++ ++ /* This is a part of HPD initialization. */ ++ drm_kms_helper_poll_init(adev->ddev); ++ ++ return 0; ++} ++ ++static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev) ++{ ++ int r; ++ ++ adev->mode_info.mode_config_initialized = true; ++ ++ amdgpu_dm_mode_funcs.fb_create = ++ amdgpu_mode_funcs.fb_create; ++ amdgpu_dm_mode_funcs.output_poll_changed = ++ amdgpu_mode_funcs.output_poll_changed; ++ ++ adev->ddev->mode_config.funcs = (void *)&amdgpu_dm_mode_funcs; ++ ++ adev->ddev->mode_config.max_width = 16384; ++ adev->ddev->mode_config.max_height = 16384; ++ ++ adev->ddev->mode_config.preferred_depth = 24; ++ adev->ddev->mode_config.prefer_shadow = 1; ++ ++ adev->ddev->mode_config.fb_base = adev->mc.aper_base; ++ ++ r = amdgpu_modeset_create_props(adev); ++ if (r) ++ return r; ++ ++ return 0; ++} ++ ++#if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) ||\ ++ defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE) ++ ++static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) ++{ ++ struct amdgpu_display_manager *dm = bl_get_data(bd); ++ ++ if (dc_link_set_backlight_level(dm->backlight_link, ++ bd->props.brightness)) ++ return 0; ++ else ++ return 1; ++} ++ ++static int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd) ++{ ++ return bd->props.brightness; ++} ++ ++static const struct backlight_ops amdgpu_dm_backlight_ops = { ++ .get_brightness = amdgpu_dm_backlight_get_brightness, ++ .update_status = amdgpu_dm_backlight_update_status, ++}; ++ ++void amdgpu_dm_register_backlight_device(struct amdgpu_display_manager *dm) ++{ ++ char bl_name[16]; ++ struct backlight_properties props = { 0 }; ++ ++ props.max_brightness = AMDGPU_MAX_BL_LEVEL; ++ props.type = BACKLIGHT_RAW; ++ ++ snprintf(bl_name, sizeof(bl_name), "amdgpu_bl%d", ++ dm->adev->ddev->primary->index); ++ ++ dm->backlight_dev = backlight_device_register(bl_name, ++ dm->adev->ddev->dev, ++ dm, ++ &amdgpu_dm_backlight_ops, ++ &props); ++ ++ if (NULL == dm->backlight_dev) ++ DRM_ERROR("DM: Backlight registration failed!\n"); ++ else ++ DRM_INFO("DM: Registered Backlight device: %s\n", bl_name); ++} ++ ++#endif ++ ++/* In this architecture, the association ++ * connector -> encoder -> crtc ++ * id not really requried. The crtc and connector will hold the ++ * display_index as an abstraction to use with DAL component ++ * ++ * Returns 0 on success ++ */ ++int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) ++{ ++ struct amdgpu_display_manager *dm = &adev->dm; ++ uint32_t link_index; ++ struct drm_connector *connector; ++ struct amdgpu_connector *aconnector; ++ struct amdgpu_encoder *aencoder; ++ struct amdgpu_crtc *acrtc; ++ struct dc_caps caps = { 0 }; ++ uint32_t link_cnt; ++ ++ dc_get_caps(dm->dc, &caps); ++ link_cnt = caps.max_links; ++ ++ if (amdgpu_dm_mode_config_init(dm->adev)) { ++ DRM_ERROR("DM: Failed to initialize mode config\n"); ++ return -1; ++ } ++ ++ /* loops over all connectors on the board */ ++ for (link_index = 0; link_index < link_cnt; link_index++) { ++ ++ if (link_index > AMDGPU_DM_MAX_DISPLAY_INDEX) { ++ DRM_ERROR( ++ "KMS: Cannot support more than %d display indeces\n", ++ AMDGPU_DM_MAX_DISPLAY_INDEX); ++ continue; ++ } ++ ++ aconnector = kzalloc(sizeof(*aconnector), GFP_KERNEL); ++ if (!aconnector) ++ goto fail_connector; ++ ++ aencoder = kzalloc(sizeof(*aencoder), GFP_KERNEL); ++ if (!aencoder) ++ goto fail_encoder; ++ ++ acrtc = kzalloc(sizeof(struct amdgpu_crtc), GFP_KERNEL); ++ if (!acrtc) ++ goto fail_crtc; ++ ++ if (amdgpu_dm_crtc_init( ++ dm, ++ acrtc, ++ link_index)) { ++ DRM_ERROR("KMS: Failed to initialize crtc\n"); ++ goto fail; ++ } ++ ++ if (amdgpu_dm_encoder_init( ++ dm->ddev, ++ aencoder, ++ link_index, ++ acrtc)) { ++ DRM_ERROR("KMS: Failed to initialize encoder\n"); ++ goto fail; ++ } ++ ++ if (amdgpu_dm_connector_init( ++ dm, ++ aconnector, ++ link_index, ++ aencoder)) { ++ DRM_ERROR("KMS: Failed to initialize connector\n"); ++ goto fail; ++ } ++ } ++ ++ dm->display_indexes_num = link_cnt; ++ ++ detect_on_all_dc_links(&adev->dm); ++ list_for_each_entry(connector, &adev->ddev->mode_config.connector_list, head) ++ amdgpu_dm_update_connector_after_detect(to_amdgpu_connector(connector)); ++ ++ /* Software is initialized. Now we can register interrupt handlers. */ ++ switch (adev->asic_type) { ++ case CHIP_CARRIZO: ++ if (dce110_register_irq_handlers(dm->adev)) { ++ DRM_ERROR("DM: Failed to initialize IRQ\n"); ++ return -1; ++ } ++ break; ++ default: ++ DRM_ERROR("Usupported ASIC type: 0x%X\n", adev->asic_type); ++ return -1; ++ } ++ ++ drm_mode_config_reset(dm->ddev); ++ ++ return 0; ++ ++fail: ++ /* clean any dongling drm structure for the last (corrupted) ++ display target */ ++ amdgpu_dm_crtc_destroy(&acrtc->base); ++fail_crtc: ++ amdgpu_dm_encoder_destroy(&aencoder->base); ++fail_encoder: ++ amdgpu_dm_connector_destroy(&aconnector->base); ++fail_connector: ++ if (dm->backlight_dev) { ++ backlight_device_unregister(dm->backlight_dev); ++ dm->backlight_dev = NULL; ++ } ++ return -1; ++} ++ ++void amdgpu_dm_destroy_drm_device( ++ struct amdgpu_display_manager *dm) ++{ ++ drm_mode_config_cleanup(dm->ddev); ++ /*switch_dev_unregister(&dm->hdmi_audio_dev);*/ ++ return; ++} ++ ++/****************************************************************************** ++ * amdgpu_display_funcs functions ++ *****************************************************************************/ ++ ++ ++static void dm_set_vga_render_state(struct amdgpu_device *adev, ++ bool render) ++{ ++ u32 tmp; ++ ++ /* Lockout access through VGA aperture*/ ++ tmp = RREG32(mmVGA_HDP_CONTROL); ++ if (render) ++ tmp = REG_SET_FIELD(tmp, VGA_HDP_CONTROL, VGA_MEMORY_DISABLE, 0); ++ else ++ tmp = REG_SET_FIELD(tmp, VGA_HDP_CONTROL, VGA_MEMORY_DISABLE, 1); ++ WREG32(mmVGA_HDP_CONTROL, tmp); ++ ++ /* disable VGA render */ ++ tmp = RREG32(mmVGA_RENDER_CONTROL); ++ if (render) ++ tmp = REG_SET_FIELD(tmp, VGA_RENDER_CONTROL, VGA_VSTATUS_CNTL, 1); ++ else ++ tmp = REG_SET_FIELD(tmp, VGA_RENDER_CONTROL, VGA_VSTATUS_CNTL, 0); ++ WREG32(mmVGA_RENDER_CONTROL, tmp); ++} ++ ++/** ++ * dm_bandwidth_update - program display watermarks ++ * ++ * @adev: amdgpu_device pointer ++ * ++ * Calculate and program the display watermarks and line buffer allocation. ++ */ ++static void dm_bandwidth_update(struct amdgpu_device *adev) ++{ ++ AMDGPU_DM_NOT_IMPL("%s\n", __func__); ++} ++ ++static void dm_set_backlight_level(struct amdgpu_encoder *amdgpu_encoder, ++ u8 level) ++{ ++ /* TODO: translate amdgpu_encoder to display_index and call DAL */ ++ AMDGPU_DM_NOT_IMPL("%s\n", __func__); ++} ++ ++static u8 dm_get_backlight_level(struct amdgpu_encoder *amdgpu_encoder) ++{ ++ /* TODO: translate amdgpu_encoder to display_index and call DAL */ ++ AMDGPU_DM_NOT_IMPL("%s\n", __func__); ++ return 0; ++} ++ ++/****************************************************************************** ++ * Page Flip functions ++ ******************************************************************************/ ++ ++void amdgpu_dm_flip_cleanup( ++ struct amdgpu_device *adev, ++ struct amdgpu_crtc *acrtc) ++{ ++ int r; ++ struct amdgpu_flip_work *works = acrtc->pflip_works; ++ ++ acrtc->pflip_works = NULL; ++ acrtc->pflip_status = AMDGPU_FLIP_NONE; ++ ++ if (works) { ++ if(works->event) ++ drm_send_vblank_event( ++ adev->ddev, ++ acrtc->crtc_id, ++ works->event); ++ ++ r = amdgpu_bo_reserve(works->old_rbo, false); ++ if (likely(r == 0)) { ++ r = amdgpu_bo_unpin(works->old_rbo); ++ if (unlikely(r != 0)) { ++ DRM_ERROR("failed to unpin buffer after flip\n"); ++ } ++ amdgpu_bo_unreserve(works->old_rbo); ++ } else ++ DRM_ERROR("failed to reserve buffer after flip\n"); ++ ++ drm_gem_object_unreference_unlocked(&works->old_rbo->gem_base); ++ kfree(works->shared); ++ kfree(works); ++ } ++} ++ ++/** ++ * dm_page_flip - called by amdgpu_flip_work_func(), which is triggered ++ * via DRM IOCTL, by user mode. ++ * ++ * @adev: amdgpu_device pointer ++ * @crtc_id: crtc to cleanup pageflip on ++ * @crtc_base: new address of the crtc (GPU MC address) ++ * ++ * Does the actual pageflip (surface address update). ++ */ ++static void dm_page_flip(struct amdgpu_device *adev, ++ int crtc_id, u64 crtc_base) ++{ ++ struct amdgpu_crtc *acrtc; ++ struct dc_target *target; ++ struct dc_flip_addrs addr = { {0} }; ++ ++ /* ++ * TODO risk of concurrency issues ++ * ++ * This should guarded by the dal_mutex but we can't do this since the ++ * caller uses a spin_lock on event_lock. ++ * ++ * If we wait on the dal_mutex a second page flip interrupt might come, ++ * spin on the event_lock, disabling interrupts while it does so. At ++ * this point the core can no longer be pre-empted and return to the ++ * thread that waited on the dal_mutex and we're deadlocked. ++ * ++ * With multiple cores the same essentially happens but might just take ++ * a little longer to lock up all cores. ++ * ++ * The reason we should lock on dal_mutex is so that we can be sure ++ * nobody messes with acrtc->target after we read and check its value. ++ * ++ * We might be able to fix our concurrency issues with a work queue ++ * where we schedule all work items (mode_set, page_flip, etc.) and ++ * execute them one by one. Care needs to be taken to still deal with ++ * any potential concurrency issues arising from interrupt calls. ++ */ ++ ++ acrtc = adev->mode_info.crtcs[crtc_id]; ++ target = acrtc->target; ++ ++ /* ++ * Received a page flip call after the display has been reset. Make sure ++ * we return the buffers. ++ */ ++ if (!target) { ++ amdgpu_dm_flip_cleanup(adev, acrtc); ++ return; ++ } ++ ++ addr.address.grph.addr.low_part = lower_32_bits(crtc_base); ++ addr.address.grph.addr.high_part = upper_32_bits(crtc_base); ++ ++ dc_flip_surface_addrs( ++ adev->dm.dc, ++ dc_target_get_status(target)->surfaces, ++ &addr, 1); ++} ++ ++static const struct amdgpu_display_funcs display_funcs = { ++ .set_vga_render_state = dm_set_vga_render_state, ++ .bandwidth_update = dm_bandwidth_update, /* called unconditionally */ ++ .vblank_get_counter = dm_vblank_get_counter,/* called unconditionally */ ++ .vblank_wait = NULL, /* not called anywhere */ ++ .is_display_hung = dm_is_display_hung,/* called unconditionally */ ++ .backlight_set_level = ++ dm_set_backlight_level,/* called unconditionally */ ++ .backlight_get_level = ++ dm_get_backlight_level,/* called unconditionally */ ++ .hpd_sense = NULL,/* called unconditionally */ ++ .hpd_set_polarity = NULL, /* called unconditionally */ ++ .hpd_get_gpio_reg = dm_hpd_get_gpio_reg,/* called unconditionally */ ++ .page_flip = dm_page_flip, /* called unconditionally */ ++ .page_flip_get_scanoutpos = ++ dm_crtc_get_scanoutpos,/* called unconditionally */ ++ .add_encoder = NULL, /* VBIOS parsing. DAL does it. */ ++ .add_connector = NULL, /* VBIOS parsing. DAL does it. */ ++ .stop_mc_access = dm_stop_mc_access, /* called unconditionally */ ++ .resume_mc_access = dm_resume_mc_access, /* called unconditionally */ ++}; ++ ++static void set_display_funcs(struct amdgpu_device *adev) ++{ ++ if (adev->mode_info.funcs == NULL) ++ adev->mode_info.funcs = &display_funcs; ++} ++ ++static int dm_early_init(void *handle) ++{ ++ struct amdgpu_device *adev = (struct amdgpu_device *)handle; ++ set_display_funcs(adev); ++ amdgpu_dm_set_irq_funcs(adev); ++ ++ switch (adev->asic_type) { ++ case CHIP_CARRIZO: ++ adev->mode_info.num_crtc = 3; ++ adev->mode_info.num_hpd = 6; ++ adev->mode_info.num_dig = 9; ++ break; ++ default: ++ DRM_ERROR("Usupported ASIC type: 0x%X\n", adev->asic_type); ++ return -EINVAL; ++ } ++ ++ /* Note: Do NOT change adev->audio_endpt_rreg and ++ * adev->audio_endpt_wreg because they are initialised in ++ * amdgpu_device_init() */ ++ ++ ++ ++ return 0; ++} ++ ++ ++bool amdgpu_dm_acquire_dal_lock(struct amdgpu_display_manager *dm) ++{ ++ /* TODO */ ++ return true; ++} ++ ++bool amdgpu_dm_release_dal_lock(struct amdgpu_display_manager *dm) ++{ ++ /* TODO */ ++ return true; ++} +diff --git a/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.h +new file mode 100644 +index 0000000..57e9c45 +--- /dev/null ++++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.h +@@ -0,0 +1,166 @@ ++/* ++ * 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 ++ * ++ */ ++ ++#ifndef __AMDGPU_DM_H__ ++#define __AMDGPU_DM_H__ ++ ++/* ++#include "linux/switch.h" ++*/ ++ ++/* ++ * This file contains the definition for amdgpu_display_manager ++ * and its API for amdgpu driver's use. ++ * This component provides all the display related functionality ++ * and this is the only component that calls DAL API. ++ * The API contained here intended for amdgpu driver use. ++ * The API that is called directly from KMS framework is located ++ * in amdgpu_dm_kms.h file ++ */ ++ ++#define AMDGPU_DM_MAX_DISPLAY_INDEX 31 ++/* ++#include "include/amdgpu_dal_power_if.h" ++#include "amdgpu_dm_irq.h" ++*/ ++ ++#include "irq_types.h" ++ ++/* Forward declarations */ ++struct amdgpu_device; ++struct drm_device; ++struct amdgpu_dm_irq_handler_data; ++ ++struct amdgpu_dm_prev_state { ++ struct drm_framebuffer *fb; ++ int32_t x; ++ int32_t y; ++ struct drm_display_mode mode; ++}; ++ ++struct common_irq_params { ++ struct amdgpu_device *adev; ++ enum dc_irq_source irq_src; ++}; ++ ++struct irq_list_head { ++ struct list_head head; ++ /* In case this interrupt needs post-processing, 'work' will be queued*/ ++ struct work_struct work; ++}; ++ ++struct amdgpu_display_manager { ++ struct dal *dal; ++ struct dc *dc; ++ void *cgs_device; ++ /* lock to be used when DAL is called from SYNC IRQ context */ ++ spinlock_t dal_lock; ++ ++ struct amdgpu_device *adev; /*AMD base driver*/ ++ struct drm_device *ddev; /*DRM base driver*/ ++ u16 display_indexes_num; ++ ++ struct amdgpu_dm_prev_state prev_state; ++ ++ /* ++ * 'irq_source_handler_table' holds a list of handlers ++ * per (DAL) IRQ source. ++ * ++ * Each IRQ source may need to be handled at different contexts. ++ * By 'context' we mean, for example: ++ * - The ISR context, which is the direct interrupt handler. ++ * - The 'deferred' context - this is the post-processing of the ++ * interrupt, but at a lower priority. ++ * ++ * Note that handlers are called in the same order as they were ++ * registered (FIFO). ++ */ ++ struct irq_list_head irq_handler_list_low_tab[DAL_IRQ_SOURCES_NUMBER]; ++ struct list_head irq_handler_list_high_tab[DAL_IRQ_SOURCES_NUMBER]; ++ ++ struct common_irq_params ++ pflip_params[DC_IRQ_SOURCE_PFLIP_LAST - DC_IRQ_SOURCE_PFLIP_FIRST + 1]; ++ ++ struct common_irq_params ++ vupdate_params[DC_IRQ_SOURCE_VUPDATE6 - DC_IRQ_SOURCE_VUPDATE1 + 1]; ++ ++ /* this spin lock synchronizes access to 'irq_handler_list_table' */ ++ spinlock_t irq_handler_list_table_lock; ++ ++ /* Timer-related data. */ ++ struct list_head timer_handler_list; ++ struct workqueue_struct *timer_workqueue; ++ ++ /* Use dal_mutex for any activity which is NOT syncronized by ++ * DRM mode setting locks. ++ * For example: amdgpu_dm_hpd_low_irq() calls into DAL *without* ++ * DRM mode setting locks being acquired. This is where dal_mutex ++ * is acquired before calling into DAL. */ ++ struct mutex dal_mutex; ++ ++ struct backlight_device *backlight_dev; ++ ++ const struct dc_link *backlight_link; ++}; ++ ++ ++/* basic init/fini API */ ++int amdgpu_dm_init(struct amdgpu_device *adev); ++ ++void amdgpu_dm_fini(struct amdgpu_device *adev); ++ ++void amdgpu_dm_destroy(void); ++ ++/* initializes drm_device display related structures, based on the information ++ * provided by DAL. The drm strcutures are: drm_crtc, drm_connector, ++ * drm_encoder, drm_mode_config ++ * ++ * Returns 0 on success ++ */ ++int amdgpu_dm_initialize_drm_device( ++ struct amdgpu_device *adev); ++ ++/* removes and deallocates the drm structures, created by the above function */ ++void amdgpu_dm_destroy_drm_device( ++ struct amdgpu_display_manager *dm); ++ ++/* Locking/Mutex */ ++bool amdgpu_dm_acquire_dal_lock(struct amdgpu_display_manager *dm); ++ ++bool amdgpu_dm_release_dal_lock(struct amdgpu_display_manager *dm); ++ ++/* Register "Backlight device" accessible by user-mode. */ ++void amdgpu_dm_register_backlight_device(struct amdgpu_display_manager *dm); ++ ++void amdgpu_dm_flip_cleanup( ++ struct amdgpu_device *adev, ++ struct amdgpu_crtc *acrtc); ++ ++extern const struct amd_ip_funcs amdgpu_dm_funcs; ++ ++void amdgpu_dm_update_connector_after_detect( ++ struct amdgpu_connector *aconnector); ++ ++#endif /* __AMDGPU_DM_H__ */ +diff --git a/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.c +new file mode 100644 +index 0000000..9491fd0 +--- /dev/null ++++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.c +@@ -0,0 +1,814 @@ ++/* ++ * 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 <drm/drmP.h> ++ ++#include "dal_services_types.h" ++#include "dc.h" ++ ++#include "amdgpu.h" ++#include "amdgpu_dm.h" ++#include "amdgpu_dm_irq.h" ++ ++ ++/****************************************************************************** ++ * Private declarations. ++ *****************************************************************************/ ++ ++struct handler_common_data { ++ struct list_head list; ++ interrupt_handler handler; ++ void *handler_arg; ++ ++ /* DM which this handler belongs to */ ++ struct amdgpu_display_manager *dm; ++}; ++ ++struct amdgpu_dm_irq_handler_data { ++ struct handler_common_data hcd; ++ /* DAL irq source which registered for this interrupt. */ ++ enum dc_irq_source irq_source; ++}; ++ ++struct amdgpu_dm_timer_handler_data { ++ struct handler_common_data hcd; ++ struct delayed_work d_work; ++}; ++ ++ ++#define DM_IRQ_TABLE_LOCK(adev, flags) \ ++ spin_lock_irqsave(&adev->dm.irq_handler_list_table_lock, flags) ++ ++#define DM_IRQ_TABLE_UNLOCK(adev, flags) \ ++ spin_unlock_irqrestore(&adev->dm.irq_handler_list_table_lock, flags) ++ ++/****************************************************************************** ++ * Private functions. ++ *****************************************************************************/ ++ ++static void init_handler_common_data( ++ struct handler_common_data *hcd, ++ void (*ih)(void *), ++ void *args, ++ struct amdgpu_display_manager *dm) ++{ ++ hcd->handler = ih; ++ hcd->handler_arg = args; ++ hcd->dm = dm; ++} ++ ++/** ++ * dm_irq_work_func - Handle an IRQ outside of the interrupt handler proper. ++ * ++ * @work: work struct ++ */ ++static void dm_irq_work_func(struct work_struct *work) ++{ ++ struct list_head *entry; ++ struct irq_list_head *irq_list_head = ++ container_of(work, struct irq_list_head, work); ++ struct list_head *handler_list = &irq_list_head->head; ++ struct amdgpu_dm_irq_handler_data *handler_data; ++ ++ list_for_each(entry, handler_list) { ++ handler_data = ++ list_entry( ++ entry, ++ struct amdgpu_dm_irq_handler_data, ++ hcd.list); ++ ++ DRM_DEBUG_KMS("DM_IRQ: work_func: for dal_src=%d\n", ++ handler_data->irq_source); ++ ++ DRM_DEBUG_KMS("DM_IRQ: schedule_work: for dal_src=%d\n", ++ handler_data->irq_source); ++ ++ handler_data->hcd.handler(handler_data->hcd.handler_arg); ++ } ++ ++ /* Call a DAL subcomponent which registered for interrupt notification ++ * at INTERRUPT_LOW_IRQ_CONTEXT. ++ * (The most common use is HPD interrupt) */ ++} ++ ++/** ++ * Remove a handler and return a pointer to hander list from which the ++ * handler was removed. ++ */ ++static struct list_head *remove_irq_handler( ++ struct amdgpu_device *adev, ++ void *ih, ++ const struct dc_interrupt_params *int_params) ++{ ++ struct list_head *hnd_list; ++ struct list_head *entry, *tmp; ++ struct amdgpu_dm_irq_handler_data *handler; ++ unsigned long irq_table_flags; ++ bool handler_removed = false; ++ enum dc_irq_source irq_source; ++ ++ DM_IRQ_TABLE_LOCK(adev, irq_table_flags); ++ ++ irq_source = int_params->irq_source; ++ ++ switch (int_params->int_context) { ++ case INTERRUPT_HIGH_IRQ_CONTEXT: ++ hnd_list = &adev->dm.irq_handler_list_high_tab[irq_source]; ++ break; ++ case INTERRUPT_LOW_IRQ_CONTEXT: ++ default: ++ hnd_list = &adev->dm.irq_handler_list_low_tab[irq_source].head; ++ break; ++ } ++ ++ list_for_each_safe(entry, tmp, hnd_list) { ++ ++ handler = list_entry(entry, struct amdgpu_dm_irq_handler_data, ++ hcd.list); ++ ++ if (ih == handler) { ++ /* Found our handler. Remove it from the list. */ ++ list_del(&handler->hcd.list); ++ handler_removed = true; ++ break; ++ } ++ } ++ ++ DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); ++ ++ if (handler_removed == false) { ++ /* Not necessarily an error - caller may not ++ * know the context. */ ++ return NULL; ++ } ++ ++ kfree(handler); ++ ++ DRM_DEBUG_KMS( ++ "DM_IRQ: removed irq handler: %p for: dal_src=%d, irq context=%d\n", ++ ih, int_params->irq_source, int_params->int_context); ++ ++ return hnd_list; ++} ++ ++/* If 'handler_in == NULL' then remove ALL handlers. */ ++static void remove_timer_handler( ++ struct amdgpu_device *adev, ++ struct amdgpu_dm_timer_handler_data *handler_in) ++{ ++ struct amdgpu_dm_timer_handler_data *handler_temp; ++ struct list_head *handler_list; ++ struct list_head *entry, *tmp; ++ unsigned long irq_table_flags; ++ bool handler_removed = false; ++ ++ DM_IRQ_TABLE_LOCK(adev, irq_table_flags); ++ ++ handler_list = &adev->dm.timer_handler_list; ++ ++ list_for_each_safe(entry, tmp, handler_list) { ++ /* Note that list_for_each_safe() guarantees that ++ * handler_temp is NOT null. */ ++ handler_temp = list_entry(entry, ++ struct amdgpu_dm_timer_handler_data, hcd.list); ++ ++ if (handler_in == NULL || handler_in == handler_temp) { ++ list_del(&handler_temp->hcd.list); ++ DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); ++ ++ DRM_DEBUG_KMS("DM_IRQ: removing timer handler: %p\n", ++ handler_temp); ++ ++ if (handler_in == NULL) { ++ /* Since it is still in the queue, it must ++ * be cancelled. */ ++ cancel_delayed_work_sync(&handler_temp->d_work); ++ } ++ ++ kfree(handler_temp); ++ handler_removed = true; ++ ++ DM_IRQ_TABLE_LOCK(adev, irq_table_flags); ++ } ++ ++ if (handler_in == NULL) { ++ /* Remove ALL handlers. */ ++ continue; ++ } ++ ++ if (handler_in == handler_temp) { ++ /* Remove a SPECIFIC handler. ++ * Found our handler - we can stop here. */ ++ break; ++ } ++ } ++ ++ DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); ++ ++ if (handler_in != NULL && handler_removed == false) { ++ DRM_ERROR("DM_IRQ: handler: %p is not in the list!\n", ++ handler_in); ++ } ++} ++ ++/** ++ * dm_timer_work_func - Handle a timer. ++ * ++ * @work: work struct ++ */ ++static void dm_timer_work_func( ++ struct work_struct *work) ++{ ++ struct amdgpu_dm_timer_handler_data *handler_data = ++ container_of(work, struct amdgpu_dm_timer_handler_data, ++ d_work.work); ++ ++ DRM_DEBUG_KMS("DM_IRQ: work_func: handler_data=%p\n", handler_data); ++ ++ /* Call a DAL subcomponent which registered for timer notification. */ ++ handler_data->hcd.handler(handler_data->hcd.handler_arg); ++ ++ /* We support only "single shot" timers. That means we must delete ++ * the handler after it was called. */ ++ remove_timer_handler(handler_data->hcd.dm->adev, handler_data); ++} ++ ++static bool validate_irq_registration_params( ++ struct dc_interrupt_params *int_params, ++ void (*ih)(void *)) ++{ ++ if (NULL == int_params || NULL == ih) { ++ DRM_ERROR("DM_IRQ: invalid input!\n"); ++ return false; ++ } ++ ++ if (int_params->int_context >= INTERRUPT_CONTEXT_NUMBER) { ++ DRM_ERROR("DM_IRQ: invalid context: %d!\n", ++ int_params->int_context); ++ return false; ++ } ++ ++ if (!DAL_VALID_IRQ_SRC_NUM(int_params->irq_source)) { ++ DRM_ERROR("DM_IRQ: invalid irq_source: %d!\n", ++ int_params->irq_source); ++ return false; ++ } ++ ++ return true; ++} ++ ++static bool validate_irq_unregistration_params( ++ enum dc_irq_source irq_source, ++ irq_handler_idx handler_idx) ++{ ++ if (DAL_INVALID_IRQ_HANDLER_IDX == handler_idx) { ++ DRM_ERROR("DM_IRQ: invalid handler_idx==NULL!\n"); ++ return false; ++ } ++ ++ if (!DAL_VALID_IRQ_SRC_NUM(irq_source)) { ++ DRM_ERROR("DM_IRQ: invalid irq_source:%d!\n", irq_source); ++ return false; ++ } ++ ++ return true; ++} ++/****************************************************************************** ++ * Public functions. ++ * ++ * Note: caller is responsible for input validation. ++ *****************************************************************************/ ++ ++void *amdgpu_dm_irq_register_interrupt( ++ struct amdgpu_device *adev, ++ struct dc_interrupt_params *int_params, ++ void (*ih)(void *), ++ void *handler_args) ++{ ++ struct list_head *hnd_list; ++ struct amdgpu_dm_irq_handler_data *handler_data; ++ unsigned long irq_table_flags; ++ enum dc_irq_source irq_source; ++ ++ if (false == validate_irq_registration_params(int_params, ih)) ++ return DAL_INVALID_IRQ_HANDLER_IDX; ++ ++ handler_data = kzalloc(sizeof(*handler_data), GFP_KERNEL); ++ if (!handler_data) { ++ DRM_ERROR("DM_IRQ: failed to allocate irq handler!\n"); ++ return DAL_INVALID_IRQ_HANDLER_IDX; ++ } ++ ++ memset(handler_data, 0, sizeof(*handler_data)); ++ ++ init_handler_common_data(&handler_data->hcd, ih, handler_args, ++ &adev->dm); ++ ++ irq_source = int_params->irq_source; ++ ++ handler_data->irq_source = irq_source; ++ ++ /* Lock the list, add the handler. */ ++ DM_IRQ_TABLE_LOCK(adev, irq_table_flags); ++ ++ switch (int_params->int_context) { ++ case INTERRUPT_HIGH_IRQ_CONTEXT: ++ hnd_list = &adev->dm.irq_handler_list_high_tab[irq_source]; ++ break; ++ case INTERRUPT_LOW_IRQ_CONTEXT: ++ default: ++ hnd_list = &adev->dm.irq_handler_list_low_tab[irq_source].head; ++ break; ++ } ++ ++ list_add_tail(&handler_data->hcd.list, hnd_list); ++ ++ DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); ++ ++ /* This pointer will be stored by code which requested interrupt ++ * registration. ++ * The same pointer will be needed in order to unregister the ++ * interrupt. */ ++ ++ DRM_DEBUG_KMS( ++ "DM_IRQ: added irq handler: %p for: dal_src=%d, irq context=%d\n", ++ handler_data, ++ irq_source, ++ int_params->int_context); ++ ++ return handler_data; ++} ++ ++void amdgpu_dm_irq_unregister_interrupt( ++ struct amdgpu_device *adev, ++ enum dc_irq_source irq_source, ++ void *ih) ++{ ++ struct list_head *handler_list; ++ struct dc_interrupt_params int_params; ++ int i; ++ ++ if (false == validate_irq_unregistration_params(irq_source, ih)) ++ return; ++ ++ memset(&int_params, 0, sizeof(int_params)); ++ ++ int_params.irq_source = irq_source; ++ ++ for (i = 0; i < INTERRUPT_CONTEXT_NUMBER; i++) { ++ ++ int_params.int_context = i; ++ ++ handler_list = remove_irq_handler(adev, ih, &int_params); ++ ++ if (handler_list != NULL) ++ break; ++ } ++ ++ if (handler_list == NULL) { ++ /* If we got here, it means we searched all irq contexts ++ * for this irq source, but the handler was not found. */ ++ DRM_ERROR( ++ "DM_IRQ: failed to find irq handler:%p for irq_source:%d!\n", ++ ih, irq_source); ++ } ++} ++ ++int amdgpu_dm_irq_init( ++ struct amdgpu_device *adev) ++{ ++ int src; ++ struct irq_list_head *lh; ++ ++ DRM_DEBUG_KMS("DM_IRQ\n"); ++ ++ spin_lock_init(&adev->dm.irq_handler_list_table_lock); ++ ++ for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { ++ /* low context handler list init */ ++ lh = &adev->dm.irq_handler_list_low_tab[src]; ++ INIT_LIST_HEAD(&lh->head); ++ INIT_WORK(&lh->work, dm_irq_work_func); ++ ++ /* high context handler init */ ++ INIT_LIST_HEAD(&adev->dm.irq_handler_list_high_tab[src]); ++ } ++ ++ INIT_LIST_HEAD(&adev->dm.timer_handler_list); ++ ++ /* allocate and initialize the workqueue for DM timer */ ++ adev->dm.timer_workqueue = create_singlethread_workqueue( ++ "dm_timer_queue"); ++ if (adev->dm.timer_workqueue == NULL) { ++ DRM_ERROR("DM_IRQ: unable to create timer queue!\n"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++void amdgpu_dm_irq_register_timer( ++ struct amdgpu_device *adev, ++ struct dc_timer_interrupt_params *int_params, ++ interrupt_handler ih, ++ void *args) ++{ ++ unsigned long jf_delay; ++ struct list_head *handler_list; ++ struct amdgpu_dm_timer_handler_data *handler_data; ++ unsigned long irq_table_flags; ++ ++ handler_data = kzalloc(sizeof(*handler_data), GFP_KERNEL); ++ if (!handler_data) { ++ DRM_ERROR("DM_IRQ: failed to allocate timer handler!\n"); ++ return; ++ } ++ ++ memset(handler_data, 0, sizeof(*handler_data)); ++ ++ init_handler_common_data(&handler_data->hcd, ih, args, &adev->dm); ++ ++ INIT_DELAYED_WORK(&handler_data->d_work, dm_timer_work_func); ++ ++ /* Lock the list, add the handler. */ ++ DM_IRQ_TABLE_LOCK(adev, irq_table_flags); ++ ++ handler_list = &adev->dm.timer_handler_list; ++ ++ list_add_tail(&handler_data->hcd.list, handler_list); ++ ++ DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); ++ ++ jf_delay = usecs_to_jiffies(int_params->micro_sec_interval); ++ ++ queue_delayed_work(adev->dm.timer_workqueue, &handler_data->d_work, ++ jf_delay); ++ ++ DRM_DEBUG_KMS("DM_IRQ: added handler:%p with micro_sec_interval=%llu\n", ++ handler_data, int_params->micro_sec_interval); ++ return; ++} ++ ++/* DM IRQ and timer resource release */ ++void amdgpu_dm_irq_fini( ++ struct amdgpu_device *adev) ++{ ++ int src; ++ struct irq_list_head *lh; ++ DRM_DEBUG_KMS("DM_IRQ: releasing resources.\n"); ++ ++ for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { ++ ++ /* The handler was removed from the table, ++ * it means it is safe to flush all the 'work' ++ * (because no code can schedule a new one). */ ++ lh = &adev->dm.irq_handler_list_low_tab[src]; ++ flush_work(&lh->work); ++ } ++ ++ /* Cancel ALL timers and release handlers (if any). */ ++ remove_timer_handler(adev, NULL); ++ /* Release the queue itself. */ ++ destroy_workqueue(adev->dm.timer_workqueue); ++} ++ ++int amdgpu_dm_irq_suspend( ++ struct amdgpu_device *adev) ++{ ++ int src; ++ struct list_head *hnd_list_h; ++ struct list_head *hnd_list_l; ++ unsigned long irq_table_flags; ++ ++ DM_IRQ_TABLE_LOCK(adev, irq_table_flags); ++ ++ DRM_DEBUG_KMS("DM_IRQ: suspend\n"); ++ ++ /* disable HW interrupt */ ++ for (src = DC_IRQ_SOURCE_HPD1; src < DAL_IRQ_SOURCES_NUMBER; src++) { ++ hnd_list_l = &adev->dm.irq_handler_list_low_tab[src].head; ++ hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; ++ if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) ++ dc_interrupt_set(adev->dm.dc, src, false); ++ ++ DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); ++ flush_work(&adev->dm.irq_handler_list_low_tab[src].work); ++ ++ DM_IRQ_TABLE_LOCK(adev, irq_table_flags); ++ } ++ ++ DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); ++ ++ return 0; ++} ++ ++int amdgpu_dm_irq_resume( ++ struct amdgpu_device *adev) ++{ ++ int src; ++ struct list_head *hnd_list_h, *hnd_list_l; ++ unsigned long irq_table_flags; ++ ++ DM_IRQ_TABLE_LOCK(adev, irq_table_flags); ++ ++ DRM_DEBUG_KMS("DM_IRQ: resume\n"); ++ ++ /* re-enable HW interrupt */ ++ for (src = DC_IRQ_SOURCE_HPD1; src < DAL_IRQ_SOURCES_NUMBER; src++) { ++ hnd_list_l = &adev->dm.irq_handler_list_low_tab[src].head; ++ hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; ++ if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) ++ dc_interrupt_set(adev->dm.dc, src, true); ++ } ++ ++ DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); ++ ++ return 0; ++} ++ ++ ++/** ++ * amdgpu_dm_irq_schedule_work - schedule all work items registered for the ++ * "irq_source". ++ */ ++static void amdgpu_dm_irq_schedule_work( ++ struct amdgpu_device *adev, ++ enum dc_irq_source irq_source) ++{ ++ unsigned long irq_table_flags; ++ ++ DM_IRQ_TABLE_LOCK(adev, irq_table_flags); ++ ++ /* Since the caller is interested in 'work_struct' then ++ * the irq will be post-processed at "INTERRUPT_LOW_IRQ_CONTEXT". */ ++ ++ schedule_work(&adev->dm.irq_handler_list_low_tab[irq_source].work); ++ ++ DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); ++} ++ ++/** amdgpu_dm_irq_immediate_work ++ * Callback high irq work immediately, don't send to work queue ++ */ ++static void amdgpu_dm_irq_immediate_work( ++ struct amdgpu_device *adev, ++ enum dc_irq_source irq_source) ++{ ++ struct amdgpu_dm_irq_handler_data *handler_data; ++ struct list_head *entry; ++ unsigned long irq_table_flags; ++ ++ DM_IRQ_TABLE_LOCK(adev, irq_table_flags); ++ ++ list_for_each( ++ entry, ++ &adev->dm.irq_handler_list_high_tab[irq_source]) { ++ ++ handler_data = ++ list_entry( ++ entry, ++ struct amdgpu_dm_irq_handler_data, ++ hcd.list); ++ ++ /* Call a subcomponent which registered for immediate ++ * interrupt notification */ ++ handler_data->hcd.handler(handler_data->hcd.handler_arg); ++ } ++ ++ DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); ++} ++ ++/* ++ * amdgpu_dm_irq_handler ++ * ++ * Generic IRQ handler, calls all registered high irq work immediately, and ++ * schedules work for low irq ++ */ ++int amdgpu_dm_irq_handler( ++ struct amdgpu_device *adev, ++ struct amdgpu_irq_src *source, ++ struct amdgpu_iv_entry *entry) ++{ ++ ++ enum dc_irq_source src = ++ dc_interrupt_to_irq_source( ++ adev->dm.dc, ++ entry->src_id, ++ entry->src_data); ++ ++ dc_interrupt_ack(adev->dm.dc, src); ++ ++ /* Call high irq work immediately */ ++ amdgpu_dm_irq_immediate_work(adev, src); ++ /*Schedule low_irq work */ ++ amdgpu_dm_irq_schedule_work(adev, src); ++ ++ return 0; ++} ++ ++static enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned type) ++{ ++ switch (type) { ++ case AMDGPU_HPD_1: ++ return DC_IRQ_SOURCE_HPD1; ++ case AMDGPU_HPD_2: ++ return DC_IRQ_SOURCE_HPD2; ++ case AMDGPU_HPD_3: ++ return DC_IRQ_SOURCE_HPD3; ++ case AMDGPU_HPD_4: ++ return DC_IRQ_SOURCE_HPD4; ++ case AMDGPU_HPD_5: ++ return DC_IRQ_SOURCE_HPD5; ++ case AMDGPU_HPD_6: ++ return DC_IRQ_SOURCE_HPD6; ++ default: ++ return DC_IRQ_SOURCE_INVALID; ++ } ++} ++ ++static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, ++ struct amdgpu_irq_src *source, ++ unsigned type, ++ enum amdgpu_interrupt_state state) ++{ ++ enum dc_irq_source src = amdgpu_dm_hpd_to_dal_irq_source(type); ++ bool st = (state == AMDGPU_IRQ_STATE_ENABLE); ++ ++ dc_interrupt_set(adev->dm.dc, src, st); ++ return 0; ++} ++ ++static inline int dm_irq_state( ++ struct amdgpu_device *adev, ++ struct amdgpu_irq_src *source, ++ unsigned crtc_id, ++ enum amdgpu_interrupt_state state, ++ const enum irq_type dal_irq_type, ++ const char *func) ++{ ++ bool st; ++ enum dc_irq_source irq_source; ++ ++ struct amdgpu_crtc *acrtc = adev->mode_info.crtcs[crtc_id]; ++ ++ if (!acrtc->target) { ++ DRM_INFO( ++ "%s: target is null for crtc %d, talk to David R\n", ++ func, ++ crtc_id); ++ WARN_ON(true); ++ return 0; ++ } ++ ++ irq_source = dc_target_get_irq_src(acrtc->target, dal_irq_type); ++ ++ st = (state == AMDGPU_IRQ_STATE_ENABLE); ++ ++ dc_interrupt_set(adev->dm.dc, irq_source, st); ++ return 0; ++} ++ ++static int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, ++ struct amdgpu_irq_src *source, ++ unsigned crtc_id, ++ enum amdgpu_interrupt_state state) ++{ ++ return dm_irq_state( ++ adev, ++ source, ++ crtc_id, ++ state, ++ IRQ_TYPE_PFLIP, ++ __func__); ++} ++ ++static int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, ++ struct amdgpu_irq_src *source, ++ unsigned crtc_id, ++ enum amdgpu_interrupt_state state) ++{ ++ return dm_irq_state( ++ adev, ++ source, ++ crtc_id, ++ state, ++ IRQ_TYPE_VUPDATE, ++ __func__); ++} ++ ++static const struct amdgpu_irq_src_funcs dm_crtc_irq_funcs = { ++ .set = amdgpu_dm_set_crtc_irq_state, ++ .process = amdgpu_dm_irq_handler, ++}; ++ ++static const struct amdgpu_irq_src_funcs dm_pageflip_irq_funcs = { ++ .set = amdgpu_dm_set_pflip_irq_state, ++ .process = amdgpu_dm_irq_handler, ++}; ++ ++static const struct amdgpu_irq_src_funcs dm_hpd_irq_funcs = { ++ .set = amdgpu_dm_set_hpd_irq_state, ++ .process = amdgpu_dm_irq_handler, ++}; ++ ++void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev) ++{ ++ adev->crtc_irq.num_types = AMDGPU_CRTC_IRQ_LAST; ++ adev->crtc_irq.funcs = &dm_crtc_irq_funcs; ++ ++ adev->pageflip_irq.num_types = AMDGPU_PAGEFLIP_IRQ_LAST; ++ adev->pageflip_irq.funcs = &dm_pageflip_irq_funcs; ++ ++ adev->hpd_irq.num_types = AMDGPU_HPD_LAST; ++ adev->hpd_irq.funcs = &dm_hpd_irq_funcs; ++} ++ ++/* ++ * amdgpu_dm_hpd_init - hpd setup callback. ++ * ++ * @adev: amdgpu_device pointer ++ * ++ * Setup the hpd pins used by the card (evergreen+). ++ * Enable the pin, set the polarity, and enable the hpd interrupts. ++ */ ++void amdgpu_dm_hpd_init(struct amdgpu_device *adev) ++{ ++ struct drm_device *dev = adev->ddev; ++ struct drm_connector *connector; ++ ++ list_for_each_entry(connector, &dev->mode_config.connector_list, head) { ++ struct amdgpu_connector *amdgpu_connector = ++ to_amdgpu_connector(connector); ++ enum dc_irq_source src = ++ amdgpu_dm_hpd_to_dal_irq_source( ++ amdgpu_connector->hpd.hpd); ++ const struct dc_link *dc_link = amdgpu_connector->dc_link; ++ ++ if (connector->connector_type == DRM_MODE_CONNECTOR_eDP || ++ connector->connector_type == DRM_MODE_CONNECTOR_LVDS) { ++ /* don't try to enable hpd on eDP or LVDS avoid breaking ++ * the aux dp channel on imac and help (but not ++ * completely fix) ++ * https://bugzilla.redhat.com/show_bug.cgi?id=726143 ++ * also avoid interrupt storms during dpms. ++ */ ++ continue; ++ } ++ ++ dc_interrupt_set(adev->dm.dc, src, true); ++ amdgpu_irq_get(adev, &adev->hpd_irq, amdgpu_connector->hpd.hpd); ++ ++ if (DC_IRQ_SOURCE_INVALID != dc_link->irq_source_hpd_rx) { ++ dc_interrupt_set(adev->dm.dc, ++ dc_link->irq_source_hpd_rx, ++ true); ++ } ++ } ++} ++ ++/** ++ * amdgpu_dm_hpd_fini - hpd tear down callback. ++ * ++ * @adev: amdgpu_device pointer ++ * ++ * Tear down the hpd pins used by the card (evergreen+). ++ * Disable the hpd interrupts. ++ */ ++void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) ++{ ++ struct drm_device *dev = adev->ddev; ++ struct drm_connector *connector; ++ ++ list_for_each_entry(connector, &dev->mode_config.connector_list, head) { ++ struct amdgpu_connector *amdgpu_connector = ++ to_amdgpu_connector(connector); ++ enum dc_irq_source src = ++ amdgpu_dm_hpd_to_dal_irq_source( ++ amdgpu_connector->hpd.hpd); ++ ++ dc_interrupt_set(adev->dm.dc, src, false); ++ amdgpu_irq_put(adev, &adev->hpd_irq, amdgpu_connector->hpd.hpd); ++ } ++} +diff --git a/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.h +new file mode 100644 +index 0000000..afedb50 +--- /dev/null ++++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.h +@@ -0,0 +1,122 @@ ++/* ++ * 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. ++ * ++ */ ++ ++#ifndef __AMDGPU_DM_IRQ_H__ ++#define __AMDGPU_DM_IRQ_H__ ++ ++#include "irq_types.h" /* DAL irq definitions */ ++ ++/* ++ * Display Manager IRQ-related interfaces (for use by DAL). ++ */ ++ ++/** ++ * amdgpu_dm_irq_init - Initialize internal structures of 'amdgpu_dm_irq'. ++ * ++ * This function should be called exactly once - during DM initialization. ++ * ++ * Returns: ++ * 0 - success ++ * non-zero - error ++ */ ++int amdgpu_dm_irq_init( ++ struct amdgpu_device *adev); ++ ++/** ++ * amdgpu_dm_irq_fini - deallocate internal structures of 'amdgpu_dm_irq'. ++ * ++ * This function should be called exactly once - during DM destruction. ++ * ++ */ ++void amdgpu_dm_irq_fini( ++ struct amdgpu_device *adev); ++ ++/** ++ * amdgpu_dm_irq_register_interrupt - register irq handler for Display block. ++ * ++ * @adev: AMD DRM device ++ * @int_params: parameters for the irq ++ * @ih: pointer to the irq hander function ++ * @handler_args: arguments which will be passed to ih ++ * ++ * Returns: ++ * IRQ Handler Index on success. ++ * NULL on failure. ++ * ++ * Cannot be called from an interrupt handler. ++ */ ++void *amdgpu_dm_irq_register_interrupt( ++ struct amdgpu_device *adev, ++ struct dc_interrupt_params *int_params, ++ void (*ih)(void *), ++ void *handler_args); ++ ++/** ++ * amdgpu_dm_irq_unregister_interrupt - unregister handler which was registered ++ * by amdgpu_dm_irq_register_interrupt(). ++ * ++ * @adev: AMD DRM device. ++ * @ih_index: irq handler index which was returned by ++ * amdgpu_dm_irq_register_interrupt ++ */ ++void amdgpu_dm_irq_unregister_interrupt( ++ struct amdgpu_device *adev, ++ enum dc_irq_source irq_source, ++ void *ih_index); ++ ++void amdgpu_dm_irq_register_timer( ++ struct amdgpu_device *adev, ++ struct dc_timer_interrupt_params *int_params, ++ interrupt_handler ih, ++ void *args); ++ ++/** ++ * amdgpu_dm_irq_handler ++ * Generic IRQ handler, calls all registered high irq work immediately, and ++ * schedules work for low irq ++ */ ++int amdgpu_dm_irq_handler( ++ struct amdgpu_device *adev, ++ struct amdgpu_irq_src *source, ++ struct amdgpu_iv_entry *entry); ++ ++void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev); ++ ++void amdgpu_dm_hpd_init(struct amdgpu_device *adev); ++void amdgpu_dm_hpd_fini(struct amdgpu_device *adev); ++ ++/** ++ * amdgpu_dm_irq_suspend - disable ASIC interrupt during suspend. ++ * ++ */ ++int amdgpu_dm_irq_suspend( ++ struct amdgpu_device *adev); ++ ++/** ++ * amdgpu_dm_irq_resume - enable ASIC interrupt during resume. ++ * ++ */ ++int amdgpu_dm_irq_resume( ++ struct amdgpu_device *adev); ++ ++#endif /* __AMDGPU_DM_IRQ_H__ */ +diff --git a/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_mst_types.c +new file mode 100644 +index 0000000..6d9ee15 +--- /dev/null ++++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_mst_types.c +@@ -0,0 +1,353 @@ ++/* ++ * 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 "dal_services.h" ++ ++#include "amdgpu.h" ++ ++#include "amdgpu_dm_types.h" ++ ++#include "amdgpu_dm_mst_types.h" ++ ++#include "dc.h" ++ ++static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) ++{ ++ struct pci_dev *pdev = to_pci_dev(aux->dev); ++ struct drm_device *drm_dev = pci_get_drvdata(pdev); ++ struct amdgpu_device *adev = drm_dev->dev_private; ++ struct dc *dc = adev->dm.dc; ++ ++ switch (msg->request) { ++ case DP_AUX_NATIVE_READ: ++ dc_read_dpcd( ++ dc, ++ TO_DM_AUX(aux)->link_index, ++ msg->address, ++ msg->buffer, ++ msg->size); ++ break; ++ case DP_AUX_NATIVE_WRITE: ++ dc_write_dpcd( ++ dc, ++ TO_DM_AUX(aux)->link_index, ++ msg->address, ++ msg->buffer, ++ msg->size); ++ break; ++ default: ++ return 0; ++ } ++ ++ return msg->size; ++} ++ ++static enum drm_connector_status ++dm_dp_mst_detect(struct drm_connector *connector, bool force) ++{ ++ struct amdgpu_connector *aconnector = to_amdgpu_connector(connector); ++ struct amdgpu_connector *master = aconnector->mst_port; ++ ++ return drm_dp_mst_detect_port(connector, &master->mst_mgr, aconnector->port); ++} ++ ++static void ++dm_dp_mst_connector_destroy(struct drm_connector *connector) ++{ ++ struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); ++ struct amdgpu_encoder *amdgpu_encoder = amdgpu_connector->mst_encoder; ++ ++ drm_encoder_cleanup(&amdgpu_encoder->base); ++ kfree(amdgpu_encoder); ++ drm_connector_cleanup(connector); ++ kfree(amdgpu_connector); ++} ++ ++static int dm_dp_mst_connector_dpms(struct drm_connector *connector, int mode) ++{ ++ DRM_DEBUG_KMS("\n"); ++ return 0; ++} ++ ++static const struct drm_connector_funcs dm_dp_mst_connector_funcs = { ++ .dpms = dm_dp_mst_connector_dpms, ++ .detect = dm_dp_mst_detect, ++ .fill_modes = drm_helper_probe_single_connector_modes, ++ .destroy = dm_dp_mst_connector_destroy, ++ .reset = amdgpu_dm_connector_funcs_reset, ++ .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state, ++ .atomic_destroy_state = amdgpu_dm_connector_atomic_destroy_state, ++ .atomic_set_property = amdgpu_dm_connector_atomic_set_property ++}; ++ ++static const struct dc_sink *dm_dp_mst_add_mst_sink( ++ struct dc_link *dc_link, ++ uint8_t *edid, ++ uint16_t len) ++{ ++ struct dc_sink *dc_sink; ++ struct sink_init_data init_params = { ++ .link = dc_link, ++ .sink_signal = SIGNAL_TYPE_DISPLAY_PORT_MST}; ++ ++ if (len > MAX_EDID_BUFFER_SIZE) { ++ DRM_ERROR("Max EDID buffer size breached!\n"); ++ return NULL; ++ } ++ ++ /* ++ * TODO make dynamic-ish? ++ * dc_link->connector_signal; ++ */ ++ ++ dc_sink = sink_create(&init_params); ++ ++ if (!dc_sink) ++ return NULL; ++ ++ dc_service_memmove(dc_sink->dc_edid.raw_edid, edid, len); ++ dc_sink->dc_edid.length = len; ++ ++ if (!dc_link_add_sink( ++ dc_link, ++ dc_sink)) ++ goto fail; ++ ++ /* dc_sink_retain(&core_sink->public); */ ++ ++ return dc_sink; ++ ++fail: ++ dc_link_remove_sink(dc_link, dc_sink); ++ return NULL; ++} ++ ++static int dm_dp_mst_get_modes(struct drm_connector *connector) ++{ ++ struct amdgpu_connector *aconnector = to_amdgpu_connector(connector); ++ struct amdgpu_connector *master = aconnector->mst_port; ++ struct edid *edid; ++ const struct dc_sink *sink; ++ int ret = 0; ++ ++ edid = drm_dp_mst_get_edid(connector, &master->mst_mgr, aconnector->port); ++ ++ if (!edid) { ++ drm_mode_connector_update_edid_property( ++ &aconnector->base, ++ NULL); ++ ++ return ret; ++ } ++ ++ aconnector->edid = edid; ++ ++ if (!aconnector->dc_sink) { ++ sink = dm_dp_mst_add_mst_sink( ++ (struct dc_link *)aconnector->dc_link, ++ (uint8_t *)edid, ++ (edid->extensions + 1) * EDID_LENGTH); ++ aconnector->dc_sink = sink; ++ } ++ ++ DRM_DEBUG_KMS("edid retrieved %p\n", edid); ++ ++ drm_mode_connector_update_edid_property( ++ &aconnector->base, ++ aconnector->edid); ++ ++ ret = drm_add_edid_modes(&aconnector->base, aconnector->edid); ++ ++ drm_edid_to_eld(&aconnector->base, aconnector->edid); ++ ++ return ret; ++} ++ ++static enum drm_mode_status ++dm_dp_mst_mode_valid(struct drm_connector *connector, ++ struct drm_display_mode *mode) ++{ ++ return MODE_OK; ++} ++ ++static struct drm_encoder *dm_mst_best_encoder(struct drm_connector *connector) ++{ ++ struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); ++ ++ return &amdgpu_connector->mst_encoder->base; ++} ++ ++static const struct drm_connector_helper_funcs dm_dp_mst_connector_helper_funcs = { ++ .get_modes = dm_dp_mst_get_modes, ++ .mode_valid = dm_dp_mst_mode_valid, ++ .best_encoder = dm_mst_best_encoder, ++}; ++ ++static struct amdgpu_encoder * ++dm_dp_create_fake_mst_encoder(struct amdgpu_connector *connector) ++{ ++ struct drm_device *dev = connector->base.dev; ++ struct amdgpu_device *adev = dev->dev_private; ++ struct amdgpu_encoder *amdgpu_encoder; ++ struct drm_encoder *encoder; ++ const struct drm_connector_helper_funcs *connector_funcs = ++ connector->base.helper_private; ++ struct drm_encoder *enc_master = ++ connector_funcs->best_encoder(&connector->base); ++ ++ DRM_DEBUG_KMS("enc master is %p\n", enc_master); ++ amdgpu_encoder = kzalloc(sizeof(*amdgpu_encoder), GFP_KERNEL); ++ if (!amdgpu_encoder) ++ return NULL; ++ ++ encoder = &amdgpu_encoder->base; ++ switch (adev->mode_info.num_crtc) { ++ case 1: ++ encoder->possible_crtcs = 0x1; ++ break; ++ case 2: ++ default: ++ encoder->possible_crtcs = 0x3; ++ break; ++ case 4: ++ encoder->possible_crtcs = 0xf; ++ break; ++ case 6: ++ encoder->possible_crtcs = 0x3f; ++ break; ++ } ++ ++ encoder->possible_crtcs = 0x1; ++ ++ drm_encoder_init( ++ dev, ++ &amdgpu_encoder->base, ++ NULL, ++ DRM_MODE_ENCODER_DPMST, ++ NULL); ++ ++ drm_encoder_helper_add(encoder, &amdgpu_dm_encoder_helper_funcs); ++ ++ return amdgpu_encoder; ++} ++ ++static struct drm_connector *dm_dp_add_mst_connector(struct drm_dp_mst_topology_mgr *mgr, ++ struct drm_dp_mst_port *port, ++ const char *pathprop) ++{ ++ struct amdgpu_connector *master = container_of(mgr, struct amdgpu_connector, mst_mgr); ++ struct drm_device *dev = master->base.dev; ++ struct amdgpu_device *adev = dev->dev_private; ++ struct amdgpu_connector *aconnector; ++ struct drm_connector *connector; ++ ++ aconnector = kzalloc(sizeof(*aconnector), GFP_KERNEL); ++ if (!aconnector) ++ return NULL; ++ ++ aconnector->is_mst_connector = true; ++ connector = &aconnector->base; ++ aconnector->port = port; ++ aconnector->mst_port = master; ++ aconnector->dc_link = master->dc_link; ++ ++ /* Initialize connector state before adding the connectror to drm and framebuffer lists */ ++ amdgpu_dm_connector_funcs_reset(connector); ++ ++ drm_connector_init(dev, connector, &dm_dp_mst_connector_funcs, DRM_MODE_CONNECTOR_DisplayPort); ++ drm_connector_helper_add(connector, &dm_dp_mst_connector_helper_funcs); ++ aconnector->mst_encoder = dm_dp_create_fake_mst_encoder(master); ++ ++ drm_object_attach_property(&connector->base, dev->mode_config.path_property, 0); ++ drm_mode_connector_set_path_property(connector, pathprop); ++ ++ mutex_lock(&dev->mode_config.mutex); ++ drm_fb_helper_add_one_connector(&adev->mode_info.rfbdev->helper, connector); ++ mutex_unlock(&dev->mode_config.mutex); ++ ++ drm_connector_register(connector); ++ ++ DRM_DEBUG_KMS(":%d\n", connector->base.id); ++ ++ return connector; ++} ++ ++static void dm_dp_destroy_mst_connector( ++ struct drm_dp_mst_topology_mgr *mgr, ++ struct drm_connector *connector) ++{ ++ struct amdgpu_connector *master = ++ container_of(mgr, struct amdgpu_connector, mst_mgr); ++ struct drm_device *dev = master->base.dev; ++ struct amdgpu_device *adev = dev->dev_private; ++ drm_connector_unregister(connector); ++ /* need to nuke the connector */ ++ mutex_lock(&dev->mode_config.mutex); ++ /* dpms off */ ++ drm_fb_helper_remove_one_connector( ++ &adev->mode_info.rfbdev->helper, ++ connector); ++ ++ drm_connector_cleanup(connector); ++ mutex_unlock(&dev->mode_config.mutex); ++ ++ kfree(connector); ++ DRM_DEBUG_KMS("\n"); ++} ++ ++static void dm_dp_mst_hotplug(struct drm_dp_mst_topology_mgr *mgr) ++{ ++ struct amdgpu_connector *master = container_of(mgr, struct amdgpu_connector, mst_mgr); ++ struct drm_device *dev = master->base.dev; ++ ++ drm_kms_helper_hotplug_event(dev); ++} ++ ++struct drm_dp_mst_topology_cbs dm_mst_cbs = { ++ .add_connector = dm_dp_add_mst_connector, ++ .destroy_connector = dm_dp_destroy_mst_connector, ++ .hotplug = dm_dp_mst_hotplug, ++}; ++ ++void amdgpu_dm_initialize_mst_connector( ++ struct amdgpu_display_manager *dm, ++ struct amdgpu_connector *aconnector) ++{ ++ aconnector->dm_dp_aux.aux.name = "dmdc"; ++ aconnector->dm_dp_aux.aux.dev = dm->adev->dev; ++ aconnector->dm_dp_aux.aux.transfer = dm_dp_aux_transfer; ++ aconnector->dm_dp_aux.link_index = aconnector->connector_id; ++ ++ drm_dp_aux_register(&aconnector->dm_dp_aux.aux); ++ ++ aconnector->mst_mgr.cbs = &dm_mst_cbs; ++ drm_dp_mst_topology_mgr_init( ++ &aconnector->mst_mgr, ++ dm->adev->dev, ++ &aconnector->dm_dp_aux.aux, ++ 16, ++ 4, ++ aconnector->connector_id); ++} +diff --git a/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_mst_types.h b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_mst_types.h +new file mode 100644 +index 0000000..6130d62 +--- /dev/null ++++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_mst_types.h +@@ -0,0 +1,36 @@ ++/* ++ * 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 ++ * ++ */ ++ ++#ifndef __DAL_AMDGPU_DM_MST_TYPES_H__ ++#define __DAL_AMDGPU_DM_MST_TYPES_H__ ++ ++struct amdgpu_display_manager; ++struct amdgpu_connector; ++ ++void amdgpu_dm_initialize_mst_connector( ++ struct amdgpu_display_manager *dm, ++ struct amdgpu_connector *aconnector); ++ ++#endif +diff --git a/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.c b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.c +new file mode 100644 +index 0000000..bfff48c +--- /dev/null ++++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.c +@@ -0,0 +1,2390 @@ ++/* ++ * Copyright 2012-13 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 "dal_services_types.h" ++ ++#include <linux/types.h> ++#include <drm/drmP.h> ++#include <drm/drm_atomic_helper.h> ++#include <drm/drm_fb_helper.h> ++#include <drm/drm_atomic.h> ++#include "amdgpu.h" ++#include "amdgpu_pm.h" ++// We need to #undef FRAME_SIZE and DEPRECATED because they conflict ++// with ptrace-abi.h's #define's of them. ++#undef FRAME_SIZE ++#undef DEPRECATED ++ ++#include "mode_query_interface.h" ++#include "dcs_types.h" ++#include "mode_manager_types.h" ++ ++/*#include "amdgpu_buffer.h"*/ ++ ++#include "dce/dce_11_0_d.h" ++#include "dce/dce_11_0_sh_mask.h" ++#include "dce/dce_11_0_enum.h" ++ ++#include "dc.h" ++ ++#include "amdgpu_dm_types.h" ++#include "amdgpu_dm_mst_types.h" ++ ++struct dm_connector_state { ++ struct drm_connector_state base; ++ ++ enum amdgpu_rmx_type scaling; ++ uint8_t underscan_vborder; ++ uint8_t underscan_hborder; ++ bool underscan_enable; ++}; ++ ++#define to_dm_connector_state(x)\ ++ container_of((x), struct dm_connector_state, base) ++ ++#define AMDGPU_CRTC_MODE_PRIVATE_FLAGS_GAMMASET 1 ++ ++void amdgpu_dm_encoder_destroy(struct drm_encoder *encoder) ++{ ++ drm_encoder_cleanup(encoder); ++ kfree(encoder); ++} ++ ++static const struct drm_encoder_funcs amdgpu_dm_encoder_funcs = { ++ .destroy = amdgpu_dm_encoder_destroy, ++}; ++ ++static void dm_set_cursor( ++ struct amdgpu_crtc *amdgpu_crtc, ++ uint64_t gpu_addr, ++ uint32_t width, ++ uint32_t height) ++{ ++ struct dc_cursor_attributes attributes; ++ amdgpu_crtc->cursor_width = width; ++ amdgpu_crtc->cursor_height = height; ++ ++ attributes.address.high_part = upper_32_bits(gpu_addr); ++ attributes.address.low_part = lower_32_bits(gpu_addr); ++ attributes.width = width-1; ++ attributes.height = height-1; ++ attributes.x_hot = 0; ++ attributes.y_hot = 0; ++ attributes.color_format = CURSOR_MODE_COLOR_PRE_MULTIPLIED_ALPHA; ++ attributes.rotation_angle = 0; ++ attributes.attribute_flags.value = 0; ++ ++ if (!dc_target_set_cursor_attributes( ++ amdgpu_crtc->target, ++ &attributes)) { ++ DRM_ERROR("DC failed to set cursor attributes\n"); ++ } ++} ++ ++static int dm_crtc_unpin_cursor_bo_old( ++ struct amdgpu_crtc *amdgpu_crtc) ++{ ++ struct amdgpu_bo *robj; ++ int ret = 0; ++ ++ if (NULL != amdgpu_crtc && NULL != amdgpu_crtc->cursor_bo) { ++ robj = gem_to_amdgpu_bo(amdgpu_crtc->cursor_bo); ++ ++ ret = amdgpu_bo_reserve(robj, false); ++ ++ if (likely(ret == 0)) { ++ amdgpu_bo_unpin(robj); ++ amdgpu_bo_unreserve(robj); ++ } ++ } else { ++ DRM_ERROR("dm_crtc_unpin_cursor_ob_old bo %x, leaked %p\n", ++ ret, ++ amdgpu_crtc->cursor_bo); ++ } ++ ++ drm_gem_object_unreference_unlocked(amdgpu_crtc->cursor_bo); ++ amdgpu_crtc->cursor_bo = NULL; ++ ++ return ret; ++} ++ ++static int dm_crtc_pin_cursor_bo_new( ++ struct drm_crtc *crtc, ++ struct drm_file *file_priv, ++ uint32_t handle, ++ struct amdgpu_bo **ret_obj, ++ uint64_t *gpu_addr) ++{ ++ struct amdgpu_crtc *amdgpu_crtc; ++ struct amdgpu_bo *robj; ++ struct drm_gem_object *obj; ++ int ret = EINVAL; ++ ++ if (NULL != crtc) { ++ amdgpu_crtc = to_amdgpu_crtc(crtc); ++ ++ obj = drm_gem_object_lookup(crtc->dev, file_priv, handle); ++ ++ if (!obj) { ++ DRM_ERROR( ++ "Cannot find cursor object %x for crtc %d\n", ++ handle, ++ amdgpu_crtc->crtc_id); ++ goto release; ++ } ++ robj = gem_to_amdgpu_bo(obj); ++ ++ ret = amdgpu_bo_reserve(robj, false); ++ ++ if (unlikely(ret != 0)) { ++ drm_gem_object_unreference_unlocked(obj); ++ DRM_ERROR("dm_crtc_pin_cursor_bo_new ret %x, handle %x\n", ++ ret, handle); ++ goto release; ++ } ++ ++ ret = amdgpu_bo_pin(robj, AMDGPU_GEM_DOMAIN_VRAM, NULL); ++ ++ if (ret == 0) { ++ *gpu_addr = amdgpu_bo_gpu_offset(robj); ++ *ret_obj = robj; ++ } ++ amdgpu_bo_unreserve(robj); ++ if (ret) ++ drm_gem_object_unreference_unlocked(obj); ++ ++ } ++release: ++ ++ return ret; ++} ++ ++static int dm_crtc_cursor_set( ++ struct drm_crtc *crtc, ++ struct drm_file *file_priv, ++ uint32_t handle, ++ uint32_t width, ++ uint32_t height) ++{ ++ struct amdgpu_bo *new_cursor_bo; ++ uint64_t gpu_addr; ++ struct dc_cursor_position position; ++ ++ int ret; ++ ++ struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); ++ ++ ret = EINVAL; ++ new_cursor_bo = NULL; ++ gpu_addr = 0; ++ ++ DRM_DEBUG_KMS( ++ "%s: crtc_id=%d with handle %d and size %d to %d, bo_object %p\n", ++ __func__, ++ amdgpu_crtc->crtc_id, ++ handle, ++ width, ++ height, ++ amdgpu_crtc->cursor_bo); ++ ++ if (!handle) { ++ /* turn off cursor */ ++ position.enable = false; ++ position.x = 0; ++ position.y = 0; ++ position.hot_spot_enable = false; ++ ++ if (amdgpu_crtc->target) { ++ /*set cursor visible false*/ ++ dc_target_set_cursor_position( ++ amdgpu_crtc->target, ++ &position); ++ } ++ /*unpin old cursor buffer and update cache*/ ++ ret = dm_crtc_unpin_cursor_bo_old(amdgpu_crtc); ++ goto release; ++ ++ } ++ ++ if ((width > amdgpu_crtc->max_cursor_width) || ++ (height > amdgpu_crtc->max_cursor_height)) { ++ DRM_ERROR( ++ "%s: bad cursor width or height %d x %d\n", ++ __func__, ++ width, ++ height); ++ goto release; ++ } ++ /*try to pin new cursor bo*/ ++ ret = dm_crtc_pin_cursor_bo_new(crtc, file_priv, handle, ++ &new_cursor_bo, &gpu_addr); ++ /*if map not successful then return an error*/ ++ if (ret) ++ goto release; ++ ++ /*program new cursor bo to hardware*/ ++ dm_set_cursor(amdgpu_crtc, gpu_addr, width, height); ++ ++ /*un map old, not used anymore cursor bo , ++ * return memory and mapping back */ ++ dm_crtc_unpin_cursor_bo_old(amdgpu_crtc); ++ ++ /*assign new cursor bo to our internal cache*/ ++ amdgpu_crtc->cursor_bo = &new_cursor_bo->gem_base; ++ ++release: ++ return ret; ++ ++} ++ ++static int dm_crtc_cursor_move(struct drm_crtc *crtc, ++ int x, int y) ++{ ++ struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); ++ int xorigin = 0, yorigin = 0; ++ struct dc_cursor_position position; ++ ++ /* avivo cursor are offset into the total surface */ ++ x += crtc->primary->state->src_x >> 16; ++ y += crtc->primary->state->src_y >> 16; ++ ++ /* ++ * TODO: for cursor debugging unguard the following ++ */ ++#if 0 ++ DRM_DEBUG_KMS( ++ "%s: x %d y %d c->x %d c->y %d\n", ++ __func__, ++ x, ++ y, ++ crtc->x, ++ crtc->y); ++#endif ++ ++ if (x < 0) { ++ xorigin = min(-x, amdgpu_crtc->max_cursor_width - 1); ++ x = 0; ++ } ++ if (y < 0) { ++ yorigin = min(-y, amdgpu_crtc->max_cursor_height - 1); ++ y = 0; ++ } ++ ++ position.enable = true; ++ position.x = x; ++ position.y = y; ++ ++ position.hot_spot_enable = true; ++ position.x_origin = xorigin; ++ position.y_origin = yorigin; ++ ++ if (!dc_target_set_cursor_position( ++ amdgpu_crtc->target, ++ &position)) { ++ DRM_ERROR("DC failed to set cursor position\n"); ++ return -EINVAL; ++ } ++ ++#if BUILD_FEATURE_TIMING_SYNC ++ { ++ struct drm_device *dev = crtc->dev; ++ struct amdgpu_device *adev = dev->dev_private; ++ struct amdgpu_display_manager *dm = &adev->dm; ++ ++ dc_print_sync_report(dm->dc); ++ } ++#endif ++ return 0; ++} ++ ++static void dm_crtc_cursor_reset(struct drm_crtc *crtc) ++{ ++ struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); ++ ++ DRM_DEBUG_KMS( ++ "%s: with cursor_bo %p\n", ++ __func__, ++ amdgpu_crtc->cursor_bo); ++ ++ if (amdgpu_crtc->cursor_bo && amdgpu_crtc->target) { ++ dm_set_cursor( ++ amdgpu_crtc, ++ amdgpu_bo_gpu_offset(gem_to_amdgpu_bo(amdgpu_crtc->cursor_bo)), ++ amdgpu_crtc->cursor_width, ++ amdgpu_crtc->cursor_height); ++ } ++} ++static bool fill_rects_from_plane_state( ++ struct drm_plane_state *state, ++ struct dc_surface *surface) ++{ ++ surface->src_rect.x = state->src_x >> 16; ++ surface->src_rect.y = state->src_y >> 16; ++ /*we ignore for now mantissa and do not to deal with floating pixels :(*/ ++ surface->src_rect.width = state->src_w >> 16; ++ ++ if (surface->src_rect.width == 0) ++ return false; ++ ++ surface->src_rect.height = state->src_h >> 16; ++ if (surface->src_rect.height == 0) ++ return false; ++ ++ surface->dst_rect.x = state->crtc_x; ++ surface->dst_rect.y = state->crtc_y; ++ ++ if (state->crtc_w == 0) ++ return false; ++ ++ surface->dst_rect.width = state->crtc_w; ++ ++ if (state->crtc_h == 0) ++ return false; ++ ++ surface->dst_rect.height = state->crtc_h; ++ ++ surface->clip_rect = surface->dst_rect; ++ ++ switch (state->rotation) { ++ case DRM_ROTATE_0: ++ surface->rotation = ROTATION_ANGLE_0; ++ break; ++ case DRM_ROTATE_90: ++ surface->rotation = ROTATION_ANGLE_90; ++ break; ++ case DRM_ROTATE_180: ++ surface->rotation = ROTATION_ANGLE_180; ++ break; ++ case DRM_ROTATE_270: ++ surface->rotation = ROTATION_ANGLE_270; ++ break; ++ default: ++ surface->rotation = ROTATION_ANGLE_0; ++ break; ++ } ++ ++ return true; ++} ++static bool get_fb_info( ++ struct amdgpu_framebuffer *amdgpu_fb, ++ uint64_t *tiling_flags, ++ uint64_t *fb_location) ++{ ++ struct amdgpu_bo *rbo = gem_to_amdgpu_bo(amdgpu_fb->obj); ++ int r = amdgpu_bo_reserve(rbo, false); ++ if (unlikely(r != 0)){ ++ DRM_ERROR("Unable to reserve buffer\n"); ++ return false; ++ } ++ ++ ++ if (fb_location) ++ *fb_location = amdgpu_bo_gpu_offset(rbo); ++ ++ if (tiling_flags) ++ amdgpu_bo_get_tiling_flags(rbo, tiling_flags); ++ ++ amdgpu_bo_unreserve(rbo); ++ ++ return true; ++} ++static void fill_plane_attributes_from_fb( ++ struct dc_surface *surface, ++ struct amdgpu_framebuffer *amdgpu_fb) ++{ ++ uint64_t tiling_flags; ++ uint64_t fb_location; ++ struct drm_framebuffer *fb = &amdgpu_fb->base; ++ ++ get_fb_info( ++ amdgpu_fb, ++ &tiling_flags, ++ &fb_location); ++ ++ surface->address.type = PLN_ADDR_TYPE_GRAPHICS; ++ surface->address.grph.addr.low_part = lower_32_bits(fb_location); ++ surface->address.grph.addr.high_part = upper_32_bits(fb_location); ++ ++ switch (fb->pixel_format) { ++ case DRM_FORMAT_C8: ++ surface->format = SURFACE_PIXEL_FORMAT_GRPH_PALETA_256_COLORS; ++ break; ++ case DRM_FORMAT_RGB565: ++ surface->format = SURFACE_PIXEL_FORMAT_GRPH_RGB565; ++ break; ++ case DRM_FORMAT_XRGB8888: ++ case DRM_FORMAT_ARGB8888: ++ surface->format = SURFACE_PIXEL_FORMAT_GRPH_ARGB8888; ++ break; ++ default: ++ DRM_ERROR("Unsupported screen depth %d\n", fb->bits_per_pixel); ++ return; ++ } ++ ++ surface->tiling_info.value = 0; ++ ++ if (AMDGPU_TILING_GET(tiling_flags, ARRAY_MODE) == ARRAY_2D_TILED_THIN1) ++ { ++ unsigned bankw, bankh, mtaspect, tile_split, num_banks; ++ ++ bankw = AMDGPU_TILING_GET(tiling_flags, BANK_WIDTH); ++ bankh = AMDGPU_TILING_GET(tiling_flags, BANK_HEIGHT); ++ mtaspect = AMDGPU_TILING_GET(tiling_flags, MACRO_TILE_ASPECT); ++ tile_split = AMDGPU_TILING_GET(tiling_flags, TILE_SPLIT); ++ num_banks = AMDGPU_TILING_GET(tiling_flags, NUM_BANKS); ++ ++ ++ /* XXX fix me for VI */ ++ surface->tiling_info.grph.NUM_BANKS = num_banks; ++ surface->tiling_info.grph.ARRAY_MODE = ++ ARRAY_2D_TILED_THIN1; ++ surface->tiling_info.grph.TILE_SPLIT = tile_split; ++ surface->tiling_info.grph.BANK_WIDTH = bankw; ++ surface->tiling_info.grph.BANK_HEIGHT = bankh; ++ surface->tiling_info.grph.TILE_ASPECT = mtaspect; ++ surface->tiling_info.grph.TILE_MODE = ++ ADDR_SURF_MICRO_TILING_DISPLAY; ++ } else if (AMDGPU_TILING_GET(tiling_flags, ARRAY_MODE) ++ == ARRAY_1D_TILED_THIN1) { ++ surface->tiling_info.grph.ARRAY_MODE = ARRAY_1D_TILED_THIN1; ++ } ++ ++ surface->tiling_info.grph.PIPE_CONFIG = ++ AMDGPU_TILING_GET(tiling_flags, PIPE_CONFIG); ++ ++ surface->plane_size.grph.surface_size.x = 0; ++ surface->plane_size.grph.surface_size.y = 0; ++ surface->plane_size.grph.surface_size.width = fb->width; ++ surface->plane_size.grph.surface_size.height = fb->height; ++ surface->plane_size.grph.surface_pitch = ++ fb->pitches[0] / (fb->bits_per_pixel / 8); ++ ++ surface->enabled = true; ++ surface->scaling_quality.h_taps_c = 2; ++ surface->scaling_quality.v_taps_c = 2; ++ ++/* TODO: unhardcode */ ++ surface->colorimetry.limited_range = false; ++ surface->colorimetry.color_space = SURFACE_COLOR_SPACE_SRGB; ++ surface->scaling_quality.h_taps = 4; ++ surface->scaling_quality.v_taps = 4; ++ surface->stereo_format = PLANE_STEREO_FORMAT_NONE; ++ ++} ++ ++static void fill_gamma_from_crtc( ++ struct drm_crtc *crtc, ++ struct dc_surface *dc_surface) ++{ ++ int i; ++ struct gamma_ramp *gamma; ++ uint16_t *red, *green, *blue; ++ int end = (crtc->gamma_size > NUM_OF_RAW_GAMMA_RAMP_RGB_256) ? ++ NUM_OF_RAW_GAMMA_RAMP_RGB_256 : crtc->gamma_size; ++ ++ red = crtc->gamma_store; ++ green = red + crtc->gamma_size; ++ blue = green + crtc->gamma_size; ++ ++ gamma = &dc_surface->gamma_correction; ++ ++ for (i = 0; i < end; i++) { ++ gamma->gamma_ramp_rgb256x3x16.red[i] = ++ (unsigned short) red[i]; ++ gamma->gamma_ramp_rgb256x3x16.green[i] = ++ (unsigned short) green[i]; ++ gamma->gamma_ramp_rgb256x3x16.blue[i] = ++ (unsigned short) blue[i]; ++ } ++ ++ gamma->type = GAMMA_RAMP_RBG256X3X16; ++ gamma->size = sizeof(gamma->gamma_ramp_rgb256x3x16); ++} ++ ++static void fill_plane_attributes( ++ struct dc_surface *surface, ++ struct drm_crtc *crtc) ++{ ++ struct amdgpu_framebuffer *amdgpu_fb = ++ to_amdgpu_framebuffer(crtc->primary->state->fb); ++ fill_rects_from_plane_state(crtc->primary->state, surface); ++ fill_plane_attributes_from_fb( ++ surface, ++ amdgpu_fb); ++ ++ /* In case of gamma set, update gamma value */ ++ if (crtc->mode.private_flags & ++ AMDGPU_CRTC_MODE_PRIVATE_FLAGS_GAMMASET) { ++ fill_gamma_from_crtc(crtc, surface); ++ /* reset trigger of gamma */ ++ crtc->mode.private_flags &= ++ ~AMDGPU_CRTC_MODE_PRIVATE_FLAGS_GAMMASET; ++ } ++} ++ ++/*****************************************************************************/ ++ ++struct amdgpu_connector *aconnector_from_drm_crtc( ++ struct drm_crtc *crtc, ++ struct drm_atomic_state *state) ++{ ++ struct drm_connector *connector; ++ struct amdgpu_connector *aconnector; ++ struct drm_connector_state *conn_state; ++ uint8_t i; ++ ++ for_each_connector_in_state(state, connector, conn_state, i) { ++ aconnector = to_amdgpu_connector(connector); ++ if (connector->state->crtc == crtc) ++ return aconnector; ++ } ++ ++ /* If we get here, not found. */ ++ return NULL; ++} ++ ++struct amdgpu_connector *aconnector_from_drm_crtc_id( ++ const struct drm_crtc *crtc) ++{ ++ struct drm_device *dev = crtc->dev; ++ struct drm_connector *connector; ++ struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc); ++ struct amdgpu_connector *aconnector; ++ ++ list_for_each_entry(connector, ++ &dev->mode_config.connector_list, head) { ++ ++ aconnector = to_amdgpu_connector(connector); ++ ++ /* acrtc->crtc_id means display_index */ ++ if (aconnector->connector_id != acrtc->crtc_id) ++ continue; ++ ++ /* Found the connector */ ++ return aconnector; ++ } ++ ++ /* If we get here, not found. */ ++ return NULL; ++} ++ ++static void dm_dc_surface_commit( ++ struct dc *dc, ++ struct drm_crtc *crtc, ++ struct amdgpu_framebuffer *afb) ++{ ++ struct dc_surface *dc_surface; ++ struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc); ++ struct dc_target *dc_target = acrtc->target; ++ ++ if (!dc_target) { ++ dal_error( ++ "%s: Failed to obtain target on crtc (%d)!\n", ++ __func__, ++ acrtc->crtc_id); ++ goto fail; ++ } ++ ++ dc_surface = dc_create_surface(dc); ++ ++ if (!dc_surface) { ++ dal_error( ++ "%s: Failed to create a surface!\n", ++ __func__); ++ goto fail; ++ } ++ ++ /* Surface programming */ ++ ++ fill_plane_attributes(dc_surface, crtc); ++ ++ if (false == dc_commit_surfaces_to_target( ++ dc, ++ &dc_surface, ++ 1, ++ dc_target)) { ++ dal_error( ++ "%s: Failed to attach surface!\n", ++ __func__); ++ } ++ ++ dc_surface_release(dc_surface); ++fail: ++ return; ++} ++ ++static enum dc_color_depth convert_color_depth_from_display_info( ++ const struct drm_connector *connector) ++{ ++ uint32_t bpc = connector->display_info.bpc; ++ ++ /* Limited color depth to 8bit ++ * TODO: Still need to handle deep color*/ ++ if (bpc > 8) ++ bpc = 8; ++ ++ switch (bpc) { ++ case 0: ++ /* Temporary Work around, DRM don't parse color depth for ++ * EDID revision before 1.4 ++ * TODO: Fix edid parsing ++ */ ++ return COLOR_DEPTH_888; ++ case 6: ++ return COLOR_DEPTH_666; ++ case 8: ++ return COLOR_DEPTH_888; ++ case 10: ++ return COLOR_DEPTH_101010; ++ case 12: ++ return COLOR_DEPTH_121212; ++ case 14: ++ return COLOR_DEPTH_141414; ++ case 16: ++ return COLOR_DEPTH_161616; ++ default: ++ return COLOR_DEPTH_UNDEFINED; ++ } ++} ++ ++static enum dc_aspect_ratio get_aspect_ratio( ++ const struct drm_display_mode *mode_in) ++{ ++ int32_t width = mode_in->crtc_hdisplay * 9; ++ int32_t height = mode_in->crtc_vdisplay * 16; ++ if ((width - height) < 10 && (width - height) > -10) ++ return ASPECT_RATIO_16_9; ++ else ++ return ASPECT_RATIO_4_3; ++} ++ ++/*****************************************************************************/ ++ ++static void dc_timing_from_drm_display_mode( ++ struct dc_crtc_timing *timing_out, ++ const struct drm_display_mode *mode_in, ++ const struct drm_connector *connector) ++{ ++ memset(timing_out, 0, sizeof(struct dc_crtc_timing)); ++ ++ timing_out->h_border_left = 0; ++ timing_out->h_border_right = 0; ++ timing_out->v_border_top = 0; ++ timing_out->v_border_bottom = 0; ++ /* TODO: un-hardcode */ ++ timing_out->pixel_encoding = PIXEL_ENCODING_RGB; ++ timing_out->timing_standard = TIMING_STANDARD_HDMI; ++ timing_out->timing_3d_format = TIMING_3D_FORMAT_NONE; ++ timing_out->display_color_depth = convert_color_depth_from_display_info( ++ connector); ++ timing_out->scan_type = SCANNING_TYPE_NODATA; ++ timing_out->hdmi_vic = 0; ++ timing_out->vic = drm_match_cea_mode(mode_in); ++ ++ timing_out->h_addressable = mode_in->crtc_hdisplay; ++ timing_out->h_total = mode_in->crtc_htotal; ++ timing_out->h_sync_width = ++ mode_in->crtc_hsync_end - mode_in->crtc_hsync_start; ++ timing_out->h_front_porch = ++ mode_in->crtc_hsync_start - mode_in->crtc_hdisplay; ++ timing_out->v_total = mode_in->crtc_vtotal; ++ timing_out->v_addressable = mode_in->crtc_vdisplay; ++ timing_out->v_front_porch = ++ mode_in->crtc_vsync_start - mode_in->crtc_vdisplay; ++ timing_out->v_sync_width = ++ mode_in->crtc_vsync_end - mode_in->crtc_vsync_start; ++ timing_out->pix_clk_khz = mode_in->crtc_clock; ++ timing_out->aspect_ratio = get_aspect_ratio(mode_in); ++} ++ ++static void fill_audio_info( ++ struct audio_info *audio_info, ++ const struct drm_connector *drm_connector, ++ const struct dc_sink *dc_sink) ++{ ++ int i = 0; ++ int cea_revision = 0; ++ const struct dc_edid_caps *edid_caps = &dc_sink->edid_caps; ++ ++ audio_info->manufacture_id = edid_caps->manufacturer_id; ++ audio_info->product_id = edid_caps->product_id; ++ ++ cea_revision = drm_connector->display_info.cea_rev; ++ ++ while (i < AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS && ++ edid_caps->display_name[i]) { ++ audio_info->display_name[i] = edid_caps->display_name[i]; ++ i++; ++ } ++ ++ if(cea_revision >= 3) { ++ audio_info->mode_count = edid_caps->audio_mode_count; ++ ++ for (i = 0; i < audio_info->mode_count; ++i) { ++ audio_info->modes[i].format_code = ++ (enum audio_format_code) ++ (edid_caps->audio_modes[i].format_code); ++ audio_info->modes[i].channel_count = ++ edid_caps->audio_modes[i].channel_count; ++ audio_info->modes[i].sample_rates.all = ++ edid_caps->audio_modes[i].sample_rate; ++ audio_info->modes[i].sample_size = ++ edid_caps->audio_modes[i].sample_size; ++ } ++ } ++ ++ audio_info->flags.all = edid_caps->speaker_flags; ++ ++ /* TODO: We only check for the progressive mode, check for interlace mode too */ ++ if(drm_connector->latency_present[0]) { ++ audio_info->video_latency = drm_connector->video_latency[0]; ++ audio_info->audio_latency = drm_connector->audio_latency[0]; ++ } ++ ++ /* TODO: For DP, video and audio latency should be calculated from DPCD caps */ ++ ++} ++ ++/*TODO: move these defines elsewhere*/ ++#define DAL_MAX_CONTROLLERS 4 ++ ++static void calculate_stream_scaling_settings( ++ const struct drm_display_mode *mode, ++ enum amdgpu_rmx_type rmx_type, ++ struct dc_stream *stream, ++ uint8_t underscan_vborder, ++ uint8_t underscan_hborder, ++ bool underscan_enable) ++{ ++ /* Full screen scaling by default */ ++ stream->src.width = mode->hdisplay; ++ stream->src.height = mode->vdisplay; ++ stream->dst.width = stream->timing.h_addressable; ++ stream->dst.height = stream->timing.v_addressable; ++ ++ if (rmx_type == RMX_ASPECT || rmx_type == RMX_OFF) { ++ if (stream->src.width * stream->dst.height < ++ stream->src.height * stream->dst.width) { ++ /* height needs less upscaling/more downscaling */ ++ stream->dst.width = stream->src.width * ++ stream->dst.height / stream->src.height; ++ } else { ++ /* width needs less upscaling/more downscaling */ ++ stream->dst.height = stream->src.height * ++ stream->dst.width / stream->src.width; ++ } ++ } else if (rmx_type == RMX_CENTER) { ++ stream->dst = stream->src; ++ } ++ ++ stream->dst.x = (stream->timing.h_addressable - stream->dst.width) / 2; ++ stream->dst.y = (stream->timing.v_addressable - stream->dst.height) / 2; ++ ++ if (underscan_enable) { ++ stream->dst.x += underscan_hborder / 2; ++ stream->dst.y += underscan_vborder / 2; ++ stream->dst.width -= underscan_hborder; ++ stream->dst.height -= underscan_vborder; ++ } ++} ++ ++ ++static void copy_crtc_timing_for_drm_display_mode( ++ const struct drm_display_mode *src_mode, ++ struct drm_display_mode *dst_mode) ++{ ++ dst_mode->crtc_hdisplay = src_mode->crtc_hdisplay; ++ dst_mode->crtc_vdisplay = src_mode->crtc_vdisplay; ++ dst_mode->crtc_clock = src_mode->crtc_clock; ++ dst_mode->crtc_hblank_start = src_mode->crtc_hblank_start; ++ dst_mode->crtc_hblank_end = src_mode->crtc_hblank_end; ++ dst_mode->crtc_hsync_start= src_mode->crtc_hsync_start; ++ dst_mode->crtc_hsync_end = src_mode->crtc_hsync_end; ++ dst_mode->crtc_htotal = src_mode->crtc_htotal; ++ dst_mode->crtc_hskew = src_mode->crtc_hskew; ++ dst_mode->crtc_vblank_start = src_mode->crtc_vblank_start;; ++ dst_mode->crtc_vblank_end = src_mode->crtc_vblank_end;; ++ dst_mode->crtc_vsync_start = src_mode->crtc_vsync_start;; ++ dst_mode->crtc_vsync_end = src_mode->crtc_vsync_end;; ++ dst_mode->crtc_vtotal = src_mode->crtc_vtotal;; ++} ++ ++static void decide_crtc_timing_for_drm_display_mode( ++ struct drm_display_mode *drm_mode, ++ const struct drm_display_mode *native_mode, ++ bool scale_enabled) ++{ ++ if (scale_enabled) { ++ copy_crtc_timing_for_drm_display_mode(native_mode, drm_mode); ++ } else if (native_mode->clock == drm_mode->clock && ++ native_mode->htotal == drm_mode->htotal && ++ native_mode->vtotal == drm_mode->vtotal) { ++ copy_crtc_timing_for_drm_display_mode(native_mode, drm_mode); ++ } else { ++ /* no scaling nor amdgpu inserted, no need to patch */ ++ } ++} ++ ++ ++static struct dc_target *create_target_for_sink( ++ const struct amdgpu_connector *aconnector, ++ struct drm_display_mode *drm_mode) ++{ ++ struct drm_display_mode *preferred_mode = NULL; ++ const struct drm_connector *drm_connector; ++ struct dm_connector_state *dm_state; ++ struct dc_target *target = NULL; ++ struct dc_stream *stream; ++ struct drm_display_mode mode = *drm_mode; ++ bool native_mode_found = false; ++ ++ if (NULL == aconnector) { ++ DRM_ERROR("aconnector is NULL!\n"); ++ goto drm_connector_null; ++ } ++ ++ drm_connector = &aconnector->base; ++ dm_state = to_dm_connector_state(drm_connector->state); ++ stream = dc_create_stream_for_sink(aconnector->dc_sink); ++ ++ if (NULL == stream) { ++ DRM_ERROR("Failed to create stream for sink!\n"); ++ goto stream_create_fail; ++ } ++ ++ list_for_each_entry(preferred_mode, &aconnector->base.modes, head) { ++ /* Search for preferred mode */ ++ if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) { ++ native_mode_found = true; ++ break; ++ } ++ } ++ if (!native_mode_found) ++ preferred_mode = list_first_entry_or_null( ++ &aconnector->base.modes, ++ struct drm_display_mode, ++ head); ++ ++ decide_crtc_timing_for_drm_display_mode( ++ &mode, preferred_mode, ++ dm_state->scaling != RMX_OFF); ++ ++ dc_timing_from_drm_display_mode(&stream->timing, ++ &mode, &aconnector->base); ++ ++ calculate_stream_scaling_settings(&mode, dm_state->scaling, stream, ++ dm_state->underscan_vborder, ++ dm_state->underscan_hborder, ++ dm_state->underscan_enable); ++ ++ ++ fill_audio_info( ++ &stream->audio_info, ++ drm_connector, ++ aconnector->dc_sink); ++ ++ target = dc_create_target_for_streams(&stream, 1); ++ dc_stream_release(stream); ++ ++ if (NULL == target) { ++ DRM_ERROR("Failed to create target with streams!\n"); ++ goto target_create_fail; ++ } ++ ++drm_connector_null: ++target_create_fail: ++stream_create_fail: ++ return target; ++} ++ ++void amdgpu_dm_crtc_destroy(struct drm_crtc *crtc) ++{ ++ drm_crtc_cleanup(crtc); ++ kfree(crtc); ++} ++ ++static void amdgpu_dm_atomic_crtc_gamma_set( ++ struct drm_crtc *crtc, ++ u16 *red, ++ u16 *green, ++ u16 *blue, ++ uint32_t start, ++ uint32_t size) ++{ ++ struct drm_device *dev = crtc->dev; ++ struct drm_property *prop = dev->mode_config.prop_crtc_id; ++ ++ crtc->mode.private_flags |= AMDGPU_CRTC_MODE_PRIVATE_FLAGS_GAMMASET; ++ ++ drm_atomic_helper_crtc_set_property(crtc, prop, 0); ++} ++ ++static int dm_crtc_funcs_atomic_set_property( ++ struct drm_crtc *crtc, ++ struct drm_crtc_state *state, ++ struct drm_property *property, ++ uint64_t val) ++{ ++ struct drm_crtc_state *new_crtc_state; ++ struct drm_crtc *new_crtc; ++ int i; ++ ++ for_each_crtc_in_state(state->state, new_crtc, new_crtc_state, i) { ++ if (new_crtc == crtc) { ++ struct drm_plane_state *plane_state; ++ ++ new_crtc_state->planes_changed = true; ++ ++ /* ++ * Bit of magic done here. We need to ensure ++ * that planes get update after mode is set. ++ * So, we need to add primary plane to state, ++ * and this way atomic_update would be called ++ * for it ++ */ ++ plane_state = ++ drm_atomic_get_plane_state( ++ state->state, ++ crtc->primary); ++ ++ if (!plane_state) ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++/* Implemented only the options currently availible for the driver */ ++static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = { ++ .reset = drm_atomic_helper_crtc_reset, ++ .cursor_set = dm_crtc_cursor_set, ++ .cursor_move = dm_crtc_cursor_move, ++ .destroy = amdgpu_dm_crtc_destroy, ++ .gamma_set = amdgpu_dm_atomic_crtc_gamma_set, ++ .set_config = drm_atomic_helper_set_config, ++ .page_flip = drm_atomic_helper_page_flip, ++ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, ++ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, ++ .atomic_set_property = dm_crtc_funcs_atomic_set_property ++}; ++ ++static enum drm_connector_status ++amdgpu_dm_connector_detect(struct drm_connector *connector, bool force) ++{ ++ bool connected; ++ struct amdgpu_connector *aconnector = ++ to_amdgpu_connector(connector); ++ ++ /* ++ * TODO: check whether we should lock here for mst_mgr.lock ++ */ ++ /* set root connector to disconnected */ ++ if (aconnector->mst_mgr.mst_state) ++ return connector_status_disconnected; ++ ++ connected = (NULL != aconnector->dc_sink); ++ return (connected ? connector_status_connected : ++ connector_status_disconnected); ++} ++ ++int amdgpu_dm_connector_atomic_set_property( ++ struct drm_connector *connector, ++ struct drm_connector_state *state, ++ struct drm_property *property, ++ uint64_t val) ++{ ++ struct drm_device *dev = connector->dev; ++ struct amdgpu_device *adev = dev->dev_private; ++ struct dm_connector_state *dm_old_state = ++ to_dm_connector_state(connector->state); ++ struct dm_connector_state *dm_new_state = ++ to_dm_connector_state(state); ++ ++ if (property == dev->mode_config.scaling_mode_property) { ++ struct drm_crtc_state *new_crtc_state; ++ struct drm_crtc *crtc; ++ int i; ++ enum amdgpu_rmx_type rmx_type; ++ ++ switch (val) { ++ case DRM_MODE_SCALE_CENTER: ++ rmx_type = RMX_CENTER; ++ break; ++ case DRM_MODE_SCALE_ASPECT: ++ rmx_type = RMX_ASPECT; ++ break; ++ case DRM_MODE_SCALE_FULLSCREEN: ++ rmx_type = RMX_FULL; ++ break; ++ case DRM_MODE_SCALE_NONE: ++ default: ++ rmx_type = RMX_OFF; ++ break; ++ } ++ ++ if (dm_old_state->scaling == rmx_type) ++ return 0; ++ ++ dm_new_state->scaling = rmx_type; ++ ++ for_each_crtc_in_state(state->state, crtc, new_crtc_state, i) { ++ if (crtc == state->crtc) { ++ struct drm_plane_state *plane_state; ++ ++ new_crtc_state->mode_changed = true; ++ ++ /* ++ * Bit of magic done here. We need to ensure ++ * that planes get update after mode is set. ++ * So, we need to add primary plane to state, ++ * and this way atomic_update would be called ++ * for it ++ */ ++ plane_state = ++ drm_atomic_get_plane_state( ++ state->state, ++ crtc->primary); ++ ++ if (!plane_state) ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++ } else if (property == adev->mode_info.underscan_hborder_property) { ++ dm_new_state->underscan_hborder = val; ++ return 0; ++ } else if (property == adev->mode_info.underscan_vborder_property) { ++ dm_new_state->underscan_vborder = val; ++ return 0; ++ } else if (property == adev->mode_info.underscan_property) { ++ struct drm_crtc_state *new_crtc_state; ++ struct drm_crtc *crtc; ++ int i; ++ ++ dm_new_state->underscan_enable = val; ++ for_each_crtc_in_state(state->state, crtc, new_crtc_state, i) { ++ if (crtc == state->crtc) { ++ struct drm_plane_state *plane_state; ++ ++ new_crtc_state->mode_changed = true; ++ ++ /* ++ * Bit of magic done here. We need to ensure ++ * that planes get update after mode is set. ++ * So, we need to add primary plane to state, ++ * and this way atomic_update would be called ++ * for it ++ */ ++ plane_state = ++ drm_atomic_get_plane_state( ++ state->state, ++ crtc->primary); ++ ++ if (!plane_state) ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++void amdgpu_dm_connector_destroy(struct drm_connector *connector) ++{ ++ /*drm_sysfs_connector_remove(connector);*/ ++ drm_connector_cleanup(connector); ++ kfree(connector); ++} ++ ++void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector) ++{ ++ struct dm_connector_state *state = ++ to_dm_connector_state(connector->state); ++ ++ kfree(state); ++ ++ state = kzalloc(sizeof(*state), GFP_KERNEL); ++ ++ if (state) { ++ state->scaling = RMX_OFF; ++ ++ connector->state = &state->base; ++ connector->state->connector = connector; ++ } ++} ++ ++struct drm_connector_state *amdgpu_dm_connector_atomic_duplicate_state( ++ struct drm_connector *connector) ++{ ++ struct dm_connector_state *state = ++ to_dm_connector_state(connector->state); ++ ++ struct dm_connector_state *new_state = ++ kzalloc(sizeof(*new_state), GFP_KERNEL); ++ ++ if (new_state) { ++ *new_state = *state; ++ ++ return &new_state->base; ++ } ++ ++ return NULL; ++} ++ ++void amdgpu_dm_connector_atomic_destroy_state( ++ struct drm_connector *connector, ++ struct drm_connector_state *state) ++{ ++ struct dm_connector_state *dm_state = ++ to_dm_connector_state(state); ++ ++ __drm_atomic_helper_connector_destroy_state(connector, state); ++ ++ kfree(dm_state); ++} ++ ++static const struct drm_connector_funcs amdgpu_dm_connector_funcs = { ++ .dpms = drm_atomic_helper_connector_dpms, ++ .reset = amdgpu_dm_connector_funcs_reset, ++ .detect = amdgpu_dm_connector_detect, ++ .fill_modes = drm_helper_probe_single_connector_modes, ++ .set_property = drm_atomic_helper_connector_set_property, ++ .destroy = amdgpu_dm_connector_destroy, ++ .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state, ++ .atomic_destroy_state = amdgpu_dm_connector_atomic_destroy_state, ++ .atomic_set_property = amdgpu_dm_connector_atomic_set_property ++}; ++ ++static struct drm_encoder *best_encoder(struct drm_connector *connector) ++{ ++ int enc_id = connector->encoder_ids[0]; ++ struct drm_mode_object *obj; ++ struct drm_encoder *encoder; ++ ++ DRM_DEBUG_KMS("Finding the best encoder\n"); ++ ++ /* pick the encoder ids */ ++ if (enc_id) { ++ obj = drm_mode_object_find(connector->dev, enc_id, DRM_MODE_OBJECT_ENCODER); ++ if (!obj) { ++ DRM_ERROR("Couldn't find a matching encoder for our connector\n"); ++ return NULL; ++ } ++ encoder = obj_to_encoder(obj); ++ return encoder; ++ } ++ DRM_ERROR("No encoder id\n"); ++ return NULL; ++} ++ ++static int get_modes(struct drm_connector *connector) ++{ ++ struct amdgpu_connector *amdgpu_connector = ++ to_amdgpu_connector(connector); ++ return amdgpu_connector->num_modes; ++} ++ ++static int mode_valid(struct drm_connector *connector, ++ struct drm_display_mode *mode) ++{ ++ int result = MODE_ERROR; ++ const struct dc_sink *dc_sink = ++ to_amdgpu_connector(connector)->dc_sink; ++ struct amdgpu_device *adev = connector->dev->dev_private; ++ struct dc_validation_set val_set = { 0 }; ++ /* TODO: Unhardcode stream count */ ++ struct dc_stream *streams[1]; ++ struct dc_target *target; ++ ++ if ((mode->flags & DRM_MODE_FLAG_INTERLACE) || ++ (mode->flags & DRM_MODE_FLAG_DBLSCAN)) ++ return result; ++ ++ if (NULL == dc_sink) { ++ DRM_ERROR("dc_sink is NULL!\n"); ++ goto stream_create_fail; ++ } ++ ++ streams[0] = dc_create_stream_for_sink(dc_sink); ++ ++ if (NULL == streams[0]) { ++ DRM_ERROR("Failed to create stream for sink!\n"); ++ goto stream_create_fail; ++ } ++ ++ drm_mode_set_crtcinfo(mode, 0); ++ dc_timing_from_drm_display_mode(&streams[0]->timing, mode, connector); ++ ++ target = dc_create_target_for_streams(streams, 1); ++ val_set.target = target; ++ ++ if (NULL == val_set.target) { ++ DRM_ERROR("Failed to create target with stream!\n"); ++ goto target_create_fail; ++ } ++ ++ val_set.surface_count = 0; ++ streams[0]->src.width = mode->hdisplay; ++ streams[0]->src.height = mode->vdisplay; ++ streams[0]->dst = streams[0]->src; ++ ++ if (dc_validate_resources(adev->dm.dc, &val_set, 1)) ++ result = MODE_OK; ++ ++ dc_target_release(target); ++target_create_fail: ++ dc_stream_release(streams[0]); ++stream_create_fail: ++ /* TODO: error handling*/ ++ return result; ++} ++ ++ ++static const struct drm_connector_helper_funcs ++amdgpu_dm_connector_helper_funcs = { ++ /* ++ * If hotplug a second bigger display in FB Con mode, bigger resolution ++ * modes will be filtered by drm_mode_validate_size(), and those modes ++ * is missing after user start lightdm. So we need to renew modes list. ++ * in get_modes call back, not just return the modes count ++ */ ++ .get_modes = get_modes, ++ .mode_valid = mode_valid, ++ .best_encoder = best_encoder ++}; ++ ++static void dm_crtc_helper_disable(struct drm_crtc *crtc) ++{ ++} ++ ++static int dm_crtc_helper_atomic_check( ++ struct drm_crtc *crtc, ++ struct drm_crtc_state *state) ++{ ++ return 0; ++} ++ ++static bool dm_crtc_helper_mode_fixup( ++ struct drm_crtc *crtc, ++ const struct drm_display_mode *mode, ++ struct drm_display_mode *adjusted_mode) ++{ ++ return true; ++} ++ ++static const struct drm_crtc_helper_funcs amdgpu_dm_crtc_helper_funcs = { ++ .disable = dm_crtc_helper_disable, ++ .atomic_check = dm_crtc_helper_atomic_check, ++ .mode_fixup = dm_crtc_helper_mode_fixup ++}; ++ ++static void dm_encoder_helper_disable(struct drm_encoder *encoder) ++{ ++ ++} ++ ++static int dm_encoder_helper_atomic_check( ++ struct drm_encoder *encoder, ++ struct drm_crtc_state *crtc_state, ++ struct drm_connector_state *conn_state) ++{ ++ return 0; ++} ++ ++const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs = { ++ .disable = dm_encoder_helper_disable, ++ .atomic_check = dm_encoder_helper_atomic_check ++}; ++ ++static const struct drm_plane_funcs dm_plane_funcs = { ++ .reset = drm_atomic_helper_plane_reset, ++ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, ++ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state ++}; ++ ++static void clear_unrelated_fields(struct drm_plane_state *state) ++{ ++ state->crtc = NULL; ++ state->fb = NULL; ++ state->state = NULL; ++ state->fence = NULL; ++} ++ ++static bool page_flip_needed( ++ struct drm_plane_state *new_state, ++ struct drm_plane_state *old_state) ++{ ++ struct drm_plane_state old_state_tmp; ++ struct drm_plane_state new_state_tmp; ++ ++ struct amdgpu_framebuffer *amdgpu_fb_old; ++ struct amdgpu_framebuffer *amdgpu_fb_new; ++ ++ uint64_t old_tiling_flags; ++ uint64_t new_tiling_flags; ++ ++ if (!old_state) ++ return false; ++ ++ if (!old_state->fb) ++ return false; ++ ++ if (!new_state) ++ return false; ++ ++ if (!new_state->fb) ++ return false; ++ ++ old_state_tmp = *old_state; ++ new_state_tmp = *new_state; ++ ++ if (!new_state->crtc->state->event) ++ return false; ++ ++ amdgpu_fb_old = to_amdgpu_framebuffer(old_state->fb); ++ amdgpu_fb_new = to_amdgpu_framebuffer(new_state->fb); ++ ++ if (!get_fb_info(amdgpu_fb_old, &old_tiling_flags, NULL)) ++ return false; ++ ++ if (!get_fb_info(amdgpu_fb_new, &new_tiling_flags, NULL)) ++ return false; ++ ++ if (old_tiling_flags != new_tiling_flags) ++ return false; ++ ++ clear_unrelated_fields(&old_state_tmp); ++ clear_unrelated_fields(&new_state_tmp); ++ ++ return memcmp(&old_state_tmp, &new_state_tmp, sizeof(old_state_tmp)) == 0; ++} ++ ++static int dm_plane_helper_prepare_fb( ++ struct drm_plane *plane, ++ const struct drm_plane_state *new_state) ++{ ++ struct amdgpu_framebuffer *afb; ++ struct drm_gem_object *obj; ++ struct amdgpu_bo *rbo; ++ int r; ++ ++ if (!new_state->fb) { ++ DRM_DEBUG_KMS("No FB bound\n"); ++ return 0; ++ } ++ ++ afb = to_amdgpu_framebuffer(new_state->fb); ++ ++ DRM_DEBUG_KMS("Pin new framebuffer: %p\n", afb); ++ obj = afb->obj; ++ rbo = gem_to_amdgpu_bo(obj); ++ r = amdgpu_bo_reserve(rbo, false); ++ if (unlikely(r != 0)) ++ return r; ++ ++ r = amdgpu_bo_pin(rbo, AMDGPU_GEM_DOMAIN_VRAM, NULL); ++ ++ amdgpu_bo_unreserve(rbo); ++ ++ if (unlikely(r != 0)) { ++ DRM_ERROR("Failed to pin framebuffer\n"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++static void dm_plane_helper_cleanup_fb( ++ struct drm_plane *plane, ++ const struct drm_plane_state *old_state) ++{ ++ struct amdgpu_bo *rbo; ++ struct amdgpu_framebuffer *afb; ++ int r; ++ ++ if (!old_state->fb) ++ return; ++ ++ afb = to_amdgpu_framebuffer(old_state->fb); ++ DRM_DEBUG_KMS("Unpin old framebuffer: %p\n", afb); ++ rbo = gem_to_amdgpu_bo(afb->obj); ++ r = amdgpu_bo_reserve(rbo, false); ++ if (unlikely(r)) { ++ DRM_ERROR("failed to reserve rbo before unpin\n"); ++ return; ++ } else { ++ amdgpu_bo_unpin(rbo); ++ amdgpu_bo_unreserve(rbo); ++ } ++} ++ ++int dm_create_validation_set_for_target(struct drm_connector *connector, ++ struct drm_display_mode *mode, struct dc_validation_set *val_set) ++{ ++ int result = MODE_ERROR; ++ const struct dc_sink *dc_sink = ++ to_amdgpu_connector(connector)->dc_sink; ++ /* TODO: Unhardcode stream count */ ++ struct dc_stream *streams[1]; ++ struct dc_target *target; ++ ++ if ((mode->flags & DRM_MODE_FLAG_INTERLACE) || ++ (mode->flags & DRM_MODE_FLAG_DBLSCAN)) ++ return result; ++ ++ if (NULL == dc_sink) { ++ DRM_ERROR("dc_sink is NULL!\n"); ++ return result; ++ } ++ ++ streams[0] = dc_create_stream_for_sink(dc_sink); ++ ++ if (NULL == streams[0]) { ++ DRM_ERROR("Failed to create stream for sink!\n"); ++ return result; ++ } ++ ++ drm_mode_set_crtcinfo(mode, 0); ++ dc_timing_from_drm_display_mode(&streams[0]->timing, mode, connector); ++ ++ target = dc_create_target_for_streams(streams, 1); ++ val_set->target = target; ++ ++ if (NULL == val_set->target) { ++ DRM_ERROR("Failed to create target with stream!\n"); ++ goto fail; ++ } ++ ++ streams[0]->src.width = mode->hdisplay; ++ streams[0]->src.height = mode->vdisplay; ++ streams[0]->dst = streams[0]->src; ++ ++ return MODE_OK; ++ ++fail: ++ dc_stream_release(streams[0]); ++ return result; ++ ++} ++ ++int dm_add_surface_to_validation_set(struct drm_plane *plane, ++ struct drm_plane_state *state, struct dc_surface **surface) ++{ ++ int res; ++ ++ struct amdgpu_framebuffer *afb; ++ struct amdgpu_connector *aconnector; ++ struct drm_crtc *crtc; ++ struct drm_framebuffer *fb; ++ ++ struct drm_device *dev; ++ struct amdgpu_device *adev; ++ ++ res = -EINVAL; ++ ++ if (NULL == plane || NULL == state) { ++ DRM_ERROR("invalid parameters dm_plane_atomic_check\n"); ++ return res; ++ } ++ ++ crtc = state->crtc; ++ fb = state->fb; ++ ++ ++ afb = to_amdgpu_framebuffer(fb); ++ ++ if (NULL == state->crtc) { ++ return res; ++ } ++ ++ aconnector = aconnector_from_drm_crtc(crtc, state->state); ++ ++ if (NULL == aconnector) { ++ DRM_ERROR("Connector is NULL in dm_plane_atomic_check\n"); ++ return res; ++ } ++ ++ if (NULL == aconnector->dc_sink) { ++ DRM_ERROR("dc_sink is NULL in dm_plane_atomic_check\n"); ++ return res; ++ } ++ dev = state->crtc->dev; ++ adev = dev->dev_private; ++ ++ *surface = dc_create_surface(adev->dm.dc); ++ if (NULL == *surface){ ++ DRM_ERROR("surface is NULL\n"); ++ return res; ++ } ++ ++ if (!fill_rects_from_plane_state( state, *surface)) { ++ DRM_ERROR("Failed to fill surface!\n"); ++ goto fail; ++ } ++ ++ fill_plane_attributes_from_fb(*surface, afb); ++ ++ return MODE_OK; ++ ++fail: ++ dc_surface_release(*surface); ++ return res; ++} ++ ++static const struct drm_plane_helper_funcs dm_plane_helper_funcs = { ++ .prepare_fb = dm_plane_helper_prepare_fb, ++ .cleanup_fb = dm_plane_helper_cleanup_fb, ++}; ++ ++/* ++ * TODO: these are currently initialized to rgb formats only. ++ * For future use cases we should either initialize them dynamically based on ++ * plane capabilities, or initialize this array to all formats, so internal drm ++ * check will succeed, and let DC to implement proper check ++ */ ++static uint32_t rgb_formats[] = { ++ DRM_FORMAT_XRGB4444, ++ DRM_FORMAT_ARGB4444, ++ DRM_FORMAT_RGBA4444, ++ DRM_FORMAT_ARGB1555, ++ DRM_FORMAT_RGB565, ++ DRM_FORMAT_RGB888, ++ DRM_FORMAT_XRGB8888, ++ DRM_FORMAT_ARGB8888, ++ DRM_FORMAT_RGBA8888, ++}; ++ ++int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm, ++ struct amdgpu_crtc *acrtc, ++ uint32_t link_index) ++{ ++ int res = -ENOMEM; ++ ++ struct drm_plane *primary_plane = ++ kzalloc(sizeof(*primary_plane), GFP_KERNEL); ++ ++ if (!primary_plane) ++ goto fail_plane; ++ ++ /* this flag is used in legacy code only */ ++ primary_plane->format_default = true; ++ ++ res = drm_universal_plane_init( ++ dm->adev->ddev, ++ primary_plane, ++ 0, ++ &dm_plane_funcs, ++ rgb_formats, ++ ARRAY_SIZE(rgb_formats), ++ DRM_PLANE_TYPE_PRIMARY, NULL); ++ ++ primary_plane->crtc = &acrtc->base; ++ ++ drm_plane_helper_add(primary_plane, &dm_plane_helper_funcs); ++ ++ res = drm_crtc_init_with_planes( ++ dm->ddev, ++ &acrtc->base, ++ primary_plane, ++ NULL, ++ &amdgpu_dm_crtc_funcs, NULL); ++ ++ if (res) ++ goto fail; ++ ++ drm_crtc_helper_add(&acrtc->base, &amdgpu_dm_crtc_helper_funcs); ++ ++ acrtc->max_cursor_width = 128; ++ acrtc->max_cursor_height = 128; ++ ++ acrtc->crtc_id = link_index; ++ acrtc->base.enabled = false; ++ ++ dm->adev->mode_info.crtcs[link_index] = acrtc; ++ drm_mode_crtc_set_gamma_size(&acrtc->base, 256); ++ ++ return 0; ++fail: ++ kfree(primary_plane); ++fail_plane: ++ acrtc->crtc_id = -1; ++ return res; ++} ++ ++static int to_drm_connector_type(enum signal_type st) ++{ ++ switch (st) { ++ case SIGNAL_TYPE_HDMI_TYPE_A: ++ return DRM_MODE_CONNECTOR_HDMIA; ++ case SIGNAL_TYPE_EDP: ++ return DRM_MODE_CONNECTOR_eDP; ++ case SIGNAL_TYPE_RGB: ++ return DRM_MODE_CONNECTOR_VGA; ++ case SIGNAL_TYPE_DISPLAY_PORT: ++ case SIGNAL_TYPE_DISPLAY_PORT_MST: ++ return DRM_MODE_CONNECTOR_DisplayPort; ++ case SIGNAL_TYPE_DVI_DUAL_LINK: ++ case SIGNAL_TYPE_DVI_SINGLE_LINK: ++ return DRM_MODE_CONNECTOR_DVID; ++ ++ default: ++ return DRM_MODE_CONNECTOR_Unknown; ++ } ++} ++ ++static void amdgpu_dm_get_native_mode(struct drm_connector *connector) ++{ ++ const struct drm_connector_helper_funcs *helper = ++ connector->helper_private; ++ struct drm_encoder *encoder; ++ struct amdgpu_encoder *amdgpu_encoder; ++ ++ encoder = helper->best_encoder(connector); ++ ++ if (encoder == NULL) ++ return; ++ ++ amdgpu_encoder = to_amdgpu_encoder(encoder); ++ ++ amdgpu_encoder->native_mode.clock = 0; ++ ++ if (!list_empty(&connector->probed_modes)) { ++ struct drm_display_mode *preferred_mode = NULL; ++ list_for_each_entry(preferred_mode, ++ &connector->probed_modes, ++ head) { ++ if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) { ++ amdgpu_encoder->native_mode = *preferred_mode; ++ } ++ break; ++ } ++ ++ } ++} ++ ++static struct drm_display_mode *amdgpu_dm_create_common_mode( ++ struct drm_encoder *encoder, char *name, ++ int hdisplay, int vdisplay) ++{ ++ struct drm_device *dev = encoder->dev; ++ struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); ++ struct drm_display_mode *mode = NULL; ++ struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; ++ ++ mode = drm_mode_duplicate(dev, native_mode); ++ ++ if(mode == NULL) ++ return NULL; ++ ++ mode->hdisplay = hdisplay; ++ mode->vdisplay = vdisplay; ++ mode->type &= ~DRM_MODE_TYPE_PREFERRED; ++ strncpy(mode->name, name, DRM_DISPLAY_MODE_LEN); ++ ++ return mode; ++ ++} ++ ++static void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, ++ struct drm_connector *connector) ++{ ++ struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); ++ struct drm_display_mode *mode = NULL; ++ struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; ++ struct amdgpu_connector *amdgpu_connector = ++ to_amdgpu_connector(connector); ++ int i; ++ int n; ++ struct mode_size { ++ char name[DRM_DISPLAY_MODE_LEN]; ++ int w; ++ int h; ++ }common_modes[] = { ++ { "640x480", 640, 480}, ++ { "800x600", 800, 600}, ++ { "1024x768", 1024, 768}, ++ { "1280x720", 1280, 720}, ++ { "1280x800", 1280, 800}, ++ {"1280x1024", 1280, 1024}, ++ { "1440x900", 1440, 900}, ++ {"1680x1050", 1680, 1050}, ++ {"1600x1200", 1600, 1200}, ++ {"1920x1080", 1920, 1080}, ++ {"1920x1200", 1920, 1200} ++ }; ++ ++ n = sizeof(common_modes) / sizeof(common_modes[0]); ++ ++ for (i = 0; i < n; i++) { ++ struct drm_display_mode *curmode = NULL; ++ bool mode_existed = false; ++ ++ if (common_modes[i].w > native_mode->hdisplay || ++ common_modes[i].h > native_mode->vdisplay || ++ (common_modes[i].w == native_mode->hdisplay && ++ common_modes[i].h == native_mode->vdisplay)) ++ continue; ++ ++ list_for_each_entry(curmode, &connector->probed_modes, head) { ++ if (common_modes[i].w == curmode->hdisplay && ++ common_modes[i].h == curmode->vdisplay) { ++ mode_existed = true; ++ break; ++ } ++ } ++ ++ if (mode_existed) ++ continue; ++ ++ mode = amdgpu_dm_create_common_mode(encoder, ++ common_modes[i].name, common_modes[i].w, ++ common_modes[i].h); ++ drm_mode_probed_add(connector, mode); ++ amdgpu_connector->num_modes++; ++ } ++} ++ ++static void amdgpu_dm_connector_ddc_get_modes( ++ struct drm_connector *connector, ++ struct edid *edid) ++{ ++ struct amdgpu_connector *amdgpu_connector = ++ to_amdgpu_connector(connector); ++ ++ if (edid) { ++ /* empty probed_modes */ ++ INIT_LIST_HEAD(&connector->probed_modes); ++ amdgpu_connector->num_modes = ++ drm_add_edid_modes(connector, edid); ++ ++ drm_edid_to_eld(connector, edid); ++ ++ amdgpu_dm_get_native_mode(connector); ++ } else ++ amdgpu_connector->num_modes = 0; ++} ++ ++int amdgpu_dm_connector_get_modes(struct drm_connector *connector) ++{ ++ const struct drm_connector_helper_funcs *helper = ++ connector->helper_private; ++ struct amdgpu_connector *amdgpu_connector = ++ to_amdgpu_connector(connector); ++ struct drm_encoder *encoder; ++ struct edid *edid = amdgpu_connector->edid; ++ ++ encoder = helper->best_encoder(connector); ++ ++ amdgpu_dm_connector_ddc_get_modes(connector, edid); ++ amdgpu_dm_connector_add_common_modes(encoder, connector); ++ return amdgpu_connector->num_modes; ++} ++ ++/* Note: this function assumes that dc_link_detect() was called for the ++ * dc_link which will be represented by this aconnector. */ ++int amdgpu_dm_connector_init( ++ struct amdgpu_display_manager *dm, ++ struct amdgpu_connector *aconnector, ++ uint32_t link_index, ++ struct amdgpu_encoder *aencoder) ++{ ++ int res, connector_type; ++ struct amdgpu_device *adev = dm->ddev->dev_private; ++ struct dc *dc = dm->dc; ++ const struct dc_link *link = dc_get_link_at_index(dc, link_index); ++ ++ DRM_DEBUG_KMS("%s()\n", __func__); ++ ++ connector_type = to_drm_connector_type(link->connector_signal); ++ ++ res = drm_connector_init( ++ dm->ddev, ++ &aconnector->base, ++ &amdgpu_dm_connector_funcs, ++ connector_type); ++ ++ if (res) { ++ DRM_ERROR("connector_init failed\n"); ++ aconnector->connector_id = -1; ++ return res; ++ } ++ ++ drm_connector_helper_add( ++ &aconnector->base, ++ &amdgpu_dm_connector_helper_funcs); ++ ++ aconnector->connector_id = link_index; ++ aconnector->dc_link = link; ++ aconnector->base.interlace_allowed = true; ++ aconnector->base.doublescan_allowed = true; ++ aconnector->hpd.hpd = link_index; /* maps to 'enum amdgpu_hpd_id' */ ++ ++ /*configure suport HPD hot plug connector_>polled default value is 0 ++ * which means HPD hot plug not supported*/ ++ switch (connector_type) { ++ case DRM_MODE_CONNECTOR_HDMIA: ++ aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; ++ break; ++ case DRM_MODE_CONNECTOR_DisplayPort: ++ aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; ++ break; ++ case DRM_MODE_CONNECTOR_DVID: ++ aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; ++ break; ++ default: ++ break; ++ } ++ ++ drm_object_attach_property(&aconnector->base.base, ++ dm->ddev->mode_config.scaling_mode_property, ++ DRM_MODE_SCALE_NONE); ++ ++ drm_object_attach_property(&aconnector->base.base, ++ adev->mode_info.underscan_property, ++ UNDERSCAN_OFF); ++ drm_object_attach_property(&aconnector->base.base, ++ adev->mode_info.underscan_hborder_property, ++ 0); ++ drm_object_attach_property(&aconnector->base.base, ++ adev->mode_info.underscan_vborder_property, ++ 0); ++ ++ /* TODO: Don't do this manually anymore ++ aconnector->base.encoder = &aencoder->base; ++ */ ++ ++ drm_mode_connector_attach_encoder( ++ &aconnector->base, &aencoder->base); ++ ++ /*drm_sysfs_connector_add(&dm_connector->base);*/ ++ ++ drm_connector_register(&aconnector->base); ++ ++ if (connector_type == DRM_MODE_CONNECTOR_DisplayPort) ++ amdgpu_dm_initialize_mst_connector(dm, aconnector); ++ ++#if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) ||\ ++ defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE) ++ ++ /* NOTE: this currently will create backlight device even if a panel ++ * is not connected to the eDP/LVDS connector. ++ * ++ * This is less than ideal but we don't have sink information at this ++ * stage since detection happens after. We can't do detection earlier ++ * since MST detection needs connectors to be created first. ++ */ ++ if (link->connector_signal & (SIGNAL_TYPE_EDP | SIGNAL_TYPE_LVDS)) { ++ /* Event if registration failed, we should continue with ++ * DM initialization because not having a backlight control ++ * is better then a black screen. */ ++ amdgpu_dm_register_backlight_device(dm); ++ ++ if (dm->backlight_dev) ++ dm->backlight_link = link; ++ } ++#endif ++ ++ return 0; ++} ++ ++int amdgpu_dm_encoder_init( ++ struct drm_device *dev, ++ struct amdgpu_encoder *aencoder, ++ uint32_t link_index, ++ struct amdgpu_crtc *acrtc) ++{ ++ int res = drm_encoder_init(dev, ++ &aencoder->base, ++ &amdgpu_dm_encoder_funcs, ++ DRM_MODE_ENCODER_TMDS, ++ NULL); ++ ++ aencoder->base.possible_crtcs = 1 << link_index; ++ ++ if (!res) ++ aencoder->encoder_id = link_index; ++ else ++ aencoder->encoder_id = -1; ++ ++ drm_encoder_helper_add(&aencoder->base, &amdgpu_dm_encoder_helper_funcs); ++ ++ return res; ++} ++ ++enum dm_commit_action { ++ DM_COMMIT_ACTION_NOTHING, ++ DM_COMMIT_ACTION_RESET, ++ DM_COMMIT_ACTION_DPMS_ON, ++ DM_COMMIT_ACTION_DPMS_OFF, ++ DM_COMMIT_ACTION_SET ++}; ++ ++enum dm_commit_action get_dm_commit_action(struct drm_crtc *crtc, ++ struct drm_crtc_state *state) ++{ ++ /* mode changed means either actually mode changed or enabled changed */ ++ /* active changed means dpms changed */ ++ if (state->mode_changed) { ++ /* if it is got disabled - call reset mode */ ++ if (!state->enable) ++ return DM_COMMIT_ACTION_RESET; ++ ++ if (state->active) ++ return DM_COMMIT_ACTION_SET; ++ else ++ return DM_COMMIT_ACTION_RESET; ++ } else { ++ /* ! mode_changed */ ++ ++ /* if it is remain disable - skip it */ ++ if (!state->enable) ++ return DM_COMMIT_ACTION_NOTHING; ++ ++ if (state->active_changed) { ++ if (state->active) { ++ return DM_COMMIT_ACTION_DPMS_ON; ++ } else { ++ return DM_COMMIT_ACTION_DPMS_OFF; ++ } ++ } else { ++ /* ! active_changed */ ++ return DM_COMMIT_ACTION_NOTHING; ++ } ++ } ++} ++ ++static void manage_dm_interrupts( ++ struct amdgpu_device *adev, ++ struct amdgpu_crtc *acrtc, ++ bool enable) ++{ ++ if (enable) { ++ drm_crtc_vblank_on(&acrtc->base); ++ amdgpu_irq_get( ++ adev, ++ &adev->pageflip_irq, ++ amdgpu_crtc_idx_to_irq_type( ++ adev, ++ acrtc->crtc_id)); ++ } else { ++ unsigned long flags; ++ amdgpu_irq_put( ++ adev, ++ &adev->pageflip_irq, ++ amdgpu_crtc_idx_to_irq_type( ++ adev, ++ acrtc->crtc_id)); ++ drm_crtc_vblank_off(&acrtc->base); ++ ++ /* ++ * should be called here, to guarantee no works left in queue. ++ * As this function sleeps it was bug to call it inside the ++ * amdgpu_dm_flip_cleanup function under locked event_lock ++ */ ++ if (acrtc->pflip_works) { ++ flush_work(&acrtc->pflip_works->flip_work); ++ flush_work(&acrtc->pflip_works->unpin_work); ++ } ++ ++ /* ++ * TODO: once Vitaly's change to adjust locking in ++ * page_flip_work_func is submitted to base driver move ++ * lock and check to amdgpu_dm_flip_cleanup function ++ */ ++ ++ spin_lock_irqsave(&adev->ddev->event_lock, flags); ++ if (acrtc->pflip_status != AMDGPU_FLIP_NONE) { ++ /* ++ * this is the case when on reset, last pending pflip ++ * interrupt did not not occur. Clean-up ++ */ ++ amdgpu_dm_flip_cleanup(adev, acrtc); ++ } ++ spin_unlock_irqrestore(&adev->ddev->event_lock, flags); ++ } ++} ++ ++/* ++ * Handle headless hotplug workaround ++ * ++ * In case of headless hotplug, if plugging the same monitor to the same ++ * DDI, DRM consider it as mode unchanged. We should check whether the ++ * sink pointer changed, and set mode_changed properly to ++ * make sure commit is doing everything. ++ */ ++static void handle_headless_hotplug( ++ const struct amdgpu_crtc *acrtc, ++ struct drm_crtc_state *state, ++ struct amdgpu_connector **aconnector) ++{ ++ struct amdgpu_connector *old_connector = ++ aconnector_from_drm_crtc_id(&acrtc->base); ++ ++ /* ++ * TODO Revisit this. This code is kinda hacky and might break things. ++ */ ++ ++ if (!old_connector) ++ return; ++ ++ if (!*aconnector) ++ *aconnector = old_connector; ++ ++ if (acrtc->target && (*aconnector)->dc_sink) { ++ if ((*aconnector)->dc_sink != ++ acrtc->target->streams[0]->sink) { ++ state->mode_changed = true; ++ } ++ } ++ ++ if (!acrtc->target) { ++ /* In case of headless with DPMS on, when system waked up, ++ * if no monitor connected, target is null and will not create ++ * new target, on that condition, we should check ++ * if any connector is connected, if connected, ++ * it means a hot plug happened after wake up, ++ * mode_changed should be set to true to make sure ++ * commit targets will do everything. ++ */ ++ state->mode_changed = ++ (*aconnector)->base.status == ++ connector_status_connected; ++ } else { ++ /* In case of headless hotplug, if plug same monitor to same ++ * DDI, DRM consider it as mode unchanged, we should check ++ * sink pointer changed, and set mode changed properly to ++ * make sure commit doing everything. ++ */ ++ /* check if sink has changed from last commit */ ++ if ((*aconnector)->dc_sink && (*aconnector)->dc_sink != ++ acrtc->target->streams[0]->sink) ++ state->mode_changed = true; ++ } ++} ++ ++int amdgpu_dm_atomic_commit( ++ struct drm_device *dev, ++ struct drm_atomic_state *state, ++ bool async) ++{ ++ struct amdgpu_device *adev = dev->dev_private; ++ struct amdgpu_display_manager *dm = &adev->dm; ++ struct drm_plane *plane; ++ struct drm_plane_state *old_plane_state; ++ uint32_t i; ++ int32_t ret; ++ uint32_t commit_targets_count = 0; ++ uint32_t new_crtcs_count = 0; ++ struct drm_crtc *crtc; ++ struct drm_crtc_state *old_crtc_state; ++ ++ struct dc_target *commit_targets[DAL_MAX_CONTROLLERS]; ++ struct amdgpu_crtc *new_crtcs[DAL_MAX_CONTROLLERS]; ++ ++ /* In this step all new fb would be pinned */ ++ ++ ret = drm_atomic_helper_prepare_planes(dev, state); ++ if (ret) ++ return ret; ++ ++ /* ++ * This is the point of no return - everything below never fails except ++ * when the hw goes bonghits. Which means we can commit the new state on ++ * the software side now. ++ */ ++ ++ drm_atomic_helper_swap_state(dev, state); ++ ++ /* ++ * From this point state become old state really. New state is ++ * initialized to appropriate objects and could be accessed from there ++ */ ++ ++ /* ++ * there is no fences usage yet in state. We can skip the following line ++ * wait_for_fences(dev, state); ++ */ ++ ++ /* update changed items */ ++ for_each_crtc_in_state(state, crtc, old_crtc_state, i) { ++ struct amdgpu_crtc *acrtc; ++ struct amdgpu_connector *aconnector; ++ enum dm_commit_action action; ++ struct drm_crtc_state *new_state = crtc->state; ++ ++ acrtc = to_amdgpu_crtc(crtc); ++ aconnector = aconnector_from_drm_crtc(crtc, state); ++ ++ /* handles headless hotplug case, updating new_state and ++ * aconnector as needed ++ */ ++ handle_headless_hotplug(acrtc, new_state, &aconnector); ++ ++ action = get_dm_commit_action(crtc, new_state); ++ ++ if (!aconnector) { ++ DRM_ERROR("Can't find connector for crtc %d\n", acrtc->crtc_id); ++ break; ++ } ++ ++ switch (action) { ++ case DM_COMMIT_ACTION_DPMS_ON: ++ case DM_COMMIT_ACTION_SET: { ++ const struct drm_connector_helper_funcs *connector_funcs; ++ struct dc_target *new_target = ++ create_target_for_sink( ++ aconnector, ++ &crtc->state->mode); ++ ++ if (!new_target) { ++ /* ++ * this could happen because of issues with ++ * userspace notifications delivery. ++ * In this case userspace tries to set mode on ++ * display which is disconnect in fact. ++ * dc_sink in NULL in this case on aconnector. ++ * We expect reset mode will come soon. ++ * ++ * This can also happen when unplug is done ++ * during resume sequence ended ++ */ ++ new_state->planes_changed = false; ++ break; ++ } ++ ++ if (acrtc->target) { ++ /* ++ * we evade vblanks and pflips on crtc that ++ * should be changed ++ */ ++ manage_dm_interrupts(adev, acrtc, false); ++ /* this is the update mode case */ ++ dc_target_release(acrtc->target); ++ acrtc->target = NULL; ++ } ++ ++ /* ++ * this loop saves set mode crtcs ++ * we needed to enable vblanks once all ++ * resources acquired in dc after dc_commit_targets ++ */ ++ new_crtcs[new_crtcs_count] = acrtc; ++ new_crtcs_count++; ++ ++ acrtc->target = new_target; ++ acrtc->enabled = true; ++ acrtc->base.enabled = true; ++ connector_funcs = aconnector->base.helper_private; ++ aconnector->base.encoder = ++ connector_funcs->best_encoder( ++ &aconnector->base); ++ break; ++ } ++ ++ case DM_COMMIT_ACTION_NOTHING: ++ break; ++ ++ case DM_COMMIT_ACTION_DPMS_OFF: ++ case DM_COMMIT_ACTION_RESET: ++ /* i.e. reset mode */ ++ if (acrtc->target) { ++ manage_dm_interrupts(adev, acrtc, false); ++ ++ dc_target_release(acrtc->target); ++ acrtc->target = NULL; ++ acrtc->enabled = false; ++ acrtc->base.enabled = false; ++ aconnector->base.encoder = NULL; ++ } ++ break; ++ } /* switch() */ ++ } /* for_each_crtc_in_state() */ ++ ++ commit_targets_count = 0; ++ ++ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { ++ ++ struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc); ++ ++ if (acrtc->target) { ++ commit_targets[commit_targets_count] = acrtc->target; ++ ++commit_targets_count; ++ } ++ } ++ ++ /* DC is optimized not to do anything if 'targets' didn't change. */ ++ dc_commit_targets(dm->dc, commit_targets, commit_targets_count); ++ ++ /* update planes when needed */ ++ for_each_plane_in_state(state, plane, old_plane_state, i) { ++ struct drm_plane_state *plane_state = plane->state; ++ struct drm_crtc *crtc = plane_state->crtc; ++ struct drm_framebuffer *fb = plane_state->fb; ++ ++ if (fb && crtc) { ++ if (!crtc->state->planes_changed) ++ continue; ++ ++ if (page_flip_needed( ++ plane_state, ++ old_plane_state)) ++ amdgpu_crtc_page_flip( ++ crtc, ++ fb, ++ crtc->state->event, ++ 0); ++ else ++ dm_dc_surface_commit( ++ dm->dc, ++ crtc, ++ to_amdgpu_framebuffer(fb)); ++ } ++ } ++ ++ for (i = 0; i < new_crtcs_count; i++) { ++ /* ++ * loop to enable interrupts on newly arrived crtc ++ */ ++ struct amdgpu_crtc *acrtc = new_crtcs[i]; ++ ++ manage_dm_interrupts(adev, acrtc, true); ++ dm_crtc_cursor_reset(&acrtc->base); ++ ++ } ++ ++ drm_atomic_helper_wait_for_vblanks(dev, state); ++ ++ /* In this state all old framebuffers would be unpinned */ ++ ++ drm_atomic_helper_cleanup_planes(dev, state); ++ ++ drm_atomic_state_free(state); ++ ++ return 0; ++} ++ ++int amdgpu_dm_atomic_check(struct drm_device *dev, ++ struct drm_atomic_state *s) ++{ ++ struct drm_crtc *crtc; ++ struct drm_crtc_state *crtc_state; ++ struct drm_plane *plane; ++ struct drm_plane_state *plane_state; ++ struct drm_connector *connector; ++ struct drm_connector_state *conn_state; ++ int i, j, ret, set_count; ++ struct dc_validation_set set[MAX_TARGET_NUM] = {{ 0 }}; ++ struct amdgpu_device *adev = dev->dev_private; ++ struct amdgpu_connector *aconnector = NULL; ++ set_count = 0; ++ ++ ret = drm_atomic_helper_check(dev,s); ++ ++ if (ret) { ++ DRM_ERROR("Atomic state integrity validation failed with error :%d !\n",ret); ++ return ret; ++ } ++ ++ ret = -EINVAL; ++ ++ if (s->num_connector > MAX_TARGET_NUM) { ++ DRM_ERROR("Exceeded max targets number !\n"); ++ return ret; ++ } ++ ++ ++ for_each_crtc_in_state(s, crtc, crtc_state, i) { ++ enum dm_commit_action action; ++ aconnector = NULL; ++ ++ action = get_dm_commit_action(crtc, crtc_state); ++ if (action == DM_COMMIT_ACTION_DPMS_OFF || DM_COMMIT_ACTION_RESET) ++ continue; ++ ++ for_each_connector_in_state(s, connector, conn_state, j) { ++ if (conn_state->crtc && conn_state->crtc == crtc) { ++ aconnector = to_amdgpu_connector(connector); ++ /*I assume at most once connector for CRTC*/ ++ break; ++ } ++ } ++ ++ /*In this case validate against existing connector if possible*/ ++ if (!aconnector) ++ aconnector = aconnector_from_drm_crtc(crtc, s); ++ ++ if (!aconnector || !aconnector->dc_sink) ++ continue; ++ ++ set[set_count].surface_count = 0; ++ ret = dm_create_validation_set_for_target(&aconnector->base, ++ &crtc_state->adjusted_mode, &set[set_count]); ++ if (ret) ++ { ++ DRM_ERROR("Creation of validation set target failed !\n"); ++ goto end; ++ } ++ ++ for_each_plane_in_state(s, plane, plane_state, j) { ++ /*Since we use drm_atomic_helper_set_config as our hook we garnteed to have the mask in correct state*/ ++ if (crtc_state->plane_mask & (1 << drm_plane_index(plane))) { ++ if (set[set_count].surface_count == MAX_SURFACE_NUM) { ++ DRM_ERROR("Exceeded max surfaces number per target!\n"); ++ ret = MODE_OK; ++ goto end; ++ } ++ ++ ret = dm_add_surface_to_validation_set(plane,plane_state, ++ (struct dc_surface **)&(set[set_count].surfaces[set[set_count].surface_count])); ++ ++ if (ret) { ++ DRM_ERROR("Failed to add surface for validation!\n"); ++ goto end; ++ } ++ ++ set[set_count].surface_count++; ++ } ++ } ++ ++ set_count++; ++ } ++ ++ if (!set_count || dc_validate_resources(adev->dm.dc, set, set_count)) { ++ ret = MODE_OK; ++ } ++end: ++ ++ for (i = 0; i < MAX_TARGET_NUM; i++) { ++ if (set[i].target) ++ dc_target_release((struct dc_target *)set[i].target); ++ } ++ ++ return ret; ++ ++} +diff --git a/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.h b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.h +new file mode 100644 +index 0000000..bda39be +--- /dev/null ++++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.h +@@ -0,0 +1,96 @@ ++/* ++ * Copyright 2012-13 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 ++ * ++ */ ++ ++ ++#ifndef __AMDGPU_DM_TYPES_H__ ++#define __AMDGPU_DM_TYPES_H__ ++ ++#include <drm/drmP.h> ++ ++struct plane_addr_flip_info; ++struct amdgpu_framebuffer; ++struct amdgpu_display_manager; ++struct dc_validation_set; ++struct dc_surface; ++ ++/*TODO Jodan Hersen use the one in amdgpu_dm*/ ++int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm, ++ struct amdgpu_crtc *amdgpu_crtc, ++ uint32_t link_index); ++int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm, ++ struct amdgpu_connector *amdgpu_connector, ++ uint32_t link_index, ++ struct amdgpu_encoder *amdgpu_encoder); ++int amdgpu_dm_encoder_init(struct drm_device *dev, ++ struct amdgpu_encoder *amdgpu_encoder, ++ uint32_t link_index, ++ struct amdgpu_crtc *amdgpu_crtc); ++ ++void amdgpu_dm_crtc_destroy(struct drm_crtc *crtc); ++void amdgpu_dm_connector_destroy(struct drm_connector *connector); ++void amdgpu_dm_encoder_destroy(struct drm_encoder *encoder); ++ ++void dm_add_display_info( ++ struct drm_display_info *disp_info, ++ struct amdgpu_display_manager *dm, ++ uint32_t display_index); ++ ++int amdgpu_dm_connector_get_modes(struct drm_connector *connector); ++ ++struct amdgpu_connector *aconnector_from_drm_crtc( ++ struct drm_crtc *crtc, ++ struct drm_atomic_state *state); ++ ++int amdgpu_dm_atomic_commit( ++ struct drm_device *dev, ++ struct drm_atomic_state *state, ++ bool async); ++int amdgpu_dm_atomic_check(struct drm_device *dev, ++ struct drm_atomic_state *state); ++ ++int dm_create_validation_set_for_target( ++ struct drm_connector *connector, ++ struct drm_display_mode *mode, ++ struct dc_validation_set *val_set); ++int dm_add_surface_to_validation_set( ++ struct drm_plane *plane, ++ struct drm_plane_state *state, ++ struct dc_surface **surface); ++ ++void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector); ++struct drm_connector_state *amdgpu_dm_connector_atomic_duplicate_state( ++ struct drm_connector *connector); ++void amdgpu_dm_connector_atomic_destroy_state( ++ struct drm_connector *connector, ++ struct drm_connector_state *state); ++int amdgpu_dm_connector_atomic_set_property( ++ struct drm_connector *connector, ++ struct drm_connector_state *state, ++ struct drm_property *property, ++ uint64_t val); ++ ++extern const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs; ++ ++#endif /* __AMDGPU_DM_TYPES_H__ */ +-- +2.7.4 + |