| /* |
| * Copyright 2015 Advanced Micro Devices, Inc. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR |
| * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
| * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
| * OTHER DEALINGS IN THE SOFTWARE. |
| * |
| * Authors: AMD |
| * |
| */ |
| |
| #include "dm_services_types.h" |
| #include "dc.h" |
| |
| #include "vid.h" |
| #include "amdgpu.h" |
| #include "amdgpu_display.h" |
| #include "atom.h" |
| #include "amdgpu_dm.h" |
| #include "amdgpu_dm_types.h" |
| |
| #include "amd_shared.h" |
| #include "amdgpu_dm_irq.h" |
| #include "dm_helpers.h" |
| |
| #include "ivsrcid/ivsrcid_vislands30.h" |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/version.h> |
| |
| #include <drm/drm_atomic.h> |
| #include <drm/drm_atomic_helper.h> |
| #include <drm/drm_dp_mst_helper.h> |
| |
| #include "modules/inc/mod_freesync.h" |
| |
| /* |
| * dm_vblank_get_counter |
| * |
| * @brief |
| * Get counter for number of vertical blanks |
| * |
| * @param |
| * struct amdgpu_device *adev - [in] desired amdgpu device |
| * int disp_idx - [in] which CRTC to get the counter from |
| * |
| * @return |
| * Counter for vertical blanks |
| */ |
| static u32 dm_vblank_get_counter(struct amdgpu_device *adev, int crtc) |
| { |
| if (crtc >= adev->mode_info.num_crtc) |
| return 0; |
| else { |
| struct amdgpu_crtc *acrtc = adev->mode_info.crtcs[crtc]; |
| |
| if (NULL == acrtc->stream) { |
| DRM_ERROR("dc_stream is NULL for crtc '%d'!\n", crtc); |
| return 0; |
| } |
| |
| return dc_stream_get_vblank_counter(acrtc->stream); |
| } |
| } |
| |
| 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; |
| else { |
| struct amdgpu_crtc *acrtc = adev->mode_info.crtcs[crtc]; |
| |
| if (NULL == acrtc->stream) { |
| DRM_ERROR("dc_stream is NULL for crtc '%d'!\n", crtc); |
| return 0; |
| } |
| |
| return dc_stream_get_scanoutpos(acrtc->stream, vbl, position); |
| } |
| |
| return 0; |
| } |
| |
| static bool dm_is_idle(void *handle) |
| { |
| /* XXX todo */ |
| return true; |
| } |
| |
| static int dm_wait_for_idle(void *handle) |
| { |
| /* XXX todo */ |
| return 0; |
| } |
| |
| static bool dm_check_soft_reset(void *handle) |
| { |
| return false; |
| } |
| |
| static int dm_soft_reset(void *handle) |
| { |
| /* XXX todo */ |
| return 0; |
| } |
| |
| static struct amdgpu_crtc *get_crtc_by_otg_inst( |
| struct amdgpu_device *adev, |
| int otg_inst) |
| { |
| struct drm_device *dev = adev->ddev; |
| struct drm_crtc *crtc; |
| struct amdgpu_crtc *amdgpu_crtc; |
| |
| /* |
| * following if is check inherited from both functions where this one is |
| * used now. Need to be checked why it could happen. |
| */ |
| if (otg_inst == -1) { |
| WARN_ON(1); |
| return adev->mode_info.crtcs[0]; |
| } |
| |
| list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { |
| amdgpu_crtc = to_amdgpu_crtc(crtc); |
| |
| if (amdgpu_crtc->otg_inst == otg_inst) |
| return amdgpu_crtc; |
| } |
| |
| return NULL; |
| } |
| |
| static void dm_pflip_high_irq(void *interrupt_params) |
| { |
| struct amdgpu_crtc *amdgpu_crtc; |
| struct common_irq_params *irq_params = interrupt_params; |
| struct amdgpu_device *adev = irq_params->adev; |
| unsigned long flags; |
| |
| amdgpu_crtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_PFLIP); |
| |
| /* IRQ could occur when in initial stage */ |
| /*TODO work and BO cleanup */ |
| if (amdgpu_crtc == NULL) { |
| DRM_DEBUG_DRIVER("CRTC is null, returning.\n"); |
| return; |
| } |
| |
| spin_lock_irqsave(&adev->ddev->event_lock, flags); |
| |
| if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_SUBMITTED){ |
| DRM_DEBUG_DRIVER("amdgpu_crtc->pflip_status = %d !=AMDGPU_FLIP_SUBMITTED(%d) on crtc:%d[%p] \n", |
| amdgpu_crtc->pflip_status, |
| AMDGPU_FLIP_SUBMITTED, |
| amdgpu_crtc->crtc_id, |
| amdgpu_crtc); |
| spin_unlock_irqrestore(&adev->ddev->event_lock, flags); |
| return; |
| } |
| |
| |
| /* wakeup usersapce */ |
| if (amdgpu_crtc->event |
| && amdgpu_crtc->event->event.base.type |
| == DRM_EVENT_FLIP_COMPLETE) { |
| drm_crtc_send_vblank_event(&amdgpu_crtc->base, amdgpu_crtc->event); |
| /* page flip completed. clean up */ |
| amdgpu_crtc->event = NULL; |
| } else |
| WARN_ON(1); |
| |
| amdgpu_crtc->pflip_status = AMDGPU_FLIP_NONE; |
| spin_unlock_irqrestore(&adev->ddev->event_lock, flags); |
| |
| DRM_DEBUG_DRIVER("%s - crtc :%d[%p], pflip_stat:AMDGPU_FLIP_NONE\n", |
| __func__, amdgpu_crtc->crtc_id, amdgpu_crtc); |
| |
| drm_crtc_vblank_put(&amdgpu_crtc->base); |
| } |
| |
| static void dm_crtc_high_irq(void *interrupt_params) |
| { |
| struct common_irq_params *irq_params = interrupt_params; |
| struct amdgpu_device *adev = irq_params->adev; |
| uint8_t crtc_index = 0; |
| struct amdgpu_crtc *acrtc; |
| |
| acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK); |
| |
| if (acrtc) |
| crtc_index = acrtc->crtc_id; |
| |
| drm_handle_vblank(adev->ddev, crtc_index); |
| } |
| |
| static int dm_set_clockgating_state(void *handle, |
| enum amd_clockgating_state state) |
| { |
| return 0; |
| } |
| |
| static int dm_set_powergating_state(void *handle, |
| enum amd_powergating_state state) |
| { |
| return 0; |
| } |
| |
| /* Prototypes of private functions */ |
| static int dm_early_init(void* handle); |
| |
| static void hotplug_notify_work_func(struct work_struct *work) |
| { |
| struct amdgpu_display_manager *dm = container_of(work, struct amdgpu_display_manager, mst_hotplug_work); |
| struct drm_device *dev = dm->ddev; |
| |
| drm_kms_helper_hotplug_event(dev); |
| } |
| |
| /* Init display KMS |
| * |
| * Returns 0 on success |
| */ |
| int amdgpu_dm_init(struct amdgpu_device *adev) |
| { |
| struct dc_init_data init_data; |
| adev->dm.ddev = adev->ddev; |
| adev->dm.adev = adev; |
| |
| DRM_INFO("DAL is enabled\n"); |
| /* 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; |
| } |
| |
| init_data.asic_id.chip_family = adev->family; |
| |
| init_data.asic_id.pci_revision_id = adev->rev_id; |
| init_data.asic_id.hw_internal_rev = adev->external_rev_id; |
| |
| init_data.asic_id.vram_width = adev->mc.vram_width; |
| /* TODO: initialize init_data.asic_id.vram_type here!!!! */ |
| init_data.asic_id.atombios_base_address = |
| adev->mode_info.atom_context->bios; |
| |
| init_data.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; |
| |
| init_data.dce_environment = DCE_ENV_PRODUCTION_DRV; |
| |
| /* Display Core create. */ |
| adev->dm.dc = dc_create(&init_data); |
| |
| if (!adev->dm.dc) |
| DRM_INFO("Display Core failed to initialize!\n"); |
| |
| INIT_WORK(&adev->dm.mst_hotplug_work, hotplug_notify_work_func); |
| |
| adev->dm.freesync_module = mod_freesync_create(adev->dm.dc); |
| if (!adev->dm.freesync_module) { |
| DRM_ERROR( |
| "amdgpu: failed to initialize freesync_module.\n"); |
| } else |
| DRM_INFO("amdgpu: freesync_module init done %p.\n", |
| adev->dm.freesync_module); |
| |
| if (amdgpu_dm_initialize_drm_device(adev)) { |
| DRM_ERROR( |
| "amdgpu: failed to initialize sw for display support.\n"); |
| goto error; |
| } |
| |
| /* Update the actual used number of crtc */ |
| adev->mode_info.num_crtc = adev->dm.display_indexes_num; |
| |
| /* TODO: Add_display_info? */ |
| |
| /* TODO use dynamic cursor width */ |
| adev->ddev->mode_config.cursor_width = 128; |
| adev->ddev->mode_config.cursor_height = 128; |
| |
| if (drm_vblank_init(adev->ddev, adev->dm.display_indexes_num)) { |
| DRM_ERROR( |
| "amdgpu: failed to initialize sw for display support.\n"); |
| goto error; |
| } |
| |
| DRM_INFO("KMS initialized.\n"); |
| |
| return 0; |
| error: |
| amdgpu_dm_fini(adev); |
| |
| return -1; |
| } |
| |
| void amdgpu_dm_fini(struct amdgpu_device *adev) |
| { |
| amdgpu_dm_destroy_drm_device(&adev->dm); |
| /* |
| * TODO: pageflip, vlank interrupt |
| * |
| * amdgpu_dm_irq_fini(adev); |
| */ |
| |
| if (adev->dm.cgs_device) { |
| amdgpu_cgs_destroy_device(adev->dm.cgs_device); |
| adev->dm.cgs_device = NULL; |
| } |
| if (adev->dm.freesync_module) { |
| mod_freesync_destroy(adev->dm.freesync_module); |
| adev->dm.freesync_module = NULL; |
| } |
| /* DC Destroy TODO: Replace destroy DAL */ |
| { |
| dc_destroy(&adev->dm.dc); |
| } |
| return; |
| } |
| |
| /* moved from amdgpu_dm_kms.c */ |
| void amdgpu_dm_destroy() |
| { |
| } |
| |
| static int dm_sw_init(void *handle) |
| { |
| return 0; |
| } |
| |
| static int dm_sw_fini(void *handle) |
| { |
| return 0; |
| } |
| |
| static int detect_mst_link_for_all_connectors(struct drm_device *dev) |
| { |
| struct amdgpu_connector *aconnector; |
| struct drm_connector *connector; |
| int ret = 0; |
| |
| drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); |
| |
| list_for_each_entry(connector, &dev->mode_config.connector_list, head) { |
| aconnector = to_amdgpu_connector(connector); |
| if (aconnector->dc_link->type == dc_connection_mst_branch) { |
| DRM_INFO("DM_MST: starting TM on aconnector: %p [id: %d]\n", |
| aconnector, aconnector->base.base.id); |
| |
| ret = drm_dp_mst_topology_mgr_set_mst(&aconnector->mst_mgr, true); |
| if (ret < 0) { |
| DRM_ERROR("DM_MST: Failed to start MST\n"); |
| ((struct dc_link *)aconnector->dc_link)->type = dc_connection_single; |
| return ret; |
| } |
| } |
| } |
| |
| drm_modeset_unlock(&dev->mode_config.connection_mutex); |
| return ret; |
| } |
| |
| static int dm_late_init(void *handle) |
| { |
| struct drm_device *dev = ((struct amdgpu_device *)handle)->ddev; |
| int r = detect_mst_link_for_all_connectors(dev); |
| |
| return r; |
| } |
| |
| static void s3_handle_mst(struct drm_device *dev, bool suspend) |
| { |
| struct amdgpu_connector *aconnector; |
| struct drm_connector *connector; |
| |
| drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); |
| |
| list_for_each_entry(connector, &dev->mode_config.connector_list, head) { |
| aconnector = to_amdgpu_connector(connector); |
| if (aconnector->dc_link->type == dc_connection_mst_branch && |
| !aconnector->mst_port) { |
| |
| if (suspend) |
| drm_dp_mst_topology_mgr_suspend(&aconnector->mst_mgr); |
| else |
| drm_dp_mst_topology_mgr_resume(&aconnector->mst_mgr); |
| } |
| } |
| |
| drm_modeset_unlock(&dev->mode_config.connection_mutex); |
| } |
| |
| 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; |
| int ret = 0; |
| |
| s3_handle_mst(adev->ddev, true); |
| |
| amdgpu_dm_irq_suspend(adev); |
| |
| adev->dm.cached_state = drm_atomic_helper_suspend(adev->ddev); |
| |
| dc_set_power_state( |
| dm->dc, |
| DC_ACPI_CM_POWER_STATE_D3 |
| ); |
| |
| return ret; |
| } |
| |
| struct amdgpu_connector *amdgpu_dm_find_first_crct_matching_connector( |
| struct drm_atomic_state *state, |
| struct drm_crtc *crtc, |
| bool from_state_var) |
| { |
| uint32_t i; |
| struct drm_connector_state *conn_state; |
| struct drm_connector *connector; |
| struct drm_crtc *crtc_from_state; |
| |
| for_each_connector_in_state( |
| state, |
| connector, |
| conn_state, |
| i) { |
| crtc_from_state = |
| from_state_var ? |
| conn_state->crtc : |
| connector->state->crtc; |
| |
| if (crtc_from_state == crtc) |
| return to_amdgpu_connector(connector); |
| } |
| |
| return NULL; |
| } |
| |
| static int dm_resume(void *handle) |
| { |
| struct amdgpu_device *adev = handle; |
| struct amdgpu_display_manager *dm = &adev->dm; |
| |
| /* power on hardware */ |
| dc_set_power_state( |
| dm->dc, |
| DC_ACPI_CM_POWER_STATE_D0 |
| ); |
| |
| return 0; |
| } |
| |
| int amdgpu_dm_display_resume(struct amdgpu_device *adev ) |
| { |
| struct drm_device *ddev = adev->ddev; |
| struct amdgpu_display_manager *dm = &adev->dm; |
| struct amdgpu_connector *aconnector; |
| struct drm_connector *connector; |
| struct drm_crtc *crtc; |
| struct drm_crtc_state *crtc_state; |
| int ret = 0; |
| int i; |
| |
| /* program HPD filter */ |
| dc_resume(dm->dc); |
| |
| /* On resume we need to rewrite the MSTM control bits to enamble MST*/ |
| s3_handle_mst(ddev, false); |
| |
| /* |
| * early enable HPD Rx IRQ, should be done before set mode as short |
| * pulse interrupts are used for MST |
| */ |
| amdgpu_dm_irq_resume_early(adev); |
| |
| /* Do detection*/ |
| list_for_each_entry(connector, |
| &ddev->mode_config.connector_list, head) { |
| aconnector = to_amdgpu_connector(connector); |
| |
| /* |
| * this is the case when traversing through already created |
| * MST connectors, should be skipped |
| */ |
| if (aconnector->mst_port) |
| continue; |
| |
| dc_link_detect(aconnector->dc_link, false); |
| aconnector->dc_sink = NULL; |
| amdgpu_dm_update_connector_after_detect(aconnector); |
| } |
| |
| /* Force mode set in atomic comit */ |
| for_each_crtc_in_state(adev->dm.cached_state, crtc, crtc_state, i) |
| crtc_state->active_changed = true; |
| |
| ret = drm_atomic_helper_resume(ddev, adev->dm.cached_state); |
| |
| amdgpu_dm_irq_resume(adev); |
| |
| return ret; |
| } |
| |
| static const struct amd_ip_funcs amdgpu_dm_funcs = { |
| .name = "dm", |
| .early_init = dm_early_init, |
| .late_init = dm_late_init, |
| .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, |
| .check_soft_reset = dm_check_soft_reset, |
| .soft_reset = dm_soft_reset, |
| .set_clockgating_state = dm_set_clockgating_state, |
| .set_powergating_state = dm_set_powergating_state, |
| }; |
| |
| const struct amdgpu_ip_block_version dm_ip_block = |
| { |
| .type = AMD_IP_BLOCK_TYPE_DCE, |
| .major = 1, |
| .minor = 0, |
| .rev = 0, |
| .funcs = &amdgpu_dm_funcs, |
| }; |
| |
| /* TODO: it is temporary non-const, should fixed later */ |
| static struct drm_mode_config_funcs amdgpu_dm_mode_funcs = { |
| .fb_create = amdgpu_user_framebuffer_create, |
| .output_poll_changed = amdgpu_output_poll_changed, |
| .atomic_check = amdgpu_dm_atomic_check, |
| .atomic_commit = drm_atomic_helper_commit |
| }; |
| |
| static struct drm_mode_config_helper_funcs amdgpu_dm_mode_config_helperfuncs = { |
| .atomic_commit_tail = amdgpu_dm_atomic_commit_tail |
| }; |
| |
| void amdgpu_dm_update_connector_after_detect( |
| struct amdgpu_connector *aconnector) |
| { |
| struct drm_connector *connector = &aconnector->base; |
| struct drm_device *dev = connector->dev; |
| const struct dc_sink *sink; |
| |
| /* MST handled by drm_mst framework */ |
| if (aconnector->mst_mgr.mst_state == true) |
| return; |
| |
| |
| sink = aconnector->dc_link->local_sink; |
| |
| /* Edid mgmt connector gets first update only in mode_valid hook and then |
| * the connector sink is set to either fake or physical sink depends on link status. |
| * don't do it here if u are during boot |
| */ |
| if (aconnector->base.force != DRM_FORCE_UNSPECIFIED |
| && aconnector->dc_em_sink) { |
| |
| /* For S3 resume with headless use eml_sink to fake stream |
| * because on resume connecotr->sink is set ti NULL |
| */ |
| mutex_lock(&dev->mode_config.mutex); |
| |
| if (sink) { |
| if (aconnector->dc_sink) { |
| amdgpu_dm_remove_sink_from_freesync_module( |
| connector); |
| /* retain and release bellow are used for |
| * bump up refcount for sink because the link don't point |
| * to it anymore after disconnect so on next crtc to connector |
| * reshuffle by UMD we will get into unwanted dc_sink release |
| */ |
| if (aconnector->dc_sink != aconnector->dc_em_sink) |
| dc_sink_release(aconnector->dc_sink); |
| } |
| aconnector->dc_sink = sink; |
| amdgpu_dm_add_sink_to_freesync_module( |
| connector, aconnector->edid); |
| } else { |
| amdgpu_dm_remove_sink_from_freesync_module(connector); |
| if (!aconnector->dc_sink) |
| aconnector->dc_sink = aconnector->dc_em_sink; |
| else if (aconnector->dc_sink != aconnector->dc_em_sink) |
| dc_sink_retain(aconnector->dc_sink); |
| } |
| |
| mutex_unlock(&dev->mode_config.mutex); |
| return; |
| } |
| |
| /* |
| * TODO: temporary guard to look for proper fix |
| * if this sink is MST sink, we should not do anything |
| */ |
| if (sink && sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT_MST) |
| return; |
| |
| if (aconnector->dc_sink == sink) { |
| /* We got a DP short pulse (Link Loss, DP CTS, etc...). |
| * Do nothing!! */ |
| DRM_INFO("DCHPD: connector_id=%d: dc_sink didn't change.\n", |
| aconnector->connector_id); |
| return; |
| } |
| |
| DRM_INFO("DCHPD: connector_id=%d: Old sink=%p New sink=%p\n", |
| aconnector->connector_id, aconnector->dc_sink, sink); |
| |
| mutex_lock(&dev->mode_config.mutex); |
| |
| /* 1. Update status of the drm connector |
| * 2. Send an event and let userspace tell us what to do */ |
| if (sink) { |
| /* TODO: check if we still need the S3 mode update workaround. |
| * If yes, put it here. */ |
| if (aconnector->dc_sink) |
| amdgpu_dm_remove_sink_from_freesync_module( |
| connector); |
| |
| aconnector->dc_sink = sink; |
| if (sink->dc_edid.length == 0) |
| aconnector->edid = NULL; |
| else { |
| aconnector->edid = |
| (struct edid *) sink->dc_edid.raw_edid; |
| |
| |
| drm_mode_connector_update_edid_property(connector, |
| aconnector->edid); |
| } |
| amdgpu_dm_add_sink_to_freesync_module(connector, aconnector->edid); |
| |
| } else { |
| amdgpu_dm_remove_sink_from_freesync_module(connector); |
| drm_mode_connector_update_edid_property(connector, NULL); |
| aconnector->num_modes = 0; |
| aconnector->dc_sink = NULL; |
| } |
| |
| mutex_unlock(&dev->mode_config.mutex); |
| } |
| |
| static void handle_hpd_irq(void *param) |
| { |
| struct amdgpu_connector *aconnector = (struct amdgpu_connector *)param; |
| struct drm_connector *connector = &aconnector->base; |
| struct drm_device *dev = connector->dev; |
| |
| /* In case of failure or MST no need to update connector status or notify the OS |
| * since (for MST case) MST does this in it's own context. |
| */ |
| mutex_lock(&aconnector->hpd_lock); |
| if (dc_link_detect(aconnector->dc_link, false)) { |
| amdgpu_dm_update_connector_after_detect(aconnector); |
| |
| |
| drm_modeset_lock_all(dev); |
| dm_restore_drm_connector_state(dev, connector); |
| drm_modeset_unlock_all(dev); |
| |
| if (aconnector->base.force == DRM_FORCE_UNSPECIFIED) |
| drm_kms_helper_hotplug_event(dev); |
| } |
| mutex_unlock(&aconnector->hpd_lock); |
| |
| } |
| |
| static void dm_handle_hpd_rx_irq(struct amdgpu_connector *aconnector) |
| { |
| uint8_t esi[DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI] = { 0 }; |
| uint8_t dret; |
| bool new_irq_handled = false; |
| int dpcd_addr; |
| int dpcd_bytes_to_read; |
| |
| const int max_process_count = 30; |
| int process_count = 0; |
| |
| const struct dc_link_status *link_status = dc_link_get_status(aconnector->dc_link); |
| |
| if (link_status->dpcd_caps->dpcd_rev.raw < 0x12) { |
| dpcd_bytes_to_read = DP_LANE0_1_STATUS - DP_SINK_COUNT; |
| /* DPCD 0x200 - 0x201 for downstream IRQ */ |
| dpcd_addr = DP_SINK_COUNT; |
| } else { |
| dpcd_bytes_to_read = DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI; |
| /* DPCD 0x2002 - 0x2005 for downstream IRQ */ |
| dpcd_addr = DP_SINK_COUNT_ESI; |
| } |
| |
| dret = drm_dp_dpcd_read( |
| &aconnector->dm_dp_aux.aux, |
| dpcd_addr, |
| esi, |
| dpcd_bytes_to_read); |
| |
| while (dret == dpcd_bytes_to_read && |
| process_count < max_process_count) { |
| uint8_t retry; |
| dret = 0; |
| |
| process_count++; |
| |
| DRM_DEBUG_KMS("ESI %02x %02x %02x\n", esi[0], esi[1], esi[2]); |
| /* handle HPD short pulse irq */ |
| if (aconnector->mst_mgr.mst_state) |
| drm_dp_mst_hpd_irq( |
| &aconnector->mst_mgr, |
| esi, |
| &new_irq_handled); |
| |
| if (new_irq_handled) { |
| /* ACK at DPCD to notify down stream */ |
| const int ack_dpcd_bytes_to_write = |
| dpcd_bytes_to_read - 1; |
| |
| for (retry = 0; retry < 3; retry++) { |
| uint8_t wret; |
| |
| wret = drm_dp_dpcd_write( |
| &aconnector->dm_dp_aux.aux, |
| dpcd_addr + 1, |
| &esi[1], |
| ack_dpcd_bytes_to_write); |
| if (wret == ack_dpcd_bytes_to_write) |
| break; |
| } |
| |
| /* check if there is new irq to be handle */ |
| dret = drm_dp_dpcd_read( |
| &aconnector->dm_dp_aux.aux, |
| dpcd_addr, |
| esi, |
| dpcd_bytes_to_read); |
| |
| new_irq_handled = false; |
| } else |
| break; |
| } |
| |
| if (process_count == max_process_count) |
| DRM_DEBUG_KMS("Loop exceeded max iterations\n"); |
| } |
| |
| static void handle_hpd_rx_irq(void *param) |
| { |
| struct amdgpu_connector *aconnector = (struct amdgpu_connector *)param; |
| struct drm_connector *connector = &aconnector->base; |
| struct drm_device *dev = connector->dev; |
| const struct dc_link *dc_link = aconnector->dc_link; |
| bool is_mst_root_connector = aconnector->mst_mgr.mst_state; |
| |
| /* TODO:Temporary add mutex to protect hpd interrupt not have a gpio |
| * conflict, after implement i2c helper, this mutex should be |
| * retired. |
| */ |
| if (aconnector->dc_link->type != dc_connection_mst_branch) |
| mutex_lock(&aconnector->hpd_lock); |
| |
| if (dc_link_handle_hpd_rx_irq(aconnector->dc_link) && |
| !is_mst_root_connector) { |
| /* Downstream Port status changed. */ |
| if (dc_link_detect(aconnector->dc_link, false)) { |
| amdgpu_dm_update_connector_after_detect(aconnector); |
| |
| |
| drm_modeset_lock_all(dev); |
| dm_restore_drm_connector_state(dev, connector); |
| drm_modeset_unlock_all(dev); |
| |
| drm_kms_helper_hotplug_event(dev); |
| } |
| } |
| if ((dc_link->cur_link_settings.lane_count != LANE_COUNT_UNKNOWN) || |
| (dc_link->type == dc_connection_mst_branch)) |
| dm_handle_hpd_rx_irq(aconnector); |
| |
| if (aconnector->dc_link->type != dc_connection_mst_branch) |
| mutex_unlock(&aconnector->hpd_lock); |
| } |
| |
| static void register_hpd_handlers(struct amdgpu_device *adev) |
| { |
| struct drm_device *dev = adev->ddev; |
| struct drm_connector *connector; |
| struct amdgpu_connector *aconnector; |
| const struct dc_link *dc_link; |
| struct dc_interrupt_params int_params = {0}; |
| |
| int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; |
| int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; |
| |
| list_for_each_entry(connector, |
| &dev->mode_config.connector_list, head) { |
| |
| aconnector = to_amdgpu_connector(connector); |
| dc_link = aconnector->dc_link; |
| |
| if (DC_IRQ_SOURCE_INVALID != dc_link->irq_source_hpd) { |
| int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; |
| int_params.irq_source = dc_link->irq_source_hpd; |
| |
| amdgpu_dm_irq_register_interrupt(adev, &int_params, |
| handle_hpd_irq, |
| (void *) aconnector); |
| } |
| |
| if (DC_IRQ_SOURCE_INVALID != dc_link->irq_source_hpd_rx) { |
| |
| /* Also register for DP short pulse (hpd_rx). */ |
| int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; |
| int_params.irq_source = dc_link->irq_source_hpd_rx; |
| |
| amdgpu_dm_irq_register_interrupt(adev, &int_params, |
| handle_hpd_rx_irq, |
| (void *) aconnector); |
| } |
| } |
| } |
| |
| /* Register IRQ sources and initialize IRQ callbacks */ |
| static int dce110_register_irq_handlers(struct amdgpu_device *adev) |
| { |
| struct dc *dc = adev->dm.dc; |
| struct common_irq_params *c_irq_params; |
| struct dc_interrupt_params int_params = {0}; |
| int r; |
| int i; |
| |
| int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; |
| int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; |
| |
| /* Actions of amdgpu_irq_add_id(): |
| * 1. Register a set() function with base driver. |
| * Base driver will call set() function to enable/disable an |
| * interrupt in DC hardware. |
| * 2. Register amdgpu_dm_irq_handler(). |
| * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts |
| * coming from DC hardware. |
| * amdgpu_dm_irq_handler() will re-direct the interrupt to DC |
| * for acknowledging and handling. */ |
| |
| /* Use VBLANK interrupt */ |
| for (i = 1; i <= adev->mode_info.num_crtc; i++) { |
| r = amdgpu_irq_add_id(adev, AMDGPU_IH_CLIENTID_LEGACY, i, &adev->crtc_irq); |
| |
| if (r) { |
| DRM_ERROR("Failed to add crtc irq id!\n"); |
| return r; |
| } |
| |
| int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; |
| int_params.irq_source = |
| dc_interrupt_to_irq_source(dc, i, 0); |
| |
| c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1]; |
| |
| c_irq_params->adev = adev; |
| c_irq_params->irq_src = int_params.irq_source; |
| |
| amdgpu_dm_irq_register_interrupt(adev, &int_params, |
| dm_crtc_high_irq, c_irq_params); |
| } |
| |
| /* Use GRPH_PFLIP interrupt */ |
| for (i = VISLANDS30_IV_SRCID_D1_GRPH_PFLIP; |
| i <= VISLANDS30_IV_SRCID_D6_GRPH_PFLIP; i += 2) { |
| r = amdgpu_irq_add_id(adev, AMDGPU_IH_CLIENTID_LEGACY, i, &adev->pageflip_irq); |
| if (r) { |
| DRM_ERROR("Failed to add page flip irq id!\n"); |
| return r; |
| } |
| |
| int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; |
| int_params.irq_source = |
| dc_interrupt_to_irq_source(dc, i, 0); |
| |
| c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; |
| |
| c_irq_params->adev = adev; |
| c_irq_params->irq_src = int_params.irq_source; |
| |
| amdgpu_dm_irq_register_interrupt(adev, &int_params, |
| dm_pflip_high_irq, c_irq_params); |
| |
| } |
| |
| /* HPD */ |
| r = amdgpu_irq_add_id(adev, AMDGPU_IH_CLIENTID_LEGACY, VISLANDS30_IV_SRCID_HOTPLUG_DETECT_A, |
| &adev->hpd_irq); |
| if (r) { |
| DRM_ERROR("Failed to add hpd irq id!\n"); |
| return r; |
| } |
| |
| register_hpd_handlers(adev); |
| |
| return 0; |
| } |
| |
| static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev) |
| { |
| int r; |
| |
| adev->mode_info.mode_config_initialized = true; |
| |
| adev->ddev->mode_config.funcs = (void *)&amdgpu_dm_mode_funcs; |
| adev->ddev->mode_config.helper_private = &amdgpu_dm_mode_config_helperfuncs; |
| |
| 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; |
| /* indicate support of immediate flip */ |
| adev->ddev->mode_config.async_page_flip = true; |
| |
| adev->ddev->mode_config.fb_base = adev->mc.aper_base; |
| |
| r = amdgpu_modeset_create_props(adev); |
| if (r) |
| return r; |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) ||\ |
| defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE) |
| |
| static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) |
| { |
| struct amdgpu_display_manager *dm = bl_get_data(bd); |
| |
| if (dc_link_set_backlight_level(dm->backlight_link, |
| bd->props.brightness, 0, 0)) |
| return 0; |
| else |
| return 1; |
| } |
| |
| static int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd) |
| { |
| return bd->props.brightness; |
| } |
| |
| static const struct backlight_ops amdgpu_dm_backlight_ops = { |
| .get_brightness = amdgpu_dm_backlight_get_brightness, |
| .update_status = amdgpu_dm_backlight_update_status, |
| }; |
| |
| void amdgpu_dm_register_backlight_device(struct amdgpu_display_manager *dm) |
| { |
| char bl_name[16]; |
| struct backlight_properties props = { 0 }; |
| |
| props.max_brightness = AMDGPU_MAX_BL_LEVEL; |
| props.type = BACKLIGHT_RAW; |
| |
| snprintf(bl_name, sizeof(bl_name), "amdgpu_bl%d", |
| dm->adev->ddev->primary->index); |
| |
| dm->backlight_dev = backlight_device_register(bl_name, |
| dm->adev->ddev->dev, |
| dm, |
| &amdgpu_dm_backlight_ops, |
| &props); |
| |
| if (NULL == dm->backlight_dev) |
| DRM_ERROR("DM: Backlight registration failed!\n"); |
| else |
| DRM_INFO("DM: Registered Backlight device: %s\n", bl_name); |
| } |
| |
| #endif |
| |
| /* In this architecture, the association |
| * connector -> encoder -> crtc |
| * id not really requried. The crtc and connector will hold the |
| * display_index as an abstraction to use with DAL component |
| * |
| * Returns 0 on success |
| */ |
| int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) |
| { |
| struct amdgpu_display_manager *dm = &adev->dm; |
| uint32_t i; |
| struct amdgpu_connector *aconnector; |
| struct amdgpu_encoder *aencoder; |
| struct amdgpu_crtc *acrtc; |
| uint32_t link_cnt; |
| |
| link_cnt = dm->dc->caps.max_links; |
| |
| if (amdgpu_dm_mode_config_init(dm->adev)) { |
| DRM_ERROR("DM: Failed to initialize mode config\n"); |
| return -1; |
| } |
| |
| for (i = 0; i < dm->dc->caps.max_streams; i++) { |
| acrtc = kzalloc(sizeof(struct amdgpu_crtc), GFP_KERNEL); |
| if (!acrtc) |
| goto fail; |
| |
| if (amdgpu_dm_crtc_init( |
| dm, |
| acrtc, |
| i)) { |
| DRM_ERROR("KMS: Failed to initialize crtc\n"); |
| kfree(acrtc); |
| goto fail; |
| } |
| } |
| |
| dm->display_indexes_num = dm->dc->caps.max_streams; |
| |
| /* loops over all connectors on the board */ |
| for (i = 0; i < link_cnt; i++) { |
| |
| if (i > AMDGPU_DM_MAX_DISPLAY_INDEX) { |
| DRM_ERROR( |
| "KMS: Cannot support more than %d display indexes\n", |
| AMDGPU_DM_MAX_DISPLAY_INDEX); |
| continue; |
| } |
| |
| aconnector = kzalloc(sizeof(*aconnector), GFP_KERNEL); |
| if (!aconnector) |
| goto fail; |
| |
| aencoder = kzalloc(sizeof(*aencoder), GFP_KERNEL); |
| if (!aencoder) { |
| goto fail_free_connector; |
| } |
| |
| if (amdgpu_dm_encoder_init(dm->ddev, aencoder, i)) { |
| DRM_ERROR("KMS: Failed to initialize encoder\n"); |
| goto fail_free_encoder; |
| } |
| |
| if (amdgpu_dm_connector_init(dm, aconnector, i, aencoder)) { |
| DRM_ERROR("KMS: Failed to initialize connector\n"); |
| goto fail_free_connector; |
| } |
| |
| if (dc_link_detect(dc_get_link_at_index(dm->dc, i), true)) |
| amdgpu_dm_update_connector_after_detect(aconnector); |
| } |
| |
| /* Software is initialized. Now we can register interrupt handlers. */ |
| switch (adev->asic_type) { |
| case CHIP_BONAIRE: |
| case CHIP_HAWAII: |
| case CHIP_TONGA: |
| case CHIP_FIJI: |
| case CHIP_CARRIZO: |
| case CHIP_STONEY: |
| case CHIP_POLARIS11: |
| case CHIP_POLARIS10: |
| case CHIP_POLARIS12: |
| if (dce110_register_irq_handlers(dm->adev)) { |
| DRM_ERROR("DM: Failed to initialize IRQ\n"); |
| return -1; |
| } |
| break; |
| default: |
| DRM_ERROR("Usupported ASIC type: 0x%X\n", adev->asic_type); |
| return -1; |
| } |
| |
| drm_mode_config_reset(dm->ddev); |
| |
| return 0; |
| fail_free_encoder: |
| kfree(aencoder); |
| fail_free_connector: |
| kfree(aconnector); |
| fail: |
| return -1; |
| } |
| |
| void amdgpu_dm_destroy_drm_device(struct amdgpu_display_manager *dm) |
| { |
| drm_mode_config_cleanup(dm->ddev); |
| return; |
| } |
| |
| /****************************************************************************** |
| * amdgpu_display_funcs functions |
| *****************************************************************************/ |
| |
| /** |
| * 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) |
| { |
| /* TODO: implement later */ |
| } |
| |
| static void dm_set_backlight_level(struct amdgpu_encoder *amdgpu_encoder, |
| u8 level) |
| { |
| /* TODO: translate amdgpu_encoder to display_index and call DAL */ |
| } |
| |
| static u8 dm_get_backlight_level(struct amdgpu_encoder *amdgpu_encoder) |
| { |
| /* TODO: translate amdgpu_encoder to display_index and call DAL */ |
| 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, bool async) |
| { |
| struct amdgpu_crtc *acrtc; |
| const struct dc_stream *stream; |
| struct dc_flip_addrs addr = { {0} }; |
| |
| /* |
| * TODO risk of concurrency issues |
| * |
| * This should guarded by the dal_mutex but we can't do this since the |
| * caller uses a spin_lock on event_lock. |
| * |
| * If we wait on the dal_mutex a second page flip interrupt might come, |
| * spin on the event_lock, disabling interrupts while it does so. At |
| * this point the core can no longer be pre-empted and return to the |
| * thread that waited on the dal_mutex and we're deadlocked. |
| * |
| * With multiple cores the same essentially happens but might just take |
| * a little longer to lock up all cores. |
| * |
| * The reason we should lock on dal_mutex is so that we can be sure |
| * nobody messes with acrtc->stream after we read and check its value. |
| * |
| * We might be able to fix our concurrency issues with a work queue |
| * where we schedule all work items (mode_set, page_flip, etc.) and |
| * execute them one by one. Care needs to be taken to still deal with |
| * any potential concurrency issues arising from interrupt calls. |
| */ |
| |
| acrtc = adev->mode_info.crtcs[crtc_id]; |
| stream = acrtc->stream; |
| |
| |
| if (acrtc->pflip_status != AMDGPU_FLIP_NONE) { |
| DRM_ERROR("flip queue: acrtc %d, already busy\n", acrtc->crtc_id); |
| /* In commit tail framework this cannot happen */ |
| BUG_ON(0); |
| } |
| |
| |
| /* |
| * Received a page flip call after the display has been reset. |
| * Just return in this case. Everything should be clean-up on reset. |
| */ |
| |
| if (!stream) { |
| WARN_ON(1); |
| return; |
| } |
| |
| addr.address.grph.addr.low_part = lower_32_bits(crtc_base); |
| addr.address.grph.addr.high_part = upper_32_bits(crtc_base); |
| addr.flip_immediate = async; |
| |
| |
| if (acrtc->base.state->event && |
| acrtc->base.state->event->event.base.type == |
| DRM_EVENT_FLIP_COMPLETE) { |
| acrtc->event = acrtc->base.state->event; |
| |
| /* Set the flip status */ |
| acrtc->pflip_status = AMDGPU_FLIP_SUBMITTED; |
| |
| /* Mark this event as consumed */ |
| acrtc->base.state->event = NULL; |
| } |
| |
| dc_flip_surface_addrs(adev->dm.dc, |
| dc_stream_get_status(stream)->surfaces, |
| &addr, 1); |
| |
| DRM_DEBUG_DRIVER("%s Flipping to hi: 0x%x, low: 0x%x \n", |
| __func__, |
| addr.address.grph.addr.high_part, |
| addr.address.grph.addr.low_part); |
| |
| } |
| |
| static int amdgpu_notify_freesync(struct drm_device *dev, void *data, |
| struct drm_file *filp) |
| { |
| struct mod_freesync_params freesync_params; |
| uint8_t num_streams; |
| uint8_t i; |
| |
| struct amdgpu_device *adev = dev->dev_private; |
| int r = 0; |
| |
| /* Get freesync enable flag from DRM */ |
| |
| num_streams = dc_get_current_stream_count(adev->dm.dc); |
| |
| for (i = 0; i < num_streams; i++) { |
| const struct dc_stream *stream; |
| stream = dc_get_stream_at_index(adev->dm.dc, i); |
| |
| mod_freesync_update_state(adev->dm.freesync_module, |
| &stream, 1, &freesync_params); |
| } |
| |
| return r; |
| } |
| |
| static const struct amdgpu_display_funcs dm_display_funcs = { |
| .bandwidth_update = dm_bandwidth_update, /* called unconditionally */ |
| .vblank_get_counter = dm_vblank_get_counter,/* called unconditionally */ |
| .vblank_wait = NULL, |
| .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 = NULL, /* VBIOS parsing. DAL does it. */ |
| .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. */ |
| .notify_freesync = amdgpu_notify_freesync, |
| |
| }; |
| |
| #if defined(CONFIG_DEBUG_KERNEL_DC) |
| |
| static ssize_t s3_debug_store( |
| struct device *device, |
| struct device_attribute *attr, |
| const char *buf, |
| size_t count) |
| { |
| int ret; |
| int s3_state; |
| struct pci_dev *pdev = to_pci_dev(device); |
| struct drm_device *drm_dev = pci_get_drvdata(pdev); |
| struct amdgpu_device *adev = drm_dev->dev_private; |
| |
| ret = kstrtoint(buf, 0, &s3_state); |
| |
| if (ret == 0) { |
| if (s3_state) { |
| dm_resume(adev); |
| amdgpu_dm_display_resume(adev); |
| drm_kms_helper_hotplug_event(adev->ddev); |
| } else |
| dm_suspend(adev); |
| } |
| |
| return ret == 0 ? count : 0; |
| } |
| |
| DEVICE_ATTR_WO(s3_debug); |
| |
| #endif |
| |
| static int dm_early_init(void *handle) |
| { |
| struct amdgpu_device *adev = (struct amdgpu_device *)handle; |
| |
| amdgpu_dm_set_irq_funcs(adev); |
| |
| switch (adev->asic_type) { |
| case CHIP_BONAIRE: |
| case CHIP_HAWAII: |
| adev->mode_info.num_crtc = 6; |
| adev->mode_info.num_hpd = 6; |
| adev->mode_info.num_dig = 6; |
| break; |
| case CHIP_FIJI: |
| case CHIP_TONGA: |
| adev->mode_info.num_crtc = 6; |
| adev->mode_info.num_hpd = 6; |
| adev->mode_info.num_dig = 7; |
| break; |
| case CHIP_CARRIZO: |
| adev->mode_info.num_crtc = 3; |
| adev->mode_info.num_hpd = 6; |
| adev->mode_info.num_dig = 9; |
| break; |
| case CHIP_STONEY: |
| adev->mode_info.num_crtc = 2; |
| adev->mode_info.num_hpd = 6; |
| adev->mode_info.num_dig = 9; |
| break; |
| case CHIP_POLARIS11: |
| case CHIP_POLARIS12: |
| adev->mode_info.num_crtc = 5; |
| adev->mode_info.num_hpd = 5; |
| adev->mode_info.num_dig = 5; |
| break; |
| case CHIP_POLARIS10: |
| adev->mode_info.num_crtc = 6; |
| adev->mode_info.num_hpd = 6; |
| adev->mode_info.num_dig = 6; |
| break; |
| default: |
| DRM_ERROR("Usupported ASIC type: 0x%X\n", adev->asic_type); |
| return -EINVAL; |
| } |
| |
| if (adev->mode_info.funcs == NULL) |
| adev->mode_info.funcs = &dm_display_funcs; |
| |
| /* Note: Do NOT change adev->audio_endpt_rreg and |
| * adev->audio_endpt_wreg because they are initialised in |
| * amdgpu_device_init() */ |
| #if defined(CONFIG_DEBUG_KERNEL_DC) |
| device_create_file( |
| adev->ddev->dev, |
| &dev_attr_s3_debug); |
| #endif |
| |
| 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; |
| } |
| |
| |