diff options
Diffstat (limited to 'drivers/gpu/drm')
-rw-r--r-- | drivers/gpu/drm/amd/amdgpu/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/amdgpu/amdgpu.h | 8 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 35 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c | 16 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 6 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/amdgpu/vi.c | 85 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/dal/Makefile | 7 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/dal/amdgpu_dm/Makefile | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dal_services.c | 232 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.c | 1093 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.h | 172 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.c | 731 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.h | 122 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.c | 1575 | ||||
-rw-r--r-- | drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.h | 69 |
16 files changed, 4151 insertions, 11 deletions
diff --git a/drivers/gpu/drm/amd/amdgpu/Makefile b/drivers/gpu/drm/amd/amdgpu/Makefile index 00804b482eb0..ac7b797ae9ed 100644 --- a/drivers/gpu/drm/amd/amdgpu/Makefile +++ b/drivers/gpu/drm/amd/amdgpu/Makefile @@ -2,7 +2,7 @@ # Makefile for the drm device driver. This driver provides support for the # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. -FULL_AMD_PATH=drivers/gpu/drm/amd +FULL_AMD_PATH=$(src)/.. DAL_FOLDER_NAME=dal FULL_AMD_DAL_PATH = $(FULL_AMD_PATH)/$(DAL_FOLDER_NAME) @@ -107,6 +107,7 @@ RELATIVE_AMD_DAL_PATH = ../$(DAL_FOLDER_NAME) include $(FULL_AMD_DAL_PATH)/Makefile amdgpu-y += $(AMD_DAL_FILES) + endif obj-$(CONFIG_DRM_AMDGPU)+= amdgpu.o diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 57b427f958da..8e4af1667e32 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -52,6 +52,7 @@ #include "amdgpu_irq.h" #include "amdgpu_ucode.h" #include "amdgpu_gds.h" +#include "amdgpu_dm.h" #include "gpu_scheduler.h" @@ -79,6 +80,7 @@ extern int amdgpu_bapm; extern int amdgpu_deep_color; extern int amdgpu_vm_size; extern int amdgpu_vm_block_size; +extern int amdgpu_dal; extern int amdgpu_enable_scheduler; extern int amdgpu_sched_jobs; extern int amdgpu_sched_hw_submission; @@ -2038,6 +2040,7 @@ struct amdgpu_device { /* display */ struct amdgpu_mode_info mode_info; + /* For pre-DCE11. DCE11 and later are in "struct amdgpu_device->dm" */ struct work_struct hotplug_work; struct amdgpu_irq_src crtc_irq; struct amdgpu_irq_src pageflip_irq; @@ -2083,6 +2086,9 @@ struct amdgpu_device { /* GDS */ struct amdgpu_gds gds; + /* display related functionality */ + struct amdgpu_display_manager dm; + const struct amdgpu_ip_block_version *ip_blocks; int num_ip_blocks; struct amdgpu_ip_block_status *ip_block_status; @@ -2117,7 +2123,7 @@ void amdgpu_io_wreg(struct amdgpu_device *adev, u32 reg, u32 v); u32 amdgpu_mm_rdoorbell(struct amdgpu_device *adev, u32 index); void amdgpu_mm_wdoorbell(struct amdgpu_device *adev, u32 index, u32 v); - +bool amdgpu_device_has_dal_support(struct amdgpu_device *adev); /* * Cast helper */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 2d569eccf41f..5dfd5c7d2805 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1356,6 +1356,28 @@ static int amdgpu_resume(struct amdgpu_device *adev) return 0; } + +/** + * amdgpu_device_has_dal_support - check if dal is supported + * + * @adev: amdgpu_device_pointer + * + * Returns true for supported, false for not supported + */ +bool amdgpu_device_has_dal_support(struct amdgpu_device *adev) +{ + + switch(adev->asic_type) { + case CHIP_CARRIZO: +#ifdef CONFIG_DRM_AMD_DAL && CONFIG_DRM_AMD_DAL_DCE11_0 + return true; +#endif + default: + return false; + } +} + + /** * amdgpu_device_init - initialize the driver * @@ -1500,8 +1522,10 @@ int amdgpu_device_init(struct amdgpu_device *adev, r = amdgpu_atombios_get_clock_info(adev); if (r) return r; + /* init i2c buses */ - amdgpu_atombios_i2c_init(adev); + if (amdgpu_dal == 0 || (amdgpu_dal != 0 && !amdgpu_device_has_dal_support(adev))) + amdgpu_atombios_i2c_init(adev); /* Fence driver */ r = amdgpu_fence_driver_init(adev); @@ -1602,7 +1626,8 @@ void amdgpu_device_fini(struct amdgpu_device *adev) adev->ip_block_status = NULL; adev->accel_working = false; /* free i2c buses */ - amdgpu_i2c_fini(adev); + if (amdgpu_dal == 0 || (amdgpu_dal != 0 && !amdgpu_device_has_dal_support(adev))) + amdgpu_i2c_fini(adev); amdgpu_atombios_fini(adev); kfree(adev->bios); adev->bios = NULL; @@ -1746,7 +1771,11 @@ int amdgpu_resume_kms(struct drm_device *dev, bool resume, bool fbcon) /* blat the mode back in */ if (fbcon) { - drm_helper_resume_force_mode(dev); + if (amdgpu_dal == 0 || (amdgpu_dal != 0 && !amdgpu_device_has_dal_support(adev))) { + /* pre DCE11 */ + drm_helper_resume_force_mode(dev); + } + /* turn on display hw */ list_for_each_entry(connector, &dev->mode_config.connector_list, head) { drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 6bb1c9c5b6f9..7e8722c9652a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -76,6 +76,7 @@ int amdgpu_deep_color = 0; int amdgpu_vm_size = 8; int amdgpu_vm_block_size = -1; int amdgpu_exp_hw_support = 0; +int amdgpu_dal = 1; int amdgpu_enable_scheduler = 0; int amdgpu_sched_jobs = 16; int amdgpu_sched_hw_submission = 2; @@ -144,6 +145,9 @@ module_param_named(vm_block_size, amdgpu_vm_block_size, int, 0444); MODULE_PARM_DESC(exp_hw_support, "experimental hw support (1 = enable, 0 = disable (default))"); module_param_named(exp_hw_support, amdgpu_exp_hw_support, int, 0444); +MODULE_PARM_DESC(dal, "DAL display driver (1 = enable (default), 0 = disable)"); +module_param_named(dal, amdgpu_dal, int, 0444); + MODULE_PARM_DESC(enable_scheduler, "enable SW GPU scheduler (1 = enable, 0 = disable ((default))"); module_param_named(enable_scheduler, amdgpu_enable_scheduler, int, 0444); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c index 0aba8e9bc8a0..ecc3e12676d4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c @@ -35,6 +35,10 @@ #include <linux/pm_runtime.h> +#ifdef CONFIG_DRM_AMD_DAL +#include "amdgpu_dm_irq.h" +#endif + #define AMDGPU_WAIT_IDLE_TIMEOUT 200 /* @@ -229,7 +233,17 @@ int amdgpu_irq_init(struct amdgpu_device *adev) } } - INIT_WORK(&adev->hotplug_work, amdgpu_hotplug_work_func); + + if (amdgpu_dal == 0 || (amdgpu_dal != 0 && !amdgpu_device_has_dal_support(adev))) { + /* pre DCE11 */ + INIT_WORK(&adev->hotplug_work, + amdgpu_hotplug_work_func); + } else { + INIT_WORK(&adev->hotplug_work, + amdgpu_hotplug_work_func); + } + + INIT_WORK(&adev->reset_work, amdgpu_irq_reset_work_func); adev->irq.installed = true; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 22367939ebf1..ea9d9584f364 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -677,6 +677,12 @@ int amdgpu_get_vblank_timestamp_kms(struct drm_device *dev, int crtc, /* Get associated drm_crtc: */ drmcrtc = &adev->mode_info.crtcs[crtc]->base; + if (!drmcrtc) { + /* This can occur on driver load if some component fails to + * initialize completely and driver is unloaded */ + DRM_ERROR("Uninitialized crtc %d\n", crtc); + return -EINVAL; + } /* Helper routine in DRM core does all the work: */ return drm_calc_vbltimestamp_from_scanoutpos(dev, crtc, max_error, diff --git a/drivers/gpu/drm/amd/amdgpu/vi.c b/drivers/gpu/drm/amd/amdgpu/vi.c index b55ceb14fdcd..3573eac99b19 100644 --- a/drivers/gpu/drm/amd/amdgpu/vi.c +++ b/drivers/gpu/drm/amd/amdgpu/vi.c @@ -71,6 +71,7 @@ #include "uvd_v5_0.h" #include "uvd_v6_0.h" #include "vce_v3_0.h" +#include "amdgpu_dm.h" /* * Indirect registers accessor @@ -1300,6 +1301,80 @@ static const struct amdgpu_ip_block_version cz_ip_blocks[] = }, }; +/* + * This is temporary. After we've gone through full testing with + * DAL we want to remove dce_v11 + */ +#if defined(CONFIG_DRM_AMD_DAL) +static const struct amdgpu_ip_block_version cz_ip_blocks_dal[] = +{ + /* ORDER MATTERS! */ + { + .type = AMD_IP_BLOCK_TYPE_COMMON, + .major = 2, + .minor = 0, + .rev = 0, + .funcs = &vi_common_ip_funcs, + }, + { + .type = AMD_IP_BLOCK_TYPE_GMC, + .major = 8, + .minor = 0, + .rev = 0, + .funcs = &gmc_v8_0_ip_funcs, + }, + { + .type = AMD_IP_BLOCK_TYPE_IH, + .major = 3, + .minor = 0, + .rev = 0, + .funcs = &cz_ih_ip_funcs, + }, + { + .type = AMD_IP_BLOCK_TYPE_SMC, + .major = 8, + .minor = 0, + .rev = 0, + .funcs = &cz_dpm_ip_funcs, + }, + { + .type = AMD_IP_BLOCK_TYPE_DCE, + .major = 11, + .minor = 0, + .rev = 0, + .funcs = &amdgpu_dm_funcs, + }, + { + .type = AMD_IP_BLOCK_TYPE_GFX, + .major = 8, + .minor = 0, + .rev = 0, + .funcs = &gfx_v8_0_ip_funcs, + }, + { + .type = AMD_IP_BLOCK_TYPE_SDMA, + .major = 3, + .minor = 0, + .rev = 0, + .funcs = &sdma_v3_0_ip_funcs, + }, + { + .type = AMD_IP_BLOCK_TYPE_UVD, + .major = 6, + .minor = 0, + .rev = 0, + .funcs = &uvd_v6_0_ip_funcs, + }, + { + .type = AMD_IP_BLOCK_TYPE_VCE, + .major = 3, + .minor = 0, + .rev = 0, + .funcs = &vce_v3_0_ip_funcs, + }, +}; +#endif + int vi_set_ip_blocks(struct amdgpu_device *adev) { switch (adev->asic_type) { @@ -1316,8 +1391,18 @@ int vi_set_ip_blocks(struct amdgpu_device *adev) adev->num_ip_blocks = ARRAY_SIZE(tonga_ip_blocks); break; case CHIP_CARRIZO: +#if defined(CONFIG_DRM_AMD_DAL) + if (amdgpu_dal && amdgpu_device_has_dal_support(adev)) { + adev->ip_blocks = cz_ip_blocks_dal; + adev->num_ip_blocks = ARRAY_SIZE(cz_ip_blocks_dal); + } else { + adev->ip_blocks = cz_ip_blocks; + adev->num_ip_blocks = ARRAY_SIZE(cz_ip_blocks); + } +#else adev->ip_blocks = cz_ip_blocks; adev->num_ip_blocks = ARRAY_SIZE(cz_ip_blocks); +#endif break; default: /* FIXME: not supported yet */ diff --git a/drivers/gpu/drm/amd/dal/Makefile b/drivers/gpu/drm/amd/dal/Makefile index d99e75f9b9ca..a1e12fc69633 100644 --- a/drivers/gpu/drm/amd/dal/Makefile +++ b/drivers/gpu/drm/amd/dal/Makefile @@ -7,10 +7,9 @@ AMDDALPATH = $(RELATIVE_AMD_DAL_PATH) subdir-ccflags-y += -I$(AMDDALPATH)/ -I$(AMDDALPATH)/include -DDAL_CZ_BRINGUP -DAL_LIBS = adapter amdgpu_dm audio asic_capability basics bios connector \ - controller dcs display_path display_service encoder gpio gpu \ - hw_sequencer i2caux interface link_service mode_manager timing_service \ - topology irq +DAL_LIBS = adapter amdgpu_dm audio asic_capability basics bios connector controller dcs \ + display_path display_service encoder gpio gpu hw_sequencer i2caux \ + interface link_service mode_manager timing_service topology irq AMD_DAL = $(addsuffix /Makefile, $(addprefix $(FULL_AMD_DAL_PATH)/,$(DAL_LIBS))) diff --git a/drivers/gpu/drm/amd/dal/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/dal/amdgpu_dm/Makefile index 1709ec8dfb33..8fa9eaec0bfc 100644 --- a/drivers/gpu/drm/amd/dal/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/Makefile @@ -2,7 +2,9 @@ # Makefile for the 'dm' sub-component of DAL. # It provides the control and status of dm blocks. -AMDGPUDM = amdgpu_dal_services.o + + +AMDGPUDM = amdgpu_dal_services.o amdgpu_dm_types.o amdgpu_dm.o amdgpu_dm_irq.o AMDGPU_DM = $(addprefix $(AMDDALPATH)/amdgpu_dm/,$(AMDGPUDM)) 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 index 9a4d38161412..571653cc3ac3 100644 --- a/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dal_services.c +++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dal_services.c @@ -32,6 +32,238 @@ #include "amdgpu.h" #include "dal_services.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_irq.h" +#include "include/dal_interface.h" + +/* +#include "logger_interface.h" +#include "acpimethod_atif.h" +#include "amdgpu_powerplay.h" +#include "amdgpu_notifications.h" +*/ + +/****************************************************************************** + * IRQ Interfaces. + *****************************************************************************/ + +void dal_register_timer_interrupt( + struct dal_context *context, + struct dal_timer_interrupt_params *int_params, + interrupt_handler ih, + void *args) +{ + struct amdgpu_device *adev = context->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); +} + +irq_handler_idx dal_register_interrupt( + struct dal_context *context, + struct dal_interrupt_params *int_params, + interrupt_handler ih, + void *handler_args) +{ + struct amdgpu_device *adev = context->driver_context; + + if (NULL == int_params || NULL == ih) { + DRM_ERROR("DM_IRQ: invalid input!\n"); + return DAL_INVALID_IRQ_HANDLER_IDX; + } + + if (int_params->int_context >= INTERRUPT_CONTEXT_NUMBER) { + DRM_ERROR("DM_IRQ: invalid context: %d!\n", + int_params->int_context); + return DAL_INVALID_IRQ_HANDLER_IDX; + } + + if (!DAL_VALID_IRQ_SRC_NUM(int_params->irq_source)) { + DRM_ERROR("DM_IRQ: invalid irq_source: %d!\n", + int_params->irq_source); + return DAL_INVALID_IRQ_HANDLER_IDX; + } + + return amdgpu_dm_irq_register_interrupt(adev, int_params, ih, + handler_args); +} + +void dal_unregister_interrupt( + struct dal_context *context, + enum dal_irq_source irq_source, + irq_handler_idx handler_idx) +{ + struct amdgpu_device *adev = context->driver_context; + + if (DAL_INVALID_IRQ_HANDLER_IDX == handler_idx) { + DRM_ERROR("DM_IRQ: invalid handler_idx==NULL!\n"); + return; + } + + if (!DAL_VALID_IRQ_SRC_NUM(irq_source)) { + DRM_ERROR("DM_IRQ: invalid irq_source:%d!\n", irq_source); + return; + } + + amdgpu_dm_irq_unregister_interrupt(adev, irq_source, handler_idx); +} + + +void dal_isr_acquire_lock(struct dal_context *context) +{ + /*TODO*/ +} + +void dal_isr_release_lock(struct dal_context *context) +{ + /*TODO*/ +} + +/****************************************************************************** + * End-of-IRQ Interfaces. + *****************************************************************************/ + +bool dal_get_platform_info(struct dal_context *dal_context, + struct platform_info_params *params) +{ + /*TODO*/ + return false; +} + +/* Next calls are to power component */ +bool dal_pp_pre_dce_clock_change(struct dal_context *ctx, + struct dal_to_power_info *input, + struct power_to_dal_info *output) +{ + /*TODO*/ + return false; +} + +bool dal_pp_post_dce_clock_change(struct dal_context *ctx) +{ + /*TODO*/ + return false; +} + +bool dal_get_system_clocks_range(struct dal_context *ctx, + struct dal_system_clock_range *sys_clks) +{ + /*TODO*/ + return false; +} + + +bool dal_pp_set_display_clock(struct dal_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 */ + +/* dal_notify_hotplug + * + * Notify display manager for hotplug event + * + * @param + * struct dal_context *dal_context - [in] pointer to specific DAL context + * + * @return + * void + * */ +void dal_notify_hotplug( + struct dal_context *ctx, + uint32_t display_index, + bool is_connected) +{ + struct amdgpu_device *adev = ctx->driver_context; + struct drm_device *dev = adev->ddev; + struct drm_connector *connector = NULL; + struct amdgpu_connector *aconnector = NULL; + + /* 1. Update status of drm connectors + * 2. Send a uevent and let userspace tell us what to do */ + list_for_each_entry(connector, + &dev->mode_config.connector_list, head) { + aconnector = to_amdgpu_connector(connector); + + /*aconnector->connector_id means display_index*/ + if (aconnector->connector_id != display_index) + continue; + + if (is_connected) { + drm_mode_connector_update_edid_property( + connector, + (struct edid *) + dal_get_display_edid( + adev->dm.dal, + display_index, + NULL)); + } else + drm_mode_connector_update_edid_property( + connector, NULL); + + break; + } + + drm_helper_hpd_irq_event(dev); +} + +void dal_notify_capability_change( + struct dal_context *ctx, + uint32_t display_index) +{ + struct amdgpu_device *adev = ctx->driver_context; + struct drm_device *dev = adev->ddev; + struct drm_connector *connector = NULL; + struct amdgpu_connector *aconnector = NULL; + + /* 1. Update status of drm connectors + * 2. Send a uevent and let userspace tell us what to do */ + + list_for_each_entry(connector, + &dev->mode_config.connector_list, head) { + aconnector = to_amdgpu_connector(connector); + + /*aconnector->connector_id means display_index*/ + if (aconnector->connector_id == display_index) { + drm_mode_connector_update_edid_property( + connector, + (struct edid *) + dal_get_display_edid( + adev->dm.dal, + display_index, + NULL)); + } + } + + drm_kms_helper_hotplug_event(dev); +} + +void dal_notify_setmode_complete(struct dal_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) { 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 000000000000..55698b533bad --- /dev/null +++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.c @@ -0,0 +1,1093 @@ +/* + * 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 "include/dal_interface.h" +#include "include/mode_query_interface.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 "dce/dce_11_0_d.h" +#include "dce/dce_11_0_sh_mask.h" +#include "dce/dce_11_0_enum.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> + + +/* 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__) + +static u32 dm_vblank_get_counter(struct amdgpu_device *adev, int crtc) +{ + if (crtc >= adev->mode_info.num_crtc) + return 0; + else + return dal_get_vblank_counter(adev->dm.dal, crtc); +} + +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; + + 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) +{ + 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; +} + +static void dm_stop_mc_access(struct amdgpu_device *adev, + struct amdgpu_mode_mc_save *save) +{ +} + +static void dm_resume_mc_access(struct amdgpu_device *adev, + struct amdgpu_mode_mc_save *save) +{ +} + +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 amdgpu_dm_pflip_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + enum dal_irq_source src = irq_params->irq_src; + unsigned long flags; + uint32_t display_index = + dal_get_display_index_from_int_src(adev->dm.dal, src); + struct amdgpu_crtc *amdgpu_crtc = adev->mode_info.crtcs[display_index]; + struct amdgpu_flip_work *works; + + /* 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_vblank_put(adev->ddev, amdgpu_crtc->crtc_id); + amdgpu_irq_put(adev, &adev->pageflip_irq, amdgpu_crtc->crtc_id); + queue_work(amdgpu_crtc->pflip_queue, &works->unpin_work); +} + +static void amdgpu_dm_crtc_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + enum dal_irq_source src = irq_params->irq_src; + + uint32_t display_index = + dal_get_display_index_from_int_src(adev->dm.dal, src); + + drm_handle_vblank(adev->ddev, display_index); +} + +static void hpd_low_irq_helper_func( + void *param, + const struct path_mode *pm) +{ + uint32_t *display_index = param; + + *display_index = pm->display_path_index; +} + +static inline struct amdgpu_connector *find_connector_by_display_index( + struct drm_device *dev, + uint32_t display_index) +{ + struct drm_connector *connector = NULL; + struct amdgpu_connector *aconnector = NULL; + + list_for_each_entry( + connector, + &dev->mode_config.connector_list, + head) { + aconnector = to_amdgpu_connector(connector); + + /*aconnector->connector_id means display_index*/ + if (aconnector->connector_id == display_index) + break; + } + + return aconnector; +} + +static void amdgpu_dm_hpd_low_irq(void *interrupt_params) +{ + struct amdgpu_device *adev = interrupt_params; + struct dal *dal = adev->dm.dal; + struct drm_device *dev = adev->ddev; + uint32_t connected_displays; + struct amdgpu_connector *aconnector = NULL; + bool trigger_drm_hpd_event = false; + + /* This function runs after dal_notify_hotplug(). + * That means the user-mode may already called DAL with a Set/Reset + * mode, that means this function must acquire the dal_mutex + * *before* calling into DAL. + * The vice-versa sequence may also happen - this function is + * calling into DAL and preempted by a call from user-mode. */ + mutex_lock(&adev->dm.dal_mutex); + + connected_displays = dal_get_connected_targets_vector(dal); + + if (connected_displays == 0) { + uint32_t display_index = INVALID_DISPLAY_INDEX; + + dal_pin_active_path_modes( + dal, + &display_index, + INVALID_DISPLAY_INDEX, + hpd_low_irq_helper_func); + + mutex_unlock(&adev->dm.dal_mutex); + + adev->dm.fake_display_index = display_index; + + aconnector = + find_connector_by_display_index(dev, display_index); + + if (!aconnector) + return; + + /* + * force connected status on fake display connector + */ + aconnector->base.status = connector_status_connected; + + /* we need to force user-space notification on changed modes */ + trigger_drm_hpd_event = true; + + } else if (adev->dm.fake_display_index != INVALID_DISPLAY_INDEX) { + /* we assume only one display is connected */ + uint32_t connected_display_index = 0; + struct drm_crtc *crtc; + + mutex_unlock(&adev->dm.dal_mutex); + + /* identify first connected display index */ + while (connected_displays) { + if (1 & connected_displays) + break; + + ++connected_display_index; + connected_displays >>= 1; + } + + aconnector = + find_connector_by_display_index( + dev, + adev->dm.fake_display_index); + + if (!aconnector) + return; + + /* + * if there is display on another connector get connected + * we need to clean-up connection status on fake display + */ + if (connected_display_index != adev->dm.fake_display_index) { + /* reset connected status on fake display connector */ + aconnector->base.status = connector_status_disconnected; + } else { + crtc = aconnector->base.encoder->crtc; + + DRM_DEBUG_KMS("Setting connector DPMS state to off\n"); + DRM_DEBUG_KMS("\t[CONNECTOR:%d] set DPMS off\n", + aconnector->base.base.id); + aconnector->base.funcs->dpms( + &aconnector->base, DRM_MODE_DPMS_OFF); + + amdgpu_dm_mode_reset(crtc); + + /* + * as mode reset is done for fake display, we should + * unreference drm fb and assign NULL pointer to the + * primary drm frame, so we will receive full set mode + * sequence later + */ + + drm_framebuffer_unreference(crtc->primary->fb); + + crtc->primary->fb = NULL; + } + + adev->dm.fake_display_index = INVALID_DISPLAY_INDEX; + + trigger_drm_hpd_event = true; + } else + mutex_unlock(&adev->dm.dal_mutex); + + if (true == trigger_drm_hpd_event) + drm_kms_helper_hotplug_event(dev); +} + +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); + + + +/* 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; + adev->dm.fake_display_index = INVALID_DISPLAY_INDEX; + + /* 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.chip_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.bits.SKIP_POWER_DOWN_ON_RESUME = 1; + + if (adev->asic_type == CHIP_CARRIZO) + init_data.asic_id.runtime_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; + + adev->dm.dal = dal_create(&init_data); + + if (!adev->dm.dal) { + DRM_ERROR( + "amdgpu: failed to initialize hw for display support.\n"); + /* Do not fail and cleanup, try to run without display */ + } + + if (amdgpu_dm_initialize_drm_device(&adev->dm)) { + 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; + } + + dal_destroy(&adev->dm.dal); + return; +} + +/* moved from amdgpu_dm_kms.c */ +void amdgpu_dm_destroy() +{ +} + +/* + * amdgpu_dm_get_vblank_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 + */ +u32 amdgpu_dm_get_vblank_counter(struct amdgpu_device *adev, int disp_idx) +{ + return dal_get_vblank_counter(adev->dm.dal, disp_idx); +} + +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; + + dal_set_power_state( + dm->dal, + DAL_ACPI_CM_POWER_STATE_D3, + DAL_VIDEO_POWER_SUSPEND); + + amdgpu_dm_irq_suspend(adev); + + return 0; +} + +static int dm_resume(void *handle) +{ + uint32_t connected_displays_vector; + uint32_t prev_connected_displays_vector; + uint32_t supported_disp = 0; /* vector of supported displays */ + uint32_t displays_number; + uint32_t current_display_index; + struct amdgpu_device *adev = handle; + struct amdgpu_display_manager *dm = &adev->dm; + uint32_t displays_vector[MAX_COFUNC_PATH]; + + dal_set_power_state( + dm->dal, + DAL_ACPI_CM_POWER_STATE_D0, + DAL_VIDEO_POWER_ON); + + prev_connected_displays_vector = + dal_get_connected_targets_vector(dm->dal); + supported_disp = dal_get_supported_displays_vector(dm->dal); + + /* save previous connected display to reset mode correctly */ + connected_displays_vector = prev_connected_displays_vector; + + amdgpu_dm_irq_resume(adev); + + dal_resume(dm->dal); + + for (displays_number = 0, current_display_index = 0; + connected_displays_vector != 0; + connected_displays_vector >>= 1, + current_display_index++) { + if ((connected_displays_vector & 1) == 1) { + struct amdgpu_crtc *crtc = + adev->mode_info.crtcs[current_display_index]; + + displays_vector[displays_number] = + current_display_index; + + memset(&crtc->base.mode, 0, sizeof(crtc->base.mode)); + + ++displays_number; + } + } + + mutex_lock(&adev->dm.dal_mutex); + dal_reset_path_mode(dm->dal, displays_number, displays_vector); + mutex_unlock(&adev->dm.dal_mutex); + + return 0; +} +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, +}; + +static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev) +{ + int r; + int i; + + /* Register IRQ sources and initialize high IRQ callbacks */ + struct common_irq_params *c_irq_params; + struct dal_interrupt_params int_params = {0}; + int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; + int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; + int_params.no_mutex_wait = false; + int_params.one_shot = false; + + for (i = 7; i < 19; i += 2) { + r = amdgpu_irq_add_id(adev, i, &adev->crtc_irq); + + /* High IRQ callback for crtc irq */ + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dal_interrupt_to_irq_source(adev->dm.dal, i, 0); + + c_irq_params = &adev->dm.vsync_params[int_params.irq_source - DAL_IRQ_SOURCE_CRTC1VSYNC]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + amdgpu_dm_irq_register_interrupt(adev, &int_params, + amdgpu_dm_crtc_high_irq, c_irq_params); + + if (r) + return r; + } + + for (i = 8; i < 20; i += 2) { + r = amdgpu_irq_add_id(adev, i, &adev->pageflip_irq); + + /* High IRQ callback for pflip irq */ + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dal_interrupt_to_irq_source(adev->dm.dal, i, 0); + + c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DAL_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, + amdgpu_dm_pflip_high_irq, c_irq_params); + + if (r) + return r; + } + + /* HPD hotplug */ + r = amdgpu_irq_add_id(adev, 42, &adev->hpd_irq); + + for (i = 0; i < adev->mode_info.num_crtc; i++) { + /* High IRQ callback for hpd irq */ + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = + dal_interrupt_to_irq_source(adev->dm.dal, 42, i); + amdgpu_dm_irq_register_interrupt(adev, &int_params, + amdgpu_dm_hpd_low_irq, adev); + } + + if (r) + return r; + + adev->mode_info.mode_config_initialized = true; + + adev->ddev->mode_config.funcs = (void *)&amdgpu_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; + + /* this is a part of HPD initialization */ + drm_kms_helper_poll_init(adev->ddev); + + return r; +} + +#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); + uint32_t current_display_index = 0; + uint32_t connected_displays_vector; + uint32_t total_supported_displays_vector; + + if (!dm->dal) + return 0; + + connected_displays_vector = + dal_get_connected_targets_vector(dm->dal); + total_supported_displays_vector = + dal_get_supported_displays_vector(dm->dal); + + /* loops over all the connected displays*/ + for (; total_supported_displays_vector != 0; + total_supported_displays_vector >>= 1, + connected_displays_vector >>= 1, + ++current_display_index) { + enum signal_type st; + + if (!(connected_displays_vector & 1)) + continue; + + st = dal_get_display_signal(dm->dal, current_display_index); + + if (dal_is_embedded_signal(st)) + break; + } + + if (dal_set_backlight_level( + dm->dal, + current_display_index, + 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, +}; +#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_display_manager *dm) +{ + int current_display_index = 0; + struct amdgpu_connector *aconnector; + struct amdgpu_encoder *aencoder; + struct amdgpu_crtc *acrtc; + + uint32_t connected_displays_vector = + dal_get_connected_targets_vector(dm->dal); + uint32_t total_supported_displays_vector = + dal_get_supported_displays_vector(dm->dal); + + + if (amdgpu_dm_mode_config_init(dm->adev)) { + DRM_ERROR("KMS: Failed to initialize mode config\n"); + return -1; + } + +#if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) ||\ + defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE) + { + struct backlight_device *bd; + char bl_name[16]; + struct backlight_properties props; + + memset(&props, 0, sizeof(props)); + 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); + bd = backlight_device_register( + bl_name, + dm->adev->ddev->dev, + dm, + &amdgpu_dm_backlight_ops, + &props); + if (!bd) { + DRM_ERROR("Backlight registration failed\n"); + goto fail_backlight_dev; + } + dm->backlight_dev = bd; + } +#endif + + /* loops over all the connected displays*/ + for (; total_supported_displays_vector != 0; + total_supported_displays_vector >>= 1, + connected_displays_vector >>= 1, + ++current_display_index) { + + if (current_display_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, + current_display_index)) { + DRM_ERROR("KMS: Failed to initialize crtc\n"); + goto fail; + } + + if (amdgpu_dm_encoder_init( + dm->ddev, + aencoder, + current_display_index, + acrtc)) { + DRM_ERROR("KMS: Failed to initialize encoder\n"); + goto fail; + } + + if (amdgpu_dm_connector_init( + dm, + aconnector, + current_display_index, + (connected_displays_vector & 1) == 1, + aencoder)) { + DRM_ERROR("KMS: Failed to initialize connector\n"); + goto fail; + } + } + + dm->display_indexes_num = current_display_index; + dm->mode_query_option = QUERY_OPTION_NO_PAN; + + 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: + backlight_device_unregister(dm->backlight_dev); +fail_backlight_dev: + return -1; +} + +void amdgpu_dm_destroy_drm_device( + struct amdgpu_display_manager *dm) +{ + drm_mode_config_cleanup(dm->ddev); + 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 + ******************************************************************************/ +/** + * 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 *amdgpu_crtc = adev->mode_info.crtcs[crtc_id]; + struct plane_addr_flip_info flip_info; + const unsigned int num_of_planes = 1; + + memset(&flip_info, 0, sizeof(flip_info)); + + flip_info.display_index = amdgpu_crtc->crtc_id; + flip_info.address_info.address.type = PLN_ADDR_TYPE_GRAPHICS; + flip_info.address_info.layer_index = LAYER_INDEX_PRIMARY; + flip_info.address_info.flags.bits.ENABLE = 1; + + flip_info.address_info.address.grph.addr.low_part = + lower_32_bits(crtc_base); + + flip_info.address_info.address.grph.addr.high_part = + upper_32_bits(crtc_base); + + dal_update_plane_addresses(adev->dm.dal, num_of_planes, &flip_info); +} + +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 000000000000..78950a25cee9 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm.h @@ -0,0 +1,172 @@ +/* + * 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 dal_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; + 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; + + u32 mode_query_option; + + 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[DAL_IRQ_SOURCE_PFLIP_LAST - DAL_IRQ_SOURCE_PFLIP_FIRST + 1]; + + struct common_irq_params + vsync_params[DAL_IRQ_SOURCE_CRTC6VSYNC - DAL_IRQ_SOURCE_CRTC1VSYNC + 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; + + /* + * The problem: + * We don't get Set Mode call if only one display is connected, and + * this display is disconnected and connected back to the same + * connector. + * + * The workaround: + * 1. When the last display is disconnected, simulate a hot-plug for a + * fake display which has the same EDID as the one which was just + * disconnected, but with a mode list reduced to a single mode + * (the fail-safe mode) 640x480. + * Because of the change in mode-list we do get Set Mode. + * 2. When the real display is connected notify the OS about the + * new mode-list, which is different from the fake one, because + * of the difference the OS calls Set Mode again, which is exactly + * what we need. */ + uint32_t fake_display_index; + /* 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; +}; + + +/* 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_display_manager *dm); + +/* 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); + +extern const struct amd_ip_funcs amdgpu_dm_funcs; + +#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 000000000000..3d5ca5a0cff6 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_irq.c @@ -0,0 +1,731 @@ +/* + * 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 "include/dal_interface.h" + +#include "amdgpu.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_irq.h" +#include "include/dal_interface.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 dal_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 dal_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 dal_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); +} + +/****************************************************************************** + * Public functions. + * + * Note: caller is responsible for input validation. + *****************************************************************************/ + +void *amdgpu_dm_irq_register_interrupt( + struct amdgpu_device *adev, + struct dal_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 dal_irq_source irq_source; + + 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 dal_irq_source irq_source, + void *ih) +{ + struct list_head *handler_list; + struct dal_interrupt_params int_params; + int i; + + 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 dal_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 = DAL_IRQ_SOURCE_HPD1; src <= DAL_IRQ_SOURCE_HPD6; 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)) + dal_interrupt_set(adev->dm.dal, src, false); + + flush_work(&adev->dm.irq_handler_list_low_tab[src].work); + } + + 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 = DAL_IRQ_SOURCE_HPD1; src <= DAL_IRQ_SOURCE_HPD6; 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)) + dal_interrupt_set(adev->dm.dal, 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 dal_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 dal_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 dal_irq_source src = + dal_interrupt_to_irq_source( + adev->dm.dal, + entry->src_id, + entry->src_data); + + dal_interrupt_ack(adev->dm.dal, 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 dal_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned type) +{ + switch (type) { + case AMDGPU_HPD_1: + return DAL_IRQ_SOURCE_HPD1; + case AMDGPU_HPD_2: + return DAL_IRQ_SOURCE_HPD2; + case AMDGPU_HPD_3: + return DAL_IRQ_SOURCE_HPD3; + case AMDGPU_HPD_4: + return DAL_IRQ_SOURCE_HPD4; + case AMDGPU_HPD_5: + return DAL_IRQ_SOURCE_HPD5; + case AMDGPU_HPD_6: + return DAL_IRQ_SOURCE_HPD6; + default: + return DAL_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 dal_irq_source src = amdgpu_dm_hpd_to_dal_irq_source(type); + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + + dal_interrupt_set(adev->dm.dal, src, st); + return 0; +} + +static int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned type, + enum amdgpu_interrupt_state state) +{ + enum dal_irq_source src = dal_get_pflip_irq_src_from_display_index( + adev->dm.dal, + type, /* this is the display_index because passed + * via work->crtc_id*/ + 0 /* plane_no */); + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + + dal_interrupt_set(adev->dm.dal, src, st); + return 0; +} + +static int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned type, + enum amdgpu_interrupt_state state) +{ + enum dal_irq_source src = dal_get_vblank_irq_src_from_display_index( + adev->dm.dal, + type); + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + + dal_interrupt_set(adev->dm.dal, src, st); + return 0; +} + +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 dal_irq_source src = + amdgpu_dm_hpd_to_dal_irq_source( + amdgpu_connector->hpd.hpd); + + 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; + } + + dal_interrupt_set(adev->dm.dal, src, true); + amdgpu_irq_get(adev, &adev->hpd_irq, amdgpu_connector->hpd.hpd); + } +} + +/** + * 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 dal_irq_source src = + amdgpu_dm_hpd_to_dal_irq_source( + amdgpu_connector->hpd.hpd); + + dal_interrupt_set(adev->dm.dal, 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 000000000000..1f3a956e0aec --- /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 "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 dal_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 dal_irq_source irq_source, + void *ih_index); + +void amdgpu_dm_irq_register_timer( + struct amdgpu_device *adev, + struct dal_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_types.c b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.c new file mode 100644 index 000000000000..c689fb8468f7 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.c @@ -0,0 +1,1575 @@ +/* + * 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 "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 "amdgpu_dm_types.h" + +#include "include/dal_interface.h" +#include "include/timing_service_types.h" +#include "include/set_mode_interface.h" +#include "include/mode_query_interface.h" +#include "include/dcs_types.h" +#include "include/mode_manager_interface.h" +#include "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" + +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 = { + .reset = NULL, + .destroy = amdgpu_dm_encoder_destroy, +}; + +static void init_dal_topology( + struct amdgpu_display_manager *dm, + struct topology *tp, + uint32_t current_display_index) +{ + DRM_DEBUG_KMS("current_display_index: %d\n", current_display_index); + + tp->display_index[0] = current_display_index; + tp->disp_path_num = 1; +} + + +static enum pixel_format convert_to_dal_pixel_format(uint32_t drm_pf) +{ + switch (drm_pf) { + case DRM_FORMAT_RGB888: + return PIXEL_FORMAT_INDEX8; + case DRM_FORMAT_RGB565: + return PIXEL_FORMAT_RGB565; + case DRM_FORMAT_ARGB8888: + return PIXEL_FORMAT_ARGB8888; + case DRM_FORMAT_ARGB2101010: + return PIXEL_FORMAT_ARGB2101010; + default: + return PIXEL_FORMAT_ARGB8888; + } +} + +static int dm_set_cursor(struct drm_crtc *crtc, struct drm_gem_object *obj, + uint32_t width, uint32_t height) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct amdgpu_bo *robj; + struct cursor_attributes attributes; + struct amdgpu_device *adev = crtc->dev->dev_private; + uint64_t gpu_addr; + int ret; + + if ((width > amdgpu_crtc->max_cursor_width) || + (height > amdgpu_crtc->max_cursor_height)) { + DRM_ERROR("bad cursor width or height %d x %d\n", width, height); + return -EINVAL; + } + + robj = gem_to_amdgpu_bo(obj); + ret = amdgpu_bo_reserve(robj, false); + if (unlikely(ret != 0)) + return ret; + ret = amdgpu_bo_pin_restricted(robj, AMDGPU_GEM_DOMAIN_VRAM, + 0, 0, &gpu_addr); + amdgpu_bo_unreserve(robj); + if (ret) + return ret; + + 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; + + dal_set_cursor_attributes(adev->dm.dal, amdgpu_crtc->crtc_id, &attributes); + + return 0; +} + + +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_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct drm_gem_object *obj; + struct amdgpu_bo *robj; + + struct amdgpu_device *adev = crtc->dev->dev_private; + int ret; + struct cursor_position position; + + if (!handle) { + /* turn off cursor */ + position.enable = false; + position.x = 0; + position.y = 0; + position.hot_spot_enable = false; + dal_set_cursor_position(adev->dm.dal, amdgpu_crtc->crtc_id, &position); + + obj = NULL; + goto unpin; + } + + 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); + return -ENOENT; + } + + ret = dm_set_cursor(crtc, obj, width, height); + + if (ret) { + drm_gem_object_unreference_unlocked(obj); + return ret; + } + +unpin: + if (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); + } + drm_gem_object_unreference_unlocked(amdgpu_crtc->cursor_bo); + } + + amdgpu_crtc->cursor_bo = obj; + return 0; + +} + +static int dm_crtc_cursor_move(struct drm_crtc *crtc, + int x, int y) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct amdgpu_device *adev = crtc->dev->dev_private; + int xorigin = 0, yorigin = 0; + struct cursor_position position; + + /* avivo cursor are offset into the total surface */ + x += crtc->x; + y += crtc->y; + DRM_DEBUG("x %d y %d c->x %d c->y %d\n", x, y, crtc->x, crtc->y); + + 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; + + dal_set_cursor_position(adev->dm.dal, amdgpu_crtc->crtc_id, &position); + + return 0; +} + +static int dm_crtc_cursor_reset(struct drm_crtc *crtc) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + int ret = 0; + + if (amdgpu_crtc->cursor_bo) { + ret = dm_set_cursor(crtc, amdgpu_crtc->cursor_bo, + amdgpu_crtc->cursor_width, amdgpu_crtc->cursor_height); + } + + return ret; +} + +static void fill_plane_attributes( + struct amdgpu_device* adev, + struct plane_config *pl_config, + struct drm_crtc *crtc) { + + uint64_t tiling_flags; + struct drm_gem_object *obj; + struct amdgpu_bo *rbo; + int r; + struct amdgpu_framebuffer *amdgpu_fb; + + amdgpu_fb = to_amdgpu_framebuffer(crtc->primary->fb); + obj = amdgpu_fb->obj; + rbo = gem_to_amdgpu_bo(obj); + r = amdgpu_bo_reserve(rbo, false); + if (unlikely(r != 0)){ + DRM_ERROR("Unable to reserve buffer\n"); + return; + } + + amdgpu_bo_get_tiling_flags(rbo, &tiling_flags); + amdgpu_bo_unreserve(rbo); + + switch (amdgpu_fb->base.pixel_format) { + case DRM_FORMAT_C8: + pl_config->config.format = + SURFACE_PIXEL_FORMAT_GRPH_PALETA_256_COLORS; + break; + case DRM_FORMAT_RGB565: + pl_config->config.format = + SURFACE_PIXEL_FORMAT_GRPH_RGB565; + break; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + pl_config->config.format = + SURFACE_PIXEL_FORMAT_GRPH_ARGB8888; + break; + default: + DRM_ERROR("Unsupported screen depth %d\n", + amdgpu_fb->base.bits_per_pixel); + return; + } + + pl_config->config.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 */ + pl_config->config.tiling_info.grph.NUM_BANKS = num_banks; + pl_config->config.tiling_info.grph.ARRAY_MODE = ARRAY_2D_TILED_THIN1; + pl_config->config.tiling_info.grph.TILE_SPLIT = tile_split; + pl_config->config.tiling_info.grph.BANK_WIDTH = bankw; + pl_config->config.tiling_info.grph.BANK_HEIGHT = bankh; + pl_config->config.tiling_info.grph.TILE_ASPECT = mtaspect; + pl_config->config.tiling_info.grph.TILE_MODE = ADDR_SURF_MICRO_TILING_DISPLAY; + } else if (AMDGPU_TILING_GET(tiling_flags, ARRAY_MODE) == ARRAY_1D_TILED_THIN1) { + pl_config->config.tiling_info.grph.ARRAY_MODE = ARRAY_1D_TILED_THIN1; + } + + pl_config->config.tiling_info.grph.PIPE_CONFIG = + AMDGPU_TILING_GET(tiling_flags, PIPE_CONFIG); + + pl_config->config.plane_size.grph.surface_size.x = 0; + pl_config->config.plane_size.grph.surface_size.y = 0; + pl_config->config.plane_size.grph.surface_size.width = amdgpu_fb->base.width; + pl_config->config.plane_size.grph.surface_size.height = amdgpu_fb->base.height; + pl_config->config.plane_size.grph.surface_pitch = + amdgpu_fb->base.pitches[0] / (amdgpu_fb->base.bits_per_pixel / 8); + + /* TODO ACHTUNG ACHTUNG - NICHT SCHIESSEN + * Correctly program src, dst, and clip */ + pl_config->attributes.dst_rect.width = crtc->mode.hdisplay; + pl_config->attributes.dst_rect.height = crtc->mode.vdisplay; + pl_config->attributes.dst_rect.x = 0; + pl_config->attributes.dst_rect.y = 0; + pl_config->attributes.clip_rect = pl_config->attributes.dst_rect; + pl_config->attributes.src_rect = pl_config->attributes.dst_rect; + + pl_config->mp_scaling_data.viewport.x = crtc->x; + pl_config->mp_scaling_data.viewport.y = crtc->y; + pl_config->mp_scaling_data.viewport.width = crtc->mode.hdisplay; + pl_config->mp_scaling_data.viewport.height = crtc->mode.vdisplay; + + pl_config->config.rotation = ROTATION_ANGLE_0; + pl_config->config.layer_index = LAYER_INDEX_PRIMARY; + pl_config->config.enabled = 1; + pl_config->mask.bits.SURFACE_CONFIG_IS_VALID = 1; + +} + +/* this function will be called in the future from other files as well */ +void amdgpu_dm_fill_surface_address(struct drm_crtc *crtc, + struct plane_addr_flip_info *info, + struct amdgpu_framebuffer *afb, + struct drm_framebuffer *old_fb) +{ + struct drm_gem_object *obj; + struct amdgpu_bo *rbo; + uint64_t fb_location; + struct amdgpu_device *adev = crtc->dev->dev_private; + int r; + + info->address_info.address.type = PLN_ADDR_TYPE_GRAPHICS; + //DD-ToDo + // info->flip_immediate = amdgpu_pflip_vsync ? false : true; + + info->address_info.layer_index = LAYER_INDEX_PRIMARY; + info->address_info.flags.bits.ENABLE = 1; + + /*Get fb location*/ + /* no fb bound */ + if (!crtc->primary->fb) { + DRM_DEBUG_KMS("No FB bound\n"); + return ; + } + + 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 = amdgpu_bo_pin(rbo, AMDGPU_GEM_DOMAIN_VRAM, &fb_location); + + amdgpu_bo_unreserve(rbo); + + if (unlikely(r != 0)) { + DRM_ERROR("Failed to pin framebuffer\n"); + return ; + } + + info->address_info.address.grph.addr.low_part = + lower_32_bits(fb_location); + info->address_info.address.grph.addr.high_part = + upper_32_bits(fb_location); + + dal_update_plane_addresses(adev->dm.dal, 1, info); + + /* unpin the old FB if surface change*/ + if (old_fb && old_fb != crtc->primary->fb) { + struct amdgpu_framebuffer *afb; + /*struct amdgpu_bo *rbo; + int r;*/ + + afb = to_amdgpu_framebuffer(old_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); + } + } +} + + +/** + * drm_crtc_set_mode - set a mode + * @crtc: CRTC to program + * @mode: mode to use + * @x: width of mode + * @y: height of mode + * + * LOCKING: + * Caller must hold mode config lock. + * + * Try to set @mode on @crtc. Give @crtc and its associated connectors a chance + * to fixup or reject the mode prior to trying to set it. + * + * RETURNS: + * True if the mode was set successfully, or false otherwise. + */ +bool amdgpu_dm_mode_set( + struct drm_crtc *crtc, + struct drm_display_mode *mode, + int x, + int y, + struct drm_framebuffer *old_fb) +{ + struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc); + struct amdgpu_display_manager *dm = + &((struct amdgpu_device *)crtc->dev->dev_private)->dm; + struct drm_device *dev = crtc->dev; + struct drm_display_mode *adjusted_mode, saved_mode, saved_hwmode; + int saved_x, saved_y; + bool ret = true; + struct amdgpu_device *adev = crtc->dev->dev_private; + struct plane_config pl_config = { { { 0 } } }; + struct plane_addr_flip_info addr_flip_info = { 0 }; + struct amdgpu_framebuffer *afb = NULL; + int num_planes = 1; + struct mode_query *mq; + const struct path_mode_set *pms; + + DRM_DEBUG_KMS("amdgpu_dm_mode_set called\n"); + + if (!adev->dm.dal) + return false; + + adjusted_mode = drm_mode_duplicate(dev, mode); + if (!adjusted_mode) + return false; + + /* for now, no support for atomic mode_set, thus old_fb is not used */ + afb = to_amdgpu_framebuffer(crtc->primary->fb); + + saved_hwmode = crtc->hwmode; + saved_mode = crtc->mode; + saved_x = crtc->x; + saved_y = crtc->y; + + /* Update crtc values up front so the driver can rely on them for mode + * setting. + */ + crtc->mode = *mode; + crtc->x = x; + crtc->y = y; + + DRM_DEBUG_KMS("[CRTC: %d, DISPLAY_IDX: %d]\n", + crtc->base.id, acrtc->crtc_id); + + /* Currently no support for atomic mode set */ + { + struct render_mode rm; + struct refresh_rate rf = { 0 }; + struct topology tp; + init_dal_topology(dm, &tp, acrtc->crtc_id); + rm.view.width = mode->hdisplay; + rm.view.height = mode->vdisplay; + rm.pixel_format = + convert_to_dal_pixel_format(crtc->primary->fb->pixel_format); + rf.field_rate = drm_mode_vrefresh(mode); + rf.VIDEO_OPTIMIZED_RATE = 0; + rf.INTERLACED = (mode->flags & DRM_MODE_FLAG_INTERLACE) != 0; + + mq = dal_get_mode_query( + adev->dm.dal, + &tp, + dm->mode_query_option); + if (!mq) + return false; + + if (!dal_mode_query_select_render_mode(mq, &rm)) { + dal_mode_query_destroy(&mq); + DRM_ERROR("dal_mode_query_select_render_mode failed\n"); + return -1; + } + + if (!dal_mode_query_select_refresh_rate(mq, &rf)) { + dal_mode_query_destroy(&mq); + DRM_ERROR("dal_mode_query_select_refresh_rate failed\n"); + return false; + } + pms = dal_mode_query_get_current_path_mode_set(mq); + + if (!pms) { + dal_mode_query_destroy(&mq); + DRM_ERROR("dal_mode_query_get_current_path_mode_set failed\n"); + return false; + } + } + + + dal_set_blanking(adev->dm.dal, acrtc->crtc_id, true); + /* the actual mode set call */ + ret = dal_set_path_mode(adev->dm.dal, pms); + + dal_mode_query_destroy(&mq); + + /* Surface programming */ + pl_config.display_index = acrtc->crtc_id; + addr_flip_info.display_index = acrtc->crtc_id; + + fill_plane_attributes(adev, &pl_config, crtc); + + dal_setup_plane_configurations(adev->dm.dal, num_planes, &pl_config); + + /*programs the surface addr and flip control*/ + amdgpu_dm_fill_surface_address(crtc, &addr_flip_info, afb, old_fb); + + dal_set_blanking(adev->dm.dal, acrtc->crtc_id, false); + /* Turn vblank on after reset */ + drm_crtc_vblank_on(crtc); + + if (ret) { + /* Store real post-adjustment hardware mode. */ + crtc->hwmode = *adjusted_mode; + crtc->enabled = true; + + /* Calculate and store various constants which + * are later needed by vblank and swap-completion + * timestamping. They are derived from true hwmode. + */ + drm_calc_timestamping_constants(crtc, &crtc->hwmode); + } + + drm_mode_destroy(dev, adjusted_mode); + if (!ret) { + crtc->hwmode = saved_hwmode; + crtc->mode = saved_mode; + crtc->x = saved_x; + crtc->y = saved_y; + } + + return true; +} + +bool amdgpu_dm_mode_reset(struct drm_crtc *crtc) +{ + bool ret; + struct amdgpu_device *adev = crtc->dev->dev_private; + struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc); + uint32_t display_index = acrtc->crtc_id; + + /* Turn vblank off before reset */ + drm_crtc_vblank_off(crtc); + + /* When we will get called from drm asking to reset mode + * when fb is null, it will lead us reset mode unnecessarily. + * So change the sequence, we won't do the actual reset mode call + * when display is connected, as that's harmful for glitchless mode + * change (when we only reprogram pipe front end). */ + if ((dal_get_connected_targets_vector(adev->dm.dal) + & (1 << display_index)) && + adev->dm.fake_display_index == INVALID_DISPLAY_INDEX) { + + /* + * Blank the display, as buffer will be invalidated. + * For the else case it would be done as part of dal reset mode + * sequence. + */ + mutex_lock(&adev->dm.dal_mutex); + dal_set_blanking(adev->dm.dal, acrtc->crtc_id, true); + mutex_unlock(&adev->dm.dal_mutex); + + ret = true; + DRM_DEBUG_KMS( + "Skip reset mode for disp_index %d\n", + display_index); + } else { + mutex_lock(&adev->dm.dal_mutex); + ret = dal_reset_path_mode(adev->dm.dal, 1, &display_index); + mutex_unlock(&adev->dm.dal_mutex); + DRM_DEBUG_KMS( + "Do reset mode for disp_index %d\n", + display_index); + } + + /* unpin the FB */ + if (crtc->primary->fb) { + struct amdgpu_framebuffer *afb; + struct amdgpu_bo *rbo; + int r; + + afb = to_amdgpu_framebuffer(crtc->primary->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"); + else { + amdgpu_bo_unpin(rbo); + amdgpu_bo_unreserve(rbo); + } + } + + if (ret) + crtc->enabled = false; + + return ret; +} + +/** + * amdgpu_dm_set_config - set a new config from userspace + * @crtc: CRTC to setup + * @crtc_info: user provided configuration + * @new_mode: new mode to set + * @connector_set: set of connectors for the new config + * @fb: new framebuffer + * + * LOCKING: + * Caller must hold mode config lock. + * + * Setup a new configuration, provided by the user in @crtc_info, and enable + * it. + * + * RETURNS: + * Zero on success + */ +int amdgpu_dm_set_config(struct drm_mode_set *set) +{ + /* TODO: + * Save + restore mode + fb info + * Call dm_set_mode to do the folloring: + * Fill Modes + * Set Mode + * SetPlaneConfig + * UpdatePlaneAddress + * FillPlaneAttributes + */ + + struct drm_device *dev; + struct amdgpu_device *adev; + struct drm_crtc *save_crtcs, *crtc; + struct drm_encoder *save_encoders, *encoder, *new_encoder; + bool mode_changed = false; /* if true do a full mode set */ + bool fb_changed = false; /* if true and !mode_changed just do a flip */ + /* if true and mode_changed do reset_mode */ + struct drm_connector *save_connectors, *connector; + const struct drm_connector_helper_funcs *connector_funcs; + struct amdgpu_crtc *acrtc = NULL; + int count = 0, fail = 0; + struct drm_mode_set save_set; + int ret = 0; + int i; + + DRM_DEBUG_KMS("\n"); + DRM_DEBUG_KMS("--- DM set_config called ---\n"); + + BUG_ON(!set); + BUG_ON(!set->crtc); + BUG_ON(!set->crtc->helper_private); + + /* Enforce sane interface api - has been abused by the fb helper. */ + BUG_ON(!set->mode && set->fb); + BUG_ON(set->fb && set->num_connectors == 0); + + if (set->num_connectors > 1) { + DRM_ERROR("Trying to set %zu connectors, but code only assumes max of one\n", + set->num_connectors); + return -EINVAL; + } + + dev = set->crtc->dev; + adev = dev->dev_private; + + if (!set->mode) + set->fb = NULL; + + /* Allocate space for the backup of all (non-pointer) crtc, encoder and + * connector data. */ + save_crtcs = kzalloc(dev->mode_config.num_crtc * + sizeof(struct drm_crtc), GFP_KERNEL); + if (!save_crtcs) + return -ENOMEM; + + save_encoders = kzalloc(dev->mode_config.num_encoder * + sizeof(struct drm_encoder), GFP_KERNEL); + if (!save_encoders) { + kfree(save_crtcs); + return -ENOMEM; + } + + save_connectors = kzalloc(dev->mode_config.num_connector * + sizeof(struct drm_connector), GFP_KERNEL); + if (!save_connectors) { + kfree(save_encoders); + kfree(save_crtcs); + return -ENOMEM; + } + + /* Copy data. Note that driver private data is not affected. + * Should anything bad happen only the expected state is + * restored, not the drivers personal bookkeeping. + */ + count = 0; + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + save_crtcs[count++] = *crtc; + } + + count = 0; + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + save_encoders[count++] = *encoder; + } + + count = 0; + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + save_connectors[count++] = *connector; + } + + + if (set->fb) { + DRM_DEBUG_KMS( + "[CRTC:%d] [FB:%d] #connectors=%d (x y) (%i %i)\n", + set->crtc->base.id, set->fb->base.id, + (int)set->num_connectors, set->x, set->y); + } else { + /*TODO: Move mode reset to crtc->disable instead, and + call drm_helper_disable_unused_functions here?*/ + + DRM_DEBUG_KMS("[CRTC:%d] [NOFB]\n", set->crtc->base.id); + + DRM_DEBUG_KMS("Setting connector DPMS state to off\n"); + for (i = 0; i < set->num_connectors; i++) { + DRM_DEBUG_KMS( + "\t[CONNECTOR:%d] set DPMS off\n", + set->connectors[i]->base.id); + + set->connectors[i]->funcs->dpms( + set->connectors[i], DRM_MODE_DPMS_OFF); + + } + + if (!amdgpu_dm_mode_reset(set->crtc)) { + DRM_ERROR("### Failed to reset mode on [CRTC:%d] ###\n", + set->crtc->base.id); + ret = -EINVAL; + goto fail; + } + DRM_DEBUG_KMS("=== Early exit dm_set_config ===\n"); + return 0; + } + + save_set.crtc = set->crtc; + save_set.mode = &set->crtc->mode; + save_set.x = set->crtc->x; + save_set.y = set->crtc->y; + save_set.fb = set->crtc->primary->fb; + + /* We should be able to check here if the fb has the same properties + * and then just flip_or_move it */ + if (set->crtc->primary->fb != set->fb) { + DRM_DEBUG_KMS("Old FB: %p, New FB[%d]: %p", + set->crtc->primary->fb, + set->fb->base.id, + set->fb); + /* If we have no fb then treat it as a full mode set */ + if (set->crtc->primary->fb == NULL) { + DRM_DEBUG_KMS("crtc has no fb, full mode set\n"); + mode_changed = true; + } else if (set->fb == NULL) { + mode_changed = true; + } else if (set->fb->depth != set->crtc->primary->fb->depth) { + mode_changed = true; + } else if (set->fb->bits_per_pixel != + set->crtc->primary->fb->bits_per_pixel) { + mode_changed = true; + } else { + fb_changed = true; + DRM_DEBUG_KMS("fbs do not match, set fb_changed to true\n"); + } + } else { + DRM_DEBUG_KMS("FB hasn't changed since last set\n"); + } + + if (set->x != set->crtc->x || set->y != set->crtc->y) { + fb_changed = true; + DRM_DEBUG_KMS("Viewport Changed. Original: (%d,%d), New: (%d,%d)\n", + set->x, + set->y, + set->crtc->x, + set->crtc->y); + } + + if (set->mode && !drm_mode_equal(set->mode, &set->crtc->mode)) { + DRM_DEBUG_KMS("modes are different, set mode_changed=true\n"); + drm_mode_debug_printmodeline(&set->crtc->mode); + drm_mode_debug_printmodeline(set->mode); + mode_changed = true; + } + + /* traverse and find the appropriate connector, + use its encoder and crtc */ + count = 0; + fail = 1; + acrtc = to_amdgpu_crtc(set->crtc); + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + /* matching the display index */ + if (to_amdgpu_connector(connector)->connector_id == + acrtc->crtc_id) { + fail = 0; + break; + } + } + + if (fail) { + ret = -EINVAL; + DRM_ERROR("Couldn't find a matching connector\n"); + goto fail; + } + + /* Get best encoder for connector found above + * TODO: Might need to traverse entire connector list at some point + */ + connector_funcs = connector->helper_private; + new_encoder = connector->encoder; + + /* NOTE: We're assuming a max of one connector per set, so no need to + * loop through connectors in set to find the correct one */ + if (set->connectors[0] == connector) { + new_encoder = connector_funcs->best_encoder(connector); + /* if we can't get an encoder for a connector + we are setting now - then fail */ + if (new_encoder == NULL) + /* don't break so fail path works correct */ + fail = 1; + if (connector->dpms != DRM_MODE_DPMS_ON) { + DRM_DEBUG_KMS("connector dpms not on, full mode switch\n"); + mode_changed = true; + } + } + + if (new_encoder != connector->encoder) { + DRM_DEBUG_KMS("encoder changed, full mode switch\n"); + mode_changed = true; + /* If the encoder is reused for another connector, then + * the appropriate crtc will be set later. + */ + if (connector->encoder) + connector->encoder->crtc = NULL; + connector->encoder = new_encoder; + } + + + if (fail) { + ret = -EINVAL; + DRM_ERROR("Couldn't find an encoder\n"); + goto fail; + } + + if (connector->encoder->crtc != set->crtc) { + mode_changed = true; + connector->encoder->crtc = set->crtc; + + DRM_DEBUG_KMS("New CRTC being used. Full mode set: [CONNECTOR:%d] to [CRTC:%d]\n", + connector->base.id, + set->crtc->base.id); + } else { + DRM_DEBUG_KMS("Crtc didn't change: [CONNECTOR:%d] on [CRTC:%d]\n", + connector->base.id, + set->crtc->base.id); + } + + if (mode_changed) { + struct amdgpu_device *adev; + + DRM_DEBUG_KMS("Attempting to set mode from userspace. Mode:\n"); + + drm_mode_debug_printmodeline(set->mode); + + set->crtc->primary->fb = set->fb; + + adev = set->crtc->dev->dev_private; + + mutex_lock(&adev->dm.dal_mutex); + if (!amdgpu_dm_mode_set( + set->crtc, + set->mode, + set->x, + set->y, + save_set.fb)) { + DRM_ERROR( + "failed to set mode on [CRTC:%d, DISPLAY_IDX: %d]\n", + set->crtc->base.id, + acrtc->crtc_id); + set->crtc->primary->fb = save_set.fb; + ret = -EINVAL; + mutex_unlock(&adev->dm.dal_mutex); + goto fail; + } + + mutex_unlock(&adev->dm.dal_mutex); + + DRM_DEBUG_KMS("Setting connector DPMS state to on\n"); + for (i = 0; i < set->num_connectors; i++) { + DRM_DEBUG_KMS( + "\t[CONNECTOR:%d] set DPMS on\n", + set->connectors[i]->base.id); + + set->connectors[i]->funcs->dpms( + set->connectors[i], DRM_MODE_DPMS_ON); + + } + + /* Re-set the cursor attributes after a successful set mode */ + dm_crtc_cursor_reset(set->crtc); + + } else if (fb_changed) { /* no mode change just surface change. */ + struct plane_addr_flip_info addr_flip_info = { 0 }; + struct amdgpu_framebuffer *afb = to_amdgpu_framebuffer(set->fb); + struct amdgpu_device *adev = dev->dev_private; + struct plane_config pl_config = { { { 0 } } }; + + DRM_DEBUG_KMS("FB Changed, update address\n"); + set->crtc->primary->fb = set->fb; + set->crtc->x = set->x; + set->crtc->y = set->y; + + /* program plane config */ + pl_config.display_index = acrtc->crtc_id; + + /* Blank display before programming surface */ + dal_set_blanking(adev->dm.dal, acrtc->crtc_id, true); + fill_plane_attributes(adev, &pl_config, set->crtc); + dal_setup_plane_configurations(adev->dm.dal, 1, &pl_config); + /* Program the surface addr and flip control */ + addr_flip_info.display_index = acrtc->crtc_id; + amdgpu_dm_fill_surface_address(set->crtc, &addr_flip_info, + afb, save_set.fb); + dal_set_blanking(adev->dm.dal, acrtc->crtc_id, false); + } + DRM_DEBUG_KMS("=== Finished dm_set_config ===\n"); + + kfree(save_connectors); + kfree(save_encoders); + kfree(save_crtcs); + + /* adjust pm to dpms */ + amdgpu_pm_compute_clocks(adev); + + return 0; + +fail: + /* Restore all previous data. */ + DRM_ERROR("### Failed set_config. Attempting to restore previous data ###\n"); + count = 0; + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + *crtc = save_crtcs[count++]; + } + + count = 0; + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + *encoder = save_encoders[count++]; + } + + count = 0; + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + *connector = save_connectors[count++]; + } + + /* Try to restore the config */ + if (mode_changed && save_set.crtc->primary->fb && + !amdgpu_dm_mode_set( + save_set.crtc, + save_set.mode, + save_set.x, + save_set.y, + save_set.fb)) + DRM_ERROR("failed to restore config after modeset failure\n"); + + kfree(save_connectors); + kfree(save_encoders); + kfree(save_crtcs); + + DRM_DEBUG_KMS("=== Finished dm_set_config ===\n"); + return ret; +} + +void amdgpu_dm_crtc_destroy(struct drm_crtc *crtc) +{ + struct amdgpu_crtc *dm_crtc = to_amdgpu_crtc(crtc); + + drm_crtc_cleanup(crtc); + destroy_workqueue(dm_crtc->pflip_queue); + kfree(crtc); +} + +static void amdgpu_dm_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green, + u16 *blue, uint32_t start, uint32_t size) +{ + struct amdgpu_device *adev = crtc->dev->dev_private; + struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc); + uint32_t display_index = acrtc->crtc_id; + int end = (start + size > 256) ? 256 : start + size; + int i; + struct raw_gamma_ramp *gamma; + + gamma = kzalloc(sizeof(struct raw_gamma_ramp), GFP_KERNEL); + + for (i = start; i < end; i++) { + gamma->rgb_256[i].red = red[i]; + gamma->rgb_256[i].green = green[i]; + gamma->rgb_256[i].blue = blue[i]; + } + + gamma->size = sizeof(gamma->rgb_256); + gamma->type = GAMMA_RAMP_TYPE_RGB256; + + dal_set_gamma(adev->dm.dal, display_index, gamma); + kfree(gamma); +} + +/* Implemented only the options currently availible for the driver */ +static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = { +/* .save = NULL, + .restore = NULL, + .reset = NULL,*/ + .cursor_set = dm_crtc_cursor_set, + .cursor_move = dm_crtc_cursor_move, + .destroy = amdgpu_dm_crtc_destroy, + .gamma_set = amdgpu_dm_crtc_gamma_set, + .set_config = amdgpu_dm_set_config, + .page_flip = amdgpu_crtc_page_flip /* this function is common for + all implementations of DCE code (original and + DAL) */ + + /*.set_property = NULL*/ +}; + +static inline void fill_drm_mode_info( + struct drm_display_mode *drm_mode, + const struct mode_timing *mode_timing, + const struct render_mode *rm, + const struct refresh_rate *rr) +{ + drm_mode->hsync_start = mode_timing->mode_info.pixel_width + + mode_timing->crtc_timing.h_front_porch; + drm_mode->hsync_end = mode_timing->mode_info.pixel_width + + mode_timing->crtc_timing.h_front_porch + + mode_timing->crtc_timing.h_sync_width; + drm_mode->htotal = mode_timing->crtc_timing.h_total; + drm_mode->hdisplay = rm->view.width; + drm_mode->vsync_start = mode_timing->mode_info.pixel_height + + mode_timing->crtc_timing.v_front_porch; + drm_mode->vsync_end = mode_timing->mode_info.pixel_height + + mode_timing->crtc_timing.v_front_porch + + mode_timing->crtc_timing.v_sync_width; + drm_mode->vtotal = mode_timing->crtc_timing.v_total; + drm_mode->vdisplay = rm->view.height; + + drm_mode->clock = mode_timing->crtc_timing.pix_clk_khz; + drm_mode->vrefresh = rr->field_rate; + if (mode_timing->crtc_timing.flags.HSYNC_POSITIVE_POLARITY) + drm_mode->flags |= DRM_MODE_FLAG_PHSYNC; + if (mode_timing->crtc_timing.flags.VSYNC_POSITIVE_POLARITY) + drm_mode->flags |= DRM_MODE_FLAG_PVSYNC; + if (mode_timing->crtc_timing.flags.INTERLACE) + drm_mode->flags |= DRM_MODE_FLAG_INTERLACE; + if (mode_timing->mode_info.flags.PREFERRED) + drm_mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(drm_mode); +} + +/** + * amdgpu_display_manager_add_mode - + * add mode for the connector + * @connector: drm connector + * @mode: the mode + * + * Add the specified modes to the connector's mode list. + * + * Return 0 on success. + */ +static int dm_add_mode( + struct drm_connector *connector, + const struct mode_timing *mt, + const struct render_mode *rm, + const struct refresh_rate *rr) +{ + struct drm_display_mode *drm_mode; + + if (!mt) + return -1; + + drm_mode = drm_mode_create(connector->dev); + + if (!drm_mode) + return -1; + + fill_drm_mode_info(drm_mode, mt, rm, rr); + + list_add(&drm_mode->head, &connector->modes); + + return 0; +} + +static void add_to_mq_helper(void *what, const struct path_mode *pm) +{ + dal_mode_query_pin_path_mode(what, pm); +} + +static enum drm_connector_status +amdgpu_dm_connector_detect(struct drm_connector *connector, bool force) +{ + struct amdgpu_connector *aconnector = + to_amdgpu_connector(connector); + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + bool connected; + uint32_t display_index = aconnector->connector_id; + + if (!adev->dm.dal) + return 0; + + connected = (dal_get_connected_targets_vector(adev->dm.dal) + & (1 << display_index)); + + return (connected ? connector_status_connected : + connector_status_disconnected); +} +static void amdgpu_dm_crtc_disable(struct drm_crtc *crtc) +{ + DRM_DEBUG_KMS("NOT IMPLEMENTED\n"); +} + +static void amdgpu_dm_encoder_disable(struct drm_encoder *encoder) +{ + DRM_DEBUG_KMS("NOT IMPLEMENTED\n"); +} + +/** + * amdgpu_display_manager_fill_modes - get complete set of + * display timing modes per drm_connector + * + * @connector: DRM device connector + * @maxX: max width for modes + * @maxY: max height for modes + * + * LOCKING: + * Caller must hold mode config lock. + * + * Query connector and try to detect modes on it. Received + * modes are assumed to be filtered and validated and supported + * by the connector assuming set_mode for that connector will + * come immediately after this function call. + * + * Therefore all these modes will be put into the normal modes + * list. + * + * Intended to be used either at bootup time or when major configuration + * changes have occurred. + * + * + * RETURNS: + * Number of modes found on @connector. + */ +int amdgpu_display_manager_fill_modes(struct drm_connector *connector, + uint32_t maxX, uint32_t maxY) +{ + struct amdgpu_connector *aconnector = + to_amdgpu_connector(connector); + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + struct drm_display_mode *mode, *t; + unsigned int non_filtered_modes_num = 0; + struct mode_query *mq; + struct topology tp; + + if (!adev->dm.dal) + return 0; + + DRM_DEBUG_KMS("[CONNECTOR:%d, DISPLAY_IDX: %d]\n", + connector->connector_type_id, aconnector->connector_id); + + /* clean all the previous modes on this connector */ + list_for_each_entry_safe(mode, t, &connector->modes, head) { + list_del(&mode->head); + drm_mode_debug_printmodeline(mode); + DRM_DEBUG_KMS("Not using %s mode %d\n", + mode->name, mode->status); + drm_mode_destroy(dev, mode); + } + + + if (connector->status == connector_status_disconnected) { + DRM_DEBUG_KMS("[CONNECTOR:%d] disconnected\n", + connector->connector_type_id); + drm_mode_connector_update_edid_property(connector, NULL); + goto prune; + } + + /* get the mode list from DAL, iterate over it and add + the modes to drm connector mode list */ + + /* the critical assumtion here is that the returned list is + clean: no duplicates, all the modes are valid, ordered, and + can be actually set on the hardware */ + + init_dal_topology(&adev->dm, &tp, aconnector->connector_id); + mq = dal_get_mode_query(adev->dm.dal, &tp, adev->dm.mode_query_option); + + if (!mq) + goto prune; + + dal_pin_active_path_modes( + adev->dm.dal, + mq, + aconnector->connector_id, + add_to_mq_helper); + + if (!dal_mode_query_select_first(mq)) + goto prune; + + do { + const struct render_mode *rm = + dal_mode_query_get_current_render_mode(mq); + + if (rm->pixel_format != PIXEL_FORMAT_ARGB8888) + continue; + + do { + const struct refresh_rate *rr = + dal_mode_query_get_current_refresh_rate(mq); + const struct path_mode_set *pms = + dal_mode_query_get_current_path_mode_set(mq); + const struct path_mode *pm = + dal_pms_get_path_mode_for_display_index( + pms, + aconnector->connector_id); + + const struct mode_timing *mt = pm->mode_timing; + + if (mt->mode_info.pixel_height > maxY || + mt->mode_info.pixel_width > maxX || + mt->mode_info.flags.INTERLACE) + continue; + + if (dm_add_mode(connector, mt, rm, rr) == 0) + ++non_filtered_modes_num; + + if (adev->dm.fake_display_index == + aconnector->connector_id) + break; + + } while (dal_mode_query_select_next_refresh_rate(mq)); + + if (adev->dm.fake_display_index == aconnector->connector_id) + break; + + } while (dal_mode_query_select_next_render_mode(mq)); + + dal_mode_query_destroy(&mq); + +prune: + DRM_DEBUG_KMS("[CONNECTOR:%d] probed modes :\n", + connector->connector_type_id); + + list_for_each_entry(mode, &connector->modes, head) { + drm_mode_set_crtcinfo(mode, 0); + drm_mode_debug_printmodeline(mode); + } + + return non_filtered_modes_num; +} + +static int amdgpu_dm_connector_set_property(struct drm_connector *connector, + struct drm_property *property, + uint64_t val) +{ + DRM_ERROR("NOT IMPLEMENTED\n"); + return 0; +} + +void amdgpu_dm_connector_destroy(struct drm_connector *connector) +{ + /*drm_sysfs_connector_remove(connector);*/ + drm_connector_cleanup(connector); + kfree(connector); +} + +static void amdgpu_dm_connector_force(struct drm_connector *connector) +{ + DRM_ERROR("NOT IMPLEMENTED\n"); +} + +static inline enum dal_power_state to_dal_power_state(int mode) +{ + switch (mode) { + case DRM_MODE_DPMS_ON: + return DAL_POWER_STATE_ON; + case DRM_MODE_DPMS_OFF: + return DAL_POWER_STATE_OFF; + case DRM_MODE_DPMS_STANDBY: + return DAL_POWER_STATE_STANDBY; + case DRM_MODE_DPMS_SUSPEND: + return DAL_POWER_STATE_SUSPEND; + default: + /* + * if unknown dpms mode passed for any reason display would be + * disabled, with log notification + */ + DRM_ERROR("Invalid DPMS mode requested\n"); + return DAL_POWER_STATE_OFF; + } +} + + +static void amdgpu_dm_connector_dpms(struct drm_connector *connector, int mode) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_connector *aconnector = to_amdgpu_connector(connector); + uint32_t display_index = aconnector->connector_id; + enum dal_power_state ps = to_dal_power_state(mode); + + if (mode == connector->dpms) + return; + + dal_set_display_dpms(adev->dm.dal, display_index, ps); + + connector->dpms = mode; + + /* adjust pm to dpms */ + amdgpu_pm_compute_clocks(adev); +} + +static const struct drm_connector_funcs amdgpu_dm_connector_funcs = { + .dpms = amdgpu_dm_connector_dpms, +/* .save = NULL, + .restore = NULL, + .reset = NULL,*/ + .detect = amdgpu_dm_connector_detect, + .fill_modes = amdgpu_display_manager_fill_modes, + .set_property = amdgpu_dm_connector_set_property, + .destroy = amdgpu_dm_connector_destroy, + .force = amdgpu_dm_connector_force +}; + +static void dm_crtc_helper_dpms(struct drm_crtc *crtc, int mode) +{ + + switch (mode) { + case DRM_MODE_DPMS_ON: + drm_crtc_vblank_on(crtc); + break; + case DRM_MODE_DPMS_OFF: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_STANDBY: + default: + drm_crtc_vblank_off(crtc); + break; + } + +} + +static const struct drm_crtc_helper_funcs amdgpu_dm_crtc_helper_funcs = { + .disable = amdgpu_dm_crtc_disable, + .dpms = dm_crtc_helper_dpms, + .load_lut = NULL +}; + +static const struct drm_encoder_helper_funcs dm_encoder_helper_funcs = { + .disable = amdgpu_dm_encoder_disable, +}; + + +int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm, + struct amdgpu_crtc *acrtc, + int display_idx) +{ + int res = drm_crtc_init( + dm->ddev, + &acrtc->base, + &amdgpu_dm_crtc_funcs); + + 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 = display_idx; + acrtc->base.enabled = false; + + dm->adev->mode_info.crtcs[display_idx] = acrtc; + drm_mode_crtc_set_gamma_size(&acrtc->base, 256); + + acrtc->pflip_queue = + create_singlethread_workqueue("amdgpu-pageflip-queue"); + + return 0; +fail: + acrtc->crtc_id = -1; + return res; +} + +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 const struct drm_connector_helper_funcs +amdgpu_dm_connector_helper_funcs = { + .best_encoder = best_encoder +}; + +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: + case SIGNAL_TYPE_DVI_SINGLE_LINK1: + return DRM_MODE_CONNECTOR_DVID; + + default: + return DRM_MODE_CONNECTOR_Unknown; + } +} + +int amdgpu_dm_connector_init( + struct amdgpu_display_manager *dm, + struct amdgpu_connector *aconnector, + int display_idx, + bool is_connected, + struct amdgpu_encoder *aencoder) +{ + int res, connector_type; + enum signal_type st = SIGNAL_TYPE_HDMI_TYPE_A; + + DRM_DEBUG_KMS("amdgpu_dm_connector_init\n"); + + if (dm->dal != NULL) + st = dal_get_display_signal(dm->dal, display_idx); + connector_type = to_drm_connector_type(st); + + 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 = display_idx; + aconnector->base.interlace_allowed = true; + aconnector->base.doublescan_allowed = true; + aconnector->hpd.hpd = display_idx; /* maps to 'enum amdgpu_hpd_id' */ + + if (is_connected) + aconnector->base.status = connector_status_connected; + else + aconnector->base.status = connector_status_disconnected; + + /*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; + } + + /* 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);*/ + + /* TODO: this switch should be updated during hotplug/unplug*/ + if (dm->dal != NULL && is_connected) { + DRM_DEBUG_KMS("Connector is connected\n"); + drm_mode_connector_update_edid_property( + &aconnector->base, + (struct edid *) + dal_get_display_edid(dm->dal, display_idx, NULL)); + } + + drm_connector_register(&aconnector->base); + + return 0; +} + +int amdgpu_dm_encoder_init( + struct drm_device *dev, + struct amdgpu_encoder *aencoder, + int display_idx, + struct amdgpu_crtc *acrtc) +{ + int res = drm_encoder_init(dev, + &aencoder->base, + &amdgpu_dm_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + + aencoder->base.possible_crtcs = 1 << display_idx; + aencoder->base.crtc = &acrtc->base; + + if (!res) + aencoder->encoder_id = display_idx; + + else + aencoder->encoder_id = -1; + + drm_encoder_helper_add(&aencoder->base, &dm_encoder_helper_funcs); + + return res; +} 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 000000000000..721d83435f93 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/amdgpu_dm/amdgpu_dm_types.h @@ -0,0 +1,69 @@ +/* + * 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; + +/*TODO Jodan Hersen use the one in amdgpu_dm*/ +int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm, + struct amdgpu_crtc *amdgpu_crtc, + int display_idx); +int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm, + struct amdgpu_connector *amdgpu_connector, + int display_idx, + bool is_connected, + struct amdgpu_encoder *amdgpu_encoder); +int amdgpu_dm_encoder_init(struct drm_device *dev, + struct amdgpu_encoder *amdgpu_encoder, + int display_idx, + struct amdgpu_crtc *amdgpu_crtc); + + +void amdgpu_dm_fill_surface_address(struct drm_crtc *crtc, + struct plane_addr_flip_info *info, + struct amdgpu_framebuffer *afb, + struct drm_framebuffer *old_fb); + +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); + +bool amdgpu_dm_mode_reset(struct drm_crtc *crtc); + +bool amdgpu_dm_mode_set( + struct drm_crtc *crtc, + struct drm_display_mode *mode, + int x, + int y, + struct drm_framebuffer *old_fb); + +#endif /* __AMDGPU_DM_TYPES_H__ */ |