/* * 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 *
*/
/* The caprices of the preprocessor require that this be declared right here */ #define CREATE_TRACE_POINTS
/* Number of bytes in PSP header for firmware. */ #define PSP_HEADER_BYTES 0x100
/* Number of bytes in PSP footer for firmware. */ #define PSP_FOOTER_BYTES 0x100
/** * DOC: overview * * The AMDgpu display manager, **amdgpu_dm** (or even simpler, * **dm**) sits between DRM and DC. It acts as a liaison, converting DRM * requests into DC requests, and DC responses into DRM responses. * * The root control structure is &struct amdgpu_display_manager.
*/
/* * 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
*/ staticint amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev); /* removes and deallocates the drm structures, created by the above function */ staticvoid amdgpu_dm_destroy_drm_device(struct amdgpu_display_manager *dm);
if ((crtc < 0) || (crtc >= adev->mode_info.num_crtc)) return -EINVAL;
acrtc = adev->mode_info.crtcs[crtc];
if (!acrtc->dm_irq_params.stream) {
drm_err(adev_to_drm(adev), "dc_stream_state is NULL for crtc '%d'!\n",
crtc); return 0;
}
if (dc && dc->caps.ips_support && dc->idle_optimizations_allowed)
dc_allow_idle_optimizations(dc, false);
/* * TODO rework base driver to use values directly. * for now parse it back into reg-format
*/
dc_stream_get_scanoutpos(acrtc->dm_irq_params.stream,
&v_blank_start,
&v_blank_end,
&h_position,
&v_position);
/* * DC will program planes with their z-order determined by their ordering * in the dc_surface_updates array. This comparator is used to sort them * by descending zpos.
*/ staticint dm_plane_layer_index_cmp(constvoid *a, constvoid *b)
{ conststruct dc_surface_update *sa = (struct dc_surface_update *)a; conststruct dc_surface_update *sb = (struct dc_surface_update *)b;
/** * update_planes_and_stream_adapter() - Send planes to be updated in DC * * DC has a generic way to update planes and stream via * dc_update_planes_and_stream function; however, DM might need some * adjustments and preparation before calling it. This function is a wrapper * for the dc_update_planes_and_stream that does any required configuration * before passing control to DC. * * @dc: Display Core control structure * @update_type: specify whether it is FULL/MEDIUM/FAST update * @planes_count: planes count to update * @stream: stream state * @stream_update: stream update * @array_of_surface_update: dc surface update pointer *
*/ staticinlinebool update_planes_and_stream_adapter(struct dc *dc, int update_type, int planes_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update, struct dc_surface_update *array_of_surface_update)
{
sort(array_of_surface_update, planes_count, sizeof(*array_of_surface_update), dm_plane_layer_index_cmp, NULL);
/* * Previous frame finished and HW is ready for optimization.
*/
dc_post_update_surfaces_to_stream(dc);
/* IRQ could occur when in initial stage */ /* TODO work and BO cleanup */ if (amdgpu_crtc == NULL) {
drm_dbg_state(dev, "CRTC is null, returning.\n"); return;
}
/* Fixed refresh rate, or VRR scanout position outside front-porch? */ if (!vrr_active ||
!dc_stream_get_scanoutpos(amdgpu_crtc->dm_irq_params.stream, &v_blank_start,
&v_blank_end, &hpos, &vpos) ||
(vpos < v_blank_start)) { /* Update to correct count and vblank timestamp if racing with * vblank irq. This also updates to the correct vblank timestamp * even in VRR mode, as scanout is past the front-porch atm.
*/
drm_crtc_accurate_vblank_count(&amdgpu_crtc->base);
/* Wake up userspace by sending the pageflip event with proper * count and timestamp of vblank of flip completion.
*/ if (e) {
drm_crtc_send_vblank_event(&amdgpu_crtc->base, e);
/* Event sent, so done with vblank for this flip */
drm_crtc_vblank_put(&amdgpu_crtc->base);
}
} elseif (e) { /* VRR active and inside front-porch: vblank count and * timestamp for pageflip event will only be up to date after * drm_crtc_handle_vblank() has been executed from late vblank * irq handler after start of back-porch (vline 0). We queue the * pageflip event for send-out by drm_crtc_handle_vblank() with * updated timestamp and count, once it runs after us. * * We need to open-code this instead of using the helper * drm_crtc_arm_vblank_event(), as that helper would * call drm_crtc_accurate_vblank_count(), which we must * not call in VRR mode while we are in front-porch!
*/
/* sequence will be replaced by real count during send-out. */
e->sequence = drm_crtc_vblank_count(&amdgpu_crtc->base);
e->pipe = amdgpu_crtc->crtc_id;
list_add_tail(&e->base.link, &adev_to_drm(adev)->vblank_event_list);
e = NULL;
}
/* Keep track of vblank of this flip for flip throttling. We use the * cooked hw counter, as that one incremented at start of this vblank * of pageflip completion, so last_flip_vblank is the forbidden count * for queueing new pageflips if vsync + VRR is enabled.
*/
amdgpu_crtc->dm_irq_params.last_flip_vblank =
amdgpu_get_vblank_counter_kms(&amdgpu_crtc->base);
/* Core vblank handling is done here after end of front-porch in * vrr mode, as vblank timestamping will give valid results * while now done after front-porch. This will also deliver * page-flip completion events that have been queued to us * if a pageflip happened inside front-porch.
*/ if (vrr_active) {
amdgpu_dm_crtc_handle_vblank(acrtc);
/* BTR processing for pre-DCE12 ASICs */ if (acrtc->dm_irq_params.stream &&
adev->family < AMDGPU_FAMILY_AI) {
spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
mod_freesync_handle_v_update(
adev->dm.freesync_module,
acrtc->dm_irq_params.stream,
&acrtc->dm_irq_params.vrr_params);
/** * Core vblank handling at start of front-porch is only possible * in non-vrr mode, as only there vblank timestamping will give * valid results while done in front-porch. Otherwise defer it * to dm_vupdate_high_irq after end of front-porch.
*/ if (!vrr_active)
amdgpu_dm_crtc_handle_vblank(acrtc);
/** * Following stuff must happen at start of vblank, for crc * computation and below-the-range btr support in vrr mode.
*/
amdgpu_dm_crtc_handle_crc_irq(&acrtc->base);
/* BTR updates need to happen before VUPDATE on Vega and above. */ if (adev->family < AMDGPU_FAMILY_AI) return;
/* * If there aren't any active_planes then DCH HUBP may be clock-gated. * In that case, pageflip completion interrupts won't fire and pageflip * completion events won't get delivered. Prevent this by sending * pending pageflip events from here if a flip is still pending. * * If any planes are enabled, use dm_pflip_high_irq() instead, to * avoid race conditions between flip programming and completion, * which could cause too early flip completion events.
*/ if (adev->family >= AMDGPU_FAMILY_RV &&
acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED &&
acrtc->dm_irq_params.active_planes == 0) { if (acrtc->event) {
drm_crtc_send_vblank_event(&acrtc->base, acrtc->event);
acrtc->event = NULL;
drm_crtc_vblank_put(&acrtc->base);
}
acrtc->pflip_status = AMDGPU_FLIP_NONE;
}
/** * dmub_aux_setconfig_callback - Callback for AUX or SET_CONFIG command. * @adev: amdgpu_device pointer * @notify: dmub notification structure * * Dmub AUX or SET_CONFIG command completion processing callback * Copies dmub notification to DM which is to be read by AUX command. * issuing thread and also signals the event to wake up the thread.
*/ staticvoid dmub_aux_setconfig_callback(struct amdgpu_device *adev, struct dmub_notification *notify)
{ if (adev->dm.dmub_notify)
memcpy(adev->dm.dmub_notify, notify, sizeof(struct dmub_notification)); if (notify->type == DMUB_NOTIFICATION_AUX_REPLY)
complete(&adev->dm.dmub_aux_transfer_done);
}
if (notify == NULL) {
drm_err(adev_to_drm(adev), "DMUB HPD callback notification was NULL"); return;
}
if (notify->link_index > adev->dm.dc->link_count) {
drm_err(adev_to_drm(adev), "DMUB HPD index (%u)is abnormal", notify->link_index); return;
}
/* Skip DMUB HPD IRQ in suspend/resume. We will probe them later. */ if (notify->type == DMUB_NOTIFICATION_HPD && adev->in_suspend) {
drm_info(adev_to_drm(adev), "Skip DMUB HPD IRQ callback in suspend/resume\n"); return;
}
link_index = notify->link_index;
link = adev->dm.dc->links[link_index];
dev = adev->dm.ddev;
if (hpd_aconnector) { if (notify->type == DMUB_NOTIFICATION_HPD) { if (hpd_aconnector->dc_link->hpd_status == (notify->hpd_status == DP_HPD_PLUG))
drm_warn(adev_to_drm(adev), "DMUB reported hpd status unchanged. link_index=%u\n", link_index);
handle_hpd_irq_helper(hpd_aconnector);
} elseif (notify->type == DMUB_NOTIFICATION_HPD_IRQ) {
handle_hpd_rx_irq(hpd_aconnector);
}
}
}
/** * dmub_hpd_sense_callback - DMUB HPD sense processing callback. * @adev: amdgpu_device pointer * @notify: dmub notification structure * * HPD sense changes can occur during low power states and need to be * notified from firmware to driver.
*/ staticvoid dmub_hpd_sense_callback(struct amdgpu_device *adev, struct dmub_notification *notify)
{
drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n");
}
/** * register_dmub_notify_callback - Sets callback for DMUB notify * @adev: amdgpu_device pointer * @type: Type of dmub notification * @callback: Dmub interrupt callback function * @dmub_int_thread_offload: offload indicator * * API to register a dmub callback handler for a dmub notification * Also sets indicator whether callback processing to be offloaded. * to dmub interrupt handling thread * Return: true if successfully registered, false if there is existing registration
*/ staticbool register_dmub_notify_callback(struct amdgpu_device *adev, enum dmub_notification_type type,
dmub_notify_interrupt_callback_t callback, bool dmub_int_thread_offload)
{ if (callback != NULL && type < ARRAY_SIZE(adev->dm.dmub_thread_offload)) {
adev->dm.dmub_callback[type] = callback;
adev->dm.dmub_thread_offload[type] = dmub_int_thread_offload;
} else returnfalse;
staticconstchar *dmub_notification_type_str(enum dmub_notification_type e)
{ switch (e) { case DMUB_NOTIFICATION_NO_DATA: return"NO_DATA"; case DMUB_NOTIFICATION_AUX_REPLY: return"AUX_REPLY"; case DMUB_NOTIFICATION_HPD: return"HPD"; case DMUB_NOTIFICATION_HPD_IRQ: return"HPD_IRQ"; case DMUB_NOTIFICATION_SET_CONFIG_REPLY: return"SET_CONFIG_REPLY"; case DMUB_NOTIFICATION_DPIA_NOTIFICATION: return"DPIA_NOTIFICATION"; case DMUB_NOTIFICATION_HPD_SENSE_NOTIFY: return"HPD_SENSE_NOTIFY"; case DMUB_NOTIFICATION_FUSED_IO: return"FUSED_IO"; default: return"<unknown>";
}
}
do { if (dc_dmub_srv_get_dmub_outbox0_msg(dm->dc, &entry)) {
trace_amdgpu_dmub_trace_high_irq(entry.trace_code, entry.tick_count,
entry.param0, entry.param1);
if (max_size) { int r = amdgpu_bo_create_kernel(adev, max_size * 4, PAGE_SIZE,
AMDGPU_GEM_DOMAIN_GTT, &compressor->bo_ptr,
&compressor->gpu_addr, &compressor->cpu_addr);
if (r)
drm_err(adev_to_drm(adev), "DM: Failed to initialize FBC\n"); else {
adev->dm.dc->ctx->fbc_gpu_addr = compressor->gpu_addr;
drm_info(adev_to_drm(adev), "DM: FBC alloc %lu\n", max_size*4);
}
}
}
staticint amdgpu_dm_audio_component_get_eld(struct device *kdev, int port, int pipe, bool *enabled, unsignedchar *buf, int max_bytes)
{ struct drm_device *dev = dev_get_drvdata(kdev); struct amdgpu_device *adev = drm_to_adev(dev); struct drm_connector *connector; struct drm_connector_list_iter conn_iter; struct amdgpu_dm_connector *aconnector; int ret = 0;
if (!dmub_srv) /* DMUB isn't supported on the ASIC. */ return 0;
if (!fb_info) {
drm_err(adev_to_drm(adev), "No framebuffer info for DMUB service.\n"); return -EINVAL;
}
if (!dmub_fw) { /* Firmware required for DMUB support. */
drm_err(adev_to_drm(adev), "No firmware provided for DMUB.\n"); return -EINVAL;
}
/* initialize register offsets for ASICs with runtime initialization available */ if (dmub_srv->hw_funcs.init_reg_offsets)
dmub_srv->hw_funcs.init_reg_offsets(dmub_srv, ctx);
status = dmub_srv_has_hw_support(dmub_srv, &has_hw_support); if (status != DMUB_STATUS_OK) {
drm_err(adev_to_drm(adev), "Error checking HW support for DMUB: %d\n", status); return -EINVAL;
}
if (!has_hw_support) {
drm_info(adev_to_drm(adev), "DMUB unsupported on ASIC\n"); return 0;
}
/* Reset DMCUB if it was previously running - before we overwrite its memory. */
status = dmub_srv_hw_reset(dmub_srv); if (status != DMUB_STATUS_OK)
drm_warn(adev_to_drm(adev), "Error resetting DMUB HW: %d\n", status);
/* if adev->firmware.load_type == AMDGPU_FW_LOAD_PSP, * amdgpu_ucode_init_single_fw will load dmub firmware * fw_inst_const part to cw0; otherwise, the firmware back door load * will be done by dm_dmub_hw_init
*/ if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) {
memcpy(fb_info->fb[DMUB_WINDOW_0_INST_CONST].cpu_addr, fw_inst_const,
fw_inst_const_size);
}
if (fw_bss_data_size)
memcpy(fb_info->fb[DMUB_WINDOW_2_BSS_DATA].cpu_addr,
fw_bss_data, fw_bss_data_size);
/* Copy firmware bios info into FB memory. */
memcpy(fb_info->fb[DMUB_WINDOW_3_VBIOS].cpu_addr, adev->bios,
adev->bios_size);
/* Reset regions that need to be reset. */
memset(fb_info->fb[DMUB_WINDOW_4_MAILBOX].cpu_addr, 0,
fb_info->fb[DMUB_WINDOW_4_MAILBOX].size);
/* backdoor load firmware and trigger dmub running */ if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP)
hw_params.load_inst_const = true;
if (dmcu)
hw_params.psp_version = dmcu->psp_version;
for (i = 0; i < fb_info->num_fb; ++i)
hw_params.fb[i] = &fb_info->fb[i];
switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(3, 1, 3): case IP_VERSION(3, 1, 4): case IP_VERSION(3, 5, 0): case IP_VERSION(3, 5, 1): case IP_VERSION(3, 6, 0): case IP_VERSION(4, 0, 1):
hw_params.dpia_supported = true;
hw_params.disable_dpia = adev->dm.dc->debug.dpia_debug.bits.disable_dpia; break; default: break;
}
switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(3, 5, 0): case IP_VERSION(3, 5, 1): case IP_VERSION(3, 6, 0):
hw_params.ips_sequential_ono = adev->external_rev_id > 0x10;
hw_params.lower_hbr3_phy_ssc = true; break; default: break;
}
status = dmub_srv_hw_init(dmub_srv, &hw_params); if (status != DMUB_STATUS_OK) {
drm_err(adev_to_drm(adev), "Error initializing DMUB HW: %d\n", status); return -EINVAL;
}
/* Wait for firmware load to finish. */
status = dmub_srv_wait_for_auto_load(dmub_srv, 100000); if (status != DMUB_STATUS_OK)
drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status);
/* Init DMCU and ABM if available. */ if (dmcu && abm) {
dmcu->funcs->dmcu_init(dmcu);
abm->dmcu_is_running = dmcu->funcs->is_dmcu_initialized(dmcu);
}
if (!adev->dm.dc->ctx->dmub_srv)
adev->dm.dc->ctx->dmub_srv = dc_dmub_srv_create(adev->dm.dc, dmub_srv); if (!adev->dm.dc->ctx->dmub_srv) {
drm_err(adev_to_drm(adev), "Couldn't allocate DC DMUB server!\n"); return -ENOMEM;
}
/* AGP aperture is disabled */ if (agp_bot > agp_top) {
logical_addr_low = adev->gmc.fb_start >> 18; if (adev->apu_flags & (AMD_APU_IS_RAVEN2 |
AMD_APU_IS_RENOIR |
AMD_APU_IS_GREEN_SARDINE)) /* * Raven2 has a HW issue that it is unable to use the vram which * is out of MC_VM_SYSTEM_APERTURE_HIGH_ADDR. So here is the * workaround that increase system aperture high address (add 1) * to get rid of the VM fault and hardware hang.
*/
logical_addr_high = (adev->gmc.fb_end >> 18) + 0x1; else
logical_addr_high = adev->gmc.fb_end >> 18;
} else {
logical_addr_low = min(adev->gmc.fb_start, adev->gmc.agp_start) >> 18; if (adev->apu_flags & (AMD_APU_IS_RAVEN2 |
AMD_APU_IS_RENOIR |
AMD_APU_IS_GREEN_SARDINE)) /* * Raven2 has a HW issue that it is unable to use the vram which * is out of MC_VM_SYSTEM_APERTURE_HIGH_ADDR. So here is the * workaround that increase system aperture high address (add 1) * to get rid of the VM fault and hardware hang.
*/
logical_addr_high = max((adev->gmc.fb_end >> 18) + 0x1, adev->gmc.agp_end >> 18); else
logical_addr_high = max(adev->gmc.fb_end, adev->gmc.agp_end) >> 18;
}
mutex_lock(&adev->dm.dc_lock); if (offload_work->data.bytes.device_service_irq.bits.AUTOMATED_TEST) {
dc_link_dp_handle_automated_test(dc_link);
if (aconnector->timing_changed) { /* force connector disconnect and reconnect */
force_connector_state(aconnector, DRM_FORCE_OFF);
msleep(100);
force_connector_state(aconnector, DRM_FORCE_UNSPECIFIED);
}
test_response.bits.ACK = 1;
core_link_write_dpcd(
dc_link,
DP_TEST_RESPONSE,
&test_response.raw, sizeof(test_response));
} elseif ((dc_link->connector_signal != SIGNAL_TYPE_EDP) &&
dc_link_check_link_loss_status(dc_link, &offload_work->data) &&
dc_link_dp_allow_hpd_rx_irq(dc_link)) { /* offload_work->data is from handle_hpd_rx_irq-> * schedule_hpd_rx_offload_work.this is defer handle * for hpd short pulse. upon here, link status may be * changed, need get latest link status from dpcd * registers. if link status is good, skip run link * training again.
*/ union hpd_irq_data irq_data;
memset(&irq_data, 0, sizeof(irq_data));
/* before dc_link_dp_handle_link_loss, allow new link lost handle * request be added to work queue if link lost at end of dc_link_ * dp_handle_link_loss
*/
spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags);
offload_work->offload_wq->is_handling_link_loss = false;
spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags);
out_err: for (i = 0; i < max_caps; i++) { if (hpd_rx_offload_wq[i].wq)
destroy_workqueue(hpd_rx_offload_wq[i].wq);
}
kfree(hpd_rx_offload_wq); return NULL;
}
/* walk the da list in DM */
list_for_each_entry(da, &adev->dm.da_list, list) { if (pvMem == da->cpu_ptr) {
amdgpu_bo_free_kernel(&da->bo, &da->gpu_addr, &da->cpu_ptr);
list_del(&da->list);
kfree(da); break;
}
}
bb = dm_allocate_gpu_mem(adev,
DC_MEM_ALLOC_TYPE_GART,
bb_size,
&addr); if (!bb) return NULL;
for (i = 0; i < 4; i++) { /* Extract 16-bit chunk */
chunk = ((uint64_t) addr >> (i * 16)) & 0xFFFF; /* Send the chunk */
ret = dm_dmub_send_vbios_gpint_command(adev, send_addrs[i], chunk, 30000); if (ret != DMUB_STATUS_OK) goto free_bb;
}
/* Now ask DMUB to copy the bb */
ret = dm_dmub_send_vbios_gpint_command(adev, DMUB_GPINT__BB_COPY, 1, 200000); if (ret != DMUB_STATUS_OK) goto free_bb;
switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(3, 5, 0): case IP_VERSION(3, 6, 0): case IP_VERSION(3, 5, 1):
ret = DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF; break; default: /* ASICs older than DCN35 do not have IPSs */ if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 5, 0))
ret = DMUB_IPS_DISABLE_ALL; break;
}
if (!adev->dm.hdcp_workqueue)
drm_err(adev_to_drm(adev), "failed to initialize hdcp_workqueue.\n"); else
drm_dbg_driver(adev_to_drm(adev), "amdgpu: hdcp_workqueue init done %p.\n", adev->dm.hdcp_workqueue);
dc_init_callbacks(adev->dm.dc, &init_params);
} if (dc_is_dmub_outbox_supported(adev->dm.dc)) {
init_completion(&adev->dm.dmub_aux_transfer_done);
adev->dm.dmub_notify = kzalloc(sizeof(struct dmub_notification), GFP_KERNEL); if (!adev->dm.dmub_notify) {
drm_info(adev_to_drm(adev), "fail to allocate adev->dm.dmub_notify"); goto error;
}
adev->dm.delayed_hpd_wq = create_singlethread_workqueue("amdgpu_dm_hpd_wq"); if (!adev->dm.delayed_hpd_wq) {
drm_err(adev_to_drm(adev), "failed to create hpd offload workqueue.\n"); goto error;
}
amdgpu_dm_outbox_init(adev); if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_AUX_REPLY,
dmub_aux_setconfig_callback, false)) {
drm_err(adev_to_drm(adev), "fail to register dmub aux callback"); goto error;
}
for (size_t i = 0; i < ARRAY_SIZE(adev->dm.fused_io); i++)
init_completion(&adev->dm.fused_io[i].replied);
if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_FUSED_IO,
dmub_aux_fused_io_callback, false)) {
drm_err(adev_to_drm(adev), "fail to register dmub fused io callback"); goto error;
} /* Enable outbox notification only after IRQ handlers are registered and DMUB is alive. * It is expected that DMUB will resend any pending notifications at this point. Note * that hpd and hpd_irq handler registration are deferred to register_hpd_handlers() to * align legacy interface initialization sequence. Connection status will be proactivly * detected once in the amdgpu_dm_initialize_drm_device.
*/
dc_enable_dmub_outbox(adev->dm.dc);
/* DPIA trace goes to dmesg logs only if outbox is enabled */ if (amdgpu_dc_debug_mask & DC_ENABLE_DPIA_TRACE)
dc_dmub_srv_enable_dpia_trace(adev->dm.dc);
}
if (amdgpu_dm_initialize_drm_device(adev)) {
drm_err(adev_to_drm(adev), "failed to initialize sw for display support.\n"); goto error;
}
/* create fake encoders for MST */
dm_dp_create_fake_mst_encoders(adev);
/* TODO: Add_display_info? */
/* TODO use dynamic cursor width */
adev_to_drm(adev)->mode_config.cursor_width = adev->dm.dc->caps.max_cursor_size;
adev_to_drm(adev)->mode_config.cursor_height = adev->dm.dc->caps.max_cursor_size;
if (drm_vblank_init(adev_to_drm(adev), adev->dm.display_indexes_num)) {
drm_err(adev_to_drm(adev), "failed to initialize sw for display support.\n"); goto error;
}
#ifdefined(CONFIG_DRM_AMD_SECURE_DISPLAY)
amdgpu_dm_crtc_secure_display_create_contexts(adev); if (!adev->dm.secure_display_ctx.crtc_ctx)
drm_err(adev_to_drm(adev), "failed to initialize secure display contexts.\n");
#ifdefined(CONFIG_DRM_AMD_SECURE_DISPLAY) if (adev->dm.secure_display_ctx.crtc_ctx) { for (i = 0; i < adev->mode_info.num_crtc; i++) { if (adev->dm.secure_display_ctx.crtc_ctx[i].crtc) {
flush_work(&adev->dm.secure_display_ctx.crtc_ctx[i].notify_ta_work);
flush_work(&adev->dm.secure_display_ctx.crtc_ctx[i].forward_roi_work);
}
}
kfree(adev->dm.secure_display_ctx.crtc_ctx);
adev->dm.secure_display_ctx.crtc_ctx = NULL;
} #endif if (adev->dm.hdcp_workqueue) {
hdcp_destroy(&adev->dev->kobj, adev->dm.hdcp_workqueue);
adev->dm.hdcp_workqueue = NULL;
}
if (adev->dm.dc) {
dc_deinit_callbacks(adev->dm.dc);
dc_dmub_srv_destroy(&adev->dm.dc->ctx->dmub_srv); if (dc_enable_dmub_notifications(adev->dm.dc)) {
kfree(adev->dm.dmub_notify);
adev->dm.dmub_notify = NULL;
destroy_workqueue(adev->dm.delayed_hpd_wq);
adev->dm.delayed_hpd_wq = NULL;
}
}
if (adev->dm.dmub_bo)
amdgpu_bo_free_kernel(&adev->dm.dmub_bo,
&adev->dm.dmub_bo_gpu_addr,
&adev->dm.dmub_bo_cpu_addr);
if (adev->dm.hpd_rx_offload_wq && adev->dm.dc) { for (i = 0; i < adev->dm.dc->caps.max_links; i++) { if (adev->dm.hpd_rx_offload_wq[i].wq) {
destroy_workqueue(adev->dm.hpd_rx_offload_wq[i].wq);
adev->dm.hpd_rx_offload_wq[i].wq = NULL;
}
}
switch (adev->asic_type) { #ifdefined(CONFIG_DRM_AMD_DC_SI) case CHIP_TAHITI: case CHIP_PITCAIRN: case CHIP_VERDE: case CHIP_OLAND: #endif case CHIP_BONAIRE: case CHIP_HAWAII: case CHIP_KAVERI: case CHIP_KABINI: case CHIP_MULLINS: case CHIP_TONGA: case CHIP_FIJI: case CHIP_CARRIZO: case CHIP_STONEY: case CHIP_POLARIS11: case CHIP_POLARIS10: case CHIP_POLARIS12: case CHIP_VEGAM: case CHIP_VEGA10: case CHIP_VEGA12: case CHIP_VEGA20: return 0; case CHIP_NAVI12:
fw_name_dmcu = FIRMWARE_NAVI12_DMCU; break; case CHIP_RAVEN: if (ASICREV_IS_PICASSO(adev->external_rev_id))
fw_name_dmcu = FIRMWARE_RAVEN_DMCU; elseif (ASICREV_IS_RAVEN2(adev->external_rev_id))
fw_name_dmcu = FIRMWARE_RAVEN_DMCU; else return 0; break; default: switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(2, 0, 2): case IP_VERSION(2, 0, 3): case IP_VERSION(2, 0, 0): case IP_VERSION(2, 1, 0): case IP_VERSION(3, 0, 0): case IP_VERSION(3, 0, 2): case IP_VERSION(3, 0, 3): case IP_VERSION(3, 0, 1): case IP_VERSION(3, 1, 2): case IP_VERSION(3, 1, 3): case IP_VERSION(3, 1, 4): case IP_VERSION(3, 1, 5): case IP_VERSION(3, 1, 6): case IP_VERSION(3, 2, 0): case IP_VERSION(3, 2, 1): case IP_VERSION(3, 5, 0): case IP_VERSION(3, 5, 1): case IP_VERSION(3, 6, 0): case IP_VERSION(4, 0, 1): return 0; default: break;
}
drm_err(adev_to_drm(adev), "Unsupported ASIC type: 0x%X\n", adev->asic_type); return -EINVAL;
}
if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) {
DRM_DEBUG_KMS("dm: DMCU firmware not supported on direct or SMU loading\n"); return 0;
}
r = amdgpu_ucode_request(adev, &adev->dm.fw_dmcu, AMDGPU_UCODE_REQUIRED, "%s", fw_name_dmcu); if (r == -ENODEV) { /* DMCU firmware is not necessary, so don't raise a fuss if it's missing */
DRM_DEBUG_KMS("dm: DMCU firmware not found\n");
adev->dm.fw_dmcu = NULL; return 0;
} if (r) {
drm_err(adev_to_drm(adev), "amdgpu_dm: Can't validate firmware \"%s\"\n",
fw_name_dmcu);
amdgpu_ucode_release(&adev->dm.fw_dmcu); return r;
}
status = dmub_srv_calc_region_info(dmub_srv, ®ion_params,
®ion_info);
if (status != DMUB_STATUS_OK) {
drm_err(adev_to_drm(adev), "Error calculating DMUB region info: %d\n", status); return -EINVAL;
}
/* * Allocate a framebuffer based on the total size of all the regions. * TODO: Move this into GART.
*/
r = amdgpu_bo_create_kernel(adev, region_info.fb_size, PAGE_SIZE,
AMDGPU_GEM_DOMAIN_VRAM |
AMDGPU_GEM_DOMAIN_GTT,
&adev->dm.dmub_bo,
&adev->dm.dmub_bo_gpu_addr,
&adev->dm.dmub_bo_cpu_addr); if (r) return r;
/* Rebase the regions on the framebuffer address. */
memset(&memory_params, 0, sizeof(memory_params));
memory_params.cpu_fb_addr = adev->dm.dmub_bo_cpu_addr;
memory_params.gpu_fb_addr = adev->dm.dmub_bo_gpu_addr;
memory_params.region_info = ®ion_info;
memory_params.window_memory_type = window_memory_type;
/* Min backlight level after ABM reduction, Don't allow below 1% * 0xFFFF x 0.01 = 0x28F
*/
params.min_abm_backlight = 0x28F; /* In the case where abm is implemented on dmcub, * dmcu object will be null. * ABM 2.4 and up are implemented on dmcub.
*/ if (dmcu) { if (!dmcu_load_iram(dmcu, params)) return -EINVAL;
} elseif (adev->dm.dc->ctx->dmub_srv) { struct dc_link *edp_links[MAX_NUM_EDP]; int edp_num;
dc_get_edp_links(adev->dm.dc, edp_links, &edp_num); for (i = 0; i < edp_num; i++) { if (!dmub_init_abm_config(adev->dm.dc->res_pool, params, i)) return -EINVAL;
}
}
mutex_lock(&mgr->lock); if (!mgr->mst_primary) goto out_fail;
if (drm_dp_read_dpcd_caps(mgr->aux, mgr->dpcd) < 0) {
drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); goto out_fail;
}
ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL,
DP_MST_EN |
DP_UP_REQ_EN |
DP_UPSTREAM_IS_SRC); if (ret < 0) {
drm_dbg_kms(mgr->dev, "mst write failed - undocked during suspend?\n"); goto out_fail;
}
/* Some hubs forget their guids after they resume */
ret = drm_dp_dpcd_read(mgr->aux, DP_GUID, buf, sizeof(buf)); if (ret != sizeof(buf)) {
drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); goto out_fail;
}
import_guid(&guid, buf);
if (guid_is_null(&guid)) {
guid_gen(&guid);
export_guid(buf, &guid);
ret = drm_dp_dpcd_write(mgr->aux, DP_GUID, buf, sizeof(buf));
if (ret != sizeof(buf)) {
drm_dbg_kms(mgr->dev, "check mstb guid failed - undocked during suspend?\n"); goto out_fail;
}
}
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) continue;
aconnector = to_amdgpu_dm_connector(connector); if (aconnector->dc_link->type != dc_connection_mst_branch ||
aconnector->mst_root) continue;
mgr = &aconnector->mst_mgr;
if (suspend) {
drm_dp_mst_topology_mgr_suspend(mgr);
} else { /* if extended timeout is supported in hardware, * default to LTTPR timeout (3.2ms) first as a W/A for DP link layer * CTS 4.2.1.1 regression introduced by CTS specs requirement update.
*/
try_to_configure_aux_timeout(aconnector->dc_link->ddc, LINK_AUX_DEFAULT_LTTPR_TIMEOUT_PERIOD); if (!dp_is_lttpr_present(aconnector->dc_link))
try_to_configure_aux_timeout(aconnector->dc_link->ddc, LINK_AUX_DEFAULT_TIMEOUT_PERIOD);
/* TODO: move resume_mst_branch_status() into drm mst resume again * once topology probing work is pulled out from mst resume into mst * resume 2nd step. mst resume 2nd step should be called after old * state getting restored (i.e. drm_atomic_helper_resume()).
*/
resume_mst_branch_status(mgr);
}
}
drm_connector_list_iter_end(&iter);
}
staticint amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev)
{ int ret = 0;
/* This interface is for dGPU Navi1x.Linux dc-pplib interface depends * on window driver dc implementation. * For Navi1x, clock settings of dcn watermarks are fixed. the settings * should be passed to smu during boot up and resume from s3. * boot up: dc calculate dcn watermark clock settings within dc_create, * dcn20_resource_construct * then call pplib functions below to pass the settings to smu: * smu_set_watermarks_for_clock_ranges * smu_set_watermarks_table * navi10_set_watermarks_table * smu_write_watermarks_table * * For Renoir, clock settings of dcn watermark are also fixed values. * dc has implemented different flow for window driver: * dc_hardware_init / dc_set_power_state * dcn10_init_hw * notify_wm_ranges * set_wm_ranges * -- Linux * smu_set_watermarks_for_clock_ranges * renoir_set_watermarks_table * smu_write_watermarks_table * * For Linux, * dc_hardware_init -> amdgpu_dm_init * dc_set_power_state --> dm_resume * * therefore, this function apply to navi10/12/14 but not Renoir * *
*/ switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(2, 0, 2): case IP_VERSION(2, 0, 0): break; default: return 0;
}
ret = amdgpu_dpm_write_watermarks_table(adev); if (ret) {
drm_err(adev_to_drm(adev), "Failed to update WMTABLE!\n"); return ret;
}
if (dm->oem_i2c) {
i2c_del_adapter(&dm->oem_i2c->base);
kfree(dm->oem_i2c);
dm->oem_i2c = NULL;
}
}
/** * dm_hw_init() - Initialize DC device * @ip_block: Pointer to the amdgpu_ip_block for this hw instance. * * Initialize the &struct amdgpu_display_manager device. This involves calling * the initializers of each DM component, then populating the struct with them. * * Although the function implies hardware initialization, both hardware and * software are initialized here. Splitting them out to their relevant init * hooks is a future TODO item. * * Some notable things that are initialized here: * * - Display Core, both software and hardware * - DC modules that we need (freesync and color management) * - DRM software states * - Interrupt sources and handlers * - Vblank support * - Debug FS entries, if enabled
*/ staticint dm_hw_init(struct amdgpu_ip_block *ip_block)
{ struct amdgpu_device *adev = ip_block->adev; int r;
/* Create DAL display manager */
r = amdgpu_dm_init(adev); if (r) return r;
amdgpu_dm_hpd_init(adev);
r = dm_oem_i2c_hw_init(adev); if (r)
drm_info(adev_to_drm(adev), "Failed to add OEM i2c bus\n");
return 0;
}
/** * dm_hw_fini() - Teardown DC device * @ip_block: Pointer to the amdgpu_ip_block for this hw instance. * * Teardown components within &struct amdgpu_display_manager that require * cleanup. This involves cleaning up the DRM device, DC, and any modules that * were loaded. Also flush IRQ workqueues and disable them.
*/ staticint dm_hw_fini(struct amdgpu_ip_block *ip_block)
{ struct amdgpu_device *adev = ip_block->adev;
if (dc_supports_vrr(adev->dm.dc->ctx->dce_version)) { if (enable) { if (amdgpu_dm_crtc_vrr_active(
to_dm_crtc_state(acrtc->base.state)))
rc = amdgpu_dm_crtc_set_vupdate_irq(
&acrtc->base, true);
} else
rc = amdgpu_dm_crtc_set_vupdate_irq(
&acrtc->base, false);
if (rc)
drm_warn(adev_to_drm(adev), "Failed to %sable vupdate interrupt\n",
enable ? "en" : "dis");
}
irq_source = IRQ_TYPE_VBLANK + acrtc->otg_inst; /* During gpu-reset we disable and then enable vblank irq, so * don't use amdgpu_irq_get/put() to avoid refcount change.
*/ if (!dc_interrupt_set(adev->dm.dc, irq_source, enable))
drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis");
}
}
}
DEFINE_FREE(state_release, struct dc_state *, if (_T) dc_state_release(_T))
staticenum dc_status amdgpu_dm_commit_zero_streams(struct dc *dc)
{ struct dc_state *context __free(state_release) = NULL; int i; struct dc_stream_state *del_streams[MAX_PIPES]; int del_streams_count = 0; struct dc_commit_streams_params params = {};
memset(del_streams, 0, sizeof(del_streams));
context = dc_state_create_current_copy(dc); if (context == NULL) return DC_ERROR_UNEXPECTED;
/* First remove from context all streams */ for (i = 0; i < context->stream_count; i++) { struct dc_stream_state *stream = context->streams[i];
del_streams[del_streams_count++] = stream;
}
/* Remove all planes for removed streams and then remove the streams */ for (i = 0; i < del_streams_count; i++) { enum dc_status res;
if (!dc_state_rem_all_planes_for_stream(dc, del_streams[i], context)) return DC_FAIL_DETACH_SURFACES;
res = dc_state_remove_stream(dc, context, del_streams[i]); if (res != DC_OK) return res;
}
/* Force mode set in atomic commit */
for_each_new_crtc_in_state(dm->cached_state, crtc, new_crtc_state, i) {
new_crtc_state->active_changed = true;
dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
reset_freesync_config_for_crtc(dm_new_crtc_state);
}
/* * atomic_check is expected to create the dc states. We need to release * them here, since they were duplicated as part of the suspend * procedure.
*/
for_each_new_crtc_in_state(dm->cached_state, crtc, new_crtc_state, i) {
dm_new_crtc_state = to_dm_crtc_state(new_crtc_state); if (dm_new_crtc_state->stream) {
WARN_ON(kref_read(&dm_new_crtc_state->stream->refcount) > 1);
dc_stream_release(dm_new_crtc_state->stream);
dm_new_crtc_state->stream = NULL;
}
dm_new_crtc_state->base.color_mgmt_changed = true;
}
if (dm->cached_dc_state)
dm_gpureset_toggle_interrupts(adev, dm->cached_dc_state, false);
res = amdgpu_dm_commit_zero_streams(dm->dc); if (res != DC_OK) {
drm_err(adev_to_drm(adev), "Failed to commit zero streams: %d\n", res); return -EINVAL;
}
amdgpu_dm_irq_suspend(adev);
hpd_rx_irq_work_suspend(dm);
return 0;
}
if (!adev->dm.cached_state) { int r = dm_cache_state(adev);
if (dm->dc->caps.ips_support) {
dc_dmub_srv_apply_idle_power_optimizations(dm->dc, false);
}
if (amdgpu_in_reset(adev)) {
dc_state = dm->cached_dc_state;
/* * The dc->current_state is backed up into dm->cached_dc_state * before we commit 0 streams. * * DC will clear link encoder assignments on the real state * but the changes won't propagate over to the copy we made * before the 0 streams commit. * * DC expects that link encoder assignments are *not* valid * when committing a state, so as a workaround we can copy * off of the current state. * * We lose the previous assignments, but we had already * commit 0 streams anyway.
*/
link_enc_cfg_copy(adev->dm.dc->current_state, dc_state);
r = dm_dmub_hw_init(adev); if (r) {
drm_err(adev_to_drm(adev), "DMUB interface failed to initialize: status=%d\n", r); return r;
}
/* set the backlight after a reset */ for (i = 0; i < dm->num_of_edps; i++) { if (dm->backlight_dev[i])
amdgpu_dm_backlight_set_level(dm, i, dm->brightness[i]);
}
return 0;
} /* Recreate dc_state - DC invalidates it when setting power state to S3. */
dc_state_release(dm_state->context);
dm_state->context = dc_state_create(dm->dc, NULL); /* TODO: Remove dc_state->dccg, use dc->dccg directly. */
/* Before powering on DC we need to re-initialize DMUB. */
dm_dmub_hw_resume(adev);
/* Re-enable outbox interrupts for DPIA. */ if (dc_is_dmub_outbox_supported(adev->dm.dc)) {
amdgpu_dm_outbox_init(adev);
dc_enable_dmub_outbox(adev->dm.dc);
}
/* power on hardware */
dc_dmub_srv_set_power_state(dm->dc->ctx->dmub_srv, DC_ACPI_CM_POWER_STATE_D0);
dc_set_power_state(dm->dc, DC_ACPI_CM_POWER_STATE_D0);
/* program HPD filter */
dc_resume(dm->dc);
/* * 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);
s3_handle_hdmi_cec(ddev, false);
/* On resume we need to rewrite the MSTM control bits to enable MST*/
s3_handle_mst(ddev, false);
/* Do detection*/
drm_connector_list_iter_begin(ddev, &iter);
drm_for_each_connector_iter(connector, &iter) { bool ret;
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) continue;
aconnector = to_amdgpu_dm_connector(connector);
if (!aconnector->dc_link) continue;
/* * this is the case when traversing through already created end sink * MST connectors, should be skipped
*/ if (aconnector->mst_root) continue;
guard(mutex)(&aconnector->hpd_lock); if (!dc_link_detect_connection_type(aconnector->dc_link, &new_connection_type))
drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
if (aconnector->base.force && new_connection_type == dc_connection_none) {
emulated_link_detect(aconnector->dc_link);
} else {
guard(mutex)(&dm->dc_lock);
dc_exit_ips_for_hw_access(dm->dc);
ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_RESUMEFROMS3S4); if (ret) { /* w/a delay for certain panels */
apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink);
}
}
if (aconnector->fake_enable && aconnector->dc_link->local_sink)
aconnector->fake_enable = false;
if (aconnector->dc_sink)
dc_sink_release(aconnector->dc_sink);
aconnector->dc_sink = NULL;
amdgpu_dm_update_connector_after_detect(aconnector);
}
drm_connector_list_iter_end(&iter);
dm_destroy_cached_state(adev);
/* Do mst topology probing after resuming cached state*/
drm_connector_list_iter_begin(ddev, &iter);
drm_for_each_connector_iter(connector, &iter) { bool init = false;
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) continue;
aconnector = to_amdgpu_dm_connector(connector); if (aconnector->dc_link->type != dc_connection_mst_branch ||
aconnector->mst_root) continue;
/** * DOC: DM Lifecycle * * DM (and consequently DC) is registered in the amdgpu base driver as a IP * block. When CONFIG_DRM_AMD_DC is enabled, the DM device IP block is added to * the base driver's device list to be initialized and torn down accordingly. * * The functions to do so are provided as hooks in &struct amd_ip_funcs.
*/
/* MST handled by drm_mst framework */ if (aconnector->mst_mgr.mst_state == true) return;
sink = aconnector->dc_link->local_sink; if (sink)
dc_sink_retain(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. * Skip if already done 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 connector->sink is set to NULL
*/
guard(mutex)(&dev->mode_config.mutex);
if (sink) { if (aconnector->dc_sink) {
amdgpu_dm_update_freesync_caps(connector, NULL); /* * retain and release below are used to * bump up refcount for sink because the link doesn't point * to it anymore after disconnect, so on next crtc to connector * reshuffle by UMD we will get into unwanted dc_sink release
*/
dc_sink_release(aconnector->dc_sink);
}
aconnector->dc_sink = sink;
dc_sink_retain(aconnector->dc_sink);
amdgpu_dm_update_freesync_caps(connector,
aconnector->drm_edid);
} else {
amdgpu_dm_update_freesync_caps(connector, NULL); if (!aconnector->dc_sink) {
aconnector->dc_sink = aconnector->dc_em_sink;
dc_sink_retain(aconnector->dc_sink);
}
}
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_dbg_kms(dev, "DCHPD: connector_id=%d: dc_sink didn't change.\n",
aconnector->connector_id); return;
}
drm_dbg_kms(dev, "DCHPD: connector_id=%d: Old sink=%p New sink=%p\n",
aconnector->connector_id, aconnector->dc_sink, sink);
guard(mutex)(&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_update_freesync_caps(connector, NULL);
dc_sink_release(aconnector->dc_sink);
}
hdmi_cec_set_edid(aconnector); if (aconnector->dc_link->aux_mode)
drm_dp_cec_attach(&aconnector->dm_dp_aux.aux,
connector->display_info.source_physical_address);
}
if (!aconnector->timing_requested) {
aconnector->timing_requested =
kzalloc(sizeof(struct dc_crtc_timing), GFP_KERNEL); if (!aconnector->timing_requested)
drm_err(dev, "failed to create aconnector->requested_timing\n");
}
amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid);
update_connector_ext_caps(aconnector);
} else {
hdmi_cec_unset_edid(aconnector);
drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux);
amdgpu_dm_update_freesync_caps(connector, NULL);
aconnector->num_modes = 0;
dc_sink_release(aconnector->dc_sink);
aconnector->dc_sink = NULL;
drm_edid_free(aconnector->drm_edid);
aconnector->drm_edid = NULL;
kfree(aconnector->timing_requested);
aconnector->timing_requested = NULL; /* Set CP to DESIRED if it was ENABLED, so we can re-enable it again on hotplug */ if (connector->state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED)
connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED;
}
/* * In case of failure or MST no need to update connector status or notify the OS * since (for MST case) MST does this in its own context.
*/
guard(mutex)(&aconnector->hpd_lock);
if (adev->dm.hdcp_workqueue) {
hdcp_reset_display(adev->dm.hdcp_workqueue, aconnector->dc_link->link_index);
dm_con_state->update_hdcp = true;
} if (aconnector->fake_enable)
aconnector->fake_enable = false;
aconnector->timing_changed = false;
if (!dc_link_detect_connection_type(aconnector->dc_link, &new_connection_type))
drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
if (aconnector->base.force && new_connection_type == dc_connection_none) {
emulated_link_detect(aconnector->dc_link);
/* * TODO:Temporary add mutex to protect hpd interrupt not have a gpio * conflict, after implement i2c helper, this mutex should be * retired.
*/
mutex_lock(&aconnector->hpd_lock);
result = dc_link_handle_hpd_rx_irq(dc_link, &hpd_irq_data,
&link_loss, true, &has_left_work);
if (!has_left_work) goto out;
if (hpd_irq_data.bytes.device_service_irq.bits.AUTOMATED_TEST) {
schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); goto out;
}
if (dc_link_dp_allow_hpd_rx_irq(dc_link)) { if (hpd_irq_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY ||
hpd_irq_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { bool skip = false;
/* * DOWN_REP_MSG_RDY is also handled by polling method * mgr->cbs->poll_hpd_irq()
*/
spin_lock(&offload_wq->offload_lock);
skip = offload_wq->is_handling_mst_msg_rdy_event;
if (!skip)
offload_wq->is_handling_mst_msg_rdy_event = true;
spin_unlock(&offload_wq->offload_lock);
if (!skip)
schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data);
if (!skip)
offload_wq->is_handling_link_loss = true;
spin_unlock(&offload_wq->offload_lock);
if (!skip)
schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data);
goto out;
}
}
out: if (result && !is_mst_root_connector) { /* Downstream Port status changed. */ if (!dc_link_detect_connection_type(dc_link, &new_connection_type))
drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
if (aconnector->base.force && new_connection_type == dc_connection_none) {
emulated_link_detect(dc_link);
if (aconnector->fake_enable)
aconnector->fake_enable = false;
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
handle_hpd_irq, (void *) aconnector)) return -ENOMEM;
}
if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) {
/* 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;
/* * 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 = 0; i < adev->mode_info.num_crtc; i++) {
r = amdgpu_irq_add_id(adev, client_id, i + 1, &adev->crtc_irq); if (r) {
drm_err(adev_to_drm(adev), "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 + 1, 0);
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
dm_crtc_high_irq, c_irq_params)) return -ENOMEM;
}
/* 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, client_id, i, &adev->pageflip_irq); if (r) {
drm_err(adev_to_drm(adev), "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);
/* * 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 = VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0; i <= VISLANDS30_IV_SRCID_D6_VERTICAL_INTERRUPT0; i++) {
r = amdgpu_irq_add_id(adev, client_id, i, &adev->crtc_irq); if (r) {
drm_err(adev_to_drm(adev), "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);
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
dm_crtc_high_irq, c_irq_params)) return -ENOMEM;
}
/* Use VUPDATE interrupt */ for (i = VISLANDS30_IV_SRCID_D1_V_UPDATE_INT; i <= VISLANDS30_IV_SRCID_D6_V_UPDATE_INT; i += 2) {
r = amdgpu_irq_add_id(adev, client_id, i, &adev->vupdate_irq); if (r) {
drm_err(adev_to_drm(adev), "Failed to add vupdate 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);
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
dm_vupdate_high_irq, c_irq_params)) return -ENOMEM;
}
/* 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, client_id, i, &adev->pageflip_irq); if (r) {
drm_err(adev_to_drm(adev), "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);
/* * 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 VSTARTUP interrupt */ for (i = DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP;
i <= DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP + adev->mode_info.num_crtc - 1;
i++) {
r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->crtc_irq);
if (r) {
drm_err(adev_to_drm(adev), "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);
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
dm_crtc_high_irq, c_irq_params)) return -ENOMEM;
}
/* Use otg vertical line interrupt */ #ifdefined(CONFIG_DRM_AMD_SECURE_DISPLAY) for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) {
r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE,
vrtl_int_srcid[i], &adev->vline0_irq);
if (r) {
drm_err(adev_to_drm(adev), "Failed to add vline0 irq id!\n"); return r;
}
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
dm_dcn_vertical_interrupt0_high_irq,
c_irq_params)) return -ENOMEM;
} #endif
/* Use VUPDATE_NO_LOCK interrupt on DCN, which seems to correspond to * the regular VUPDATE interrupt on DCE. We want DC_IRQ_SOURCE_VUPDATEx * to trigger at end of each vblank, regardless of state of the lock, * matching DCE behaviour.
*/ for (i = DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT;
i <= DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT + adev->mode_info.num_crtc - 1;
i++) {
r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->vupdate_irq);
if (r) {
drm_err(adev_to_drm(adev), "Failed to add vupdate 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);
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
dm_vupdate_high_irq, c_irq_params)) return -ENOMEM;
}
/* Use GRPH_PFLIP interrupt */ for (i = DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT;
i <= DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT + dc->caps.max_otg_num - 1;
i++) {
r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->pageflip_irq); if (r) {
drm_err(adev_to_drm(adev), "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);
r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT,
&adev->dmub_outbox_irq); if (r) {
drm_err(adev_to_drm(adev), "Failed to add outbox irq id!\n"); return r;
}
if (dc->ctx->dmub_srv) {
i = DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT;
int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
int_params.irq_source =
dc_interrupt_to_irq_source(dc, i, 0);
if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
dm_dmub_outbox1_low_irq, c_irq_params)) return -ENOMEM;
}
return 0;
}
/* * Acquires the lock for the atomic state object and returns * the new atomic state. * * This should only be called during atomic check.
*/ int dm_atomic_get_state(struct drm_atomic_state *state, struct dm_atomic_state **dm_state)
{ struct drm_device *dev = state->dev; struct amdgpu_device *adev = drm_to_adev(dev); struct amdgpu_display_manager *dm = &adev->dm; struct drm_private_state *priv_state;
if (*dm_state) return 0;
priv_state = drm_atomic_get_private_obj_state(state, &dm->atomic_obj); if (IS_ERR(priv_state)) return PTR_ERR(priv_state);
adev_to_drm(adev)->mode_config.preferred_depth = 24; if (adev->asic_type == CHIP_HAWAII) /* disable prefer shadow for now due to hibernation issues */
adev_to_drm(adev)->mode_config.prefer_shadow = 0; else
adev_to_drm(adev)->mode_config.prefer_shadow = 1; /* indicates support for immediate flip */
adev_to_drm(adev)->mode_config.async_page_flip = true;
state = kzalloc(sizeof(*state), GFP_KERNEL); if (!state) return -ENOMEM;
state->context = dc_state_create_current_copy(adev->dm.dc); if (!state->context) {
kfree(state); return -ENOMEM;
}
if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE) return;
if (!caps->data_points) return;
/* choose start to run less interpolation steps */ if (caps->luminance_data[caps->data_points/2].input_signal > brightness)
i = caps->data_points/2; do {
u8 signal = caps->luminance_data[i].input_signal;
u8 lum = caps->luminance_data[i].luminance;
/* * brightness == signal: luminance is percent numerator * brightness < signal: interpolate between previous and current luminance numerator * brightness > signal: find next data point
*/ if (brightness > signal) {
prev_signal = signal;
prev_lum = lum;
i++; continue;
} if (brightness < signal)
lum = prev_lum + DIV_ROUND_CLOSEST((lum - prev_lum) *
(brightness - prev_signal),
signal - prev_signal);
*user_brightness = scale_fw_to_input(min, max,
DIV_ROUND_CLOSEST(lum * brightness, 101)); return;
} while (i < caps->data_points);
}
/* Change brightness based on AUX property */
mutex_lock(&dm->dc_lock); if (dm->dc->caps.ips_support && dm->dc->ctx->dmub_srv->idle_allowed) {
dc_allow_idle_optimizations(dm->dc, false);
reallow_idle = true;
}
for (i = 0; i < dm->num_of_edps; i++) { if (bd == dm->backlight_dev[i]) break;
} if (i >= AMDGPU_DM_MAX_NUM_EDP)
i = 0;
amdgpu_dm_backlight_set_level(dm, i, bd->props.brightness);
return 0;
}
static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx)
{ int ret; struct amdgpu_dm_backlight_caps caps; struct dc_link *link = (struct dc_link *)dm->backlight_link[bl_idx];
for (i = 0; i < dm->num_of_edps; i++) { if (bd == dm->backlight_dev[i]) break;
} if (i >= AMDGPU_DM_MAX_NUM_EDP)
i = 0; return amdgpu_dm_backlight_get_level(dm, i);
}
staticint initialize_plane(struct amdgpu_display_manager *dm, struct amdgpu_mode_info *mode_info, int plane_id, enum drm_plane_type plane_type, conststruct dc_plane_cap *plane_cap)
{ struct drm_plane *plane; unsignedlong possible_crtcs; int ret = 0;
plane = kzalloc(sizeof(struct drm_plane), GFP_KERNEL); if (!plane) {
drm_err(adev_to_drm(dm->adev), "KMS: Failed to allocate plane\n"); return -ENOMEM;
}
plane->type = plane_type;
/* * HACK: IGT tests expect that the primary plane for a CRTC * can only have one possible CRTC. Only expose support for * any CRTC if they're not going to be used as a primary plane * for a CRTC - like overlay or underlay planes.
*/
possible_crtcs = 1 << plane_id; if (plane_id >= dm->dc->caps.max_streams)
possible_crtcs = 0xff;
ret = amdgpu_dm_plane_init(dm, plane, possible_crtcs, plane_cap);
if (ret) {
drm_err(adev_to_drm(dm->adev), "KMS: Failed to initialize plane\n");
kfree(plane); return ret;
}
if (mode_info)
mode_info->planes[plane_id] = plane;
/* * 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
*/ staticint amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev)
{ struct amdgpu_display_manager *dm = &adev->dm;
s32 i; struct amdgpu_dm_connector *aconnector = NULL; struct amdgpu_encoder *aencoder = NULL; struct amdgpu_mode_info *mode_info = &adev->mode_info;
u32 link_cnt;
s32 primary_planes; enum dc_connection_type new_connection_type = dc_connection_none; conststruct dc_plane_cap *plane; bool psr_feature_enabled = false; bool replay_feature_enabled = false; int max_overlay = dm->dc->caps.max_slave_planes;
dm->display_indexes_num = dm->dc->caps.max_streams; /* Update the actual used number of crtc */
adev->mode_info.num_crtc = adev->dm.display_indexes_num;
amdgpu_dm_set_irq_funcs(adev);
link_cnt = dm->dc->caps.max_links; if (amdgpu_dm_mode_config_init(dm->adev)) {
drm_err(adev_to_drm(adev), "DM: Failed to initialize mode config\n"); return -EINVAL;
}
/* There is one primary plane per CRTC */
primary_planes = dm->dc->caps.max_streams; if (primary_planes > AMDGPU_MAX_PLANES) {
drm_err(adev_to_drm(adev), "DM: Plane nums out of 6 planes\n"); return -EINVAL;
}
/* * Initialize primary planes, implicit planes for legacy IOCTLS. * Order is reversed to match iteration order in atomic check.
*/ for (i = (primary_planes - 1); i >= 0; i--) {
plane = &dm->dc->caps.planes[i];
if (initialize_plane(dm, mode_info, i,
DRM_PLANE_TYPE_PRIMARY, plane)) {
drm_err(adev_to_drm(adev), "KMS: Failed to initialize primary plane\n"); goto fail;
}
}
/* * Initialize overlay planes, index starting after primary planes. * These planes have a higher DRM index than the primary planes since * they should be considered as having a higher z-order. * Order is reversed to match iteration order in atomic check. * * Only support DCN for now, and only expose one so we don't encourage * userspace to use up all the pipes.
*/ for (i = 0; i < dm->dc->caps.max_planes; ++i) { struct dc_plane_cap *plane = &dm->dc->caps.planes[i];
/* Do not create overlay if MPO disabled */ if (amdgpu_dc_debug_mask & DC_DISABLE_MPO) break;
if (plane->type != DC_PLANE_TYPE_DCN_UNIVERSAL) continue;
if (!plane->pixel_format_support.argb8888) continue;
if (max_overlay-- == 0) break;
if (initialize_plane(dm, NULL, primary_planes + i,
DRM_PLANE_TYPE_OVERLAY, plane)) {
drm_err(adev_to_drm(adev), "KMS: Failed to initialize overlay plane\n"); goto fail;
}
}
for (i = 0; i < dm->dc->caps.max_streams; i++) if (amdgpu_dm_crtc_init(dm, mode_info->planes[i], i)) {
drm_err(adev_to_drm(adev), "KMS: Failed to initialize crtc\n"); goto fail;
}
/* Use Outbox interrupt */ switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(3, 0, 0): case IP_VERSION(3, 1, 2): case IP_VERSION(3, 1, 3): case IP_VERSION(3, 1, 4): case IP_VERSION(3, 1, 5): case IP_VERSION(3, 1, 6): case IP_VERSION(3, 2, 0): case IP_VERSION(3, 2, 1): case IP_VERSION(2, 1, 0): case IP_VERSION(3, 5, 0): case IP_VERSION(3, 5, 1): case IP_VERSION(3, 6, 0): case IP_VERSION(4, 0, 1): if (register_outbox_irq_handlers(dm->adev)) {
drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n"); goto fail;
} break; default:
DRM_DEBUG_KMS("Unsupported DCN IP version for outbox: 0x%X\n",
amdgpu_ip_version(adev, DCE_HWIP, 0));
}
/* Determine whether to enable PSR support by default. */ if (!(amdgpu_dc_debug_mask & DC_DISABLE_PSR)) { switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(3, 1, 2): case IP_VERSION(3, 1, 3): case IP_VERSION(3, 1, 4): case IP_VERSION(3, 1, 5): case IP_VERSION(3, 1, 6): case IP_VERSION(3, 2, 0): case IP_VERSION(3, 2, 1): case IP_VERSION(3, 5, 0): case IP_VERSION(3, 5, 1): case IP_VERSION(3, 6, 0): case IP_VERSION(4, 0, 1):
psr_feature_enabled = true; break; default:
psr_feature_enabled = amdgpu_dc_feature_mask & DC_PSR_MASK; break;
}
}
/* Determine whether to enable Replay support by default. */ if (!(amdgpu_dc_debug_mask & DC_DISABLE_REPLAY)) { switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(3, 1, 4): case IP_VERSION(3, 2, 0): case IP_VERSION(3, 2, 1): case IP_VERSION(3, 5, 0): case IP_VERSION(3, 5, 1): case IP_VERSION(3, 6, 0):
replay_feature_enabled = true; break;
aconnector = kzalloc(sizeof(*aconnector), GFP_KERNEL); if (!aconnector) goto fail;
aencoder = kzalloc(sizeof(*aencoder), GFP_KERNEL); if (!aencoder) goto fail;
if (amdgpu_dm_encoder_init(dm->ddev, aencoder, i)) {
drm_err(adev_to_drm(adev), "KMS: Failed to initialize encoder\n"); goto fail;
}
if (amdgpu_dm_connector_init(dm, aconnector, i, aencoder)) {
drm_err(adev_to_drm(adev), "KMS: Failed to initialize connector\n"); goto fail;
}
if (dm->hpd_rx_offload_wq)
dm->hpd_rx_offload_wq[aconnector->base.index].aconnector =
aconnector;
if (!dc_link_detect_connection_type(link, &new_connection_type))
drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
if (aconnector->base.force && new_connection_type == dc_connection_none) {
emulated_link_detect(link);
amdgpu_dm_update_connector_after_detect(aconnector);
} else { bool ret = false;
mutex_lock(&dm->dc_lock);
dc_exit_ips_for_hw_access(dm->dc);
ret = dc_link_detect(link, DETECT_REASON_BOOT);
mutex_unlock(&dm->dc_lock);
if (ret) {
amdgpu_dm_update_connector_after_detect(aconnector);
setup_backlight_device(dm, aconnector);
/* Disable PSR if Replay can be enabled */ if (replay_feature_enabled) if (amdgpu_dm_set_replay_caps(link, aconnector))
psr_feature_enabled = false;
if (psr_feature_enabled) {
amdgpu_dm_set_psr_caps(link);
drm_info(adev_to_drm(adev), "PSR support %d, DC PSR ver %d, sink PSR ver %d DPCD caps 0x%x su_y_granularity %d\n",
link->psr_settings.psr_feature_enabled,
link->psr_settings.psr_version,
link->dpcd_caps.psr_info.psr_version,
link->dpcd_caps.psr_info.psr_dpcd_caps.raw,
link->dpcd_caps.psr_info.psr2_su_y_granularity_cap);
}
}
}
amdgpu_set_panel_orientation(&aconnector->base);
}
/* Software is initialized. Now we can register interrupt handlers. */ switch (adev->asic_type) { #ifdefined(CONFIG_DRM_AMD_DC_SI) case CHIP_TAHITI: case CHIP_PITCAIRN: case CHIP_VERDE: case CHIP_OLAND: if (dce60_register_irq_handlers(dm->adev)) {
drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n"); goto fail;
} break; #endif case CHIP_BONAIRE: case CHIP_HAWAII: case CHIP_KAVERI: case CHIP_KABINI: case CHIP_MULLINS: case CHIP_TONGA: case CHIP_FIJI: case CHIP_CARRIZO: case CHIP_STONEY: case CHIP_POLARIS11: case CHIP_POLARIS10: case CHIP_POLARIS12: case CHIP_VEGAM: case CHIP_VEGA10: case CHIP_VEGA12: case CHIP_VEGA20: if (dce110_register_irq_handlers(dm->adev)) {
drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n"); goto fail;
} break; default: switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(1, 0, 0): case IP_VERSION(1, 0, 1): case IP_VERSION(2, 0, 2): case IP_VERSION(2, 0, 3): case IP_VERSION(2, 0, 0): case IP_VERSION(2, 1, 0): case IP_VERSION(3, 0, 0): case IP_VERSION(3, 0, 2): case IP_VERSION(3, 0, 3): case IP_VERSION(3, 0, 1): case IP_VERSION(3, 1, 2): case IP_VERSION(3, 1, 3): case IP_VERSION(3, 1, 4): case IP_VERSION(3, 1, 5): case IP_VERSION(3, 1, 6): case IP_VERSION(3, 2, 0): case IP_VERSION(3, 2, 1): case IP_VERSION(3, 5, 0): case IP_VERSION(3, 5, 1): case IP_VERSION(3, 6, 0): case IP_VERSION(4, 0, 1): if (dcn10_register_irq_handlers(dm->adev)) {
drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n"); goto fail;
} break; default:
drm_err(adev_to_drm(adev), "Unsupported DCE IP versions: 0x%X\n",
amdgpu_ip_version(adev, DCE_HWIP, 0)); goto fail;
} break;
}
/* * dm_bandwidth_update - program display watermarks * * @adev: amdgpu_device pointer * * Calculate and program the display watermarks and line buffer allocation.
*/ staticvoid dm_bandwidth_update(struct amdgpu_device *adev)
{ /* TODO: implement later */
}
staticconststruct amdgpu_display_funcs dm_display_funcs = {
.bandwidth_update = dm_bandwidth_update, /* called unconditionally */
.vblank_get_counter = dm_vblank_get_counter,/* called unconditionally */
.backlight_set_level = NULL, /* never called for DC */
.backlight_get_level = NULL, /* never called for DC */
.hpd_sense = NULL,/* called unconditionally */
.hpd_set_polarity = NULL, /* called unconditionally */
.hpd_get_gpio_reg = NULL, /* VBIOS parsing. DAL does it. */
.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. */
};
/* if there is no object header, skip DM */ if (!amdgpu_atom_parse_data_header(ctx, index, NULL, NULL, NULL, &data_offset)) {
adev->harvest_ip_mask |= AMD_HARVEST_IP_DMU_MASK;
drm_info(adev_to_drm(adev), "No object header, skipping DM\n"); return -ENOENT;
}
switch (adev->asic_type) { #ifdefined(CONFIG_DRM_AMD_DC_SI) case CHIP_TAHITI: case CHIP_PITCAIRN: case CHIP_VERDE:
adev->mode_info.num_crtc = 6;
adev->mode_info.num_hpd = 6;
adev->mode_info.num_dig = 6; break; case CHIP_OLAND:
adev->mode_info.num_crtc = 2;
adev->mode_info.num_hpd = 2;
adev->mode_info.num_dig = 2; break; #endif 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_KAVERI:
adev->mode_info.num_crtc = 4;
adev->mode_info.num_hpd = 6;
adev->mode_info.num_dig = 7; break; case CHIP_KABINI: case CHIP_MULLINS:
adev->mode_info.num_crtc = 2;
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: case CHIP_VEGAM:
adev->mode_info.num_crtc = 6;
adev->mode_info.num_hpd = 6;
adev->mode_info.num_dig = 6; break; case CHIP_VEGA10: case CHIP_VEGA12: case CHIP_VEGA20:
adev->mode_info.num_crtc = 6;
adev->mode_info.num_hpd = 6;
adev->mode_info.num_dig = 6; break; default:
switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(2, 0, 2): case IP_VERSION(3, 0, 0):
adev->mode_info.num_crtc = 6;
adev->mode_info.num_hpd = 6;
adev->mode_info.num_dig = 6; break; case IP_VERSION(2, 0, 0): case IP_VERSION(3, 0, 2):
adev->mode_info.num_crtc = 5;
adev->mode_info.num_hpd = 5;
adev->mode_info.num_dig = 5; break; case IP_VERSION(2, 0, 3): case IP_VERSION(3, 0, 3):
adev->mode_info.num_crtc = 2;
adev->mode_info.num_hpd = 2;
adev->mode_info.num_dig = 2; break; case IP_VERSION(1, 0, 0): case IP_VERSION(1, 0, 1): case IP_VERSION(3, 0, 1): case IP_VERSION(2, 1, 0): case IP_VERSION(3, 1, 2): case IP_VERSION(3, 1, 3): case IP_VERSION(3, 1, 4): case IP_VERSION(3, 1, 5): case IP_VERSION(3, 1, 6): case IP_VERSION(3, 2, 0): case IP_VERSION(3, 2, 1): case IP_VERSION(3, 5, 0): case IP_VERSION(3, 5, 1): case IP_VERSION(3, 6, 0): case IP_VERSION(4, 0, 1):
adev->mode_info.num_crtc = 4;
adev->mode_info.num_hpd = 4;
adev->mode_info.num_dig = 4; break; default:
drm_err(adev_to_drm(adev), "Unsupported DCE IP versions: 0x%x\n",
amdgpu_ip_version(adev, DCE_HWIP, 0)); return -EINVAL;
} break;
}
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()
*/ #ifdefined(CONFIG_DEBUG_KERNEL_DC)
device_create_file(
adev_to_drm(adev)->dev,
&dev_attr_s3_debug); #endif
adev->dc_enabled = true;
switch (fb->format->format) { case DRM_FORMAT_C8:
plane_info->format =
SURFACE_PIXEL_FORMAT_GRPH_PALETA_256_COLORS; break; case DRM_FORMAT_RGB565:
plane_info->format = SURFACE_PIXEL_FORMAT_GRPH_RGB565; break; case DRM_FORMAT_XRGB8888: case DRM_FORMAT_ARGB8888:
plane_info->format = SURFACE_PIXEL_FORMAT_GRPH_ARGB8888; break; case DRM_FORMAT_XRGB2101010: case DRM_FORMAT_ARGB2101010:
plane_info->format = SURFACE_PIXEL_FORMAT_GRPH_ARGB2101010; break; case DRM_FORMAT_XBGR2101010: case DRM_FORMAT_ABGR2101010:
plane_info->format = SURFACE_PIXEL_FORMAT_GRPH_ABGR2101010; break; case DRM_FORMAT_XBGR8888: case DRM_FORMAT_ABGR8888:
plane_info->format = SURFACE_PIXEL_FORMAT_GRPH_ABGR8888; break; case DRM_FORMAT_NV21:
plane_info->format = SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr; break; case DRM_FORMAT_NV12:
plane_info->format = SURFACE_PIXEL_FORMAT_VIDEO_420_YCrCb; break; case DRM_FORMAT_P010:
plane_info->format = SURFACE_PIXEL_FORMAT_VIDEO_420_10bpc_YCrCb; break; case DRM_FORMAT_XRGB16161616F: case DRM_FORMAT_ARGB16161616F:
plane_info->format = SURFACE_PIXEL_FORMAT_GRPH_ARGB16161616F; break; case DRM_FORMAT_XBGR16161616F: case DRM_FORMAT_ABGR16161616F:
plane_info->format = SURFACE_PIXEL_FORMAT_GRPH_ABGR16161616F; break; case DRM_FORMAT_XRGB16161616: case DRM_FORMAT_ARGB16161616:
plane_info->format = SURFACE_PIXEL_FORMAT_GRPH_ARGB16161616; break; case DRM_FORMAT_XBGR16161616: case DRM_FORMAT_ABGR16161616:
plane_info->format = SURFACE_PIXEL_FORMAT_GRPH_ABGR16161616; break; default:
drm_err(adev_to_drm(adev), "Unsupported screen format %p4cc\n",
&fb->format->format); return -EINVAL;
}
switch (plane_state->rotation & DRM_MODE_ROTATE_MASK) { case DRM_MODE_ROTATE_0:
plane_info->rotation = ROTATION_ANGLE_0; break; case DRM_MODE_ROTATE_90:
plane_info->rotation = ROTATION_ANGLE_90; break; case DRM_MODE_ROTATE_180:
plane_info->rotation = ROTATION_ANGLE_180; break; case DRM_MODE_ROTATE_270:
plane_info->rotation = ROTATION_ANGLE_270; break; default:
plane_info->rotation = ROTATION_ANGLE_0; break;
}
/* * Always set input transfer function, since plane state is refreshed * every time.
*/
ret = amdgpu_dm_update_plane_color_mgmt(dm_crtc_state,
plane_state,
dc_plane_state); if (ret) return ret;
return 0;
}
staticinlinevoid fill_dc_dirty_rect(struct drm_plane *plane, struct rect *dirty_rect, int32_t x,
s32 y, s32 width, s32 height, int *i, bool ffu)
{
WARN_ON(*i >= DC_MAX_DIRTY_RECTS);
if (ffu)
drm_dbg(plane->dev, "[PLANE:%d] PSR FFU dirty rect size (%d, %d)\n",
plane->base.id, width, height); else
drm_dbg(plane->dev, "[PLANE:%d] PSR SU dirty rect at (%d, %d) size (%d, %d)",
plane->base.id, x, y, width, height);
(*i)++;
}
/** * fill_dc_dirty_rects() - Fill DC dirty regions for PSR selective updates * * @plane: DRM plane containing dirty regions that need to be flushed to the eDP * remote fb * @old_plane_state: Old state of @plane * @new_plane_state: New state of @plane * @crtc_state: New state of CRTC connected to the @plane * @flip_addrs: DC flip tracking struct, which also tracts dirty rects * @is_psr_su: Flag indicating whether Panel Self Refresh Selective Update (PSR SU) is enabled. * If PSR SU is enabled and damage clips are available, only the regions of the screen * that have changed will be updated. If PSR SU is not enabled, * or if damage clips are not available, the entire screen will be updated. * @dirty_regions_changed: dirty regions changed * * For PSR SU, DC informs the DMUB uController of dirty rectangle regions * (referred to as "damage clips" in DRM nomenclature) that require updating on * the eDP remote buffer. The responsibility of specifying the dirty regions is * amdgpu_dm's. * * A damage-aware DRM client should fill the FB_DAMAGE_CLIPS property on the * plane with regions that require flushing to the eDP remote buffer. In * addition, certain use cases - such as cursor and multi-plane overlay (MPO) - * implicitly provide damage clips without any client support via the plane * bounds.
*/ staticvoid fill_dc_dirty_rects(struct drm_plane *plane, struct drm_plane_state *old_plane_state, struct drm_plane_state *new_plane_state, struct drm_crtc_state *crtc_state, struct dc_flip_addrs *flip_addrs, bool is_psr_su, bool *dirty_regions_changed)
{ struct dm_crtc_state *dm_crtc_state = to_dm_crtc_state(crtc_state); struct rect *dirty_rects = flip_addrs->dirty_rects;
u32 num_clips; struct drm_mode_rect *clips; bool bb_changed; bool fb_changed;
u32 i = 0;
*dirty_regions_changed = false;
/* * Cursor plane has it's own dirty rect update interface. See * dcn10_dmub_update_cursor_data and dmub_cmd_update_cursor_info_data
*/ if (plane->type == DRM_PLANE_TYPE_CURSOR) return;
if (new_plane_state->rotation != DRM_MODE_ROTATE_0) goto ffu;
/* * MPO is requested. Add entire plane bounding box to dirty rects if * flipped to or damaged. * * If plane is moved or resized, also add old bounding box to dirty * rects.
*/
fb_changed = old_plane_state->fb->base.id !=
new_plane_state->fb->base.id;
bb_changed = (old_plane_state->crtc_x != new_plane_state->crtc_x ||
old_plane_state->crtc_y != new_plane_state->crtc_y ||
old_plane_state->crtc_w != new_plane_state->crtc_w ||
old_plane_state->crtc_h != new_plane_state->crtc_h);
drm_dbg(plane->dev, "[PLANE:%d] PSR bb_changed:%d fb_changed:%d num_clips:%d\n",
new_plane_state->plane->base.id,
bb_changed, fb_changed, num_clips);
/* Add old plane bounding-box if plane is moved or resized */
fill_dc_dirty_rect(new_plane_state->plane, &dirty_rects[i],
old_plane_state->crtc_x,
old_plane_state->crtc_y,
old_plane_state->crtc_w,
old_plane_state->crtc_h, &i, false);
}
/* Cap display bpc based on HDMI 2.0 HF-VSDB */ if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48)
bpc = 16; elseif (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36)
bpc = 12; elseif (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30)
bpc = 10;
} else {
bpc = (uint8_t)connector->display_info.bpc; /* Assume 8 bpc by default if no bpc is specified. */
bpc = bpc ? bpc : 8;
}
if (requested_bpc > 0) { /* * Cap display bpc based on the user requested value. * * The value for state->max_bpc may not correctly updated * depending on when the connector gets added to the state * or if this was called outside of atomic check, so it * can't be used directly.
*/
bpc = min_t(u8, bpc, requested_bpc);
/* Round down to the nearest even number. */
bpc = bpc - (bpc & 1);
}
switch (bpc) { case 0: /* * Temporary Work around, DRM doesn't parse color depth for * EDID revision before 1.4 * TODO: Fix edid parsing
*/ return COLOR_DEPTH_888; case 6: return COLOR_DEPTH_666; case 8: return COLOR_DEPTH_888; case 10: return COLOR_DEPTH_101010; case 12: return COLOR_DEPTH_121212; case 14: return COLOR_DEPTH_141414; case 16: return COLOR_DEPTH_161616; default: return COLOR_DEPTH_UNDEFINED;
}
}
staticenum dc_aspect_ratio
get_aspect_ratio(conststruct drm_display_mode *mode_in)
{ /* 1-1 mapping, since both enums follow the HDMI spec. */ return (enum dc_aspect_ratio) mode_in->picture_aspect_ratio;
}
if (cea_revision >= 3) {
audio_info->mode_count = edid_caps->audio_mode_count;
for (i = 0; i < audio_info->mode_count; ++i) {
audio_info->modes[i].format_code =
(enum audio_format_code)
(edid_caps->audio_modes[i].format_code);
audio_info->modes[i].channel_count =
edid_caps->audio_modes[i].channel_count;
audio_info->modes[i].sample_rates.all =
edid_caps->audio_modes[i].sample_rate;
audio_info->modes[i].sample_size =
edid_caps->audio_modes[i].sample_size;
}
}
audio_info->flags.all = edid_caps->speaker_flags;
/* TODO: We only check for the progressive mode, check for interlace mode too */ if (drm_connector->latency_present[0]) {
audio_info->video_latency = drm_connector->video_latency[0];
audio_info->audio_latency = drm_connector->audio_latency[0];
}
/* TODO: For DP, video and audio latency should be calculated from DPCD caps */
staticvoid dm_enable_per_frame_crtc_master_sync(struct dc_state *context)
{ int i = 0; struct dc_stream_state *stream;
if (context->stream_count < 2) return; for (i = 0; i < context->stream_count ; i++) { if (!context->streams[i]) continue; /* * TODO: add a function to read AMD VSDB bits and set * crtc_sync_master.multi_sync_enabled flag * For now it's set to false
*/
}
for (i = 0; i < context->stream_count ; i++) {
stream = context->streams[i];
if (!stream) continue;
set_multisync_trigger_params(stream);
}
}
/** * DOC: FreeSync Video * * When a userspace application wants to play a video, the content follows a * standard format definition that usually specifies the FPS for that format. * The below list illustrates some video format and the expected FPS, * respectively: * * - TV/NTSC (23.976 FPS) * - Cinema (24 FPS) * - TV/PAL (25 FPS) * - TV/NTSC (29.97 FPS) * - TV/NTSC (30 FPS) * - Cinema HFR (48 FPS) * - TV/PAL (50 FPS) * - Commonly used (60 FPS) * - Multiples of 24 (48,72,96 FPS) * * The list of standards video format is not huge and can be added to the * connector modeset list beforehand. With that, userspace can leverage * FreeSync to extends the front porch in order to attain the target refresh * rate. Such a switch will happen seamlessly, without screen blanking or * reprogramming of the output in any other way. If the userspace requests a * modesetting change compatible with FreeSync modes that only differ in the * refresh rate, DC will skip the full update and avoid blink during the * transition. For example, the video player can change the modesetting from * 60Hz to 30Hz for playing TV/NTSC content when it goes full screen without * causing any display blink. This same concept can be applied to a mode * setting change.
*/ staticstruct drm_display_mode *
get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector, bool use_probed_modes)
{ struct drm_display_mode *m, *m_pref = NULL;
u16 current_refresh, highest_refresh; struct list_head *list_head = use_probed_modes ?
&aconnector->base.probed_modes :
&aconnector->base.modes;
if (aconnector->base.connector_type == DRM_MODE_CONNECTOR_WRITEBACK) return NULL;
if (aconnector->freesync_vid_base.clock != 0) return &aconnector->freesync_vid_base;
/* Find the preferred mode */
list_for_each_entry(m, list_head, head) { if (m->type & DRM_MODE_TYPE_PREFERRED) {
m_pref = m; break;
}
}
if (!m_pref) { /* Probably an EDID with no preferred mode. Fallback to first entry */
m_pref = list_first_entry_or_null(
&aconnector->base.modes, struct drm_display_mode, head); if (!m_pref) {
drm_dbg_driver(aconnector->base.dev, "No preferred mode found in EDID\n"); return NULL;
}
}
highest_refresh = drm_mode_vrefresh(m_pref);
/* * Find the mode with highest refresh rate with same resolution. * For some monitors, preferred mode is not the mode with highest * supported refresh rate.
*/
list_for_each_entry(m, list_head, head) {
current_refresh = drm_mode_vrefresh(m);
/* Set DSC policy according to dsc_clock_en */
dc_dsc_policy_set_enable_dsc_when_not_needed(
aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE);
if (timing_bw_in_kbps > max_supported_bw_in_kbps &&
max_supported_bw_in_kbps > 0 &&
dsc_max_supported_bw_in_kbps > 0) if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0],
dsc_caps,
&dsc_options,
dsc_max_supported_bw_in_kbps,
&stream->timing,
dc_link_get_highest_encoding_format(aconnector->dc_link),
&stream->timing.dsc_cfg)) {
stream->timing.flags.DSC = 1;
drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from DP-HDMI PCON\n",
__func__, drm_connector->name);
}
}
}
/* Overwrite the stream flag if DSC is enabled through debugfs */ if (aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE)
stream->timing.flags.DSC = 1;
if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_h)
stream->timing.dsc_cfg.num_slices_h = aconnector->dsc_settings.dsc_num_slices_h;
if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_v)
stream->timing.dsc_cfg.num_slices_v = aconnector->dsc_settings.dsc_num_slices_v;
if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_bits_per_pixel)
stream->timing.dsc_cfg.bits_per_pixel = aconnector->dsc_settings.dsc_bits_per_pixel;
} #endif
list_for_each_entry(preferred_mode, &connector->modes, head) { /* Search for preferred mode */ if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) {
native_mode_found = true; break;
}
} if (!native_mode_found)
preferred_mode = list_first_entry_or_null(
&connector->modes, struct drm_display_mode,
head);
mode_refresh = drm_mode_vrefresh(&mode);
if (preferred_mode == NULL) { /* * This may not be an error, the use case is when we have no * usermode calls to reset and set mode upon hotplug. In this * case, we call set mode ourselves to restore the previous mode * and the modelist may not be filled in time.
*/
drm_dbg_driver(dev, "No preferred mode found\n");
} elseif (aconnector) {
recalculate_timing = amdgpu_freesync_vid_mode &&
is_freesync_video_mode(&mode, aconnector); if (recalculate_timing) {
freesync_mode = get_highest_refresh_rate_mode(aconnector, false);
drm_mode_copy(&saved_mode, &mode);
saved_mode.picture_aspect_ratio = mode.picture_aspect_ratio;
drm_mode_copy(&mode, freesync_mode);
mode.picture_aspect_ratio = saved_mode.picture_aspect_ratio;
} else {
decide_crtc_timing_for_drm_display_mode(
&mode, preferred_mode, scale);
if (recalculate_timing)
drm_mode_set_crtcinfo(&saved_mode, 0);
/* * If scaling is enabled and refresh rate didn't change * we copy the vic and polarities of the old timings
*/ if (!scale || mode_refresh != preferred_refresh)
fill_stream_properties_from_drm_display_mode(
stream, &mode, connector, con_state, NULL,
requested_bpc); else
fill_stream_properties_from_drm_display_mode(
stream, &mode, connector, con_state, old_stream,
requested_bpc);
/* The rest isn't needed for writeback connectors */ if (!aconnector) goto finish;
if (aconnector->timing_changed) {
drm_dbg(aconnector->base.dev, "overriding timing for automated test, bpc %d, changing to %d\n",
stream->timing.display_color_depth,
aconnector->timing_requested->display_color_depth);
stream->timing = *aconnector->timing_requested;
}
/* * Notes: * 1. This interface is NOT called in context of HPD irq. * 2. This interface *is called* in context of user-mode ioctl. Which * makes it a bad place for *any* MST-related activity.
*/
if (property == dev->mode_config.scaling_mode_property) { switch (dm_state->scaling) { case RMX_CENTER:
*val = DRM_MODE_SCALE_CENTER; break; case RMX_ASPECT:
*val = DRM_MODE_SCALE_ASPECT; break; case RMX_FULL:
*val = DRM_MODE_SCALE_FULLSCREEN; break; case RMX_OFF: default:
*val = DRM_MODE_SCALE_NONE; break;
}
ret = 0;
} elseif (property == adev->mode_info.underscan_hborder_property) {
*val = dm_state->underscan_hborder;
ret = 0;
} elseif (property == adev->mode_info.underscan_vborder_property) {
*val = dm_state->underscan_vborder;
ret = 0;
} elseif (property == adev->mode_info.underscan_property) {
*val = dm_state->underscan_enable;
ret = 0;
}
return ret;
}
/** * DOC: panel power savings * * The display manager allows you to set your desired **panel power savings** * level (between 0-4, with 0 representing off), e.g. using the following:: * * # echo 3 > /sys/class/drm/card0-eDP-1/amdgpu/panel_power_savings * * Modifying this value can have implications on color accuracy, so tread * carefully.
*/
/* * Call only if mst_mgr was initialized before since it's not done * for all connector types.
*/ if (aconnector->mst_mgr.dev)
drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr);
if (aconnector->bl_idx != -1) {
backlight_device_unregister(dm->backlight_dev[aconnector->bl_idx]);
dm->backlight_dev[aconnector->bl_idx] = NULL;
}
if (aconnector->dc_em_sink)
dc_sink_release(aconnector->dc_em_sink);
aconnector->dc_em_sink = NULL; if (aconnector->dc_sink)
dc_sink_release(aconnector->dc_sink);
aconnector->dc_sink = NULL;
drm_dp_cec_unregister_connector(&aconnector->dm_dp_aux.aux);
drm_connector_unregister(connector);
drm_connector_cleanup(connector); if (aconnector->i2c) {
i2c_del_adapter(&aconnector->i2c->base);
kfree(aconnector->i2c);
}
kfree(aconnector->dm_dp_aux.aux.name);
drm_edid = drm_edid_read_ddc(connector, ddc);
drm_edid_connector_update(connector, drm_edid); if (!drm_edid) {
drm_err(dev, "No EDID found on connector: %s.\n", connector->name); return;
}
aconnector->drm_edid = drm_edid; /* Update emulated (virtual) sink's EDID */ if (dc_em_sink && dc_link) { // FIXME: Get rid of drm_edid_raw() conststruct edid *edid = drm_edid_raw(drm_edid);
drm_edid = drm_edid_read_ddc(connector, ddc);
drm_edid_connector_update(connector, drm_edid); if (!drm_edid) {
drm_err(connector->dev, "No EDID found on connector: %s.\n", connector->name); return;
}
if (connector->display_info.is_hdmi)
init_params.sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
/* * In case of headless boot with force on for DP managed connector * Those settings have to be != 0 to get initial modeset
*/ if (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT) {
link->verified_link_cap.lane_count = LANE_COUNT_FOUR;
link->verified_link_cap.link_rate = LINK_RATE_HIGH2;
}
} while (stream == NULL && requested_bpc >= bpc_limit);
switch (dc_result) { /* * If we failed to validate DP bandwidth stream with the requested RGB color depth, * we try to fallback and configure in order: * YUV422 (8bpc, 6bpc) * YUV420 (8bpc, 6bpc)
*/ case DC_FAIL_ENC_VALIDATE: case DC_EXCEED_DONGLE_CAP: case DC_NO_DP_LINK_BANDWIDTH: /* recursively entered twice and already tried both YUV422 and YUV420 */ if (aconnector->force_yuv422_output && aconnector->force_yuv420_output) break; /* first failure; try YUV422 */ if (!aconnector->force_yuv422_output) {
drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV422\n",
__func__, __LINE__, dc_result);
aconnector->force_yuv422_output = true; /* recursively entered and YUV422 failed, try YUV420 */
} elseif (!aconnector->force_yuv420_output) {
drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV420\n",
__func__, __LINE__, dc_result);
aconnector->force_yuv420_output = true;
}
stream = create_validate_stream_for_sink(connector, drm_mode,
dm_state, old_stream);
aconnector->force_yuv422_output = false;
aconnector->force_yuv420_output = false; break; case DC_OK: break; default:
drm_dbg_kms(connector->dev, "%s:%d Unhandled validation failure %d\n",
__func__, __LINE__, dc_result); break;
}
return stream;
}
enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector, conststruct drm_display_mode *mode)
{ int result = MODE_ERROR; struct dc_sink *dc_sink; struct drm_display_mode *test_mode; /* TODO: Unhardcode stream count */ struct dc_stream_state *stream; /* we always have an amdgpu_dm_connector here since we got * here via the amdgpu_dm_connector_helper_funcs
*/ struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
if ((mode->flags & DRM_MODE_FLAG_INTERLACE) ||
(mode->flags & DRM_MODE_FLAG_DBLSCAN)) return result;
/* * Only run this the first time mode_valid is called to initilialize * EDID mgmt
*/ if (aconnector->base.force != DRM_FORCE_UNSPECIFIED &&
!aconnector->dc_em_sink)
handle_edid_mgmt(aconnector);
if (conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort) {
ret = drm_dp_mst_root_conn_atomic_check(new_con_state, &aconn->mst_mgr); if (ret < 0) return ret;
}
if (!crtc) return 0;
if (new_con_state->colorspace != old_con_state->colorspace) {
new_crtc_state = drm_atomic_get_crtc_state(state, crtc); if (IS_ERR(new_crtc_state)) return PTR_ERR(new_crtc_state);
new_crtc_state->mode_changed = true;
}
if (new_con_state->content_type != old_con_state->content_type) {
new_crtc_state = drm_atomic_get_crtc_state(state, crtc); if (IS_ERR(new_crtc_state)) return PTR_ERR(new_crtc_state);
new_crtc_state->mode_changed = true;
}
if (!drm_connector_atomic_hdr_metadata_equal(old_con_state, new_con_state)) { struct dc_info_packet hdr_infopacket;
ret = fill_hdr_info_packet(new_con_state, &hdr_infopacket); if (ret) return ret;
new_crtc_state = drm_atomic_get_crtc_state(state, crtc); if (IS_ERR(new_crtc_state)) return PTR_ERR(new_crtc_state);
/* * DC considers the stream backends changed if the * static metadata changes. Forcing the modeset also * gives a simple way for userspace to switch from * 8bpc to 10bpc when setting the metadata to enter * or exit HDR. * * Changing the static metadata after it's been * set is permissible, however. So only force a * modeset if we're entering or exiting HDR.
*/
new_crtc_state->mode_changed = new_crtc_state->mode_changed ||
!old_con_state->hdr_output_metadata ||
!new_con_state->hdr_output_metadata;
}
return 0;
}
staticconststruct drm_connector_helper_funcs
amdgpu_dm_connector_helper_funcs = { /* * If hotplugging a second bigger display in FB Con mode, bigger resolution * modes will be filtered by drm_mode_validate_size(), and those modes * are missing after user start lightdm. So we need to renew modes list. * in get_modes call back, not just return the modes count
*/
.get_modes = get_modes,
.mode_valid = amdgpu_dm_connector_mode_valid,
.atomic_check = amdgpu_dm_connector_atomic_check,
};
staticint 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_LVDS: return DRM_MODE_CONNECTOR_LVDS; case SIGNAL_TYPE_RGB: return DRM_MODE_CONNECTOR_VGA; case SIGNAL_TYPE_DISPLAY_PORT: case SIGNAL_TYPE_DISPLAY_PORT_MST: return DRM_MODE_CONNECTOR_DisplayPort; case SIGNAL_TYPE_DVI_DUAL_LINK: case SIGNAL_TYPE_DVI_SINGLE_LINK: return DRM_MODE_CONNECTOR_DVID; case SIGNAL_TYPE_VIRTUAL: return DRM_MODE_CONNECTOR_VIRTUAL;
/* sorting the probed modes before calling function * amdgpu_dm_get_native_mode() since EDID can have * more than one preferred mode. The modes that are * later in the probed mode list could be of higher * and preferred resolution. For example, 3840x2160 * resolution in base EDID preferred timing and 4096x2160 * preferred resolution in DID extension block later.
*/
drm_mode_sort(&connector->probed_modes);
amdgpu_dm_get_native_mode(connector);
/* Freesync capabilities are reset by calling * drm_edid_connector_add_modes() and need to be * restored here.
*/
amdgpu_dm_update_freesync_caps(connector, drm_edid);
} else {
amdgpu_dm_connector->num_modes = 0;
}
}
/* * Find mode with highest refresh rate with the same resolution * as the preferred mode. Some monitors report a preferred mode * with lower resolution than the highest refresh rate supported.
*/
m = get_highest_refresh_rate_mode(aconnector, true); if (!m) return 0;
for (i = 0; i < ARRAY_SIZE(common_rates); i++) {
u64 target_vtotal, target_vtotal_diff;
u64 num, den;
if (drm_mode_vrefresh(m) * 1000 < common_rates[i]) continue;
void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, struct amdgpu_dm_connector *aconnector, int connector_type, struct dc_link *link, int link_index)
{ struct amdgpu_device *adev = drm_to_adev(dm->ddev);
/* * Some of the properties below require access to state, like bpc. * Allocate some default initial connector state with our reset helper.
*/ if (aconnector->base.funcs->reset)
aconnector->base.funcs->reset(&aconnector->base);
if (connector_type == DRM_MODE_CONNECTOR_HDMIA) { /* Content Type is currently only implemented for HDMI. */
drm_connector_attach_content_type_property(&aconnector->base);
}
if (connector_type == DRM_MODE_CONNECTOR_HDMIA) { if (!drm_mode_create_hdmi_colorspace_property(&aconnector->base, supported_colorspaces))
drm_connector_attach_colorspace_property(&aconnector->base);
} elseif ((connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root) ||
connector_type == DRM_MODE_CONNECTOR_eDP) { if (!drm_mode_create_dp_colorspace_property(&aconnector->base, supported_colorspaces))
drm_connector_attach_colorspace_property(&aconnector->base);
}
for (i = 0; i < num; i++) {
cmd.payloads[i].write = !(msgs[i].flags & I2C_M_RD);
cmd.payloads[i].address = msgs[i].addr;
cmd.payloads[i].length = msgs[i].len;
cmd.payloads[i].data = msgs[i].buf;
}
if (i2c->oem) { if (dc_submit_i2c_oem(
ddc_service->ctx->dc,
&cmd))
result = num;
} else { if (dc_submit_i2c(
ddc_service->ctx->dc,
ddc_service->link->link_index,
&cmd))
result = num;
}
cec_fill_conn_info_from_drm(&conn_info, &aconnector->base);
aconnector->notifier =
cec_notifier_conn_register(hdmi_dev, NULL, &conn_info); if (!aconnector->notifier) {
drm_err(ddev, "Failed to create cec notifier\n"); return -ENOMEM;
}
return 0;
}
/* * Note: this function assumes that dc_link_detect() was called for the * dc_link which will be represented by this aconnector.
*/ staticint amdgpu_dm_connector_init(struct amdgpu_display_manager *dm, struct amdgpu_dm_connector *aconnector,
u32 link_index, struct amdgpu_encoder *aencoder)
{ int res = 0; int connector_type; struct dc *dc = dm->dc; struct dc_link *link = dc_get_link_at_index(dc, link_index); struct amdgpu_i2c_adapter *i2c;
/* Not needed for writeback connector */
link->priv = aconnector;
i2c = create_i2c(link->ddc, false); if (!i2c) {
drm_err(adev_to_drm(dm->adev), "Failed to create i2c adapter data\n"); return -ENOMEM;
}
aconnector->i2c = i2c;
res = i2c_add_adapter(&i2c->base);
if (res) {
drm_err(adev_to_drm(dm->adev), "Failed to register hw i2c %d\n", link->link_index); goto out_free;
}
int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev)
{ switch (adev->mode_info.num_crtc) { case 1: return 0x1; case 2: return 0x3; case 3: return 0x7; case 4: return 0xf; case 5: return 0x1f; case 6: default: return 0x3f;
}
}
staticvoid manage_dm_interrupts(struct amdgpu_device *adev, struct amdgpu_crtc *acrtc, struct dm_crtc_state *acrtc_state)
{ /* * We cannot be sure that the frontend index maps to the same * backend index - some even map to more than one. * So we have to go through the CRTC to find the right IRQ.
*/ int irq_type = amdgpu_display_crtc_idx_to_irq_type(
adev,
acrtc->crtc_id); struct drm_device *dev = adev_to_drm(adev);
struct drm_vblank_crtc_config config = {0}; struct dc_crtc_timing *timing; int offdelay;
if (acrtc_state) {
timing = &acrtc_state->stream->timing;
/* * Depending on when the HW latching event of double-buffered * registers happen relative to the PSR SDP deadline, and how * bad the Panel clock has drifted since the last ALPM off * event, there can be up to 3 frames of delay between sending * the PSR exit cmd to DMUB fw, and when the panel starts * displaying live frames. * * We can set: * * 20/100 * offdelay_ms = 3_frames_ms * => offdelay_ms = 5 * 3_frames_ms * * This ensures that `3_frames_ms` will only be experienced as a * 20% delay on top how long the display has been static, and * thus make the delay less perceivable.
*/ if (acrtc_state->stream->link->psr_settings.psr_version <
DC_PSR_VERSION_UNSUPPORTED) {
offdelay = DIV64_U64_ROUND_UP((u64)5 * 3 * 10 *
timing->v_total *
timing->h_total,
timing->pix_clk_100hz);
config.offdelay_ms = offdelay ?: 30;
} elseif (amdgpu_ip_version(adev, DCE_HWIP, 0) <
IP_VERSION(3, 5, 0) ||
!(adev->flags & AMD_IS_APU)) { /* * Older HW and DGPU have issues with instant off; * use a 2 frame offdelay.
*/
offdelay = DIV64_U64_ROUND_UP((u64)20 *
timing->v_total *
timing->h_total,
timing->pix_clk_100hz);
/** * This reads the current state for the IRQ and force reapplies * the setting to hardware.
*/
amdgpu_irq_update(adev, &adev->pageflip_irq, irq_type);
}
/* CP is being re enabled, ignore this */ if (old_conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED &&
new_conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED) { if (new_crtc_state && new_crtc_state->mode_changed) {
new_conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED;
pr_debug("[HDCP_DM] ENABLED->DESIRED & mode_changed %s :true\n", __func__); returntrue;
}
new_conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED;
pr_debug("[HDCP_DM] ENABLED -> DESIRED %s :false\n", __func__); returnfalse;
}
/* S3 resume case, since old state will always be 0 (UNDESIRED) and the restored state will be ENABLED * * Handles: UNDESIRED -> ENABLED
*/ if (old_conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_UNDESIRED &&
new_conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED)
new_conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED;
/* Stream removed and re-enabled * * Can sometimes overlap with the HPD case, * thus set update_hdcp to false to avoid * setting HDCP multiple times. * * Handles: DESIRED -> DESIRED (Special case)
*/ if (!(old_conn_state->crtc && old_conn_state->crtc->enabled) &&
new_conn_state->crtc && new_conn_state->crtc->enabled &&
connector->state->content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED) {
dm_con_state->update_hdcp = false;
pr_debug("[HDCP_DM] DESIRED->DESIRED (Stream removed and re-enabled) %s :true\n",
__func__); returntrue;
}
/* Hot-plug, headless s3, dpms * * Only start HDCP if the display is connected/enabled. * update_hdcp flag will be set to false until the next * HPD comes in. * * Handles: DESIRED -> DESIRED (Special case)
*/ if (dm_con_state->update_hdcp &&
new_conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED &&
connector->dpms == DRM_MODE_DPMS_ON && aconnector->dc_sink != NULL) {
dm_con_state->update_hdcp = false;
pr_debug("[HDCP_DM] DESIRED->DESIRED (Hot-plug, headless s3, dpms) %s :true\n",
__func__); returntrue;
}
if (old_conn_state->content_protection == new_conn_state->content_protection) { if (new_conn_state->content_protection >= DRM_MODE_CONTENT_PROTECTION_DESIRED) { if (new_crtc_state && new_crtc_state->mode_changed) {
pr_debug("[HDCP_DM] DESIRED->DESIRED or ENABLE->ENABLE mode_change %s :true\n",
__func__); returntrue;
}
pr_debug("[HDCP_DM] DESIRED->DESIRED & ENABLE->ENABLE %s :false\n",
__func__); returnfalse;
}
/* * TODO: Determine why min/max totals and vrefresh can be 0 here. * For now it's sufficient to just guard against these conditions.
*/ if (!new_stream->timing.h_total || !new_stream->timing.v_total) return;
if (!old_vrr_active && new_vrr_active) { /* Transition VRR inactive -> active: * While VRR is active, we must not disable vblank irq, as a * reenable after disable would compute bogus vblank/pflip * timestamps if it likely happened inside display front-porch. * * We also need vupdate irq for the actual core vblank handling * at end of vblank.
*/
WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0);
WARN_ON(drm_crtc_vblank_get(new_state->base.crtc) != 0);
drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR off->on: Get vblank ref\n",
__func__, new_state->base.crtc->base.id);
} elseif (old_vrr_active && !new_vrr_active) { /* Transition VRR active -> inactive: * Allow vblank irq disable again for fixed refresh rate.
*/
WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0);
drm_crtc_vblank_put(new_state->base.crtc);
drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR on->off: Drop vblank ref\n",
__func__, new_state->base.crtc->base.id);
}
}
/* * TODO: Make this per-stream so we don't issue redundant updates for * commits with multiple streams.
*/
for_each_old_plane_in_state(state, plane, old_plane_state, i) if (plane->type == DRM_PLANE_TYPE_CURSOR)
amdgpu_dm_plane_handle_cursor_update(plane, old_plane_state);
}
/* Enable cursor degamma ROM on DCN3+ for implicit sRGB degamma in DRM * legacy gamma setup.
*/ if (crtc_state->cm_is_degamma_srgb &&
adev->dm.dc->caps.color.dpp.gamma_corr)
attributes.attribute_flags.bits.ENABLE_CURSOR_DEGAMMA = 1;
if (afb)
attributes.pitch = afb->base.pitches[0] / afb->base.format->cpp[0];
if (crtc_state->stream) { if (!dc_stream_set_cursor_attributes(crtc_state->stream,
&attributes))
drm_err(adev_to_drm(adev), "DC failed to set cursor attributes\n");
if (acrtc_state->update_type > UPDATE_TYPE_FAST) { if (pr->config.replay_supported && !pr->replay_feature_enabled)
amdgpu_dm_link_setup_replay(acrtc_state->stream->link, aconn); elseif (psr->psr_version != DC_PSR_VERSION_UNSUPPORTED &&
!psr->psr_feature_enabled) if (!aconn->disallow_edp_enter_psr)
amdgpu_dm_link_setup_psr(acrtc_state->stream);
}
/* Decrement skip count when SR is enabled and we're doing fast updates. */ if (acrtc_state->update_type == UPDATE_TYPE_FAST &&
(psr->psr_feature_enabled || pr->config.replay_supported)) { if (aconn->sr_skip_count > 0)
aconn->sr_skip_count--;
/* Allow SR when skip count is 0. */
acrtc_attach->dm_irq_params.allow_sr_entry = !aconn->sr_skip_count;
/* * If sink supports PSR SU/Panel Replay, there is no need to rely on * a vblank event disable request to enable PSR/RP. PSR SU/RP * can be enabled immediately once OS demonstrates an * adequate number of fast atomic commits to notify KMD * of update events. See `vblank_control_worker()`.
*/ if (!vrr_active &&
acrtc_attach->dm_irq_params.allow_sr_entry && #ifdef CONFIG_DRM_AMD_SECURE_DISPLAY
!amdgpu_dm_crc_window_is_activated(acrtc_state->base.crtc) && #endif
(current_ts - psr->psr_dirty_rects_change_timestamp_ns) > 500000000) { if (pr->replay_feature_enabled && !pr->replay_allow_active)
amdgpu_dm_replay_enable(acrtc_state->stream, true); if (psr->psr_version == DC_PSR_VERSION_SU_1 &&
!psr->psr_allow_active && !aconn->disallow_edp_enter_psr)
amdgpu_dm_psr_enable(acrtc_state->stream);
}
} else {
acrtc_attach->dm_irq_params.allow_sr_entry = false;
}
}
if (!bundle) {
drm_err(dev, "Failed to allocate update bundle\n"); goto cleanup;
}
/* * Disable the cursor first if we're disabling all the planes. * It'll remain on the screen after the planes are re-enabled * if we don't. * * If the cursor is transitioning from native to overlay mode, the * native cursor needs to be disabled first.
*/ if (acrtc_state->cursor_mode == DM_CURSOR_OVERLAY_MODE &&
dm_old_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE) { struct dc_cursor_position cursor_position = {0};
if (!dc_stream_set_cursor_position(acrtc_state->stream,
&cursor_position))
drm_err(dev, "DC failed to disable native cursor\n");
/* * If the dirty regions changed, PSR-SU need to be disabled temporarily * and enabled it again after dirty regions are stable to avoid video glitch. * PSR-SU will be enabled in vblank_control_worker() if user pause the video * during the PSR-SU was disabled.
*/ if (acrtc_state->stream->link->psr_settings.psr_version >= DC_PSR_VERSION_SU_1 &&
acrtc_attach->dm_irq_params.allow_sr_entry && #ifdef CONFIG_DRM_AMD_SECURE_DISPLAY
!amdgpu_dm_crc_window_is_activated(acrtc_state->base.crtc) && #endif
dirty_rects_changed) {
mutex_lock(&dm->dc_lock);
acrtc_state->stream->link->psr_settings.psr_dirty_rects_change_timestamp_ns =
timestamp_ns; if (acrtc_state->stream->link->psr_settings.psr_allow_active)
amdgpu_dm_psr_disable(acrtc_state->stream, true);
mutex_unlock(&dm->dc_lock);
}
}
/* * Only allow immediate flips for fast updates that don't * change memory domain, FB pitch, DCC state, rotation or * mirroring. * * dm_crtc_helper_atomic_check() only accepts async flips with * fast updates.
*/ if (crtc->state->async_flip &&
(acrtc_state->update_type != UPDATE_TYPE_FAST ||
get_mem_type(old_plane_state->fb) != get_mem_type(fb)))
drm_warn_once(state->dev, "[PLANE:%d:%s] async flip with non-fast update\n",
plane->base.id, plane->name);
if (pflip_present) { if (!vrr_active) { /* Use old throttling in non-vrr fixed refresh rate mode * to keep flip scheduling based on target vblank counts * working in a backwards compatible way, e.g., for * clients using the GLX_OML_sync_control extension or * DRI3/Present extension with defined target_msc.
*/
last_flip_vblank = amdgpu_get_vblank_counter_kms(pcrtc);
} else { /* For variable refresh rate mode only: * Get vblank of last completed flip to avoid > 1 vrr * flips per video frame by use of throttling, but allow * flip programming anywhere in the possibly large * variable vrr vblank interval for fine-grained flip * timing control and more opportunity to avoid stutter * on late submission of flips.
*/
spin_lock_irqsave(&pcrtc->dev->event_lock, flags);
last_flip_vblank = acrtc_attach->dm_irq_params.last_flip_vblank;
spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags);
}
/* * Wait until we're out of the vertical blank period before the one * targeted by the flip
*/ while ((acrtc_attach->enabled &&
(amdgpu_display_get_crtc_scanoutpos(dm->ddev, acrtc_attach->crtc_id,
0, &vpos, &hpos, NULL,
NULL, &pcrtc->hwmode)
& (DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_IN_VBLANK)) ==
(DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_IN_VBLANK) &&
(int)(target_vblank -
amdgpu_get_vblank_counter_kms(pcrtc)) > 0)) {
usleep_range(1000, 1100);
}
/** * Prepare the flip event for the pageflip interrupt to handle. * * This only works in the case where we've already turned on the * appropriate hardware blocks (eg. HUBP) so in the transition case * from 0 -> n planes we have to skip a hardware generated event * and rely on sending it from software.
*/ if (acrtc_attach->base.state->event &&
acrtc_state->active_planes > 0) {
drm_crtc_vblank_get(pcrtc);
/* Update the planes if changed or disable if we don't have any. */ if ((planes_count || acrtc_state->active_planes == 0) &&
acrtc_state->stream) { /* * If PSR or idle optimizations are enabled then flush out * any pending work before hardware programming.
*/ if (dm->vblank_control_workqueue)
flush_workqueue(dm->vblank_control_workqueue);
if (new_pcrtc_state->color_mgmt_changed) { /* * TODO: This isn't fully correct since we've actually * already modified the stream in place.
*/
bundle->stream_update.gamut_remap =
&acrtc_state->stream->gamut_remap_matrix;
bundle->stream_update.output_csc_transform =
&acrtc_state->stream->csc_color_matrix;
bundle->stream_update.out_transfer_func =
&acrtc_state->stream->out_transfer_func;
bundle->stream_update.lut3d_func =
(struct dc_3dlut *) acrtc_state->stream->lut3d_func;
bundle->stream_update.func_shaper =
(struct dc_transfer_func *) acrtc_state->stream->func_shaper;
}
acrtc_state->stream->abm_level = acrtc_state->abm_level; if (acrtc_state->abm_level != dm_old_crtc_state->abm_level)
bundle->stream_update.abm_level = &acrtc_state->abm_level;
mutex_lock(&dm->dc_lock); if ((acrtc_state->update_type > UPDATE_TYPE_FAST) || vrr_active) { if (acrtc_state->stream->link->replay_settings.replay_allow_active)
amdgpu_dm_replay_disable(acrtc_state->stream); if (acrtc_state->stream->link->psr_settings.psr_allow_active)
amdgpu_dm_psr_disable(acrtc_state->stream, true);
}
mutex_unlock(&dm->dc_lock);
/* * If FreeSync state on the stream has changed then we need to * re-adjust the min/max bounds now that DC doesn't handle this * as part of commit.
*/ if (is_dc_timing_adjust_needed(dm_old_crtc_state, acrtc_state)) {
spin_lock_irqsave(&pcrtc->dev->event_lock, flags);
dc_stream_adjust_vmin_vmax(
dm->dc, acrtc_state->stream,
&acrtc_attach->dm_irq_params.vrr_params.adjust);
spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags);
}
mutex_lock(&dm->dc_lock);
update_planes_and_stream_adapter(dm->dc,
acrtc_state->update_type,
planes_count,
acrtc_state->stream,
&bundle->stream_update,
bundle->surface_updates);
updated_planes_and_streams = true;
/** * Enable or disable the interrupts on the backend. * * Most pipes are put into power gating when unused. * * When power gating is enabled on a pipe we lose the * interrupt enablement state when power gating is disabled. * * So we need to update the IRQ control state in hardware * whenever the pipe turns on (since it could be previously * power gated) or off (since some pipes can't be power gated * on some ASICs).
*/ if (dm_old_crtc_state->active_planes != acrtc_state->active_planes)
dm_update_pflip_irq_state(drm_to_adev(dev),
acrtc_attach);
/* * Update cursor state *after* programming all the planes. * This avoids redundant programming in the case where we're going * to be disabling a single plane - those pipes are being disabled.
*/ if (acrtc_state->active_planes &&
(!updated_planes_and_streams || amdgpu_ip_version(dm->adev, DCE_HWIP, 0) == 0) &&
acrtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE)
amdgpu_dm_commit_cursors(state);
/* * amdgpu_dm_crtc_copy_transient_flags - copy mirrored flags from DRM to DC * @crtc_state: the DRM CRTC state * @stream_state: the DC stream state. * * Copy the mirrored transient state flags from DRM, to DC. It is used to bring * a dc_stream_state's flags in sync with a drm_crtc_state's flags.
*/ staticvoid amdgpu_dm_crtc_copy_transient_flags(struct drm_crtc_state *crtc_state, struct dc_stream_state *stream_state)
{
stream_state->mode_changed = drm_atomic_crtc_needs_modeset(crtc_state);
}
/* Copy all transient state flags into dc state */ if (dm_new_crtc_state->stream) {
amdgpu_dm_crtc_copy_transient_flags(&dm_new_crtc_state->base,
dm_new_crtc_state->stream);
}
/* handles headless hotplug case, updating new_state and * aconnector as needed
*/
if (amdgpu_dm_crtc_modeset_required(new_crtc_state, dm_new_crtc_state->stream, dm_old_crtc_state->stream)) {
drm_dbg_atomic(dev, "Atomic commit: SET crtc id %d: [%p]\n",
acrtc->crtc_id, acrtc);
if (!dm_new_crtc_state->stream) { /* * this could happen because of issues with * userspace notifications delivery. * In this case userspace tries to set mode on * display which is disconnected in fact. * dc_sink is NULL in this case on aconnector. * We expect reset mode will come soon. * * This can also happen when unplug is done * during resume sequence ended * * In this case, we want to pretend we still * have a sink to keep the pipe running so that * hw state is consistent with the sw state
*/
drm_dbg_atomic(dev, "Failed to create new stream for crtc %d\n",
acrtc->base.base.id); continue;
}
if (dm_old_crtc_state->stream)
remove_stream(adev, acrtc, dm_old_crtc_state->stream);
/* if there mode set or reset, disable eDP PSR, Replay */ if (mode_set_reset_required) { if (dm->vblank_control_workqueue)
flush_workqueue(dm->vblank_control_workqueue);
/* Allow idle optimization when vblank count is 0 for display off */ if ((dm->active_vblank_irq_count == 0) && amdgpu_dm_is_headless(dm->adev))
dc_allow_idle_optimizations(dm->dc, true);
mutex_unlock(&dm->dc_lock);
if (dm_new_crtc_state->stream != NULL) { conststruct dc_stream_status *status =
dc_stream_get_status(dm_new_crtc_state->stream);
if (!status)
status = dc_state_get_stream_status(dc_state,
dm_new_crtc_state->stream); if (!status)
drm_err(dev, "got no status for stream %p on acrtc%p\n",
dm_new_crtc_state->stream, acrtc); else
acrtc->otg_inst = status->primary_otg_inst;
}
}
/* During boot up and resume the DC layer will reset the panel brightness * to fix a flicker issue. * It will cause the dm->actual_brightness is not the current panel brightness * level. (the dm->brightness is the correct panel level) * So we set the backlight level with dm->brightness value after set mode
*/ if (set_backlight_level) { for (i = 0; i < dm->num_of_edps; i++) { if (dm->backlight_dev[i])
amdgpu_dm_backlight_set_level(dm, i, dm->brightness[i]);
}
}
}
wb_info = kzalloc(sizeof(*wb_info), GFP_KERNEL); if (!wb_info) {
drm_err(adev_to_drm(adev), "Failed to allocate wb_info\n"); return;
}
acrtc = to_amdgpu_crtc(wb_conn->encoder.crtc); if (!acrtc) {
drm_err(adev_to_drm(adev), "no amdgpu_crtc found\n");
kfree(wb_info); return;
}
afb = to_amdgpu_framebuffer(new_con_state->writeback_job->fb); if (!afb) {
drm_err(adev_to_drm(adev), "No amdgpu_framebuffer found\n");
kfree(wb_info); return;
}
for (i = 0; i < MAX_PIPES; i++) { if (dm->dc->current_state->res_ctx.pipe_ctx[i].stream == crtc_state->stream) {
pipe = &dm->dc->current_state->res_ctx.pipe_ctx[i]; break;
}
}
staticint amdgpu_dm_atomic_setup_commit(struct drm_atomic_state *state)
{ struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state, *new_crtc_state; struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state; int i, ret;
ret = drm_dp_mst_atomic_setup_commit(state); if (ret) return ret;
for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
dm_old_crtc_state = to_dm_crtc_state(old_crtc_state);
dm_new_crtc_state = to_dm_crtc_state(new_crtc_state); /* * Color management settings. We also update color properties * when a modeset is needed, to ensure it gets reprogrammed.
*/ if (dm_new_crtc_state->base.active && dm_new_crtc_state->stream &&
(dm_new_crtc_state->base.color_mgmt_changed ||
dm_old_crtc_state->regamma_tf != dm_new_crtc_state->regamma_tf ||
drm_atomic_crtc_needs_modeset(new_crtc_state))) {
ret = amdgpu_dm_update_crtc_color_mgmt(dm_new_crtc_state); if (ret) {
drm_dbg_atomic(state->dev, "Failed to update color state\n"); return ret;
}
}
}
return 0;
}
/** * amdgpu_dm_atomic_commit_tail() - AMDgpu DM's commit tail implementation. * @state: The atomic state to commit * * This will tell DC to commit the constructed DC state from atomic_check, * programming the hardware. Any failures here implies a hardware failure, since * atomic check should have filtered anything non-kosher.
*/ staticvoid amdgpu_dm_atomic_commit_tail(struct drm_atomic_state *state)
{ struct drm_device *dev = state->dev; struct amdgpu_device *adev = drm_to_adev(dev); struct amdgpu_display_manager *dm = &adev->dm; struct dm_atomic_state *dm_state; struct dc_state *dc_state = NULL;
u32 i, j; struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state, *new_crtc_state; unsignedlong flags; bool wait_for_vblank = true; struct drm_connector *connector; struct drm_connector_state *old_con_state, *new_con_state; struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state; int crtc_disable_count = 0;
if (is_content_protection_different(new_crtc_state, old_crtc_state, new_con_state,
old_con_state, connector, adev->dm.hdcp_workqueue)) { /* when display is unplugged from mst hub, connctor will * be destroyed within dm_dp_mst_connector_destroy. connector * hdcp perperties, like type, undesired, desired, enabled, * will be lost. So, save hdcp properties into hdcp_work within * amdgpu_dm_atomic_commit_tail. if the same display is * plugged back with same display index, its hdcp properties * will be retrieved from hdcp_work within dm_dp_mst_get_modes
*/
bool enable_encryption = false;
if (new_con_state->content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED)
enable_encryption = true;
if (hdr_changed) {
fill_hdr_info_packet(new_con_state, &hdr_packet);
stream_update.hdr_static_metadata = &hdr_packet;
}
status = dc_stream_get_status(dm_new_crtc_state->stream);
if (WARN_ON(!status)) continue;
WARN_ON(!status->plane_count);
/* * TODO: DC refuses to perform stream updates without a dc_surface_update. * Here we create an empty update on each plane. * To fix this, DC should permit updating only stream properties.
*/
dummy_updates = kzalloc(sizeof(struct dc_surface_update) * MAX_SURFACES, GFP_ATOMIC); if (!dummy_updates) {
drm_err(adev_to_drm(adev), "Failed to allocate memory for dummy_updates.\n"); continue;
} for (j = 0; j < status->plane_count; j++)
dummy_updates[j].surface = status->plane_states[0];
/** * Enable interrupts for CRTCs that are newly enabled or went through * a modeset. It was intentionally deferred until after the front end * state was modified to wait until the OTG was on and so the IRQ * handlers didn't access stale or invalid state.
*/
for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc); #ifdef CONFIG_DEBUG_FS enum amdgpu_dm_pipe_crc_source cur_crc_src; #endif /* Count number of newly disabled CRTCs for dropping PM refs later. */ if (old_crtc_state->active && !new_crtc_state->active)
crtc_disable_count++;
#ifdef CONFIG_DEBUG_FS if (new_crtc_state->active &&
(!old_crtc_state->active ||
drm_atomic_crtc_needs_modeset(new_crtc_state))) { /** * Frontend may have changed so reapply the CRC capture * settings for the stream.
*/ if (amdgpu_dm_is_valid_crc_source(cur_crc_src)) { #ifdefined(CONFIG_DRM_AMD_SECURE_DISPLAY) if (amdgpu_dm_crc_window_is_activated(crtc)) {
uint8_t cnt;
spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); for (cnt = 0; cnt < MAX_CRC_WINDOW_NUM; cnt++) { if (acrtc->dm_irq_params.window_param[cnt].enable) {
acrtc->dm_irq_params.window_param[cnt].update_win = true;
/** * It takes 2 frames for HW to stably generate CRC when * resuming from suspend, so we set skip_frame_cnt 2.
*/
acrtc->dm_irq_params.window_param[cnt].skip_frame_cnt = 2;
}
}
spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
} #endif if (amdgpu_dm_crtc_configure_crc_source(
crtc, dm_new_crtc_state, cur_crc_src))
drm_dbg_atomic(dev, "Failed to configure crc source");
}
} #endif
}
for_each_new_crtc_in_state(state, crtc, new_crtc_state, j) if (new_crtc_state->async_flip)
wait_for_vblank = false;
/* update planes when needed per crtc*/
for_each_new_crtc_in_state(state, crtc, new_crtc_state, j) {
dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
if (dm_new_crtc_state->stream)
amdgpu_dm_commit_planes(state, dev, dm, crtc, wait_for_vblank);
}
/* Update audio instances for each connector. */
amdgpu_dm_commit_audio(dev, state);
/* restore the backlight level */ for (i = 0; i < dm->num_of_edps; i++) { if (dm->backlight_dev[i] &&
(dm->actual_brightness[i] != dm->brightness[i]))
amdgpu_dm_backlight_set_level(dm, i, dm->brightness[i]);
}
/* * send vblank event on all events not handled in flip and * mark consumed event for drm_atomic_helper_commit_hw_done
*/
spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
if (new_crtc_state->event)
drm_send_event_locked(dev, &new_crtc_state->event->base);
/* Signal HW programming completion */
drm_atomic_helper_commit_hw_done(state);
if (wait_for_vblank)
drm_atomic_helper_wait_for_flip_done(dev, state);
drm_atomic_helper_cleanup_planes(dev, state);
/* Don't free the memory if we are hitting this as part of suspend. * This way we don't free any memory during suspend; see * amdgpu_bo_free_kernel(). The memory will be freed in the first * non-suspend modeset or when the driver is torn down.
*/ if (!adev->in_suspend) { /* return the stolen vga memory back to VRAM */ if (!adev->mman.keep_stolen_vga_memory)
amdgpu_bo_free_kernel(&adev->mman.stolen_vga_memory, NULL, NULL);
amdgpu_bo_free_kernel(&adev->mman.stolen_extended_memory, NULL, NULL);
}
/* * Finally, drop a runtime PM reference for each newly disabled CRTC, * so we can put the GPU into runtime suspend if we're not driving any * displays anymore
*/ for (i = 0; i < crtc_disable_count; i++)
pm_runtime_put_autosuspend(dev->dev);
pm_runtime_mark_last_busy(dev->dev);
/* Check for error in getting connector state */ if (IS_ERR(conn_state)) {
ret = PTR_ERR(conn_state); goto out;
}
/* Attach crtc to drm_atomic_state*/
crtc_state = drm_atomic_get_crtc_state(state, &disconnected_acrtc->base);
/* Check for error in getting crtc state */ if (IS_ERR(crtc_state)) {
ret = PTR_ERR(crtc_state); goto out;
}
/* force a restore */
crtc_state->mode_changed = true;
/* Attach plane to drm_atomic_state */
plane_state = drm_atomic_get_plane_state(state, plane);
/* Check for error in getting plane state */ if (IS_ERR(plane_state)) {
ret = PTR_ERR(plane_state); goto out;
}
/* Call commit internally with the state we just constructed */
ret = drm_atomic_commit(state);
out:
drm_atomic_state_put(state); if (ret)
drm_err(ddev, "Restoring old state failed with %i\n", ret);
return ret;
}
/* * This function handles all cases when set mode does not come upon hotplug. * This includes when a display is unplugged then plugged back into the * same port and when running without usermode desktop manager supprot
*/ void dm_restore_drm_connector_state(struct drm_device *dev, struct drm_connector *connector)
{ struct amdgpu_dm_connector *aconnector; struct amdgpu_crtc *disconnected_acrtc; struct dm_crtc_state *acrtc_state;
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) return;
aconnector = to_amdgpu_dm_connector(connector);
if (!aconnector->dc_sink || !connector->state || !connector->encoder) return;
disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc); if (!disconnected_acrtc) return;
acrtc_state = to_dm_crtc_state(disconnected_acrtc->base.state); if (!acrtc_state->stream) return;
/* * If the previous sink is not released and different from the current, * we deduce we are in a state where we can not rely on usermode call * to turn on the display, so we do it here
*/ if (acrtc_state->stream->sink != aconnector->dc_sink)
dm_force_atomic_commit(&aconnector->base);
}
/* * Grabs all modesetting locks to serialize against any blocking commits, * Waits for completion of all non blocking commits.
*/ staticint do_aquire_global_lock(struct drm_device *dev, struct drm_atomic_state *state)
{ struct drm_crtc *crtc; struct drm_crtc_commit *commit; long ret;
/* * Adding all modeset locks to aquire_ctx will * ensure that when the framework release it the * extra locks we are locking here will get released to
*/
ret = drm_modeset_lock_all_ctx(dev, state->acquire_ctx); if (ret) return ret;
/* TODO This hack should go away */ if (connector && enable) { /* Make sure fake sink is created in plug-in scenario */
drm_new_conn_state = drm_atomic_get_new_connector_state(state,
connector);
drm_old_conn_state = drm_atomic_get_old_connector_state(state,
connector);
if (WARN_ON(!drm_new_conn_state)) {
ret = -EINVAL; goto fail;
}
/* * we can have no stream on ACTION_SET if a display * was disconnected during S3, in this case it is not an * error, the OS will be updated after detection, and * will do the right thing on next atomic commit
*/
if (!new_stream) {
drm_dbg_driver(adev_to_drm(adev), "%s: Failed to create new stream for crtc %d\n",
__func__, acrtc->base.base.id);
ret = -ENOMEM; goto fail;
}
/* * TODO: Check VSDB bits to decide whether this should * be enabled or not.
*/
new_stream->triggered_crtc_reset.enabled =
dm->force_timing_sync;
ret = fill_hdr_info_packet(drm_new_conn_state,
&new_stream->hdr_static_metadata); if (ret) goto fail;
/* * If we already removed the old stream from the context * (and set the new stream to NULL) then we can't reuse * the old stream even if the stream and scaling are unchanged. * We'll hit the BUG_ON and black screen. * * TODO: Refactor this function to allow this check to work * in all conditions.
*/ if (amdgpu_freesync_vid_mode &&
dm_new_crtc_state->stream &&
is_timing_unchanged_for_freesync(new_crtc_state, old_crtc_state)) goto skip_modeset;
if (dm_new_crtc_state->stream &&
dc_is_stream_unchanged(new_stream, dm_old_crtc_state->stream) &&
dc_is_stream_scaling_unchanged(new_stream, dm_old_crtc_state->stream)) {
new_crtc_state->mode_changed = false;
drm_dbg_driver(adev_to_drm(adev), "Mode change not required, setting mode_changed to %d",
new_crtc_state->mode_changed);
}
}
/* mode_changed flag may get updated above, need to check again */ if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) goto skip_modeset;
/* Remove stream for any changed/disabled CRTC */ if (!enable) {
if (!dm_old_crtc_state->stream) goto skip_modeset;
/* Unset freesync video if it was active before */ if (dm_old_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED) {
dm_new_crtc_state->freesync_config.state = VRR_STATE_INACTIVE;
dm_new_crtc_state->freesync_config.fixed_refresh_in_uhz = 0;
}
/* Now check if we should set freesync video mode */ if (amdgpu_freesync_vid_mode && dm_new_crtc_state->stream &&
dc_is_stream_unchanged(new_stream, dm_old_crtc_state->stream) &&
dc_is_stream_scaling_unchanged(new_stream, dm_old_crtc_state->stream) &&
is_timing_unchanged_for_freesync(new_crtc_state,
old_crtc_state)) {
new_crtc_state->mode_changed = false;
drm_dbg_driver(adev_to_drm(adev), "Mode change not required for front porch change, setting mode_changed to %d",
new_crtc_state->mode_changed);
} else {/* Add stream for any updated/enabled CRTC */ /* * Quick fix to prevent NULL pointer on new_stream when * added MST connectors not found in existing crtc_state in the chained mode * TODO: need to dig out the root cause of that
*/ if (!connector) goto skip_modeset;
if (modereset_required(new_crtc_state)) goto skip_modeset;
if (amdgpu_dm_crtc_modeset_required(new_crtc_state, new_stream,
dm_old_crtc_state->stream)) {
WARN_ON(dm_new_crtc_state->stream);
ret = dm_atomic_get_state(state, &dm_state); if (ret) goto fail;
if (dc_state_add_stream(
dm->dc,
dm_state->context,
dm_new_crtc_state->stream) != DC_OK) {
ret = -EINVAL; goto fail;
}
*lock_and_validation_needed = true;
}
}
skip_modeset: /* Release extra reference */ if (new_stream)
dc_stream_release(new_stream);
/* * We want to do dc stream updates that do not require a * full modeset below.
*/ if (!(enable && connector && new_crtc_state->active)) return 0; /* * Given above conditions, the dc state cannot be NULL because: * 1. We're in the process of enabling CRTCs (just been added * to the dc context, or already is on the context) * 2. Has a valid connector attached, and * 3. Is currently active and enabled. * => The dc stream state currently exists.
*/
BUG_ON(dm_new_crtc_state->stream == NULL);
/* Scaling or underscan settings */ if (is_scaling_state_different(dm_old_conn_state, dm_new_conn_state) ||
drm_atomic_crtc_needs_modeset(new_crtc_state))
update_stream_scaling_settings(
&new_crtc_state->mode, dm_new_conn_state, dm_new_crtc_state->stream);
/* * Color management settings. We also update color properties * when a modeset is needed, to ensure it gets reprogrammed.
*/ if (dm_new_crtc_state->base.color_mgmt_changed ||
dm_old_crtc_state->regamma_tf != dm_new_crtc_state->regamma_tf ||
drm_atomic_crtc_needs_modeset(new_crtc_state)) {
ret = amdgpu_dm_check_crtc_color_mgmt(dm_new_crtc_state, true); if (ret) goto fail;
}
/* * TODO: Remove this hack for all asics once it proves that the * fast updates works fine on DCN3.2+.
*/ if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 2, 0) &&
state->allow_modeset) returntrue;
if (amdgpu_in_reset(adev) && state->allow_modeset) returntrue;
/* Exit early if we know that we're adding or removing the plane. */ if (old_plane_state->crtc != new_plane_state->crtc) returntrue;
/* old crtc == new_crtc == NULL, plane not in context. */ if (!new_plane_state->crtc) returnfalse;
/* * A change in cursor mode means a new dc pipe needs to be acquired or * released from the state
*/
old_dm_crtc_state = to_dm_crtc_state(old_crtc_state);
new_dm_crtc_state = to_dm_crtc_state(new_crtc_state); if (plane->type == DRM_PLANE_TYPE_CURSOR &&
old_dm_crtc_state != NULL &&
old_dm_crtc_state->cursor_mode != new_dm_crtc_state->cursor_mode) { returntrue;
}
/* CRTC Degamma changes currently require us to recreate planes. */ if (new_crtc_state->color_mgmt_changed) returntrue;
/* * On zpos change, planes need to be reordered by removing and re-adding * them one by one to the dc state, in order of descending zpos. * * TODO: We can likely skip bandwidth validation if the only thing that * changed about the plane was it'z z-ordering.
*/ if (old_plane_state->normalized_zpos != new_plane_state->normalized_zpos) returntrue;
if (drm_atomic_crtc_needs_modeset(new_crtc_state)) returntrue;
/* * If there are any new primary or overlay planes being added or * removed then the z-order can potentially change. To ensure * correct z-order and pipe acquisition the current DC architecture * requires us to remove and recreate all existing planes. * * TODO: Come up with a more elegant solution for this.
*/
for_each_oldnew_plane_in_state(state, other, old_other_state, new_other_state, i) { struct amdgpu_framebuffer *old_afb, *new_afb; struct dm_plane_state *dm_new_other_state, *dm_old_other_state;
/* Pitch in pixels */
pitch = fb->pitches[0] / fb->format->cpp[0];
if (fb->width != pitch) {
DRM_DEBUG_ATOMIC("Cursor FB width %d doesn't match pitch %d",
fb->width, pitch); return -EINVAL;
}
switch (pitch) { case 64: case 128: case 256: /* FB pitch is supported by cursor plane */ break; default:
DRM_DEBUG_ATOMIC("Bad cursor FB pitch %d px\n", pitch); return -EINVAL;
}
/* Core DRM takes care of checking FB modifiers, so we only need to * check tiling flags when the FB doesn't have a modifier.
*/ if (!(fb->flags & DRM_MODE_FB_MODIFIERS)) { if (adev->family >= AMDGPU_FAMILY_GC_12_0_0) {
linear = AMDGPU_TILING_GET(afb->tiling_flags, GFX12_SWIZZLE_MODE) == 0;
} elseif (adev->family >= AMDGPU_FAMILY_AI) {
linear = AMDGPU_TILING_GET(afb->tiling_flags, SWIZZLE_MODE) == 0;
} else {
linear = AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_2D_TILED_THIN1 &&
AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_1D_TILED_THIN1 &&
AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE) == 0;
} if (!linear) {
DRM_DEBUG_ATOMIC("Cursor FB not linear"); return -EINVAL;
}
}
return 0;
}
/* * Helper function for checking the cursor in native mode
*/ staticint dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc, struct drm_plane *plane, struct drm_plane_state *new_plane_state, bool enable)
{
struct amdgpu_crtc *new_acrtc; int ret;
if (!enable || !new_plane_crtc ||
drm_atomic_plane_disabling(plane->state, new_plane_state)) return 0;
new_acrtc = to_amdgpu_crtc(new_plane_crtc);
if (new_plane_state->src_x != 0 || new_plane_state->src_y != 0) {
DRM_DEBUG_ATOMIC("Cropping not supported for cursor plane\n"); return -EINVAL;
}
if (new_plane_state->fb) {
ret = dm_check_cursor_fb(new_acrtc, new_plane_state,
new_plane_state->fb); if (ret) return ret;
}
ret = amdgpu_dm_plane_helper_check_state(new_plane_state, new_crtc_state); if (ret) goto out;
WARN_ON(dm_new_plane_state->dc_state);
dc_new_plane_state = dc_create_plane_state(dc); if (!dc_new_plane_state) {
ret = -ENOMEM; goto out;
}
DRM_DEBUG_ATOMIC("Enabling DRM plane: %d on DRM crtc %d\n",
plane->base.id, new_plane_crtc->base.id);
ret = fill_dc_plane_attributes(
drm_to_adev(new_plane_crtc->dev),
dc_new_plane_state,
new_plane_state,
new_crtc_state); if (ret) {
dc_plane_state_release(dc_new_plane_state); goto out;
}
ret = dm_atomic_get_state(state, &dm_state); if (ret) {
dc_plane_state_release(dc_new_plane_state); goto out;
}
/* * Any atomic check errors that occur after this will * not need a release. The plane state will be attached * to the stream, and therefore part of the atomic * state. It'll be released when the atomic state is * cleaned.
*/ if (!dc_state_add_plane(
dc,
dm_new_crtc_state->stream,
dc_new_plane_state,
dm_state->context)) {
dc_plane_state_release(dc_new_plane_state);
ret = -EINVAL; goto out;
}
/* Tell DC to do a full surface update every time there * is a plane change. Inefficient, but works for now.
*/
dm_new_plane_state->dc_state->update_flags.bits.full_update = 1;
*lock_and_validation_needed = true;
}
out: /* If enabling cursor overlay failed, attempt fallback to native mode */ if (enable && ret == -EINVAL && plane->type == DRM_PLANE_TYPE_CURSOR) {
ret = dm_check_native_cursor_state(new_plane_crtc, plane,
new_plane_state, enable); if (ret) return ret;
/* * The normalized_zpos value cannot be used by this iterator directly. It's only * calculated for enabled planes, potentially causing normalized_zpos collisions * between enabled/disabled planes in the atomic state. We need a unique value * so that the iterator will not generate the same object twice, or loop * indefinitely.
*/ staticinlinestruct __drm_planes_state *__get_next_zpos( struct drm_atomic_state *state, struct __drm_planes_state *prev)
{ unsignedint highest_zpos = 0, prev_zpos = 256;
uint32_t highest_id = 0, prev_id = UINT_MAX; struct drm_plane_state *new_plane_state; struct drm_plane *plane; int i, highest_i = -1;
for_each_new_plane_in_state(state, plane, new_plane_state, i) { /* Skip planes with higher zpos than the previously returned */ if (new_plane_state->zpos > prev_zpos ||
(new_plane_state->zpos == prev_zpos &&
plane->base.id >= prev_id)) continue;
/* Save the index of the plane with highest zpos */ if (new_plane_state->zpos > highest_zpos ||
(new_plane_state->zpos == highest_zpos &&
plane->base.id > highest_id)) {
highest_zpos = new_plane_state->zpos;
highest_id = plane->base.id;
highest_i = i;
}
}
if (highest_i < 0) return NULL;
return &state->planes[highest_i];
}
/* * Use the uniqueness of the plane's (zpos, drm obj ID) combination to iterate * by descending zpos, as read from the new plane state. This is the same * ordering as defined by drm_atomic_normalize_zpos().
*/ #define for_each_oldnew_plane_in_descending_zpos(__state, plane, old_plane_state, new_plane_state) \ for (struct __drm_planes_state *__i = __get_next_zpos((__state), NULL); \
__i != NULL; __i = __get_next_zpos((__state), __i)) \
for_each_if(((plane) = __i->ptr, \
(void)(plane) /* Only to avoid unused-but-set-variable warning */, \
(old_plane_state) = __i->old_state, \
(new_plane_state) = __i->new_state, 1))
/** * DOC: Cursor Modes - Native vs Overlay * * In native mode, the cursor uses a integrated cursor pipe within each DCN hw * plane. It does not require a dedicated hw plane to enable, but it is * subjected to the same z-order and scaling as the hw plane. It also has format * restrictions, a RGB cursor in native mode cannot be enabled within a non-RGB * hw plane. * * In overlay mode, the cursor uses a separate DCN hw plane, and thus has its * own scaling and z-pos. It also has no blending restrictions. It lends to a * cursor behavior more akin to a DRM client's expectations. However, it does * occupy an extra DCN plane, and therefore will only be used if a DCN plane is * available.
*/
/** * dm_crtc_get_cursor_mode() - Determine the required cursor mode on crtc * @adev: amdgpu device * @state: DRM atomic state * @dm_crtc_state: amdgpu state for the CRTC containing the cursor * @cursor_mode: Returns the required cursor mode on dm_crtc_state * * Get whether the cursor should be enabled in native mode, or overlay mode, on * the dm_crtc_state. * * The cursor should be enabled in overlay mode if there exists an underlying * plane - on which the cursor may be blended - that is either YUV formatted, or * scaled differently from the cursor. * * Since zpos info is required, drm_atomic_normalize_zpos must be called before * calling this function. * * Return: 0 on success, or an error code if getting the cursor plane state * failed.
*/ staticint dm_crtc_get_cursor_mode(struct amdgpu_device *adev, struct drm_atomic_state *state, struct dm_crtc_state *dm_crtc_state, enum amdgpu_dm_cursor_mode *cursor_mode)
{ struct drm_plane_state *old_plane_state, *plane_state, *cursor_state; struct drm_crtc_state *crtc_state = &dm_crtc_state->base; struct drm_plane *plane; bool consider_mode_change = false; bool entire_crtc_covered = false; bool cursor_changed = false; int underlying_scale_w, underlying_scale_h; int cursor_scale_w, cursor_scale_h; int i;
/* Overlay cursor not supported on HW before DCN * DCN401 does not have the cursor-on-scaled-plane or cursor-on-yuv-plane restrictions * as previous DCN generations, so enable native mode on DCN401 in addition to DCE
*/ if (amdgpu_ip_version(adev, DCE_HWIP, 0) == 0 ||
amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1)) {
*cursor_mode = DM_CURSOR_NATIVE_MODE; return 0;
}
/* Init cursor_mode to be the same as current */
*cursor_mode = dm_crtc_state->cursor_mode;
/* * Cursor mode can change if a plane's format changes, scale changes, is * enabled/disabled, or z-order changes.
*/
for_each_oldnew_plane_in_state(state, plane, old_plane_state, plane_state, i) { int new_scale_w, new_scale_h, old_scale_w, old_scale_h;
/* Only care about planes on this CRTC */ if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0) continue;
if (plane->type == DRM_PLANE_TYPE_CURSOR)
cursor_changed = true;
if (!consider_mode_change && !crtc_state->zpos_changed) return 0;
/* * If no cursor change on this CRTC, and not enabled on this CRTC, then * no need to set cursor mode. This avoids needlessly locking the cursor * state.
*/ if (!cursor_changed &&
!(drm_plane_mask(crtc_state->crtc->cursor) & crtc_state->plane_mask)) { return 0;
}
cursor_state = drm_atomic_get_plane_state(state,
crtc_state->crtc->cursor); if (IS_ERR(cursor_state)) return PTR_ERR(cursor_state);
/* Cursor is disabled */ if (!cursor_state->fb) return 0;
/* For all planes in descending z-order (all of which are below cursor * as per zpos definitions), check their scaling and format
*/
for_each_oldnew_plane_in_descending_zpos(state, plane, old_plane_state, plane_state) {
/* Only care about non-cursor planes on this CRTC */ if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0 ||
plane->type == DRM_PLANE_TYPE_CURSOR) continue;
/* Underlying plane is YUV format - use overlay cursor */ if (amdgpu_dm_plane_is_video_format(plane_state->fb->format->format)) {
*cursor_mode = DM_CURSOR_OVERLAY_MODE; return 0;
}
/* Underlying plane has different scale - use overlay cursor */ if (cursor_scale_w != underlying_scale_w &&
cursor_scale_h != underlying_scale_h) {
*cursor_mode = DM_CURSOR_OVERLAY_MODE; return 0;
}
/* If this plane covers the whole CRTC, no need to check planes underneath */ if (plane_state->crtc_x <= 0 && plane_state->crtc_y <= 0 &&
plane_state->crtc_x + plane_state->crtc_w >= crtc_state->mode.hdisplay &&
plane_state->crtc_y + plane_state->crtc_h >= crtc_state->mode.vdisplay) {
entire_crtc_covered = true; break;
}
}
/* If planes do not cover the entire CRTC, use overlay mode to enable * cursor over holes
*/ if (entire_crtc_covered)
*cursor_mode = DM_CURSOR_NATIVE_MODE; else
*cursor_mode = DM_CURSOR_OVERLAY_MODE;
if (IS_ERR(new_plane_state) || IS_ERR(old_plane_state)) {
drm_err(dev, "Failed to get plane state for plane %s\n", plane->name); returnfalse;
}
if (old_plane_state->fb && new_plane_state->fb &&
get_mem_type(old_plane_state->fb) != get_mem_type(new_plane_state->fb)) returntrue;
}
returnfalse;
}
/** * amdgpu_dm_atomic_check() - Atomic check implementation for AMDgpu DM. * * @dev: The DRM device * @state: The atomic state to commit * * Validate that the given atomic state is programmable by DC into hardware. * This involves constructing a &struct dc_state reflecting the new hardware * state we wish to commit, then querying DC to see if it is programmable. It's * important not to modify the existing DC state. Otherwise, atomic_check * may unexpectedly commit hardware changes. * * When validating the DC state, it's important that the right locks are * acquired. For full updates case which removes/adds/updates streams on one * CRTC while flipping on another CRTC, acquiring global lock will guarantee * that any such full update commit will wait for completion of any outstanding * flip using DRMs synchronization events. * * Note that DM adds the affected connectors for all CRTCs in state, when that * might not seem necessary. This is because DC stream creation requires the * DC sink, which is tied to the DRM connector state. Cleaning this up should * be possible but non-trivial - a possible TODO item. * * Return: -Error code if validation failed.
*/ staticint amdgpu_dm_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
{ struct amdgpu_device *adev = drm_to_adev(dev); struct dm_atomic_state *dm_state = NULL; struct dc *dc = adev->dm.dc; struct drm_connector *connector; struct drm_connector_state *old_con_state, *new_con_state; struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state, *new_crtc_state; struct drm_plane *plane; struct drm_plane_state *old_plane_state, *new_plane_state, *new_cursor_state; enum dc_status status; int ret, i; bool lock_and_validation_needed = false; bool is_top_most_overlay = true; struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state; struct drm_dp_mst_topology_mgr *mgr; struct drm_dp_mst_topology_state *mst_state; struct dsc_mst_fairness_vars vars[MAX_PIPES] = {0};
trace_amdgpu_dm_atomic_check_begin(state);
ret = drm_atomic_helper_check_modeset(dev, state); if (ret) {
drm_dbg_atomic(dev, "drm_atomic_helper_check_modeset() failed\n"); goto fail;
}
ret = amdgpu_dm_verify_lut_sizes(new_crtc_state); if (ret) {
drm_dbg_atomic(dev, "amdgpu_dm_verify_lut_sizes() failed\n"); goto fail;
}
if (!new_crtc_state->enable) continue;
ret = drm_atomic_add_affected_connectors(state, crtc); if (ret) {
drm_dbg_atomic(dev, "drm_atomic_add_affected_connectors() failed\n"); goto fail;
}
ret = drm_atomic_add_affected_planes(state, crtc); if (ret) {
drm_dbg_atomic(dev, "drm_atomic_add_affected_planes() failed\n"); goto fail;
}
if (dm_old_crtc_state->dsc_force_changed)
new_crtc_state->mode_changed = true;
}
/* * Add all primary and overlay planes on the CRTC to the state * whenever a plane is enabled to maintain correct z-ordering * and to enable fast surface updates.
*/
drm_for_each_crtc(crtc, dev) { bool modified = false;
if (IS_ERR(new_plane_state)) {
ret = PTR_ERR(new_plane_state);
drm_dbg_atomic(dev, "new_plane_state is BAD\n"); goto fail;
}
}
}
/* * DC consults the zpos (layer_index in DC terminology) to determine the * hw plane on which to enable the hw cursor (see * `dcn10_can_pipe_disable_cursor`). By now, all modified planes are in * atomic state, so call drm helper to normalize zpos.
*/
ret = drm_atomic_normalize_zpos(dev, state); if (ret) {
drm_dbg(dev, "drm_atomic_normalize_zpos() failed\n"); goto fail;
}
/* * Determine whether cursors on each CRTC should be enabled in native or * overlay mode.
*/
for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
ret = dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state,
&dm_new_crtc_state->cursor_mode); if (ret) {
drm_dbg(dev, "Failed to determine cursor mode\n"); goto fail;
}
/* * If overlay cursor is needed, DC cannot go through the * native cursor update path. All enabled planes on the CRTC * need to be added for DC to not disable a plane by mistake
*/ if (dm_new_crtc_state->cursor_mode == DM_CURSOR_OVERLAY_MODE) {
ret = drm_atomic_add_affected_planes(state, crtc); if (ret) goto fail;
}
}
/* Remove exiting planes if they are modified */
for_each_oldnew_plane_in_descending_zpos(state, plane, old_plane_state, new_plane_state) {
ret = dm_update_plane_state(dc, state, plane,
old_plane_state,
new_plane_state, false,
&lock_and_validation_needed,
&is_top_most_overlay); if (ret) {
drm_dbg_atomic(dev, "dm_update_plane_state() failed\n"); goto fail;
}
}
/* Disable all crtcs which require disable */
for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
ret = dm_update_crtc_state(&adev->dm, state, crtc,
old_crtc_state,
new_crtc_state, false,
&lock_and_validation_needed); if (ret) {
drm_dbg_atomic(dev, "DISABLE: dm_update_crtc_state() failed\n"); goto fail;
}
}
/* Enable all crtcs which require enable */
for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
ret = dm_update_crtc_state(&adev->dm, state, crtc,
old_crtc_state,
new_crtc_state, true,
&lock_and_validation_needed); if (ret) {
drm_dbg_atomic(dev, "ENABLE: dm_update_crtc_state() failed\n"); goto fail;
}
}
#ifdefined(CONFIG_DRM_AMD_DC_FP) if (dc_resource_is_dsc_encoding_supported(dc)) {
ret = pre_validate_dsc(state, &dm_state, vars); if (ret != 0) goto fail;
} #endif
/* Run this here since we want to validate the streams we created */
ret = drm_atomic_helper_check_planes(dev, state); if (ret) {
drm_dbg_atomic(dev, "drm_atomic_helper_check_planes() failed\n"); goto fail;
}
for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
dm_new_crtc_state = to_dm_crtc_state(new_crtc_state); if (dm_new_crtc_state->mpo_requested)
drm_dbg_atomic(dev, "MPO enablement requested on crtc:[%p]\n", crtc);
}
if (is_rotated || is_scaled) {
drm_dbg_driver(
crtc->dev, "[CRTC:%d:%s] cannot enable hardware cursor due to rotation/scaling\n",
crtc->base.id, crtc->name);
ret = -EINVAL; goto fail;
}
}
/* If HW can only do native cursor, check restrictions again */
ret = dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state,
&required_cursor_mode); if (ret) {
drm_dbg_driver(crtc->dev, "[CRTC:%d:%s] Checking cursor mode failed\n",
crtc->base.id, crtc->name); goto fail;
} elseif (required_cursor_mode == DM_CURSOR_OVERLAY_MODE) {
drm_dbg_driver(crtc->dev, "[CRTC:%d:%s] Cannot enable native cursor due to scaling or YUV restrictions\n",
crtc->base.id, crtc->name);
ret = -EINVAL; goto fail;
}
}
if (state->legacy_cursor_update) { /* * This is a fast cursor update coming from the plane update * helper, check if it can be done asynchronously for better * performance.
*/
state->async_update =
!drm_atomic_helper_async_check(dev, state);
/* * Skip the remaining global validation if this is an async * update. Cursor updates can be done without affecting * state or bandwidth calcs and this avoids the performance * penalty of locking the private state object and * allocating a new dc_state.
*/ if (state->async_update) return 0;
}
/* Check scaling and underscan changes*/ /* TODO Removed scaling changes validation due to inability to commit * new stream into context w\o causing full reset. Need to * decide how to handle.
*/
for_each_oldnew_connector_in_state(state, connector, old_con_state, new_con_state, i) { struct dm_connector_state *dm_old_con_state = to_dm_connector_state(old_con_state); struct dm_connector_state *dm_new_con_state = to_dm_connector_state(new_con_state); struct amdgpu_crtc *acrtc = to_amdgpu_crtc(dm_new_con_state->base.crtc);
/* Skip any modesets/resets */ if (!acrtc || drm_atomic_crtc_needs_modeset(
drm_atomic_get_new_crtc_state(state, &acrtc->base))) continue;
/* Skip any thing not scale or underscan changes */ if (!is_scaling_state_different(dm_new_con_state, dm_old_con_state)) continue;
lock_and_validation_needed = true;
}
/* set the slot info for each mst_state based on the link encoding format */
for_each_new_mst_mgr_in_state(state, mgr, mst_state, i) { struct amdgpu_dm_connector *aconnector; struct drm_connector *connector; struct drm_connector_list_iter iter;
u8 link_coding_cap;
/** * Streams and planes are reset when there are changes that affect * bandwidth. Anything that affects bandwidth needs to go through * DC global validation to ensure that the configuration can be applied * to hardware. * * We have to currently stall out here in atomic_check for outstanding * commits to finish in this case because our IRQ handlers reference * DRM state directly - we can end up disabling interrupts too early * if we don't. * * TODO: Remove this stall and drop DM state private objects.
*/ if (lock_and_validation_needed) {
ret = dm_atomic_get_state(state, &dm_state); if (ret) {
drm_dbg_atomic(dev, "dm_atomic_get_state() failed\n"); goto fail;
}
ret = do_aquire_global_lock(dev, state); if (ret) {
drm_dbg_atomic(dev, "do_aquire_global_lock() failed\n"); goto fail;
}
#ifdefined(CONFIG_DRM_AMD_DC_FP) if (dc_resource_is_dsc_encoding_supported(dc)) {
ret = compute_mst_dsc_configs_for_state(state, dm_state->context, vars); if (ret) {
drm_dbg_atomic(dev, "MST_DSC compute_mst_dsc_configs_for_state() failed\n");
ret = -EINVAL; goto fail;
}
} #endif
ret = dm_update_mst_vcpi_slots_for_dsc(state, dm_state->context, vars); if (ret) {
drm_dbg_atomic(dev, "dm_update_mst_vcpi_slots_for_dsc() failed\n"); goto fail;
}
/* * Perform validation of MST topology in the state: * We need to perform MST atomic check before calling * dc_validate_global_state(), or there is a chance * to get stuck in an infinite loop and hang eventually.
*/
ret = drm_dp_mst_atomic_check(state); if (ret) {
drm_dbg_atomic(dev, "MST drm_dp_mst_atomic_check() failed\n"); goto fail;
}
status = dc_validate_global_state(dc, dm_state->context, DC_VALIDATE_MODE_ONLY); if (status != DC_OK) {
drm_dbg_atomic(dev, "DC global validation failure: %s (%d)",
dc_status_to_str(status), status);
ret = -EINVAL; goto fail;
}
} else { /* * The commit is a fast update. Fast updates shouldn't change * the DC context, affect global validation, and can have their * commit work done in parallel with other commits not touching * the same resource. If we have a new DC context as part of * the DM atomic state from validation we need to free it and * retain the existing one instead. * * Furthermore, since the DM atomic state only contains the DC * context and can safely be annulled, we can free the state * and clear the associated private object now to free * some memory and avoid a possible use-after-free later.
*/
for (i = 0; i < state->num_private_objs; i++) { struct drm_private_obj *obj = state->private_objs[i].ptr;
if (obj->funcs == adev->dm.atomic_obj.funcs) { int j = state->num_private_objs-1;
/* If i is not at the end of the array then the * last element needs to be moved to where i was * before the array can safely be truncated.
*/ if (i != j)
state->private_objs[i] =
state->private_objs[j];
/* Store the overall update type for use later in atomic check. */
for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { struct dm_crtc_state *dm_new_crtc_state =
to_dm_crtc_state(new_crtc_state);
/* * Only allow async flips for fast updates that don't change * the FB pitch, the DCC state, rotation, mem_type, etc.
*/ if (new_crtc_state->async_flip &&
(lock_and_validation_needed ||
amdgpu_dm_crtc_mem_type_changed(dev, state, new_crtc_state))) {
drm_dbg_atomic(crtc->dev, "[CRTC:%d:%s] async flips are only supported for fast updates\n",
crtc->base.id, crtc->name);
ret = -EINVAL; goto fail;
}
res = dc_wake_and_execute_dmub_cmd(dm->dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY); if (!res) {
drm_err(adev_to_drm(dm->adev), "EDID CEA parser failed\n"); returnfalse;
}
output = &cmd.edid_cea.data.output;
if (output->type == DMUB_CMD__EDID_CEA_ACK) { if (!output->ack.success) {
drm_err(adev_to_drm(dm->adev), "EDID CEA ack failed at offset %d\n",
output->ack.offset);
}
} elseif (output->type == DMUB_CMD__EDID_CEA_AMD_VSDB) { if (!output->amd_vsdb.vsdb_found) returnfalse;
staticbool parse_edid_cea_dmcu(struct amdgpu_display_manager *dm,
u8 *edid_ext, int len, struct amdgpu_hdmi_vsdb_info *vsdb_info)
{ int i;
/* send extension block to DMCU for parsing */ for (i = 0; i < len; i += 8) { bool res; int offset;
/* send 8 bytes a time */ if (!dc_edid_parser_send_cea(dm->dc, i, len, &edid_ext[i], 8)) returnfalse;
if (i+8 == len) { /* EDID block sent completed, expect result */ int version, min_rate, max_rate;
res = dc_edid_parser_recv_amd_vsdb(dm->dc, &version, &min_rate, &max_rate); if (res) { /* amd vsdb found */
vsdb_info->freesync_supported = 1;
vsdb_info->amd_vsdb_version = version;
vsdb_info->min_refresh_rate_hz = min_rate;
vsdb_info->max_refresh_rate_hz = max_rate; returntrue;
} /* not amd vsdb */ returnfalse;
}
/* check for ack*/
res = dc_edid_parser_recv_cea_ack(dm->dc, &offset); if (!res) returnfalse;
}
returnfalse;
}
staticbool parse_edid_cea_dmub(struct amdgpu_display_manager *dm,
u8 *edid_ext, int len, struct amdgpu_hdmi_vsdb_info *vsdb_info)
{ int i;
/* send extension block to DMCU for parsing */ for (i = 0; i < len; i += 8) { /* send 8 bytes a time */ if (!dm_edid_parser_send_cea(dm, i, len, &edid_ext[i], 8, vsdb_info)) returnfalse;
}
/*----- drm_find_cea_extension() -----*/ /* No EDID or EDID extensions */ if (edid == NULL || edid->extensions == 0) return -ENODEV;
/* Find CEA extension */ for (i = 0; i < edid->extensions; i++) {
edid_ext = (uint8_t *)edid + EDID_LENGTH * (i + 1); if (edid_ext[0] == CEA_EXT) break;
}
if (i == edid->extensions) return -ENODEV;
/*----- cea_db_offsets() -----*/ if (edid_ext[0] != CEA_EXT) return -ENODEV;
/** * amdgpu_dm_update_freesync_caps - Update Freesync capabilities * * @connector: Connector to query. * @drm_edid: DRM EDID from monitor * * Amdgpu supports Freesync in DP and HDMI displays, and it is required to keep * track of some of the display information in the internal data struct used by * amdgpu_dm. This function checks which type of connector we need to set the * FreeSync parameters.
*/ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, conststruct drm_edid *drm_edid)
{ int i = 0; struct amdgpu_dm_connector *amdgpu_dm_connector =
to_amdgpu_dm_connector(connector); struct dm_connector_state *dm_con_state = NULL; struct dc_sink *sink; struct amdgpu_device *adev = drm_to_adev(connector->dev); struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; conststruct edid *edid; bool freesync_capable = false; enum adaptive_sync_type as_type = ADAPTIVE_SYNC_TYPE_NONE;
if (!connector->state) {
drm_err(adev_to_drm(adev), "%s - Connector has no state", __func__); goto update;
}
if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version)) goto update;
edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw()
/* Some eDP panels only have the refresh rate range info in DisplayID */ if ((connector->display_info.monitor_range.min_vfreq == 0 ||
connector->display_info.monitor_range.max_vfreq == 0))
parse_edid_displayid_vrr(connector, edid);
if (connector->vrr_capable_property)
drm_connector_set_vrr_capable_property(connector,
freesync_capable);
}
void amdgpu_dm_trigger_timing_sync(struct drm_device *dev)
{ struct amdgpu_device *adev = drm_to_adev(dev); struct dc *dc = adev->dm.dc; int i;
mutex_lock(&adev->dm.dc_lock); if (dc->current_state) { for (i = 0; i < dc->current_state->stream_count; ++i)
dc->current_state->streams[i]
->triggered_crtc_reset.enabled =
adev->dm.force_timing_sync;
if (p_notify->result != AUX_RET_SUCCESS) { /* * Transient states before tunneling is enabled could * lead to this error. We can ignore this for now.
*/ if (p_notify->result == AUX_RET_ERROR_PROTOCOL_ERROR) {
drm_warn(adev_to_drm(adev), "DPIA AUX failed on 0x%x(%d), error %d\n",
payload->address, payload->length,
p_notify->result);
}
*operation_result = p_notify->result; goto out;
}
payload->reply[0] = adev->dm.dmub_notify->aux_reply.command & 0xF; if (adev->dm.dmub_notify->aux_reply.command & 0xF0) /* The reply is stored in the top nibble of the command. */
payload->reply[0] = (adev->dm.dmub_notify->aux_reply.command >> 4) & 0xF;
/*write req may receive a byte indicating partially written number as well*/ if (p_notify->aux_reply.length)
memcpy(payload->data, p_notify->aux_reply.data,
p_notify->aux_reply.length);
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.416Bemerkung:
(vorverarbeitet am 2026-04-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.