diff options
| author | Thomas Zimmermann <tzimmermann@suse.de> | 2026-06-30 10:16:00 +0200 |
|---|---|---|
| committer | Thomas Zimmermann <tzimmermann@suse.de> | 2026-06-30 10:16:00 +0200 |
| commit | 827b9aabd3ea3e96d5e48abed9f44dbd1e550d4e (patch) | |
| tree | 845a97a83f20fe0585840a66d9eed8fcf1286772 /drivers/gpu/drm/amd/display/amdgpu_dm | |
| parent | 3b3bce4a692ac60d9f4a341e6b597dd1fd0a28f9 (diff) | |
| parent | dc59e4fea9d83f03bad6bddf3fa2e52491777482 (diff) | |
| download | linux-next-827b9aabd3ea3e96d5e48abed9f44dbd1e550d4e.tar.gz linux-next-827b9aabd3ea3e96d5e48abed9f44dbd1e550d4e.zip | |
Merge drm/drm-next into drm-misc-next
Backmerging to get drm-misc-next to v7.2-rc1.
Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
Diffstat (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm')
31 files changed, 4438 insertions, 215 deletions
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index 89350aa9ca7e..54a93e4255b3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -59,3 +59,11 @@ AMDGPU_DM = $(addprefix $(AMDDALPATH)/amdgpu_dm/,$(AMDGPUDM)) AMD_DISPLAY_FILES += $(AMDGPU_DM) endif + +# KUnit tests as separate module +ifneq ($(CONFIG_DRM_AMD_DC_KUNIT_TEST),) +obj-y += $(AMDDALPATH)/amdgpu_dm/tests/ + +# Enable gcov for amdgpu_dm KUnit builds +GCOV_PROFILE := y +endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index bf27717d5651..d2dcef74625c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -158,6 +158,9 @@ MODULE_FIRMWARE(FIRMWARE_DCN_401_DMUB); #define FIRMWARE_DCN_42_DMUB "amdgpu/dcn_4_2_dmcub.bin" MODULE_FIRMWARE(FIRMWARE_DCN_42_DMUB); +#define FIRMWARE_DCN_42B_DMUB "amdgpu/dcn_4_2_1_dmcub.bin" +MODULE_FIRMWARE(FIRMWARE_DCN_42B_DMUB); + /** * DOC: overview * @@ -1375,6 +1378,7 @@ static int dm_dmub_hw_init(struct amdgpu_device *adev) case IP_VERSION(3, 5, 1): case IP_VERSION(3, 6, 0): case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): hw_params.ips_sequential_ono = adev->external_rev_id > 0x10; hw_params.lower_hbr3_phy_ssc = true; break; @@ -1823,6 +1827,7 @@ static void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev) bb_size = sizeof(struct dml2_soc_bb); break; case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): bb_size = sizeof(struct dml2_soc_bb); break; default: @@ -1870,6 +1875,7 @@ static enum dmub_ips_disable_type dm_get_default_ips_mode( ret = DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF; break; case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): ret = DMUB_IPS_ENABLE; break; default: @@ -1946,6 +1952,40 @@ static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm) return 0; } +static void hdmi_frl_status_polling_work(struct work_struct *work) +{ + struct amdgpu_display_manager *dm = + container_of(to_delayed_work(work), struct amdgpu_display_manager, + hdmi_frl_status_polling_work); + struct dc *dc = dm->dc; + struct dc_link *dc_link; + bool link_update = false; + + for (int i = 0; i < MAX_LINKS; i++) { + dc_link = dc->links[i]; + + if (!dc_link || !dc_link->local_sink) + continue; + + if (!dc_is_hdmi_signal(dc_link->connector_signal)) + continue; + + if (dc_link->connector_signal != SIGNAL_TYPE_HDMI_FRL) + continue; + + link_update = dc_link_frl_poll_status_flag(dc_link); + if (link_update) { + mutex_lock(&dm->dc_lock); + dc_link_detect(dc_link, DETECT_REASON_RETRAIN); + mutex_unlock(&dm->dc_lock); + } + } + + queue_delayed_work(dm->hdmi_frl_status_polling_wq, + &dm->hdmi_frl_status_polling_work, + msecs_to_jiffies(dm->hdmi_frl_status_polling_delay_ms)); +} + static int amdgpu_dm_init(struct amdgpu_device *adev) { struct dc_init_data init_data; @@ -2050,6 +2090,9 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) if (amdgpu_dc_feature_mask & DC_DISABLE_LTTPR_DP2_0) init_data.flags.allow_lttpr_non_transparent_mode.bits.DP2_0 = true; + if (amdgpu_dc_feature_mask & DC_FRL_MASK) + init_data.flags.enable_frl = true; + init_data.flags.seamless_boot_edp_requested = false; if (amdgpu_device_seamless_boot_supported(adev)) { @@ -2217,6 +2260,14 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) dc_init_callbacks(adev->dm.dc, &init_params); } + if (adev->dm.dc->caps.max_links > 0) { + adev->dm.hdmi_frl_status_polling_wq = + create_singlethread_workqueue("hdmi_frl_status_polling_workqueue"); + if (!adev->dm.hdmi_frl_status_polling_wq) + drm_err(adev_to_drm(adev), "failed to initialize hdmi_frl_status_polling_workqueue\n"); + adev->dm.hdmi_frl_status_polling_delay_ms = 200; + INIT_DELAYED_WORK(&adev->dm.hdmi_frl_status_polling_work, hdmi_frl_status_polling_work); + } if (dc_is_dmub_outbox_supported(adev->dm.dc)) { init_completion(&adev->dm.dmub_aux_transfer_done); adev->dm.dmub_notify = kzalloc_obj(struct dmub_notification); @@ -2483,6 +2534,7 @@ static int load_dmcu_fw(struct amdgpu_device *adev) case IP_VERSION(3, 6, 0): case IP_VERSION(4, 0, 1): case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): return 0; default: break; @@ -2621,6 +2673,9 @@ static int dm_dmub_sw_init(struct amdgpu_device *adev) case IP_VERSION(4, 2, 0): dmub_asic = DMUB_ASIC_DCN42; break; + case IP_VERSION(4, 2, 1): + dmub_asic = DMUB_ASIC_DCN42B; + break; default: /* ASIC doesn't support DMUB. */ return 0; @@ -5747,6 +5802,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) case IP_VERSION(3, 6, 0): case IP_VERSION(4, 0, 1): case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): if (register_outbox_irq_handlers(dm->adev)) { drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n"); goto fail; @@ -5772,6 +5828,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) case IP_VERSION(3, 6, 0): case IP_VERSION(4, 0, 1): case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): psr_feature_enabled = true; break; default: @@ -5790,6 +5847,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) case IP_VERSION(3, 5, 1): case IP_VERSION(3, 6, 0): case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): replay_feature_enabled = true; break; @@ -5951,6 +6009,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) case IP_VERSION(3, 6, 0): case IP_VERSION(4, 0, 1): case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): if (dcn10_register_irq_handlers(dm->adev)) { drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n"); goto fail; @@ -6102,6 +6161,9 @@ static int dm_init_microcode(struct amdgpu_device *adev) case IP_VERSION(4, 2, 0): fw_name_dmub = FIRMWARE_DCN_42_DMUB; break; + case IP_VERSION(4, 2, 1): + fw_name_dmub = FIRMWARE_DCN_42B_DMUB; + break; default: /* ASIC doesn't support DMUB. */ return 0; @@ -6230,6 +6292,7 @@ static int dm_early_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(3, 6, 0): case IP_VERSION(4, 0, 1): case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): adev->mode_info.num_crtc = 4; adev->mode_info.num_hpd = 4; adev->mode_info.num_dig = 4; @@ -6926,9 +6989,9 @@ static void fill_stream_properties_from_drm_display_mode( timing_out->v_border_top = 0; timing_out->v_border_bottom = 0; - want_420 = (aconnector && aconnector->force_yuv420_output) || + want_420 = (aconnector && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) || (connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR420); - want_422 = (aconnector && aconnector->force_yuv422_output) || + want_422 = (aconnector && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR422) || (connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR422); if (drm_mode_is_420_only(info, mode_in) && @@ -6981,7 +7044,8 @@ static void fill_stream_properties_from_drm_display_mode( timing_out->flags.VSYNC_POSITIVE_POLARITY = 1; } - if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A) { + if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + stream->signal == SIGNAL_TYPE_HDMI_FRL) { err = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame, (struct drm_connector *)connector, mode_in); @@ -7326,12 +7390,30 @@ static void update_dsc_caps(struct amdgpu_dm_connector *aconnector, if (aconnector->dc_link && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || sink->sink_signal == SIGNAL_TYPE_EDP)) { - if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE || - sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) + if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE) dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc, aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw, aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw, dsc_caps); + else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) { + if (aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.fields.dsc_support.DSC_PASSTHROUGH_SUPPORT && + !aconnector->dsc_settings.dsc_force_disable_passthrough && + aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps > 0 && + sink->edid_caps.frl_dsc_support && + sink->edid_caps.max_frl_rate > 0 && + sink->edid_caps.frl_dsc_max_frl_rate > 0) + dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps); + else + dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc, + aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw, + aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw, + dsc_caps); + } + } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { + if (sink->edid_caps.frl_dsc_support && + sink->edid_caps.max_frl_rate > 0 && + sink->edid_caps.frl_dsc_max_frl_rate > 0) + dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps); } } @@ -7405,6 +7487,10 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, struct drm_connector *drm_connector = &aconnector->base; u32 link_bandwidth_kbps; struct dc *dc = sink->ctx->dc; + const struct dc_hdmi_frl_link_settings *frl_verified_link_cap = NULL; + u32 converter_bw_in_kbps; + u32 sink_bw_in_kbps; + u32 dsc_sink_bw_in_kbps; u32 max_supported_bw_in_kbps, timing_bw_in_kbps; u32 dsc_max_supported_bw_in_kbps; u32 max_dsc_target_bpp_limit_override = @@ -7443,8 +7529,18 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, } else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) { timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing, dc_link_get_highest_encoding_format(aconnector->dc_link)); - max_supported_bw_in_kbps = link_bandwidth_kbps; - dsc_max_supported_bw_in_kbps = link_bandwidth_kbps; + converter_bw_in_kbps = aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps; + sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.max_frl_rate); + dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate); + + if (dsc_caps->is_frl) { + max_supported_bw_in_kbps = min(link_bandwidth_kbps, converter_bw_in_kbps); + max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, sink_bw_in_kbps); + dsc_max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, dsc_sink_bw_in_kbps); + } else { + max_supported_bw_in_kbps = link_bandwidth_kbps; + dsc_max_supported_bw_in_kbps = link_bandwidth_kbps; + } if (timing_bw_in_kbps > max_supported_bw_in_kbps && max_supported_bw_in_kbps > 0 && @@ -7457,11 +7553,41 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, 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); + drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from %s\n", + __func__, drm_connector->name, + (dsc_caps->is_frl == 1) ? "HDMI FRL RX" : "DP-HDMI PCON"); } } } + else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { + struct dc_dsc_policy dsc_policy = {0}; + + frl_verified_link_cap = dc_link_get_frl_link_cap(stream->link); + if (frl_verified_link_cap->frl_link_rate != HDMI_FRL_LINK_RATE_DISABLE && + aconnector->dc_link->frl_flags.force_frl_dsc) { + dc_dsc_policy_set_enable_dsc_when_not_needed(true); + dc_dsc_get_policy_for_timing(&stream->timing, 0, &dsc_policy, dc_link_get_highest_encoding_format(stream->link)); + } + + timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing, DC_LINK_ENCODING_HDMI_FRL); + link_bandwidth_kbps = dc_link_frl_bandwidth_kbps(stream->link, frl_verified_link_cap->frl_link_rate); + dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate); + + if ((timing_bw_in_kbps > link_bandwidth_kbps && dsc_sink_bw_in_kbps > 0) || + (dsc_policy.enable_dsc_when_not_needed || dsc_options.force_dsc_when_not_needed)) { + if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], + dsc_caps, + &dsc_options, + dsc_sink_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: HDMI_FRL_DSC [%s] DSC is selected from HDMI FRL RX\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) @@ -7500,7 +7626,7 @@ create_stream_for_sink(struct drm_connector *connector, int preferred_refresh = 0; enum color_transfer_func tf = TRANSFER_FUNC_UNKNOWN; #if defined(CONFIG_DRM_AMD_DC_FP) - struct dsc_dec_dpcd_caps dsc_caps; + struct dsc_dec_dpcd_caps dsc_caps = {0}; #endif struct dc_link *link = NULL; struct dc_sink *sink = NULL; @@ -7628,8 +7754,9 @@ create_stream_for_sink(struct drm_connector *connector, update_stream_signal(stream, sink); - if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A) - mod_build_hf_vsif_infopacket(stream, &stream->vsp_infopacket); + if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + stream->signal == SIGNAL_TYPE_HDMI_FRL) + mod_build_hf_vsif_infopacket(stream, &stream->vsp_infopacket, false, false); if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT || stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST || @@ -8363,6 +8490,7 @@ create_validate_stream_for_sink(struct drm_connector *connector, if (aconnector && (aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_TYPE_A || + aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_FRL || aconnector->dc_link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER)) bpc_limit = 8; @@ -10286,7 +10414,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, continue; bundle->surface_updates[planes_count].surface = dc_plane; - if (new_pcrtc_state->color_mgmt_changed) { + if (new_pcrtc_state->color_mgmt_changed || new_plane_state->color_mgmt_changed) { bundle->surface_updates[planes_count].gamma = &dc_plane->gamma_correction; bundle->surface_updates[planes_count].in_transfer_func = &dc_plane->in_transfer_func; bundle->surface_updates[planes_count].gamut_remap_matrix = &dc_plane->gamut_remap_matrix; @@ -10942,6 +11070,25 @@ static void amdgpu_dm_commit_streams(struct drm_atomic_commit *state, dc_exit_ips_for_hw_access(dm->dc); WARN_ON(!dc_commit_streams(dm->dc, ¶ms)); + bool frl_stream_found = false; + + for (i = 0; i < params.stream_count; i++) { + struct dc_stream_state *stream = params.streams[i]; + + if (stream->signal == SIGNAL_TYPE_HDMI_FRL) { + frl_stream_found = true; + break; + } + } + if (frl_stream_found) { + if (queue_delayed_work(dm->hdmi_frl_status_polling_wq, + &dm->hdmi_frl_status_polling_work, + msecs_to_jiffies(dm->hdmi_frl_status_polling_delay_ms))) + drm_dbg_kms(dev, "200ms frl status polling starts ...\n"); + } else { + if (cancel_delayed_work_sync(&dm->hdmi_frl_status_polling_work)) + drm_dbg_kms(dev, "200ms frl status polling stops ...\n"); + } /* 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); @@ -12128,6 +12275,10 @@ static bool should_reset_plane(struct drm_atomic_commit *state, if (new_crtc_state->color_mgmt_changed) return true; + /* Plane color pipeline or its colorop changes. */ + if (new_plane_state->color_mgmt_changed) + return true; + /* * 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. @@ -12726,7 +12877,8 @@ static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev, * as previous DCN generations, so enable native mode on DCN401/420 */ if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) || - amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0)) { + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) || + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1)) { *cursor_mode = DM_CURSOR_NATIVE_MODE; return 0; } @@ -12853,13 +13005,11 @@ static bool amdgpu_dm_crtc_mem_type_changed(struct drm_device *dev, struct drm_plane_state *new_plane_state, *old_plane_state; drm_for_each_plane_mask(plane, dev, crtc_state->plane_mask) { - new_plane_state = drm_atomic_get_plane_state(state, plane); - old_plane_state = drm_atomic_get_plane_state(state, plane); + new_plane_state = drm_atomic_get_new_plane_state(state, plane); + old_plane_state = drm_atomic_get_old_plane_state(state, plane); - 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); - return false; - } + if (!old_plane_state || !new_plane_state) + continue; if (old_plane_state->fb && new_plane_state->fb && get_mem_type(old_plane_state->fb) != get_mem_type(new_plane_state->fb)) @@ -13162,9 +13312,11 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, continue; /* Check if rotation or scaling is enabled on DCN401 */ - if ((drm_plane_mask(crtc->cursor) & new_crtc_state->plane_mask) && - (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) || - amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1))) { + if ((drm_plane_mask(crtc->cursor) & + new_crtc_state->plane_mask) && + (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) || + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) || + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1))) { new_cursor_state = drm_atomic_get_new_plane_state(state, crtc->cursor); is_rotated = new_cursor_state && @@ -13750,17 +13902,15 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, } /* Handle MCCS */ - if (do_mccs) + if (do_mccs) { dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); - if ((sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || - as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) && - (!sink->edid_caps.freesync_vcp_code || - (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported))) - freesync_capable = false; + if (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported) + freesync_capable = false; - if (do_mccs && sink->mccs_caps.freesync_supported && freesync_capable) - dm_helpers_mccs_vcp_set(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); + if (sink->mccs_caps.freesync_supported && freesync_capable) + dm_helpers_mccs_vcp_set(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); + } update: if (dm_con_state) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 74f700fbeb6f..dd199e0b7922 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -45,8 +45,6 @@ * in amdgpu_dm_kms.h file */ -#define AMDGPU_DM_MAX_DISPLAY_INDEX 31 - #define AMDGPU_DM_MAX_CRTC 6 #define AMDGPU_DM_MAX_NUM_EDP 2 @@ -706,6 +704,14 @@ struct amdgpu_display_manager { struct completion replied; char reply_data[0x40]; // Cannot include dmub_cmd here } fused_io[8]; + /** + * @hdmi_frl_status_polling_work: + * + * workqueue for 200ms frl status polling + */ + struct workqueue_struct *hdmi_frl_status_polling_wq; + struct delayed_work hdmi_frl_status_polling_work; + unsigned int hdmi_frl_status_polling_delay_ms; /** * @dm_boot_time_crc_info: @@ -842,6 +848,7 @@ struct amdgpu_dm_connector { bool fake_enable; bool force_yuv420_output; bool force_yuv422_output; + uint8_t force_yuv_pixel_format; struct dsc_preferred_settings dsc_settings; struct psr_caps psr_caps; union dp_downstream_port_present mst_downstream_port_present; @@ -852,6 +859,8 @@ struct amdgpu_dm_connector { bool disallow_edp_enter_psr; bool disallow_edp_enter_replay; + union dwnstream_portxcaps mst_downstream_port_caps; + /* Record progress status of mst*/ uint8_t mst_status; @@ -1107,7 +1116,7 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, void amdgpu_dm_update_connector_after_detect( struct amdgpu_dm_connector *aconnector); -void populate_hdmi_info_from_connector(struct drm_hdmi_info *info, +void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *info, struct dc_edid_caps *edid_caps); extern const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index fa6883ae4dfb..86086d10c543 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -29,9 +29,12 @@ #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" +#include "amdgpu_dm_color.h" #include "amdgpu_dm_colorop.h" #include "dc.h" #include "modules/color/color_gamma.h" +#include "amdgpu_dm_kunit_helpers.h" + /** * DOC: overview @@ -157,8 +160,6 @@ * */ -#define MAX_DRM_LUT_VALUE 0xFFFF -#define MAX_DRM_LUT32_VALUE 0xFFFFFFFF #define SDR_WHITE_LEVEL_INIT_VALUE 80 /** @@ -172,7 +173,8 @@ void amdgpu_dm_init_color_mod(void) setup_x_points_distribution(); } -static inline struct fixed31_32 amdgpu_dm_fixpt_from_s3132(__u64 x) +STATIC_IFN_KUNIT INLINE_IFN_KUNIT +struct fixed31_32 amdgpu_dm_fixpt_from_s3132(__u64 x) { struct fixed31_32 val; @@ -183,6 +185,7 @@ static inline struct fixed31_32 amdgpu_dm_fixpt_from_s3132(__u64 x) val.value = x; return val; } +EXPORT_IF_KUNIT(amdgpu_dm_fixpt_from_s3132); #ifdef AMD_PRIVATE_COLOR /* Pre-defined Transfer Functions (TF) @@ -421,12 +424,14 @@ amdgpu_dm_create_color_properties(struct amdgpu_device *adev) * Returns: * DRM LUT or NULL */ -static const struct drm_color_lut * +STATIC_IFN_KUNIT +const struct drm_color_lut * __extract_blob_lut(const struct drm_property_blob *blob, uint32_t *size) { *size = blob ? drm_color_lut_size(blob) : 0; return blob ? (struct drm_color_lut *)blob->data : NULL; } +EXPORT_IF_KUNIT(__extract_blob_lut); /** * __extract_blob_lut32 - Extracts the DRM lut and lut size from a blob. @@ -436,12 +441,14 @@ __extract_blob_lut(const struct drm_property_blob *blob, uint32_t *size) * Returns: * DRM LUT or NULL */ -static const struct drm_color_lut32 * +STATIC_IFN_KUNIT +const struct drm_color_lut32 * __extract_blob_lut32(const struct drm_property_blob *blob, uint32_t *size) { *size = blob ? drm_color_lut32_size(blob) : 0; return blob ? (struct drm_color_lut32 *)blob->data : NULL; } +EXPORT_IF_KUNIT(__extract_blob_lut32); /** * __is_lut_linear - check if the given lut is a linear mapping of values @@ -456,7 +463,8 @@ __extract_blob_lut32(const struct drm_property_blob *blob, uint32_t *size) * True if the given lut is a linear mapping of values, i.e. it acts like a * bypass LUT. Otherwise, false. */ -static bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size) +STATIC_IFN_KUNIT +bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size) { int i; uint32_t expected; @@ -476,6 +484,7 @@ static bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size) } return true; } +EXPORT_IF_KUNIT(__is_lut_linear); /** * __drm_lut_to_dc_gamma - convert the drm_color_lut to dc_gamma. @@ -485,7 +494,8 @@ static bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size) * * The conversion depends on the size of the lut - whether or not it's legacy. */ -static void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut, +STATIC_IFN_KUNIT +void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut, struct dc_gamma *gamma, bool is_legacy) { uint32_t r, g, b; @@ -515,6 +525,7 @@ static void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut, gamma->entries.blue[i] = dc_fixpt_from_fraction(b, MAX_DRM_LUT_VALUE); } } +EXPORT_IF_KUNIT(__drm_lut_to_dc_gamma); /** * __drm_lut32_to_dc_gamma - convert the drm_color_lut to dc_gamma. @@ -523,7 +534,8 @@ static void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut, * * The conversion depends on the size of the lut - whether or not it's legacy. */ -static void __drm_lut32_to_dc_gamma(const struct drm_color_lut32 *lut, struct dc_gamma *gamma) +STATIC_IFN_KUNIT +void __drm_lut32_to_dc_gamma(const struct drm_color_lut32 *lut, struct dc_gamma *gamma) { int i; @@ -533,6 +545,7 @@ static void __drm_lut32_to_dc_gamma(const struct drm_color_lut32 *lut, struct dc gamma->entries.blue[i] = dc_fixpt_from_fraction(lut[i].blue, MAX_DRM_LUT32_VALUE); } } +EXPORT_IF_KUNIT(__drm_lut32_to_dc_gamma); /** * __drm_ctm_to_dc_matrix - converts a DRM CTM to a DC CSC float matrix @@ -541,8 +554,9 @@ static void __drm_lut32_to_dc_gamma(const struct drm_color_lut32 *lut, struct dc * * The matrix needs to be a 3x4 (12 entry) matrix. */ -static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, - struct fixed31_32 *matrix) +STATIC_IFN_KUNIT +void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, + struct fixed31_32 *matrix) { int i; @@ -565,6 +579,7 @@ static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, matrix[i] = amdgpu_dm_fixpt_from_s3132(ctm->matrix[i - (i / 4)]); } } +EXPORT_IF_KUNIT(__drm_ctm_to_dc_matrix); /** * __drm_ctm_3x4_to_dc_matrix - converts a DRM CTM 3x4 to a DC CSC float matrix @@ -573,8 +588,9 @@ static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, * * The matrix needs to be a 3x4 (12 entry) matrix. */ -static void __drm_ctm_3x4_to_dc_matrix(const struct drm_color_ctm_3x4 *ctm, - struct fixed31_32 *matrix) +STATIC_IFN_KUNIT +void __drm_ctm_3x4_to_dc_matrix(const struct drm_color_ctm_3x4 *ctm, + struct fixed31_32 *matrix) { int i; @@ -587,6 +603,7 @@ static void __drm_ctm_3x4_to_dc_matrix(const struct drm_color_ctm_3x4 *ctm, matrix[i] = amdgpu_dm_fixpt_from_s3132(ctm->matrix[i]); } } +EXPORT_IF_KUNIT(__drm_ctm_3x4_to_dc_matrix); /** * __set_legacy_tf - Calculates the legacy transfer function @@ -742,8 +759,23 @@ static int __set_output_tf_32(struct dc_transfer_func *func, return res ? 0 : -ENOMEM; } +STATIC_IFN_KUNIT void __set_tf_bypass(struct dc_transfer_func *tf) +{ + tf->type = TF_TYPE_BYPASS; + tf->tf = TRANSFER_FUNCTION_LINEAR; +} +EXPORT_IF_KUNIT(__set_tf_bypass); + +STATIC_IFN_KUNIT void __set_tf_distributed_points(struct dc_transfer_func *tf, + enum dc_transfer_func_predefined predefined_tf) +{ + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = predefined_tf; + tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; +} +EXPORT_IF_KUNIT(__set_tf_distributed_points); -static int amdgpu_dm_set_atomic_regamma(struct dc_transfer_func *out_tf, +STATIC_IFN_KUNIT int amdgpu_dm_set_atomic_regamma(struct dc_transfer_func *out_tf, const struct drm_color_lut *regamma_lut, uint32_t regamma_size, bool has_rom, enum dc_transfer_func_predefined tf) @@ -762,22 +794,19 @@ static int amdgpu_dm_set_atomic_regamma(struct dc_transfer_func *out_tf, * pre-defined TF and the custom LUT values into the LUT that's * actually programmed. */ - out_tf->type = TF_TYPE_DISTRIBUTED_POINTS; - out_tf->tf = tf; - out_tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; - + __set_tf_distributed_points(out_tf, tf); ret = __set_output_tf(out_tf, regamma_lut, regamma_size, has_rom); } else { /* * No CRTC RGM means we can just put the block into bypass * since we don't have any plane level adjustments using it. */ - out_tf->type = TF_TYPE_BYPASS; - out_tf->tf = TRANSFER_FUNCTION_LINEAR; + __set_tf_bypass(out_tf); } return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_set_atomic_regamma); /** * __set_input_tf - calculates the input transfer function based on expected @@ -851,7 +880,8 @@ static int __set_input_tf_32(struct dc_color_caps *caps, struct dc_transfer_func return res ? 0 : -ENOMEM; } -static enum dc_transfer_func_predefined +STATIC_IFN_KUNIT +enum dc_transfer_func_predefined amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf) { switch (tf) { @@ -879,8 +909,10 @@ amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf) return TRANSFER_FUNCTION_GAMMA26; } } +EXPORT_IF_KUNIT(amdgpu_tf_to_dc_tf); -static enum dc_transfer_func_predefined +STATIC_IFN_KUNIT +enum dc_transfer_func_predefined amdgpu_colorop_tf_to_dc_tf(enum drm_colorop_curve_1d_type tf) { switch (tf) { @@ -900,8 +932,10 @@ amdgpu_colorop_tf_to_dc_tf(enum drm_colorop_curve_1d_type tf) return TRANSFER_FUNCTION_LINEAR; } } +EXPORT_IF_KUNIT(amdgpu_colorop_tf_to_dc_tf); -static void __to_dc_lut3d_color(struct dc_rgb *rgb, +STATIC_IFN_KUNIT +void __to_dc_lut3d_color(struct dc_rgb *rgb, const struct drm_color_lut lut, int bit_precision) { @@ -909,8 +943,10 @@ static void __to_dc_lut3d_color(struct dc_rgb *rgb, rgb->green = drm_color_lut_extract(lut.green, bit_precision); rgb->blue = drm_color_lut_extract(lut.blue, bit_precision); } +EXPORT_IF_KUNIT(__to_dc_lut3d_color); -static void __drm_3dlut_to_dc_3dlut(const struct drm_color_lut *lut, +STATIC_IFN_KUNIT +void __drm_3dlut_to_dc_3dlut(const struct drm_color_lut *lut, uint32_t lut3d_size, struct tetrahedral_params *params, bool use_tetrahedral_9, @@ -953,8 +989,10 @@ static void __drm_3dlut_to_dc_3dlut(const struct drm_color_lut *lut, /* lut0 has 1229 points (lut_size/4 + 1) */ __to_dc_lut3d_color(&lut0[lut_i], lut[i], bit_depth); } +EXPORT_IF_KUNIT(__drm_3dlut_to_dc_3dlut); -static void __to_dc_lut3d_32_color(struct dc_rgb *rgb, +STATIC_IFN_KUNIT +void __to_dc_lut3d_32_color(struct dc_rgb *rgb, const struct drm_color_lut32 lut, int bit_precision) { @@ -962,8 +1000,10 @@ static void __to_dc_lut3d_32_color(struct dc_rgb *rgb, rgb->green = drm_color_lut32_extract(lut.green, bit_precision); rgb->blue = drm_color_lut32_extract(lut.blue, bit_precision); } +EXPORT_IF_KUNIT(__to_dc_lut3d_32_color); -static void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut, +STATIC_IFN_KUNIT +void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut, uint32_t lut3d_size, struct tetrahedral_params *params, bool use_tetrahedral_9, @@ -1006,6 +1046,7 @@ static void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut, /* lut0 has 1229 points (lut_size/4 + 1) */ __to_dc_lut3d_32_color(&lut0[lut_i], lut[i], bit_depth); } +EXPORT_IF_KUNIT(__drm_3dlut32_to_dc_3dlut); /* amdgpu_dm_atomic_lut3d - set DRM 3D LUT to DC stream * @drm_lut3d: user 3D LUT @@ -1015,9 +1056,9 @@ static void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut, * Map user 3D LUT data to DC 3D LUT and all necessary bits to program it * on DCN accordingly. */ -static void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut3d, - uint32_t drm_lut3d_size, - struct dc_3dlut *lut) +STATIC_IFN_KUNIT void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut3d, + uint32_t drm_lut3d_size, + struct dc_3dlut *lut) { if (!drm_lut3d_size) { lut->state.bits.initialized = 0; @@ -1033,8 +1074,9 @@ static void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut3d, MAX_COLOR_3DLUT_BITDEPTH); } } +EXPORT_IF_KUNIT(amdgpu_dm_atomic_lut3d); -static int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *shaper_lut, +STATIC_IFN_KUNIT int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *shaper_lut, bool has_rom, enum dc_transfer_func_predefined tf, uint32_t shaper_size, @@ -1047,20 +1089,17 @@ static int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *shaper_lut, * If user shaper LUT is set, we assume a linear color space * (linearized by degamma 1D LUT or not). */ - func_shaper->type = TF_TYPE_DISTRIBUTED_POINTS; - func_shaper->tf = tf; - func_shaper->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; - + __set_tf_distributed_points(func_shaper, tf); ret = __set_output_tf(func_shaper, shaper_lut, shaper_size, has_rom); } else { - func_shaper->type = TF_TYPE_BYPASS; - func_shaper->tf = TRANSFER_FUNCTION_LINEAR; + __set_tf_bypass(func_shaper); } return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_atomic_shaper_lut); -static int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut, +STATIC_IFN_KUNIT int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut, bool has_rom, enum dc_transfer_func_predefined tf, uint32_t blend_size, @@ -1076,18 +1115,15 @@ static int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut, * module to fill the parameters that will be translated to HW * points. */ - func_blend->type = TF_TYPE_DISTRIBUTED_POINTS; - func_blend->tf = tf; - func_blend->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; - + __set_tf_distributed_points(func_blend, tf); ret = __set_input_tf(NULL, func_blend, blend_lut, blend_size); } else { - func_blend->type = TF_TYPE_BYPASS; - func_blend->tf = TRANSFER_FUNCTION_LINEAR; + __set_tf_bypass(func_blend); } return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_atomic_blend_lut); /** * amdgpu_dm_verify_lut3d_size - verifies if 3D LUT is supported and if user @@ -1169,6 +1205,7 @@ int amdgpu_dm_verify_lut_sizes(const struct drm_crtc_state *crtc_state) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_verify_lut_sizes); /** * amdgpu_dm_check_crtc_color_mgmt: Check if DRM color props are programmable by DC. @@ -1464,7 +1501,7 @@ __set_dm_plane_degamma(struct drm_plane_state *plane_state, return 0; } -static int +STATIC_IFN_KUNIT int __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state, struct drm_colorop_state *colorop_state) { @@ -1479,8 +1516,7 @@ __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state, return -EINVAL; if (colorop_state->bypass) { - tf->type = TF_TYPE_BYPASS; - tf->tf = TRANSFER_FUNCTION_LINEAR; + __set_tf_bypass(tf); return 0; } @@ -1491,6 +1527,7 @@ __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state, return 0; } +EXPORT_IF_KUNIT(__set_colorop_in_tf_1d_curve); static int __set_dm_plane_colorop_degamma(struct drm_plane_state *plane_state, @@ -1676,9 +1713,9 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, * Returns: * 0 on success. -EINVAL if drm_lut3d_size is zero. */ -static int __set_colorop_3dlut(const struct drm_color_lut32 *drm_lut3d, - uint32_t drm_lut3d_size, - struct dc_3dlut *lut) +STATIC_IFN_KUNIT int __set_colorop_3dlut(const struct drm_color_lut32 *drm_lut3d, + uint32_t drm_lut3d_size, + struct dc_3dlut *lut) { if (!drm_lut3d_size) { lut->state.bits.initialized = 0; @@ -1695,6 +1732,7 @@ static int __set_colorop_3dlut(const struct drm_color_lut32 *drm_lut3d, return 0; } +EXPORT_IF_KUNIT(__set_colorop_3dlut); static int __set_dm_plane_colorop_3dlut(struct drm_plane_state *plane_state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h new file mode 100644 index 000000000000..e4f53b7bc753 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#ifndef __AMDGPU_DM_COLOR_H__ +#define __AMDGPU_DM_COLOR_H__ + +#define MAX_DRM_LUT_VALUE 0xFFFF +#define MAX_DRM_LUT32_VALUE 0xFFFFFFFF + +#include <linux/types.h> + +struct drm_color_lut; +struct drm_color_lut32; +struct drm_color_ctm; +struct drm_color_ctm_3x4; +struct drm_colorop_state; +struct drm_property_blob; +struct dc_gamma; +struct dc_rgb; +struct dc_plane_state; +struct fixed31_32; +struct tetrahedral_params; +struct dc_transfer_func; + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +/* + * Prototypes for functions exposed to KUnit tests. The enum types + * used below (dc_transfer_func_predefined, amdgpu_transfer_function, + * drm_colorop_curve_1d_type) must be defined before this header is + * included — the source file (amdgpu_dm_color.c) ensures this via + * its own includes of dc.h, amdgpu_dm.h, and drm/drm_colorop.h. + */ +struct fixed31_32 amdgpu_dm_fixpt_from_s3132(__u64 x); +bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size); +void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut, + struct dc_gamma *gamma, bool is_legacy); +void __drm_lut32_to_dc_gamma(const struct drm_color_lut32 *lut, + struct dc_gamma *gamma); +void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, + struct fixed31_32 *matrix); +void __drm_ctm_3x4_to_dc_matrix(const struct drm_color_ctm_3x4 *ctm, + struct fixed31_32 *matrix); +enum dc_transfer_func_predefined +amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf); +enum dc_transfer_func_predefined +amdgpu_colorop_tf_to_dc_tf(enum drm_colorop_curve_1d_type tf); +const struct drm_color_lut * +__extract_blob_lut(const struct drm_property_blob *blob, uint32_t *size); +const struct drm_color_lut32 * +__extract_blob_lut32(const struct drm_property_blob *blob, uint32_t *size); +void __to_dc_lut3d_color(struct dc_rgb *rgb, + const struct drm_color_lut lut, + int bit_precision); +void __drm_3dlut_to_dc_3dlut(const struct drm_color_lut *lut, + uint32_t lut3d_size, + struct tetrahedral_params *params, + bool use_tetrahedral_9, + int bit_depth); +void __to_dc_lut3d_32_color(struct dc_rgb *rgb, + const struct drm_color_lut32 lut, + int bit_precision); +void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut, + uint32_t lut3d_size, + struct tetrahedral_params *params, + bool use_tetrahedral_9, + int bit_depth); +struct dc_3dlut; +void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut3d, + uint32_t drm_lut3d_size, + struct dc_3dlut *lut); +int __set_colorop_3dlut(const struct drm_color_lut32 *drm_lut3d, + uint32_t drm_lut3d_size, + struct dc_3dlut *lut); +void __set_tf_bypass(struct dc_transfer_func *tf); +void __set_tf_distributed_points(struct dc_transfer_func *tf, + enum dc_transfer_func_predefined predefined_tf); +int amdgpu_dm_set_atomic_regamma(struct dc_transfer_func *out_tf, + const struct drm_color_lut *regamma_lut, + uint32_t regamma_size, bool has_rom, + enum dc_transfer_func_predefined tf); +int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *shaper_lut, + bool has_rom, + enum dc_transfer_func_predefined tf, + uint32_t shaper_size, + struct dc_transfer_func *func_shaper); +int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut, + bool has_rom, + enum dc_transfer_func_predefined tf, + uint32_t blend_size, + struct dc_transfer_func *func_blend); +int __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state, + struct drm_colorop_state *colorop_state); +#endif + +#endif /* __AMDGPU_DM_COLOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c index 7ee051cb3c05..48f5c431eaf9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c @@ -31,6 +31,7 @@ #include "amdgpu.h" #include "amdgpu_dm_colorop.h" +#include "amdgpu_dm_kunit_helpers.h" #include "dc.h" const u64 amdgpu_dm_supported_degam_tfs = @@ -38,18 +39,21 @@ const u64 amdgpu_dm_supported_degam_tfs = BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) | BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) | BIT(DRM_COLOROP_1D_CURVE_GAMMA22); +EXPORT_IF_KUNIT(amdgpu_dm_supported_degam_tfs); const u64 amdgpu_dm_supported_shaper_tfs = BIT(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF) | BIT(DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF) | BIT(DRM_COLOROP_1D_CURVE_BT2020_OETF) | BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV); +EXPORT_IF_KUNIT(amdgpu_dm_supported_shaper_tfs); const u64 amdgpu_dm_supported_blnd_tfs = BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) | BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) | BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) | BIT(DRM_COLOROP_1D_CURVE_GAMMA22); +EXPORT_IF_KUNIT(amdgpu_dm_supported_blnd_tfs); #define MAX_COLOR_PIPELINE_OPS 10 @@ -59,12 +63,11 @@ static const struct drm_colorop_funcs dm_colorop_funcs = { .destroy = drm_colorop_destroy, }; -int amdgpu_dm_initialize_default_pipeline(struct drm_plane *plane, struct drm_prop_enum_list *list) +STATIC_IFN_KUNIT int +amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane, + bool hw_3d_lut, struct drm_prop_enum_list *list) { struct drm_colorop *ops[MAX_COLOR_PIPELINE_OPS]; - struct drm_device *dev = plane->dev; - struct amdgpu_device *adev = drm_to_adev(dev); - bool has_3dlut = adev->dm.dc->caps.color.dpp.hw_3d_lut || adev->dm.dc->caps.color.mpc.preblend; int ret; int i = 0; @@ -120,7 +123,7 @@ int amdgpu_dm_initialize_default_pipeline(struct drm_plane *plane, struct drm_pr i++; - if (has_3dlut) { + if (hw_3d_lut) { /* 1D curve - SHAPER TF */ ops[i] = kzalloc_obj(*ops[0]); if (!ops[i]) { @@ -215,9 +218,20 @@ int amdgpu_dm_initialize_default_pipeline(struct drm_plane *plane, struct drm_pr cleanup: if (ret == -ENOMEM) - drm_err(plane->dev, "KMS: Failed to allocate colorop\n"); + drm_err(dev, "KMS: Failed to allocate colorop\n"); drm_colorop_pipeline_destroy(dev); return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_build_default_pipeline); + +int amdgpu_dm_initialize_default_pipeline(struct drm_plane *plane, struct drm_prop_enum_list *list) +{ + struct drm_device *dev = plane->dev; + struct amdgpu_device *adev = drm_to_adev(dev); + bool hw_3d_lut = adev->dm.dc->caps.color.dpp.hw_3d_lut || + adev->dm.dc->caps.color.mpc.preblend; + + return amdgpu_dm_build_default_pipeline(dev, plane, hw_3d_lut, list); +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.h index 2e1617ffc8ee..77364d954d3b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.h @@ -33,4 +33,9 @@ extern const u64 amdgpu_dm_supported_blnd_tfs; int amdgpu_dm_initialize_default_pipeline(struct drm_plane *plane, struct drm_prop_enum_list *list); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +int amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane, + bool hw_3d_lut, struct drm_prop_enum_list *list); +#endif + #endif /* __AMDGPU_DM_COLOROP_H__*/ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c index 5adbb0f6a0c8..88f7cfea5624 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c @@ -33,6 +33,7 @@ #include "amdgpu_securedisplay.h" #include "amdgpu_dm_psr.h" #include "amdgpu_dm_replay.h" +#include "amdgpu_dm_kunit_helpers.h" static const char *const pipe_crc_sources[] = { "none", @@ -43,7 +44,8 @@ static const char *const pipe_crc_sources[] = { "auto", }; -static enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source) +STATIC_IFN_KUNIT +enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source) { if (!source || !strcmp(source, "none")) return AMDGPU_DM_PIPE_CRC_SOURCE_NONE; @@ -58,25 +60,32 @@ static enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source) return AMDGPU_DM_PIPE_CRC_SOURCE_INVALID; } +EXPORT_IF_KUNIT(dm_parse_crc_source); -static bool dm_is_crc_source_crtc(enum amdgpu_dm_pipe_crc_source src) +STATIC_IFN_KUNIT +bool dm_is_crc_source_crtc(enum amdgpu_dm_pipe_crc_source src) { return (src == AMDGPU_DM_PIPE_CRC_SOURCE_CRTC) || (src == AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER); } +EXPORT_IF_KUNIT(dm_is_crc_source_crtc); -static bool dm_is_crc_source_dprx(enum amdgpu_dm_pipe_crc_source src) +STATIC_IFN_KUNIT +bool dm_is_crc_source_dprx(enum amdgpu_dm_pipe_crc_source src) { return (src == AMDGPU_DM_PIPE_CRC_SOURCE_DPRX) || (src == AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER); } +EXPORT_IF_KUNIT(dm_is_crc_source_dprx); -static bool dm_need_crc_dither(enum amdgpu_dm_pipe_crc_source src) +STATIC_IFN_KUNIT +bool dm_need_crc_dither(enum amdgpu_dm_pipe_crc_source src) { return (src == AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER) || (src == AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER) || (src == AMDGPU_DM_PIPE_CRC_SOURCE_NONE); } +EXPORT_IF_KUNIT(dm_need_crc_dither); const char *const *amdgpu_dm_crtc_get_crc_sources(struct drm_crtc *crtc, size_t *count) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h index 8538513ea879..c9aa0c82038f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h @@ -27,8 +27,11 @@ #ifndef AMD_DAL_DEV_AMDGPU_DM_AMDGPU_DM_CRC_H_ #define AMD_DAL_DEV_AMDGPU_DM_AMDGPU_DM_CRC_H_ +#include "dc_types.h" + struct drm_crtc; struct dm_crtc_state; +struct amdgpu_device; enum amdgpu_dm_pipe_crc_source { AMDGPU_DM_PIPE_CRC_SOURCE_NONE = 0, @@ -148,4 +151,11 @@ void amdgpu_dm_crtc_secure_display_create_contexts(struct amdgpu_device *adev); #define amdgpu_dm_crtc_secure_display_create_contexts(x) #endif +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source); +bool dm_is_crc_source_crtc(enum amdgpu_dm_pipe_crc_source src); +bool dm_is_crc_source_dprx(enum amdgpu_dm_pipe_crc_source src); +bool dm_need_crc_dither(enum amdgpu_dm_pipe_crc_source src); +#endif + #endif /* AMD_DAL_DEV_AMDGPU_DM_AMDGPU_DM_CRC_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c index 4b09a740f205..7db38ad3f848 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c @@ -1345,8 +1345,13 @@ static ssize_t dp_sdp_message_debugfs_write(struct file *f, const char __user *b if (size == 0) return 0; + if (!connector->base.state || !connector->base.state->crtc) + return -ENODEV; + acrtc_state = to_dm_crtc_state(connector->base.state->crtc->state); + write_size = min_t(size_t, size, sizeof(data)); + r = copy_from_user(data, buf, write_size); write_size -= r; @@ -3137,6 +3142,7 @@ static int force_yuv420_output_set(void *data, u64 val) struct amdgpu_dm_connector *connector = data; connector->force_yuv420_output = (bool)val; + connector->force_yuv_pixel_format = PIXEL_ENCODING_YCBCR420; return 0; } @@ -3156,6 +3162,31 @@ static int force_yuv420_output_get(void *data, u64 *val) DEFINE_DEBUGFS_ATTRIBUTE(force_yuv420_output_fops, force_yuv420_output_get, force_yuv420_output_set, "%llu\n"); +static int force_yuv422_output_set(void *data, u64 val) +{ + struct amdgpu_dm_connector *connector = data; + + connector->force_yuv422_output = (bool)val; + connector->force_yuv_pixel_format = PIXEL_ENCODING_YCBCR422; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(force_yuv422_output_fops, NULL, + force_yuv422_output_set, "%llu\n"); + +static int force_yuv444_output_set(void *data, u64 val) +{ + struct amdgpu_dm_connector *connector = data; + + connector->force_yuv_pixel_format = PIXEL_ENCODING_YCBCR444; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(force_yuv444_output_fops, NULL, + force_yuv444_output_set, "%llu\n"); + /* * Read Replay state */ @@ -3605,6 +3636,8 @@ static const struct { const struct file_operations *fops; } connector_debugfs_entries[] = { {"force_yuv420_output", &force_yuv420_output_fops}, + {"force_yuv422_output", &force_yuv422_output_fops}, + {"force_yuv444_output", &force_yuv444_output_fops}, {"trigger_hotplug", &trigger_hotplug_debugfs_fops}, {"internal_display", &internal_display_fops}, {"odm_combine_segments", &odm_combine_segments_fops} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index eb73bbf8f411..4c164ae4a4f9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -31,6 +31,7 @@ #include "dm_helpers.h" #include <drm/display/drm_hdcp_helper.h> #include "hdcp_psp.h" +#include "amdgpu_dm_kunit_helpers.h" /* * If the SRM version being loaded is less than or equal to the @@ -158,7 +159,8 @@ static int psp_set_srm(struct psp_context *psp, return 0; } -static void process_output(struct hdcp_workqueue *hdcp_work) +STATIC_IFN_KUNIT +void process_output(struct hdcp_workqueue *hdcp_work) { struct mod_hdcp_output output = hdcp_work->output; @@ -178,6 +180,7 @@ static void process_output(struct hdcp_workqueue *hdcp_work) schedule_delayed_work(&hdcp_work->property_validate_dwork, msecs_to_jiffies(0)); } +EXPORT_IF_KUNIT(process_output); static void link_lock(struct hdcp_workqueue *work, bool lock) { @@ -578,6 +581,8 @@ static void update_config(void *handle, struct cp_psp_stream_config *config) link->dp.mst_enabled = config->mst_enabled; link->dp.dp2_enabled = config->dp2_enabled; link->dp.usb4_enabled = config->usb4_enabled; + if (aconnector->dc_sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) + link->hdmi.frl_enabled = config->frl_enabled; display->adjust.disable = MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION; link->adjust.auth_delay = 2; link->adjust.retry_limit = MAX_NUM_OF_ATTEMPTS; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index 4faa344f196e..90b18c450ca6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -31,12 +31,19 @@ #include "hdcp.h" #include "dc.h" #include "dm_cp_psp.h" -#include "amdgpu.h" + +/* + * Minimal declarations needed by this header. + * Full amdgpu/DM definitions come from amdgpu_dm.h included by each .c file. + */ +#define AMDGPU_DM_MAX_DISPLAY_INDEX 31 +struct amdgpu_dm_connector; struct mod_hdcp; struct mod_hdcp_link; struct mod_hdcp_display; struct cp_psp; +struct amdgpu_device; struct hdcp_workqueue { struct work_struct cpirq_work; @@ -87,4 +94,8 @@ void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *work); struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev, struct cp_psp *cp_psp, struct dc *dc); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void process_output(struct hdcp_workqueue *hdcp_work); +#endif + #endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index a3cb05490dc9..c6f94eb71ffa 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -95,8 +95,11 @@ static u32 edid_extract_panel_id(struct edid *edid) (u32)EDID_PRODUCT_ID(edid); } -static void apply_edid_quirks(struct drm_device *dev, struct edid *edid, struct dc_edid_caps *edid_caps) +static void apply_edid_quirks(struct dc_link *link, struct edid *edid, + struct dc_edid_caps *edid_caps) { + struct amdgpu_dm_connector *aconnector = link->priv; + struct drm_device *dev = aconnector->base.dev; uint32_t panel_id = edid_extract_panel_id(edid); switch (panel_id) { @@ -126,6 +129,11 @@ static void apply_edid_quirks(struct drm_device *dev, struct edid *edid, struct drm_dbg_driver(dev, "Disabling VSC on monitor with panel id %X\n", panel_id); edid_caps->panel_patch.disable_colorimetry = true; break; + /* Workaround for monitors that get corrupted by the PHY SSC reduction */ + case drm_edid_encode_panel_id('D', 'E', 'L', 0x4147): + drm_dbg_driver(dev, "Skip PHY SSC reduction on panel id %X\n", panel_id); + link->wa_flags.skip_phy_ssc_reduction = true; + break; default: return; } @@ -147,7 +155,6 @@ enum dc_edid_status dm_helpers_parse_edid_caps( { struct amdgpu_dm_connector *aconnector = link->priv; struct drm_connector *connector = &aconnector->base; - struct drm_device *dev = connector->dev; struct edid *edid_buf = edid ? (struct edid *) edid->raw_edid : NULL; struct cea_sad *sads; int sad_count = -1; @@ -178,10 +185,17 @@ enum dc_edid_status dm_helpers_parse_edid_caps( edid_caps->edid_hdmi = connector->display_info.is_hdmi; - if (edid_caps->edid_hdmi) - populate_hdmi_info_from_connector(&connector->display_info.hdmi, edid_caps); + if (edid_caps->edid_hdmi) { + populate_hdmi_info_from_connector(link->dc->config.enable_frl, &connector->display_info.hdmi, edid_caps); + drm_dbg_driver(connector->dev, "%s: HDMI_FRL [%s] max_frl_rate %d\n", __func__, connector->name, edid_caps->max_frl_rate); + if (edid_caps->frl_dsc_support) + drm_dbg_driver(connector->dev, "%s: HDMI_FRL_DSC [%s] frl_dsc_10bpc %d, frl_dsc_12bpc %d, frl_dsc_all_bpp %d, frl_dsc_native_420 %d, frl_dsc_max_slices %d, frl_dsc_max_frl_rate %d, frl_dsc_total_chunk_kbytes %d\n", + __func__, connector->name, edid_caps->frl_dsc_10bpc, edid_caps->frl_dsc_12bpc, \ + edid_caps->frl_dsc_all_bpp, edid_caps->frl_dsc_native_420, edid_caps->frl_dsc_max_slices, \ + edid_caps->frl_dsc_max_frl_rate, edid_caps->frl_dsc_total_chunk_kbytes); + } - apply_edid_quirks(dev, edid_buf, edid_caps); + apply_edid_quirks(link, edid_buf, edid_caps); sad_count = drm_edid_to_sad((struct edid *) edid->raw_edid, &sads); if (sad_count <= 0) @@ -961,7 +975,7 @@ bool dm_helpers_is_dp_sink_present(struct dc_link *link) struct amdgpu_dm_connector *aconnector = link->priv; if (!aconnector) { - BUG_ON("Failed to find connector for link!"); + DRM_ERROR("Failed to find connector for link!"); return true; } @@ -1071,9 +1085,70 @@ dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, struct amdgpu_dm_conn return edid; } -void populate_hdmi_info_from_connector(struct drm_hdmi_info *hdmi, struct dc_edid_caps *edid_caps) +static uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane) +{ + uint8_t max_frl_rate; + + if ((max_lanes == 3) && (max_rate_per_lane == 3)) + max_frl_rate = 1; + else if ((max_lanes == 3) && (max_rate_per_lane == 6)) + max_frl_rate = 2; + else if ((max_lanes == 4) && (max_rate_per_lane == 6)) + max_frl_rate = 3; + else if ((max_lanes == 4) && (max_rate_per_lane == 8)) + max_frl_rate = 4; + else if ((max_lanes == 4) && (max_rate_per_lane == 10)) + max_frl_rate = 5; + else if ((max_lanes == 4) && (max_rate_per_lane == 12)) + max_frl_rate = 6; + else + max_frl_rate = 0; + + return max_frl_rate; +} + +static uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice) +{ + uint8_t dsc_max_slices; + + if ((max_slices == 1) && (clk_per_slice == 340)) + dsc_max_slices = 1; + else if ((max_slices == 2) && (clk_per_slice == 340)) + dsc_max_slices = 2; + else if ((max_slices == 4) && (clk_per_slice == 340)) + dsc_max_slices = 3; + else if ((max_slices == 8) && (clk_per_slice == 340)) + dsc_max_slices = 4; + else if ((max_slices == 8) && (clk_per_slice == 400)) + dsc_max_slices = 5; + else if ((max_slices == 12) && (clk_per_slice == 400)) + dsc_max_slices = 6; + else if ((max_slices == 16) && (clk_per_slice == 400)) + dsc_max_slices = 7; + else + dsc_max_slices = 0; + + return dsc_max_slices; +} + +void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *hdmi, struct dc_edid_caps *edid_caps) { edid_caps->scdc_present = hdmi->scdc.supported; + if (enable_frl) { + edid_caps->max_frl_rate = get_max_frl_rate(hdmi->max_lanes, hdmi->max_frl_rate_per_lane); + edid_caps->frl_dsc_support = hdmi->dsc_cap.v_1p2; + if (edid_caps->frl_dsc_support) { + if (hdmi->dsc_cap.bpc_supported == 10) + edid_caps->frl_dsc_10bpc = true; + else if (hdmi->dsc_cap.bpc_supported == 12) + edid_caps->frl_dsc_12bpc = true; + edid_caps->frl_dsc_all_bpp = hdmi->dsc_cap.all_bpp; + edid_caps->frl_dsc_native_420 = hdmi->dsc_cap.native_420; + edid_caps->frl_dsc_max_slices = get_dsc_max_slices(hdmi->dsc_cap.max_slices, hdmi->dsc_cap.clk_per_slice); + edid_caps->frl_dsc_max_frl_rate = get_max_frl_rate(hdmi->dsc_cap.max_lanes, hdmi->dsc_cap.max_frl_rate_per_lane); + edid_caps->frl_dsc_total_chunk_kbytes = hdmi->dsc_cap.total_chunk_kbytes; + } + } } enum dc_edid_status dm_helpers_read_local_edid( diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c index 7dcc587c45e9..32391b56097e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c @@ -32,6 +32,8 @@ #include "amdgpu_dm_ism.h" #include "amdgpu_dm_crtc.h" #include "amdgpu_dm_trace.h" +#include "amdgpu_dm_kunit_helpers.h" + /** * dm_ism_next_state - Get next state based on current state and event @@ -42,9 +44,10 @@ * This function defines the idle state management FSM. Invalid transitions * are ignored and will not progress the FSM. */ -static bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state, - enum amdgpu_dm_ism_event event, - enum amdgpu_dm_ism_state *next_state) +STATIC_IFN_KUNIT +bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state, + enum amdgpu_dm_ism_event event, + enum amdgpu_dm_ism_state *next_state) { switch (STATE_EVENT(current_state, event)) { case STATE_EVENT(DM_ISM_STATE_FULL_POWER_RUNNING, @@ -125,8 +128,10 @@ static bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state, } return true; } +EXPORT_IF_KUNIT(dm_ism_next_state); -static uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism, +STATIC_IFN_KUNIT +uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism, const struct dc_stream_state *stream) { const struct amdgpu_dm_ism_config *config = &ism->config; @@ -148,6 +153,7 @@ static uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism, return sso_delay_ns; } +EXPORT_IF_KUNIT(dm_ism_get_sso_delay); /** * dm_ism_get_idle_allow_delay - Calculate hysteresis-based idle allow delay @@ -157,8 +163,9 @@ static uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism, * Calculates the delay before allowing idle optimizations based on recent * idle history and the current stream timing. */ -static uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism, - const struct dc_stream_state *stream) +STATIC_IFN_KUNIT +uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism, + const struct dc_stream_state *stream) { const struct amdgpu_dm_ism_config *config = &ism->config; uint32_t v_total, h_total; @@ -217,12 +224,14 @@ static uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism, return ret_ns; } +EXPORT_IF_KUNIT(dm_ism_get_idle_allow_delay); /** * dm_ism_insert_record - Insert a record into the circular history buffer * @ism: ISM instance */ -static void dm_ism_insert_record(struct amdgpu_dm_ism *ism) +STATIC_IFN_KUNIT +void dm_ism_insert_record(struct amdgpu_dm_ism *ism) { struct amdgpu_dm_ism_record *record; @@ -237,15 +246,19 @@ static void dm_ism_insert_record(struct amdgpu_dm_ism *ism) record->duration_ns = record->timestamp_ns - ism->last_idle_timestamp_ns; } +EXPORT_IF_KUNIT(dm_ism_insert_record); -static void dm_ism_set_last_idle_ts(struct amdgpu_dm_ism *ism) +STATIC_IFN_KUNIT +void dm_ism_set_last_idle_ts(struct amdgpu_dm_ism *ism) { ism->last_idle_timestamp_ns = ktime_get_ns(); } +EXPORT_IF_KUNIT(dm_ism_set_last_idle_ts); -static bool dm_ism_trigger_event(struct amdgpu_dm_ism *ism, +STATIC_IFN_KUNIT +bool dm_ism_trigger_event(struct amdgpu_dm_ism *ism, enum amdgpu_dm_ism_event event) { enum amdgpu_dm_ism_state next_state; @@ -260,6 +273,7 @@ static bool dm_ism_trigger_event(struct amdgpu_dm_ism *ism, return gotNextState; } +EXPORT_IF_KUNIT(dm_ism_trigger_event); static void dm_ism_commit_idle_optimization_state(struct amdgpu_dm_ism *ism, @@ -318,15 +332,36 @@ static void dm_ism_commit_idle_optimization_state(struct amdgpu_dm_ism *ism, } } +STATIC_IFN_KUNIT +enum amdgpu_dm_ism_event dm_ism_dispatch_next_event( + enum amdgpu_dm_ism_state current_state, + uint64_t delay_ns, + uint64_t sso_delay_ns) +{ + switch (current_state) { + case DM_ISM_STATE_HYSTERESIS_WAITING: + if (delay_ns == 0) + return DM_ISM_EVENT_IMMEDIATE; + break; + case DM_ISM_STATE_OPTIMIZED_IDLE: + if (sso_delay_ns == 0) + return DM_ISM_EVENT_IMMEDIATE; + break; + case DM_ISM_STATE_TIMER_ABORTED: + return DM_ISM_EVENT_IMMEDIATE; + default: + break; + } + return DM_ISM_NUM_EVENTS; +} +EXPORT_IF_KUNIT(dm_ism_dispatch_next_event); static enum amdgpu_dm_ism_event dm_ism_dispatch_power_state( struct amdgpu_dm_ism *ism, - struct dm_crtc_state *acrtc_state, - enum amdgpu_dm_ism_event event) + struct dm_crtc_state *acrtc_state) { - enum amdgpu_dm_ism_event ret = event; const struct amdgpu_dm_ism_config *config = &ism->config; - uint64_t delay_ns, sso_delay_ns; + uint64_t delay_ns = 0, sso_delay_ns = 0; switch (ism->previous_state) { case DM_ISM_STATE_HYSTERESIS_WAITING: @@ -360,30 +395,15 @@ static enum amdgpu_dm_ism_event dm_ism_dispatch_power_state( switch (ism->current_state) { case DM_ISM_STATE_HYSTERESIS_WAITING: dm_ism_set_last_idle_ts(ism); - - /* CRTC can be disabled; allow immediate idle */ - if (!acrtc_state->stream) { - ret = DM_ISM_EVENT_IMMEDIATE; - break; - } - - delay_ns = dm_ism_get_idle_allow_delay(ism, - acrtc_state->stream); - if (delay_ns == 0) { - ret = DM_ISM_EVENT_IMMEDIATE; - break; - } - + delay_ns = dm_ism_get_idle_allow_delay(ism, acrtc_state->stream); /* Schedule worker */ - mod_delayed_work(system_unbound_wq, &ism->delayed_work, - nsecs_to_jiffies(delay_ns)); - + if (delay_ns > 0) + mod_delayed_work(system_dfl_wq, &ism->delayed_work, + nsecs_to_jiffies(delay_ns)); break; case DM_ISM_STATE_OPTIMIZED_IDLE: sso_delay_ns = dm_ism_get_sso_delay(ism, acrtc_state->stream); - if (sso_delay_ns == 0) - ret = DM_ISM_EVENT_IMMEDIATE; - else if (config->sso_num_frames < config->filter_num_frames) { + if (sso_delay_ns > 0) { /* * If sso_num_frames is less than hysteresis frames, it * indicates that allowing idle here, then disallowing @@ -391,14 +411,11 @@ static enum amdgpu_dm_ism_event dm_ism_dispatch_power_state( * have a negative power impact. Skip idle allow here, * and let the sso_delayed_work handle it. */ - mod_delayed_work(system_unbound_wq, - &ism->sso_delayed_work, - nsecs_to_jiffies(sso_delay_ns)); - } else { - /* Enable idle optimization without SSO */ - dm_ism_commit_idle_optimization_state( - ism, acrtc_state->stream, false, false); - mod_delayed_work(system_unbound_wq, + if (config->sso_num_frames >= config->filter_num_frames) + dm_ism_commit_idle_optimization_state(ism, acrtc_state->stream, + false, false); + + mod_delayed_work(system_dfl_wq, &ism->sso_delayed_work, nsecs_to_jiffies(sso_delay_ns)); } @@ -412,13 +429,12 @@ static enum amdgpu_dm_ism_event dm_ism_dispatch_power_state( dm_ism_insert_record(ism); dm_ism_commit_idle_optimization_state(ism, acrtc_state->stream, true, false); - ret = DM_ISM_EVENT_IMMEDIATE; break; default: break; } - return ret; + return dm_ism_dispatch_next_event(ism->current_state, delay_ns, sso_delay_ns); } static char *dm_ism_events_str[DM_ISM_NUM_EVENTS] = { @@ -473,8 +489,7 @@ void amdgpu_dm_ism_commit_event(struct amdgpu_dm_ism *ism, dm_ism_states_str[ism->previous_state], dm_ism_states_str[ism->current_state], dm_ism_events_str[event]); - next_event = dm_ism_dispatch_power_state( - ism, acrtc_state, next_event); + next_event = dm_ism_dispatch_power_state(ism, acrtc_state); } else { trace_amdgpu_dm_ism_event( acrtc->crtc_id, @@ -625,6 +640,7 @@ void amdgpu_dm_ism_init(struct amdgpu_dm_ism *ism, INIT_DELAYED_WORK(&ism->delayed_work, dm_ism_delayed_work_func); INIT_DELAYED_WORK(&ism->sso_delayed_work, dm_ism_sso_delayed_work_func); } +EXPORT_IF_KUNIT(amdgpu_dm_ism_init); void amdgpu_dm_ism_fini(struct amdgpu_dm_ism *ism) @@ -632,3 +648,4 @@ void amdgpu_dm_ism_fini(struct amdgpu_dm_ism *ism) cancel_delayed_work_sync(&ism->sso_delayed_work); cancel_delayed_work_sync(&ism->delayed_work); } +EXPORT_IF_KUNIT(amdgpu_dm_ism_fini); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h index 964408cd9a83..afce16f7085a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h @@ -149,4 +149,21 @@ void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm); void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm); void amdgpu_dm_ism_enable(struct amdgpu_display_manager *dm); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state, + enum amdgpu_dm_ism_event event, + enum amdgpu_dm_ism_state *next_state); +uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism, + const struct dc_stream_state *stream); +uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism, + const struct dc_stream_state *stream); +void dm_ism_insert_record(struct amdgpu_dm_ism *ism); +void dm_ism_set_last_idle_ts(struct amdgpu_dm_ism *ism); +bool dm_ism_trigger_event(struct amdgpu_dm_ism *ism, + enum amdgpu_dm_ism_event event); +enum amdgpu_dm_ism_event dm_ism_dispatch_next_event(enum amdgpu_dm_ism_state current_state, + uint64_t delay_ns, + uint64_t sso_delay_ns); +#endif + #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h new file mode 100644 index 000000000000..4b2864375105 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#ifndef AMDGPU_DM_KUNIT_HELPERS_H +#define AMDGPU_DM_KUNIT_HELPERS_H + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +#define STATIC_IFN_KUNIT +#define INLINE_IFN_KUNIT inline +#define EXPORT_IF_KUNIT(symbol) EXPORT_SYMBOL(symbol) +#else +#define STATIC_IFN_KUNIT static +#define INLINE_IFN_KUNIT +#define EXPORT_IF_KUNIT(symbol) +#endif + +#endif /* AMDGPU_DM_KUNIT_HELPERS_H */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index 51924ae87705..b3af7445b457 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -1174,6 +1174,77 @@ static int try_disable_dsc(struct drm_atomic_commit *state, return 0; } +static bool get_conv_frl_bw(struct amdgpu_dm_connector *aconnector, + uint32_t *bw_in_kbps, uint32_t *dsc_bw_in_kbps) +{ + unsigned int max_conv_bw_in_kbps = 0; + unsigned int max_sink_bw_in_kbps = 0; + unsigned int dsc_max_sink_bw_in_kbps = 0; + + if (aconnector->dc_link->dc->caps.dp_hdmi21_pcon_support && + aconnector->mst_downstream_port_caps.bytes.byte0.bits.DWN_STRM_PORTX_TYPE == DOWN_STREAM_DETAILED_HDMI) { + max_conv_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data( + aconnector->dc_link->dc, + aconnector->mst_downstream_port_caps.bytes.byte2.bits.MAX_ENCODED_LINK_BW_SUPPORT); + if (aconnector->dc_sink->edid_caps.max_frl_rate && max_conv_bw_in_kbps) { + max_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data( + aconnector->dc_link->dc, + aconnector->dc_sink->edid_caps.max_frl_rate); + dsc_max_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data( + aconnector->dc_link->dc, + aconnector->dc_sink->edid_caps.frl_dsc_max_frl_rate); + + *bw_in_kbps = min(max_conv_bw_in_kbps, max_sink_bw_in_kbps); + *dsc_bw_in_kbps = min(*bw_in_kbps, dsc_max_sink_bw_in_kbps); + } + } + + return *bw_in_kbps > 0; // Frl endpoint is detected +} + +static void build_frl_mst_dsc_params(struct amdgpu_dm_connector *aconnector, + struct dc_stream_state *stream, + struct dc_dsc_policy *dsc_policy, + struct dsc_mst_fairness_params *params, + uint32_t frl_conv_dsc_bw_in_kbps) +{ + uint32_t min_bpp_x16, max_bpp_x16; + struct dc_dsc_config_options dsc_options = {0}; + + min_bpp_x16 = dsc_policy->min_target_bpp * 16; + max_bpp_x16 = dsc_policy->max_target_bpp * 16; + + dc_dsc_get_default_config_option(stream->sink->ctx->dc, &dsc_options); + dsc_options.max_target_bpp_limit_override_x16 = + stream->sink->edid_caps.panel_patch.max_dsc_target_bpp_limit * 16; + + if (dc_dsc_compute_config( + stream->sink->ctx->dc->res_pool->dscs[0], + &stream->sink->dsc_caps.dsc_dec_caps, + &dsc_options, + frl_conv_dsc_bw_in_kbps, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &stream->timing.dsc_cfg)) { + // The timing can enable dsc + if (stream->sink->dsc_caps.dsc_dec_caps.is_vic_all_bpp && min_bpp_x16 <= stream->timing.dsc_cfg.bits_per_pixel) { + // with all supported bpp within the range limit + params->bw_range.max_target_bpp_x16 = min(stream->timing.dsc_cfg.bits_per_pixel, dsc_policy->max_target_bpp * 16); + params->bw_range.min_target_bpp_x16 = min_bpp_x16; + params->bw_range.max_kbps = (params->bw_range.max_target_bpp_x16 * stream->timing.pix_clk_100hz + 159) / 160; + params->bw_range.min_kbps = (params->bw_range.min_target_bpp_x16 * stream->timing.pix_clk_100hz + 159) / 160; + } else if (!stream->sink->dsc_caps.dsc_dec_caps.is_vic_all_bpp && + min_bpp_x16 <= stream->timing.dsc_cfg.bits_per_pixel && + max_bpp_x16 >= stream->timing.dsc_cfg.bits_per_pixel) { + // with selected bpp only within the range limit + params->bw_range.max_target_bpp_x16 = stream->timing.dsc_cfg.bits_per_pixel; + params->bw_range.max_kbps = (params->bw_range.max_target_bpp_x16 * stream->timing.pix_clk_100hz + 159) / 160; + params->bw_range.min_target_bpp_x16 = params->bw_range.max_target_bpp_x16; + params->bw_range.min_kbps = params->bw_range.max_kbps; + } + } +} + static void log_dsc_params(int count, struct dsc_mst_fairness_vars *vars, int k) { int i; @@ -1199,6 +1270,8 @@ static int compute_mst_dsc_configs_for_link(struct drm_atomic_commit *state, bool debugfs_overwrite = false; uint16_t fec_overhead_multiplier_x1000 = get_fec_overhead_multiplier(dc_link); struct drm_connector_state *new_conn_state; + bool is_frl_endpoint_present; + uint32_t frl_conv_bw_in_kbps, frl_conv_dsc_bw_in_kbps; memset(params, 0, sizeof(params)); @@ -1244,6 +1317,12 @@ static int compute_mst_dsc_configs_for_link(struct drm_atomic_commit *state, params[count].bpp_overwrite = aconnector->dsc_settings.dsc_bits_per_pixel; params[count].compression_possible = stream->sink->dsc_caps.dsc_dec_caps.is_dsc_supported; dc_dsc_get_policy_for_timing(params[count].timing, 0, &dsc_policy, dc_link_get_highest_encoding_format(stream->link)); + is_frl_endpoint_present = get_conv_frl_bw(aconnector, &frl_conv_bw_in_kbps, &frl_conv_dsc_bw_in_kbps); + if (stream->sink->dsc_caps.dsc_dec_caps.is_dsc_supported && + is_frl_endpoint_present && + frl_conv_dsc_bw_in_kbps && + stream->sink->dsc_caps.dsc_dec_caps.is_frl) + build_frl_mst_dsc_params(aconnector, stream, &dsc_policy, ¶ms[count], frl_conv_dsc_bw_in_kbps); if (!dc_dsc_compute_bandwidth_range( stream->sink->ctx->dc->res_pool->dscs[0], stream->sink->ctx->dc->debug.dsc_min_slice_height_override, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index e957657b06c7..c7f8e08feaf4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -1859,6 +1859,7 @@ static const struct drm_plane_funcs dm_plane_funcs = { .atomic_duplicate_state = amdgpu_dm_plane_drm_plane_duplicate_state, .atomic_destroy_state = amdgpu_dm_plane_drm_plane_destroy_state, .format_mod_supported = amdgpu_dm_plane_format_mod_supported, + .format_mod_supported_async = amdgpu_dm_plane_format_mod_supported, #ifdef AMD_PRIVATE_COLOR .atomic_set_property = dm_atomic_plane_set_property, .atomic_get_property = dm_atomic_plane_get_property, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c index 11b2ea6edf95..2cdb8fea504a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c @@ -183,33 +183,6 @@ static enum amd_pp_clock_type dc_to_pp_clock_type( return amd_pp_clk_type; } -static enum dm_pp_clocks_state pp_to_dc_powerlevel_state( - enum PP_DAL_POWERLEVEL max_clocks_state) -{ - switch (max_clocks_state) { - case PP_DAL_POWERLEVEL_0: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_0; - case PP_DAL_POWERLEVEL_1: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_1; - case PP_DAL_POWERLEVEL_2: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_2; - case PP_DAL_POWERLEVEL_3: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_3; - case PP_DAL_POWERLEVEL_4: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_4; - case PP_DAL_POWERLEVEL_5: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_5; - case PP_DAL_POWERLEVEL_6: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_6; - case PP_DAL_POWERLEVEL_7: - return DM_PP_CLOCKS_DPM_STATE_LEVEL_7; - default: - DRM_ERROR("DM_PPLIB: invalid powerlevel state: %d!\n", - max_clocks_state); - return DM_PP_CLOCKS_STATE_INVALID; - } -} - static void pp_to_dc_clock_levels( const struct amd_pp_clocks *pp_clks, struct dm_pp_clock_levels *dc_clks, @@ -315,7 +288,6 @@ bool dm_pp_get_clock_levels_by_type( DRM_INFO("DM_PPLIB: Warning: using default validation clocks!\n"); validation_clks.engine_max_clock = 72000; validation_clks.memory_max_clock = 80000; - validation_clks.level = 0; } DRM_INFO("DM_PPLIB: Validation clocks:\n"); @@ -323,8 +295,6 @@ bool dm_pp_get_clock_levels_by_type( validation_clks.engine_max_clock); DRM_INFO("DM_PPLIB: memory_max_clock: %d\n", validation_clks.memory_max_clock); - DRM_INFO("DM_PPLIB: level : %d\n", - validation_clks.level); /* Translate 10 kHz to kHz. */ validation_clks.engine_max_clock *= 10; @@ -417,14 +387,6 @@ bool dm_pp_notify_wm_clock_changes( return false; } -bool dm_pp_apply_power_level_change_request( - const struct dc_context *ctx, - struct dm_pp_power_level_change_request *level_change_req) -{ - /* TODO: to be implemented */ - return false; -} - bool dm_pp_apply_clock_for_voltage_request( const struct dc_context *ctx, struct dm_pp_clock_for_voltage_req *clock_for_voltage_req) @@ -446,23 +408,6 @@ bool dm_pp_apply_clock_for_voltage_request( return true; } -bool dm_pp_get_static_clocks( - const struct dc_context *ctx, - struct dm_pp_static_clock_info *static_clk_info) -{ - struct amdgpu_device *adev = ctx->driver_context; - struct amd_pp_clock_info pp_clk_info = {0}; - - if (amdgpu_dpm_get_current_clocks(adev, &pp_clk_info)) - return false; - - static_clk_info->max_clocks_state = pp_to_dc_powerlevel_state(pp_clk_info.max_clocks_state); - static_clk_info->max_mclk_khz = pp_clk_info.max_memory_clock * 10; - static_clk_info->max_sclk_khz = pp_clk_info.max_engine_clock * 10; - - return true; -} - static void pp_rv_set_wm_ranges(struct pp_smu *pp, struct pp_smu_wm_range_sets *ranges) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c index dc5913a6456e..0dadc0bb214f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c @@ -25,10 +25,13 @@ */ #include "amdgpu_dm_psr.h" +#include "amdgpu.h" #include "dc_dmub_srv.h" #include "dc.h" #include "amdgpu_dm.h" #include "modules/power/power_helpers.h" +#include "amdgpu_dm_kunit_helpers.h" + static bool link_supports_psrsu(struct dc_link *link) { @@ -58,7 +61,8 @@ static bool link_supports_psrsu(struct dc_link *link) return false; } -static void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps) +STATIC_IFN_KUNIT +void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps) { struct dpcd_caps *dpcd_caps = &link->dpcd_caps; unsigned int power_opts = 0; @@ -86,6 +90,7 @@ static void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps) caps->rate_control_caps = 0; /* TODO: read in rc caps from aux */ caps->psr_power_opt_flag = power_opts; } +EXPORT_IF_KUNIT(amdgpu_dm_psr_fill_caps); /* * amdgpu_dm_set_psr_caps() - set link psr capabilities @@ -118,7 +123,7 @@ bool amdgpu_dm_set_psr_caps(struct dc_link *link, struct amdgpu_dm_connector *ac return false; /*disable allow psr/psrsu/replay on eDP1*/ - if (dc_get_edp_link_panel_inst(link->ctx->dc, link, &panel_inst) && panel_inst == 1) + if (dc_get_edp_link_panel_inst(dc, link, &panel_inst) && panel_inst == 1) return false; if (link_supports_psrsu(link)) @@ -140,22 +145,17 @@ bool amdgpu_dm_set_psr_caps(struct dc_link *link, struct amdgpu_dm_connector *ac bool amdgpu_dm_psr_is_active_allowed(struct amdgpu_display_manager *dm) { unsigned int i; - bool allow_active = false; - for (i = 0; i < dm->dc->current_state->stream_count ; i++) { - struct dc_link *link; - struct dc_stream_state *stream = dm->dc->current_state->streams[i]; + for (i = 0; i < dm->dc->current_state->stream_count; i++) { + const struct dc_link *link = dm->dc->current_state->streams[i]->link; - link = stream->link; if (!link) continue; - if (link->psr_settings.psr_feature_enabled && - link->psr_settings.psr_allow_active) { - allow_active = true; - break; - } + + if (link->psr_settings.psr_feature_enabled && link->psr_settings.psr_allow_active) + return true; } - return allow_active; + return false; } /* @@ -189,3 +189,4 @@ bool amdgpu_dm_psr_set_event(struct amdgpu_display_manager *dm, struct dc_stream return mod_power_set_psr_event(dm->power_module, stream, set_event, event, wait_for_disable); } +EXPORT_IF_KUNIT(amdgpu_dm_psr_set_event); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h index 16d535806ad6..40a09b5dc606 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h @@ -27,10 +27,12 @@ #ifndef AMDGPU_DM_AMDGPU_DM_PSR_H_ #define AMDGPU_DM_AMDGPU_DM_PSR_H_ -#include "amdgpu.h" #include "dc.h" #include "modules/inc/mod_power.h" +struct amdgpu_display_manager; +struct amdgpu_dm_connector; + /* the number of pageflips before enabling psr */ #define AMDGPU_DM_PSR_ENTRY_DELAY 5 @@ -39,4 +41,9 @@ bool amdgpu_dm_psr_is_active_allowed(struct amdgpu_display_manager *dm); bool amdgpu_dm_psr_set_event(struct amdgpu_display_manager *dm, struct dc_stream_state *stream, bool set_event, enum psr_event event, bool wait_for_disable); + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps); +#endif + #endif /* AMDGPU_DM_AMDGPU_DM_PSR_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c index 297125d1db70..22aa4305d2af 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c @@ -31,6 +31,8 @@ #include "modules/power/power_helpers.h" #include "dmub/inc/dmub_cmd.h" #include "dc/inc/link_service.h" +#include "amdgpu_dm_kunit_helpers.h" + /* * amdgpu_dm_link_supports_replay() - check if the link supports replay @@ -68,6 +70,7 @@ bool amdgpu_dm_link_supports_replay(struct dc_link *link, struct amdgpu_dm_conne return true; } +EXPORT_IF_KUNIT(amdgpu_dm_link_supports_replay); /* * amdgpu_dm_set_replay_caps() - setup Replay capabilities diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig new file mode 100644 index 000000000000..bd1bf8d959f9 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig @@ -0,0 +1,22 @@ +CONFIG_KUNIT=y +CONFIG_PCI=y +CONFIG_DRM=y +CONFIG_DRM_AMDGPU=y +CONFIG_DRM_AMD_DC=y +CONFIG_DEBUG_FS=y +CONFIG_DRM_AMD_DC_KUNIT_TEST=y +CONFIG_FW_LOADER=y +CONFIG_DRM_KUNIT_TEST=y +CONFIG_DRM_KUNIT_TEST_HELPERS=y +CONFIG_DRM_KMS_HELPER=y +CONFIG_DRM_TTM=y +CONFIG_HWMON=y +CONFIG_I2C=y +CONFIG_POWER_SUPPLY=y +CONFIG_CRC16=y + +# GCOV Coverage - see tools/testing/kunit/configs/coverage_uml.config +CONFIG_DEBUG_KERNEL=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y +CONFIG_GCOV=y diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile new file mode 100644 index 000000000000..768f9bbc50e1 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for amdgpu_dm KUnit tests. + +ccflags-y += -I$(src)/.. +ccflags-y += -I$(src)/../.. +ccflags-y += -I$(src)/../../include +ccflags-y += -I$(src)/../../modules/inc +ccflags-y += -I$(src)/../../dc +ccflags-y += -I$(src)/../../../amdgpu + +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c new file mode 100644 index 000000000000..f943361b70e8 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -0,0 +1,1639 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_color.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include <kunit/test.h> +#include <linux/types.h> +#include <drm/drm_colorop.h> +#include <drm/drm_property.h> +#include <uapi/drm/drm_mode.h> + +#include "dc.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_color.h" + +/* ---- Tests for amdgpu_dm_fixpt_from_s3132 ---- */ + +static void dm_test_fixpt_from_s3132_zero(struct kunit *test) +{ + struct fixed31_32 val = amdgpu_dm_fixpt_from_s3132(0ULL); + + KUNIT_EXPECT_EQ(test, val.value, 0LL); +} + +static void dm_test_fixpt_from_s3132_one(struct kunit *test) +{ + /* 1.0 in S31.32 signed-magnitude = 1ULL << 32 */ + struct fixed31_32 val = amdgpu_dm_fixpt_from_s3132(1ULL << 32); + + KUNIT_EXPECT_EQ(test, val.value, (long long)(1ULL << 32)); +} + +static void dm_test_fixpt_from_s3132_negative_one(struct kunit *test) +{ + /* -1.0 in S31.32 signed-magnitude: sign bit set | magnitude 1<<32 */ + __u64 neg_one = (1ULL << 63) | (1ULL << 32); + struct fixed31_32 val = amdgpu_dm_fixpt_from_s3132(neg_one); + + /* 2's complement of 1.0 is -(1<<32) */ + KUNIT_EXPECT_EQ(test, val.value, -(long long)(1ULL << 32)); +} + +static void dm_test_fixpt_from_s3132_half(struct kunit *test) +{ + /* 0.5 in S31.32 = 1ULL << 31 */ + struct fixed31_32 val = amdgpu_dm_fixpt_from_s3132(1ULL << 31); + + KUNIT_EXPECT_EQ(test, val.value, (long long)(1ULL << 31)); +} + +static void dm_test_fixpt_from_s3132_neg_half(struct kunit *test) +{ + __u64 neg_half = (1ULL << 63) | (1ULL << 31); + struct fixed31_32 val = amdgpu_dm_fixpt_from_s3132(neg_half); + + KUNIT_EXPECT_EQ(test, val.value, -(long long)(1ULL << 31)); +} + +/* ---- Tests for __is_lut_linear ---- */ + +static void dm_test_is_lut_linear_with_linear_lut(struct kunit *test) +{ + const uint32_t size = 256; + struct drm_color_lut *lut; + int i; + + lut = kunit_kcalloc(test, size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, lut); + + for (i = 0; i < size; i++) { + uint16_t val = (uint16_t)(i * MAX_DRM_LUT_VALUE / (size - 1)); + + lut[i].red = val; + lut[i].green = val; + lut[i].blue = val; + } + + KUNIT_EXPECT_TRUE(test, __is_lut_linear(lut, size)); +} + +static void dm_test_is_lut_linear_with_nonlinear_lut(struct kunit *test) +{ + const uint32_t size = 256; + struct drm_color_lut *lut; + int i; + + lut = kunit_kcalloc(test, size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, lut); + + /* Fill with all-max values: clearly non-linear */ + for (i = 0; i < size; i++) { + lut[i].red = MAX_DRM_LUT_VALUE; + lut[i].green = MAX_DRM_LUT_VALUE; + lut[i].blue = MAX_DRM_LUT_VALUE; + } + + KUNIT_EXPECT_FALSE(test, __is_lut_linear(lut, size)); +} + +static void dm_test_is_lut_linear_rgb_mismatch(struct kunit *test) +{ + const uint32_t size = 256; + struct drm_color_lut *lut; + int i; + + lut = kunit_kcalloc(test, size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, lut); + + for (i = 0; i < size; i++) { + uint16_t val = (uint16_t)(i * MAX_DRM_LUT_VALUE / (size - 1)); + + lut[i].red = val; + lut[i].green = val; + lut[i].blue = val; + } + + /* Introduce R/G mismatch at entry 128 */ + lut[128].red = lut[128].green + 10; + + KUNIT_EXPECT_FALSE(test, __is_lut_linear(lut, size)); +} + +/* ---- Tests for __drm_ctm_to_dc_matrix ---- */ + +static void dm_test_drm_ctm_to_dc_matrix_identity(struct kunit *test) +{ + struct drm_color_ctm ctm; + struct fixed31_32 matrix[12]; + int i; + long long one = 1LL << 32; + + memset(&ctm, 0, sizeof(ctm)); + + /* Identity 3x3 in S31.32 signed-magnitude: diagonal = 1.0 */ + ctm.matrix[0] = 1ULL << 32; /* [0][0] */ + ctm.matrix[4] = 1ULL << 32; /* [1][1] */ + ctm.matrix[8] = 1ULL << 32; /* [2][2] */ + + __drm_ctm_to_dc_matrix(&ctm, matrix); + + /* Expect 3x4 identity: diag=1.0, 4th column=0, off-diag=0 */ + for (i = 0; i < 12; i++) { + if (i == 0 || i == 5 || i == 10) + KUNIT_EXPECT_EQ(test, matrix[i].value, one); + else + KUNIT_EXPECT_EQ(test, matrix[i].value, 0LL); + } +} + +static void dm_test_drm_ctm_to_dc_matrix_negative(struct kunit *test) +{ + struct drm_color_ctm ctm; + struct fixed31_32 matrix[12]; + long long neg_one = -(1LL << 32); + + memset(&ctm, 0, sizeof(ctm)); + + /* -1.0 in S31.32 signed-magnitude */ + ctm.matrix[0] = (1ULL << 63) | (1ULL << 32); + + __drm_ctm_to_dc_matrix(&ctm, matrix); + + KUNIT_EXPECT_EQ(test, matrix[0].value, neg_one); +} + +static void dm_test_drm_ctm_to_dc_matrix_4th_col_zero(struct kunit *test) +{ + struct drm_color_ctm ctm; + struct fixed31_32 matrix[12]; + + memset(&ctm, 0, sizeof(ctm)); + + /* Fill all 9 CTM entries with 1.0 */ + for (int i = 0; i < 9; i++) + ctm.matrix[i] = 1ULL << 32; + + __drm_ctm_to_dc_matrix(&ctm, matrix); + + /* 4th column (indices 3, 7, 11) must always be zero */ + KUNIT_EXPECT_EQ(test, matrix[3].value, 0LL); + KUNIT_EXPECT_EQ(test, matrix[7].value, 0LL); + KUNIT_EXPECT_EQ(test, matrix[11].value, 0LL); +} + +/* ---- Tests for __drm_ctm_3x4_to_dc_matrix ---- */ + +static void dm_test_drm_ctm_3x4_to_dc_matrix_identity(struct kunit *test) +{ + struct drm_color_ctm_3x4 ctm; + struct fixed31_32 matrix[12]; + int i; + long long one = 1LL << 32; + + memset(&ctm, 0, sizeof(ctm)); + + /* Identity with offsets in 4th column */ + ctm.matrix[0] = 1ULL << 32; /* [0][0] */ + ctm.matrix[5] = 1ULL << 32; /* [1][1] */ + ctm.matrix[10] = 1ULL << 32; /* [2][2] */ + + __drm_ctm_3x4_to_dc_matrix(&ctm, matrix); + + for (i = 0; i < 12; i++) { + if (i == 0 || i == 5 || i == 10) + KUNIT_EXPECT_EQ(test, matrix[i].value, one); + else + KUNIT_EXPECT_EQ(test, matrix[i].value, 0LL); + } +} + +static void dm_test_drm_ctm_3x4_to_dc_matrix_offset(struct kunit *test) +{ + struct drm_color_ctm_3x4 ctm; + struct fixed31_32 matrix[12]; + long long half = 1LL << 31; + + memset(&ctm, 0, sizeof(ctm)); + + /* Set 4th column (offsets) to 0.5 */ + ctm.matrix[3] = 1ULL << 31; + ctm.matrix[7] = 1ULL << 31; + ctm.matrix[11] = 1ULL << 31; + + __drm_ctm_3x4_to_dc_matrix(&ctm, matrix); + + KUNIT_EXPECT_EQ(test, matrix[3].value, half); + KUNIT_EXPECT_EQ(test, matrix[7].value, half); + KUNIT_EXPECT_EQ(test, matrix[11].value, half); +} + +/* ---- Tests for amdgpu_tf_to_dc_tf ---- */ + +static void dm_test_tf_to_dc_tf_default(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_DEFAULT), + (int)TRANSFER_FUNCTION_LINEAR); +} + +static void dm_test_tf_to_dc_tf_identity(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_IDENTITY), + (int)TRANSFER_FUNCTION_LINEAR); +} + +static void dm_test_tf_to_dc_tf_srgb(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF), + (int)TRANSFER_FUNCTION_SRGB); + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF), + (int)TRANSFER_FUNCTION_SRGB); +} + +static void dm_test_tf_to_dc_tf_bt709(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_BT709_OETF), + (int)TRANSFER_FUNCTION_BT709); + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF), + (int)TRANSFER_FUNCTION_BT709); +} + +static void dm_test_tf_to_dc_tf_pq(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_PQ_EOTF), + (int)TRANSFER_FUNCTION_PQ); + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF), + (int)TRANSFER_FUNCTION_PQ); +} + +static void dm_test_tf_to_dc_tf_gamma22(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF), + (int)TRANSFER_FUNCTION_GAMMA22); + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF), + (int)TRANSFER_FUNCTION_GAMMA22); +} + +static void dm_test_tf_to_dc_tf_gamma24(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF), + (int)TRANSFER_FUNCTION_GAMMA24); + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF), + (int)TRANSFER_FUNCTION_GAMMA24); +} + +static void dm_test_tf_to_dc_tf_gamma26(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF), + (int)TRANSFER_FUNCTION_GAMMA26); + KUNIT_EXPECT_EQ(test, (int)amdgpu_tf_to_dc_tf(AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF), + (int)TRANSFER_FUNCTION_GAMMA26); +} + +/* ---- Tests for amdgpu_colorop_tf_to_dc_tf ---- */ + +static void dm_test_colorop_tf_to_dc_tf_srgb(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_SRGB_EOTF), + (int)TRANSFER_FUNCTION_SRGB); + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF), + (int)TRANSFER_FUNCTION_SRGB); +} + +static void dm_test_colorop_tf_to_dc_tf_pq(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_PQ_125_EOTF), + (int)TRANSFER_FUNCTION_PQ); + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF), + (int)TRANSFER_FUNCTION_PQ); +} + +static void dm_test_colorop_tf_to_dc_tf_bt2020(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF), + (int)TRANSFER_FUNCTION_BT709); + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_BT2020_OETF), + (int)TRANSFER_FUNCTION_BT709); +} + +static void dm_test_colorop_tf_to_dc_tf_gamma22(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_GAMMA22), + (int)TRANSFER_FUNCTION_GAMMA22); + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_GAMMA22_INV), + (int)TRANSFER_FUNCTION_GAMMA22); +} + +static void dm_test_colorop_tf_to_dc_tf_default(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_colorop_tf_to_dc_tf(DRM_COLOROP_1D_CURVE_COUNT), + (int)TRANSFER_FUNCTION_LINEAR); +} + +/* ---- Tests for __drm_lut_to_dc_gamma (legacy path) ---- */ + +static void dm_test_drm_lut_to_dc_gamma_legacy_zero(struct kunit *test) +{ + struct drm_color_lut *lut; + struct dc_gamma *gamma; + + lut = kunit_kcalloc(test, MAX_COLOR_LEGACY_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + __drm_lut_to_dc_gamma(lut, gamma, true); + + /* All-zero LUT should produce all-zero gamma entries */ + for (int i = 0; i < MAX_COLOR_LEGACY_LUT_ENTRIES; i++) { + KUNIT_EXPECT_EQ(test, gamma->entries.red[i].value, 0LL); + KUNIT_EXPECT_EQ(test, gamma->entries.green[i].value, 0LL); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[i].value, 0LL); + } +} + +static void dm_test_drm_lut_to_dc_gamma_legacy_max(struct kunit *test) +{ + struct drm_color_lut *lut; + struct dc_gamma *gamma; + long long expected = (long long)MAX_DRM_LUT_VALUE << 32; + + lut = kunit_kcalloc(test, MAX_COLOR_LEGACY_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + /* Set first and last entries to max */ + lut[0].red = MAX_DRM_LUT_VALUE; + lut[0].green = MAX_DRM_LUT_VALUE; + lut[0].blue = MAX_DRM_LUT_VALUE; + lut[MAX_COLOR_LEGACY_LUT_ENTRIES - 1].red = MAX_DRM_LUT_VALUE; + lut[MAX_COLOR_LEGACY_LUT_ENTRIES - 1].green = MAX_DRM_LUT_VALUE; + lut[MAX_COLOR_LEGACY_LUT_ENTRIES - 1].blue = MAX_DRM_LUT_VALUE; + + __drm_lut_to_dc_gamma(lut, gamma, true); + + /* Legacy uses dc_fixpt_from_int(val) = val << 32 */ + KUNIT_EXPECT_EQ(test, gamma->entries.red[0].value, expected); + KUNIT_EXPECT_EQ(test, gamma->entries.green[0].value, expected); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[0].value, expected); + KUNIT_EXPECT_EQ(test, gamma->entries.red[MAX_COLOR_LEGACY_LUT_ENTRIES - 1].value, + expected); +} + +static void dm_test_drm_lut_to_dc_gamma_legacy_channels(struct kunit *test) +{ + struct drm_color_lut *lut; + struct dc_gamma *gamma; + + lut = kunit_kcalloc(test, MAX_COLOR_LEGACY_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + /* Set distinct values per channel at index 1 */ + lut[1].red = 100; + lut[1].green = 200; + lut[1].blue = 300; + + __drm_lut_to_dc_gamma(lut, gamma, true); + + KUNIT_EXPECT_EQ(test, gamma->entries.red[1].value, 100LL << 32); + KUNIT_EXPECT_EQ(test, gamma->entries.green[1].value, 200LL << 32); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[1].value, 300LL << 32); +} + +/* ---- Tests for __drm_lut_to_dc_gamma (non-legacy path) ---- */ + +static void dm_test_drm_lut_to_dc_gamma_nonlegacy_zero(struct kunit *test) +{ + struct drm_color_lut *lut; + struct dc_gamma *gamma; + + lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + __drm_lut_to_dc_gamma(lut, gamma, false); + + /* All-zero LUT → fraction(0, 0xFFFF) = 0 */ + KUNIT_EXPECT_EQ(test, gamma->entries.red[0].value, 0LL); + KUNIT_EXPECT_EQ(test, gamma->entries.green[0].value, 0LL); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[0].value, 0LL); +} + +static void dm_test_drm_lut_to_dc_gamma_nonlegacy_max(struct kunit *test) +{ + struct drm_color_lut *lut; + struct dc_gamma *gamma; + long long one = 1LL << 32; + + lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + /* Max LUT value should map to 1.0 in fixed-point */ + lut[0].red = MAX_DRM_LUT_VALUE; + lut[0].green = MAX_DRM_LUT_VALUE; + lut[0].blue = MAX_DRM_LUT_VALUE; + + __drm_lut_to_dc_gamma(lut, gamma, false); + + /* dc_fixpt_from_fraction(0xFFFF, 0xFFFF) = 1.0 */ + KUNIT_EXPECT_EQ(test, gamma->entries.red[0].value, one); + KUNIT_EXPECT_EQ(test, gamma->entries.green[0].value, one); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[0].value, one); +} + +/* ---- Tests for __drm_lut32_to_dc_gamma ---- */ + +static void dm_test_drm_lut32_to_dc_gamma_zero(struct kunit *test) +{ + struct drm_color_lut32 *lut; + struct dc_gamma *gamma; + + lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + __drm_lut32_to_dc_gamma(lut, gamma); + + /* All-zero LUT → fraction(0, 0xFFFFFFFF) = 0 */ + KUNIT_EXPECT_EQ(test, gamma->entries.red[0].value, 0LL); + KUNIT_EXPECT_EQ(test, gamma->entries.green[0].value, 0LL); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[0].value, 0LL); +} + +static void dm_test_drm_lut32_to_dc_gamma_max(struct kunit *test) +{ + struct drm_color_lut32 *lut; + struct dc_gamma *gamma; + long long one = 1LL << 32; + + lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + /* Max 32-bit LUT value should map to 1.0 in fixed-point */ + lut[0].red = MAX_DRM_LUT32_VALUE; + lut[0].green = MAX_DRM_LUT32_VALUE; + lut[0].blue = MAX_DRM_LUT32_VALUE; + + __drm_lut32_to_dc_gamma(lut, gamma); + + /* dc_fixpt_from_fraction(0xFFFFFFFF, 0xFFFFFFFF) = 1.0 */ + KUNIT_EXPECT_EQ(test, gamma->entries.red[0].value, one); + KUNIT_EXPECT_EQ(test, gamma->entries.green[0].value, one); + KUNIT_EXPECT_EQ(test, gamma->entries.blue[0].value, one); +} + +static void dm_test_drm_lut32_to_dc_gamma_channels(struct kunit *test) +{ + struct drm_color_lut32 *lut; + struct dc_gamma *gamma; + + lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, + sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + gamma = kunit_kzalloc(test, sizeof(*gamma), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, gamma); + + /* Set distinct values per channel at index 1 */ + lut[1].red = 1000; + lut[1].green = 2000; + lut[1].blue = 3000; + + __drm_lut32_to_dc_gamma(lut, gamma); + + /* Channels should differ */ + KUNIT_EXPECT_NE(test, gamma->entries.red[1].value, + gamma->entries.green[1].value); + KUNIT_EXPECT_NE(test, gamma->entries.green[1].value, + gamma->entries.blue[1].value); + /* Red < Green < Blue since 1000 < 2000 < 3000 */ + KUNIT_EXPECT_LT(test, gamma->entries.red[1].value, + gamma->entries.green[1].value); + KUNIT_EXPECT_LT(test, gamma->entries.green[1].value, + gamma->entries.blue[1].value); +} + +/* ---- Tests for __extract_blob_lut ---- */ + +static void dm_test_extract_blob_lut_null(struct kunit *test) +{ + uint32_t size = 42; + const struct drm_color_lut *lut; + + lut = __extract_blob_lut(NULL, &size); + + KUNIT_EXPECT_NULL(test, lut); + KUNIT_EXPECT_EQ(test, size, 0); +} + +static void dm_test_extract_blob_lut_valid(struct kunit *test) +{ + const int num_entries = 4; + struct drm_property_blob *blob; + struct drm_color_lut *data; + const struct drm_color_lut *lut; + uint32_t size = 0; + + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + + data = kunit_kcalloc(test, num_entries, sizeof(*data), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, data); + + data[0].red = 100; + data[0].green = 200; + data[0].blue = 300; + + blob->data = data; + blob->length = num_entries * sizeof(struct drm_color_lut); + + lut = __extract_blob_lut(blob, &size); + + KUNIT_EXPECT_EQ(test, size, (uint32_t)num_entries); + KUNIT_ASSERT_NOT_NULL(test, lut); + KUNIT_EXPECT_EQ(test, lut[0].red, 100); + KUNIT_EXPECT_EQ(test, lut[0].green, 200); + KUNIT_EXPECT_EQ(test, lut[0].blue, 300); +} + +/* ---- Tests for __extract_blob_lut32 ---- */ + +static void dm_test_extract_blob_lut32_null(struct kunit *test) +{ + uint32_t size = 42; + const struct drm_color_lut32 *lut; + + lut = __extract_blob_lut32(NULL, &size); + + KUNIT_EXPECT_NULL(test, lut); + KUNIT_EXPECT_EQ(test, size, 0); +} + +static void dm_test_extract_blob_lut32_valid(struct kunit *test) +{ + const int num_entries = 4; + struct drm_property_blob *blob; + struct drm_color_lut32 *data; + const struct drm_color_lut32 *lut; + uint32_t size = 0; + + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + + data = kunit_kcalloc(test, num_entries, sizeof(*data), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, data); + + data[0].red = 100000; + data[0].green = 200000; + data[0].blue = 300000; + + blob->data = data; + blob->length = num_entries * sizeof(struct drm_color_lut32); + + lut = __extract_blob_lut32(blob, &size); + + KUNIT_EXPECT_EQ(test, size, (uint32_t)num_entries); + KUNIT_ASSERT_NOT_NULL(test, lut); + KUNIT_EXPECT_EQ(test, lut[0].red, (uint32_t)100000); + KUNIT_EXPECT_EQ(test, lut[0].green, (uint32_t)200000); + KUNIT_EXPECT_EQ(test, lut[0].blue, (uint32_t)300000); +} + +/* ---- Tests for __to_dc_lut3d_color ---- */ + +static void dm_test_to_dc_lut3d_color_zero(struct kunit *test) +{ + struct dc_rgb rgb = {0}; + struct drm_color_lut lut = {0}; + + __to_dc_lut3d_color(&rgb, lut, 12); + + KUNIT_EXPECT_EQ(test, rgb.red, 0U); + KUNIT_EXPECT_EQ(test, rgb.green, 0U); + KUNIT_EXPECT_EQ(test, rgb.blue, 0U); +} + +static void dm_test_to_dc_lut3d_color_max(struct kunit *test) +{ + struct dc_rgb rgb = {0}; + struct drm_color_lut lut = { + .red = 0xFFFF, + .green = 0xFFFF, + .blue = 0xFFFF, + }; + + /* 12-bit extraction: 0xFFFF maps to (1 << 12) - 1 = 4095 */ + __to_dc_lut3d_color(&rgb, lut, 12); + + KUNIT_EXPECT_EQ(test, rgb.red, 4095U); + KUNIT_EXPECT_EQ(test, rgb.green, 4095U); + KUNIT_EXPECT_EQ(test, rgb.blue, 4095U); +} + +static void dm_test_to_dc_lut3d_color_channels(struct kunit *test) +{ + struct dc_rgb rgb = {0}; + struct drm_color_lut lut = { + .red = 0x8000, + .green = 0x4000, + .blue = 0xC000, + }; + + __to_dc_lut3d_color(&rgb, lut, 12); + + /* Channels should be distinct and ordered: green < red < blue */ + KUNIT_EXPECT_GT(test, rgb.red, rgb.green); + KUNIT_EXPECT_GT(test, rgb.blue, rgb.red); +} + +/* ---- Tests for __drm_3dlut_to_dc_3dlut ---- */ + +static void dm_test_3dlut_to_dc_3dlut_distribution(struct kunit *test) +{ + /* + * Use 9 entries: loop processes 2 groups of 4, then lut0 gets + * one extra final entry. Total: lut0=3, lut1=2, lut2=2, lut3=2. + */ + const uint32_t lut3d_size = 9; + struct drm_color_lut *lut; + struct tetrahedral_params *params; + int i; + + lut = kunit_kcalloc(test, lut3d_size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + params = kunit_kzalloc(test, sizeof(*params), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, params); + + /* Fill LUT with distinct values per entry */ + for (i = 0; i < lut3d_size; i++) { + lut[i].red = (i + 1) * 1000; + lut[i].green = (i + 1) * 2000; + lut[i].blue = (i + 1) * 3000; + } + + __drm_3dlut_to_dc_3dlut(lut, lut3d_size, params, true, 12); + + /* Group 0: lut[0]→lut0[0], lut[1]→lut1[0], lut[2]→lut2[0], lut[3]→lut3[0] */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].red, + drm_color_lut_extract(lut[0].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut1[0].red, + drm_color_lut_extract(lut[1].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut2[0].red, + drm_color_lut_extract(lut[2].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].red, + drm_color_lut_extract(lut[3].red, 12)); + + /* Group 1: lut[4]→lut0[1], lut[5]→lut1[1], lut[6]→lut2[1], lut[7]→lut3[1] */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[1].red, + drm_color_lut_extract(lut[4].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut1[1].red, + drm_color_lut_extract(lut[5].red, 12)); + + /* Final extra entry: lut[8]→lut0[2] */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[2].red, + drm_color_lut_extract(lut[8].red, 12)); +} + +static void dm_test_3dlut_to_dc_3dlut_tetrahedral_17(struct kunit *test) +{ + /* Minimal test with 5 entries using tetrahedral_17 path */ + const uint32_t lut3d_size = 5; + struct drm_color_lut *lut; + struct tetrahedral_params *params; + + lut = kunit_kcalloc(test, lut3d_size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + params = kunit_kzalloc(test, sizeof(*params), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, params); + + lut[0].red = 0xFFFF; + lut[0].green = 0; + lut[0].blue = 0; + lut[1].red = 0; + lut[1].green = 0xFFFF; + lut[1].blue = 0; + lut[2].red = 0; + lut[2].green = 0; + lut[2].blue = 0xFFFF; + lut[3].red = 0x8000; + lut[3].green = 0x8000; + lut[3].blue = 0x8000; + lut[4].red = 0xFFFF; + lut[4].green = 0xFFFF; + lut[4].blue = 0xFFFF; + + __drm_3dlut_to_dc_3dlut(lut, lut3d_size, params, false, 12); + + /* lut[0]→lut0[0]: red=4095, green=0, blue=0 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[0].red, 4095U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[0].green, 0U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[0].blue, 0U); + + /* lut[1]→lut1[0]: red=0, green=4095, blue=0 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut1[0].green, 4095U); + + /* lut[2]→lut2[0]: red=0, green=0, blue=4095 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut2[0].blue, 4095U); + + /* lut[4]→lut0[1] (extra final entry): all 4095 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[1].red, 4095U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[1].green, 4095U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[1].blue, 4095U); +} + +static void dm_test_3dlut_to_dc_3dlut_green_blue(struct kunit *test) +{ + /* Verify green and blue channels are also correctly distributed */ + const uint32_t lut3d_size = 5; + struct drm_color_lut *lut; + struct tetrahedral_params *params; + + lut = kunit_kcalloc(test, lut3d_size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + params = kunit_kzalloc(test, sizeof(*params), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, params); + + lut[0].red = 100; + lut[0].green = 200; + lut[0].blue = 300; + lut[3].red = 400; + lut[3].green = 500; + lut[3].blue = 600; + + __drm_3dlut_to_dc_3dlut(lut, lut3d_size, params, true, 12); + + /* lut[0]→lut0[0]: verify all channels */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].red, + drm_color_lut_extract(100, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].green, + drm_color_lut_extract(200, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].blue, + drm_color_lut_extract(300, 12)); + + /* lut[3]→lut3[0]: verify all channels */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].red, + drm_color_lut_extract(400, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].green, + drm_color_lut_extract(500, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].blue, + drm_color_lut_extract(600, 12)); +} + +/* ---- Tests for __to_dc_lut3d_32_color ---- */ + +static void dm_test_to_dc_lut3d_32_color_zero(struct kunit *test) +{ + struct dc_rgb rgb = {0}; + struct drm_color_lut32 lut = {0}; + + __to_dc_lut3d_32_color(&rgb, lut, 12); + + KUNIT_EXPECT_EQ(test, rgb.red, 0U); + KUNIT_EXPECT_EQ(test, rgb.green, 0U); + KUNIT_EXPECT_EQ(test, rgb.blue, 0U); +} + +static void dm_test_to_dc_lut3d_32_color_max(struct kunit *test) +{ + struct dc_rgb rgb = {0}; + struct drm_color_lut32 lut = { + .red = 0xFFFFFFFF, + .green = 0xFFFFFFFF, + .blue = 0xFFFFFFFF, + }; + + /* 12-bit extraction: 0xFFFFFFFF maps to (1 << 12) - 1 = 4095 */ + __to_dc_lut3d_32_color(&rgb, lut, 12); + + KUNIT_EXPECT_EQ(test, rgb.red, 4095U); + KUNIT_EXPECT_EQ(test, rgb.green, 4095U); + KUNIT_EXPECT_EQ(test, rgb.blue, 4095U); +} + +static void dm_test_to_dc_lut3d_32_color_channels(struct kunit *test) +{ + struct dc_rgb rgb = {0}; + struct drm_color_lut32 lut = { + .red = 0x80000000, + .green = 0x40000000, + .blue = 0xC0000000, + }; + + __to_dc_lut3d_32_color(&rgb, lut, 12); + + /* Channels should be distinct and ordered: green < red < blue */ + KUNIT_EXPECT_GT(test, rgb.red, rgb.green); + KUNIT_EXPECT_GT(test, rgb.blue, rgb.red); +} + +/* ---- Tests for __drm_3dlut32_to_dc_3dlut ---- */ + +static void dm_test_3dlut32_to_dc_3dlut_distribution(struct kunit *test) +{ + /* + * Use 9 entries: loop processes 2 groups of 4, then lut0 gets + * one extra final entry. Total: lut0=3, lut1=2, lut2=2, lut3=2. + */ + const uint32_t lut3d_size = 9; + struct drm_color_lut32 *lut; + struct tetrahedral_params *params; + int i; + + lut = kunit_kcalloc(test, lut3d_size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + params = kunit_kzalloc(test, sizeof(*params), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, params); + + /* Fill LUT with distinct values per entry */ + for (i = 0; i < lut3d_size; i++) { + lut[i].red = (i + 1) * 100000; + lut[i].green = (i + 1) * 200000; + lut[i].blue = (i + 1) * 300000; + } + + __drm_3dlut32_to_dc_3dlut(lut, lut3d_size, params, true, 12); + + /* Group 0: lut[0]→lut0[0], lut[1]→lut1[0], lut[2]→lut2[0], lut[3]→lut3[0] */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].red, + drm_color_lut32_extract(lut[0].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut1[0].red, + drm_color_lut32_extract(lut[1].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut2[0].red, + drm_color_lut32_extract(lut[2].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].red, + drm_color_lut32_extract(lut[3].red, 12)); + + /* Group 1: lut[4]→lut0[1], lut[5]→lut1[1], lut[6]→lut2[1], lut[7]→lut3[1] */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[1].red, + drm_color_lut32_extract(lut[4].red, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut1[1].red, + drm_color_lut32_extract(lut[5].red, 12)); + + /* Final extra entry: lut[8]→lut0[2] */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[2].red, + drm_color_lut32_extract(lut[8].red, 12)); +} + +static void dm_test_3dlut32_to_dc_3dlut_tetrahedral_17(struct kunit *test) +{ + /* Minimal test with 5 entries using tetrahedral_17 path */ + const uint32_t lut3d_size = 5; + struct drm_color_lut32 *lut; + struct tetrahedral_params *params; + + lut = kunit_kcalloc(test, lut3d_size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + params = kunit_kzalloc(test, sizeof(*params), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, params); + + lut[0].red = 0xFFFFFFFF; + lut[0].green = 0; + lut[0].blue = 0; + lut[1].red = 0; + lut[1].green = 0xFFFFFFFF; + lut[1].blue = 0; + lut[2].red = 0; + lut[2].green = 0; + lut[2].blue = 0xFFFFFFFF; + lut[3].red = 0x80000000; + lut[3].green = 0x80000000; + lut[3].blue = 0x80000000; + lut[4].red = 0xFFFFFFFF; + lut[4].green = 0xFFFFFFFF; + lut[4].blue = 0xFFFFFFFF; + + __drm_3dlut32_to_dc_3dlut(lut, lut3d_size, params, false, 12); + + /* lut[0]→lut0[0]: red=4095, green=0, blue=0 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[0].red, 4095U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[0].green, 0U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[0].blue, 0U); + + /* lut[1]→lut1[0]: red=0, green=4095, blue=0 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut1[0].green, 4095U); + + /* lut[2]→lut2[0]: red=0, green=0, blue=4095 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut2[0].blue, 4095U); + + /* lut[4]→lut0[1] (extra final entry): all 4095 */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[1].red, 4095U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[1].green, 4095U); + KUNIT_EXPECT_EQ(test, params->tetrahedral_17.lut0[1].blue, 4095U); +} + +static void dm_test_3dlut32_to_dc_3dlut_green_blue(struct kunit *test) +{ + /* Verify green and blue channels are also correctly distributed */ + const uint32_t lut3d_size = 5; + struct drm_color_lut32 *lut; + struct tetrahedral_params *params; + + lut = kunit_kcalloc(test, lut3d_size, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + params = kunit_kzalloc(test, sizeof(*params), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, params); + + lut[0].red = 100000; + lut[0].green = 200000; + lut[0].blue = 300000; + lut[3].red = 400000; + lut[3].green = 500000; + lut[3].blue = 600000; + + __drm_3dlut32_to_dc_3dlut(lut, lut3d_size, params, true, 12); + + /* lut[0]→lut0[0]: verify all channels */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].red, + drm_color_lut32_extract(100000, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].green, + drm_color_lut32_extract(200000, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut0[0].blue, + drm_color_lut32_extract(300000, 12)); + + /* lut[3]→lut3[0]: verify all channels */ + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].red, + drm_color_lut32_extract(400000, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].green, + drm_color_lut32_extract(500000, 12)); + KUNIT_EXPECT_EQ(test, params->tetrahedral_9.lut3[0].blue, + drm_color_lut32_extract(600000, 12)); +} + +/* ---- Tests for amdgpu_dm_verify_lut_sizes ---- */ + +/** + * dm_test_make_lut_blob - Allocate a fake drm_property_blob for testing + * @test: KUnit test context + * @num_entries: number of LUT entries the blob will report + * + * Allocates a fake blob whose drm_color_lut_size() returns exactly + * @num_entries. The data pointer is non-NULL so that + * __extract_blob_lut() returns a non-NULL lut pointer and the size + * check inside amdgpu_dm_verify_lut_sizes() is actually exercised. + * + * Return: pointer to the allocated blob + */ +static struct drm_property_blob * +dm_test_make_lut_blob(struct kunit *test, uint32_t num_entries) +{ + struct drm_property_blob *blob; + + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + + blob->length = num_entries * sizeof(struct drm_color_lut); + blob->data = kunit_kcalloc(test, num_entries, + sizeof(struct drm_color_lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob->data); + + return blob; +} + +/** + * dm_test_verify_lut_sizes_null_luts - Both LUTs absent: must succeed + * @test: KUnit test context + */ +static void dm_test_verify_lut_sizes_null_luts(struct kunit *test) +{ + struct drm_crtc_state *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + /* degamma_lut and gamma_lut are NULL (zeroed allocation) */ + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut_sizes(state), 0); +} + +/** + * dm_test_verify_lut_sizes_valid_degamma - Degamma LUT with the correct atomic size: must succeed + * @test: KUnit test context + */ +static void dm_test_verify_lut_sizes_valid_degamma(struct kunit *test) +{ + struct drm_crtc_state *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + state->degamma_lut = dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut_sizes(state), 0); +} + +/** + * dm_test_verify_lut_sizes_invalid_degamma - Degamma LUT with a wrong size: must return -EINVAL + * @test: KUnit test context + */ +static void dm_test_verify_lut_sizes_invalid_degamma(struct kunit *test) +{ + struct drm_crtc_state *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + /* Use an arbitrary size that is neither atomic nor legacy */ + state->degamma_lut = dm_test_make_lut_blob(test, 128); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut_sizes(state), -EINVAL); +} + +/** + * dm_test_verify_lut_sizes_valid_gamma_atomic - Gamma LUT with correct atomic size: must succeed + * @test: KUnit test context + */ +static void dm_test_verify_lut_sizes_valid_gamma_atomic(struct kunit *test) +{ + struct drm_crtc_state *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + state->gamma_lut = dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut_sizes(state), 0); +} + +/** + * dm_test_verify_lut_sizes_valid_gamma_legacy - Gamma LUT with legacy 256-entry size: must succeed + * @test: KUnit test context + */ +static void dm_test_verify_lut_sizes_valid_gamma_legacy(struct kunit *test) +{ + struct drm_crtc_state *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + state->gamma_lut = dm_test_make_lut_blob(test, MAX_COLOR_LEGACY_LUT_ENTRIES); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut_sizes(state), 0); +} + +/** + * dm_test_verify_lut_sizes_invalid_gamma - Size is neither atomic nor legacy: must return -EINVAL + * @test: KUnit test context + */ +static void dm_test_verify_lut_sizes_invalid_gamma(struct kunit *test) +{ + struct drm_crtc_state *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + state->gamma_lut = dm_test_make_lut_blob(test, 128); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut_sizes(state), -EINVAL); +} + +/** + * dm_test_verify_lut_sizes_both_valid - Both LUTs set to valid sizes: must succeed + * @test: KUnit test context + */ +static void dm_test_verify_lut_sizes_both_valid(struct kunit *test) +{ + struct drm_crtc_state *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + state->degamma_lut = dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + state->gamma_lut = dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut_sizes(state), 0); +} + +/** + * dm_test_verify_lut_sizes_invalid_degamma_valid_gamma - Bad degamma overrides valid gamma: -EINVAL + * @test: KUnit test context + */ +static void dm_test_verify_lut_sizes_invalid_degamma_valid_gamma(struct kunit *test) +{ + struct drm_crtc_state *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + state->degamma_lut = dm_test_make_lut_blob(test, 128); + state->gamma_lut = dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut_sizes(state), -EINVAL); +} + +/* ---- Tests for amdgpu_dm_atomic_lut3d ---- */ + +/** + * dm_test_atomic_lut3d_zero_size - Zero LUT size: initialized must be cleared, no LUT data written + * @test: KUnit test context + */ +static void dm_test_atomic_lut3d_zero_size(struct kunit *test) +{ + struct dc_3dlut *lut; + u32 initialized; + + lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + /* Pre-set initialized so we can confirm it is cleared */ + lut->state.bits.initialized = 1; + + amdgpu_dm_atomic_lut3d(NULL, 0, lut); + + /* Copy bit-field: typeof cannot be applied to a bit-field */ + initialized = lut->state.bits.initialized; + KUNIT_EXPECT_EQ(test, initialized, 0U); +} + +/** + * dm_test_atomic_lut3d_nonzero_state_bits - Non-zero size: state bits and mode flags must be set + * @test: KUnit test context + */ +static void dm_test_atomic_lut3d_nonzero_state_bits(struct kunit *test) +{ + const uint32_t lut3d_size = 5; + struct drm_color_lut *lut_data; + struct dc_3dlut *lut; + u32 initialized; + + lut_data = kunit_kcalloc(test, lut3d_size, sizeof(*lut_data), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut_data); + + lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, lut); + + /* Copy bit-field: typeof cannot be applied to a bit-field */ + initialized = lut->state.bits.initialized; + KUNIT_EXPECT_EQ(test, initialized, 1U); + KUNIT_EXPECT_FALSE(test, lut->lut_3d.use_tetrahedral_9); + KUNIT_EXPECT_TRUE(test, lut->lut_3d.use_12bits); +} + +/** + * dm_test_atomic_lut3d_data_forwarded - Non-zero size: LUT data forwarded to tetrahedral_17 + * @test: KUnit test context + */ +static void dm_test_atomic_lut3d_data_forwarded(struct kunit *test) +{ + const uint32_t lut3d_size = 5; + struct drm_color_lut *lut_data; + struct dc_3dlut *lut; + + lut_data = kunit_kcalloc(test, lut3d_size, sizeof(*lut_data), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut_data); + + lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + lut_data[0].red = 0xFFFF; + lut_data[0].green = 0x8000; + lut_data[0].blue = 0x4000; + + amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, lut); + + /* + * use_tetrahedral_9 == false → data goes into tetrahedral_17. + * lut[0] maps to lut0[0] (first element of the first group). + */ + KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].red, + drm_color_lut_extract(0xFFFF, MAX_COLOR_3DLUT_BITDEPTH)); + KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].green, + drm_color_lut_extract(0x8000, MAX_COLOR_3DLUT_BITDEPTH)); + KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].blue, + drm_color_lut_extract(0x4000, MAX_COLOR_3DLUT_BITDEPTH)); +} + +/* ---- Tests for __set_colorop_3dlut ---- */ + +/** + * dm_test_set_colorop_3dlut_zero_size - Zero LUT size: must return -EINVAL and clear initialized + * @test: KUnit test context + */ +static void dm_test_set_colorop_3dlut_zero_size(struct kunit *test) +{ + struct dc_3dlut *lut; + u32 initialized; + + lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + lut->state.bits.initialized = 1; + + KUNIT_EXPECT_EQ(test, __set_colorop_3dlut(NULL, 0, lut), -EINVAL); + /* Copy bit-field: typeof cannot be applied to a bit-field */ + initialized = lut->state.bits.initialized; + KUNIT_EXPECT_EQ(test, initialized, 0U); +} + +/** + * dm_test_set_colorop_3dlut_nonzero_state_bits - Non-zero size: must return 0 and set state bits + * @test: KUnit test context + */ +static void dm_test_set_colorop_3dlut_nonzero_state_bits(struct kunit *test) +{ + const uint32_t lut3d_size = 5; + struct drm_color_lut32 *lut_data; + struct dc_3dlut *lut; + u32 initialized; + + lut_data = kunit_kcalloc(test, lut3d_size, sizeof(*lut_data), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut_data); + + lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + KUNIT_EXPECT_EQ(test, __set_colorop_3dlut(lut_data, lut3d_size, lut), 0); + /* Copy bit-field: typeof cannot be applied to a bit-field */ + initialized = lut->state.bits.initialized; + KUNIT_EXPECT_EQ(test, initialized, 1U); + KUNIT_EXPECT_FALSE(test, lut->lut_3d.use_tetrahedral_9); + KUNIT_EXPECT_TRUE(test, lut->lut_3d.use_12bits); +} + +/** + * dm_test_set_colorop_3dlut_data_forwarded - Non-zero size: 32-bit data forwarded to tetrahedral_17 + * @test: KUnit test context + */ +static void dm_test_set_colorop_3dlut_data_forwarded(struct kunit *test) +{ + const uint32_t lut3d_size = 5; + struct drm_color_lut32 *lut_data; + struct dc_3dlut *lut; + + lut_data = kunit_kcalloc(test, lut3d_size, sizeof(*lut_data), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut_data); + + lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + lut_data[0].red = 0xFFFFFFFF; + lut_data[0].green = 0x80000000; + lut_data[0].blue = 0x40000000; + + KUNIT_EXPECT_EQ(test, __set_colorop_3dlut(lut_data, lut3d_size, lut), 0); + + /* + * use_tetrahedral_9 == false → data goes into tetrahedral_17. + * lut[0] maps to lut0[0]. Bit depth used by __set_colorop_3dlut is 12. + */ + KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].red, + drm_color_lut32_extract(0xFFFFFFFF, 12)); + KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].green, + drm_color_lut32_extract(0x80000000, 12)); + KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].blue, + drm_color_lut32_extract(0x40000000, 12)); +} + +/** + * dm_test_set_tf_bypass - __set_tf_bypass: sets TF_TYPE_BYPASS and TRANSFER_FUNCTION_LINEAR + * @test: KUnit test context + */ +static void dm_test_set_tf_bypass(struct kunit *test) +{ + struct dc_transfer_func *tf; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = TRANSFER_FUNCTION_SRGB; + + __set_tf_bypass(tf); + + KUNIT_EXPECT_EQ(test, (int)tf->type, (int)TF_TYPE_BYPASS); + KUNIT_EXPECT_EQ(test, (int)tf->tf, (int)TRANSFER_FUNCTION_LINEAR); +} + +/** + * dm_test_set_tf_distributed_points_srgb - __set_tf_distributed_points: sRGB predefined TF + * @test: KUnit test context + */ +static void dm_test_set_tf_distributed_points_srgb(struct kunit *test) +{ + struct dc_transfer_func *tf; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + + __set_tf_distributed_points(tf, TRANSFER_FUNCTION_SRGB); + + KUNIT_EXPECT_EQ(test, (int)tf->type, (int)TF_TYPE_DISTRIBUTED_POINTS); + KUNIT_EXPECT_EQ(test, (int)tf->tf, (int)TRANSFER_FUNCTION_SRGB); + KUNIT_EXPECT_EQ(test, tf->sdr_ref_white_level, 80U); +} + +/** + * dm_test_set_tf_distributed_points_pq - __set_tf_distributed_points: PQ predefined TF + * @test: KUnit test context + */ +static void dm_test_set_tf_distributed_points_pq(struct kunit *test) +{ + struct dc_transfer_func *tf; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + + __set_tf_distributed_points(tf, TRANSFER_FUNCTION_PQ); + + KUNIT_EXPECT_EQ(test, (int)tf->type, (int)TF_TYPE_DISTRIBUTED_POINTS); + KUNIT_EXPECT_EQ(test, (int)tf->tf, (int)TRANSFER_FUNCTION_PQ); + KUNIT_EXPECT_EQ(test, tf->sdr_ref_white_level, 80U); +} + +/** + * dm_test_set_atomic_regamma_bypass - No LUT and linear TF: must take bypass path + * @test: KUnit test context + */ +static void dm_test_set_atomic_regamma_bypass(struct kunit *test) +{ + struct dc_transfer_func *out_tf; + + out_tf = kunit_kzalloc(test, sizeof(*out_tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, out_tf); + + /* size=0 and tf=LINEAR: must take the bypass branch */ + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_atomic_regamma(out_tf, NULL, 0, false, + TRANSFER_FUNCTION_LINEAR), + 0); + KUNIT_EXPECT_EQ(test, (int)out_tf->type, (int)TF_TYPE_BYPASS); + KUNIT_EXPECT_EQ(test, (int)out_tf->tf, (int)TRANSFER_FUNCTION_LINEAR); +} + +/** + * dm_test_atomic_shaper_lut_bypass - No LUT and linear TF: must take bypass path + * @test: KUnit test context + */ +static void dm_test_atomic_shaper_lut_bypass(struct kunit *test) +{ + struct dc_transfer_func *func_shaper; + + func_shaper = kunit_kzalloc(test, sizeof(*func_shaper), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, func_shaper); + + /* size=0 and tf=LINEAR: must take the bypass branch */ + KUNIT_EXPECT_EQ(test, + amdgpu_dm_atomic_shaper_lut(NULL, false, + TRANSFER_FUNCTION_LINEAR, + 0, func_shaper), + 0); + KUNIT_EXPECT_EQ(test, (int)func_shaper->type, (int)TF_TYPE_BYPASS); + KUNIT_EXPECT_EQ(test, (int)func_shaper->tf, (int)TRANSFER_FUNCTION_LINEAR); +} + +/** + * dm_test_atomic_blend_lut_bypass - amdgpu_dm_atomic_blend_lut bypass: no LUT, linear TF -> bypass + * @test: KUnit test context + */ +static void dm_test_atomic_blend_lut_bypass(struct kunit *test) +{ + struct dc_transfer_func *func_blend; + + func_blend = kunit_kzalloc(test, sizeof(*func_blend), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, func_blend); + + /* size=0 and tf=LINEAR: must take the bypass branch */ + KUNIT_EXPECT_EQ(test, + amdgpu_dm_atomic_blend_lut(NULL, false, + TRANSFER_FUNCTION_LINEAR, + 0, func_blend), + 0); + KUNIT_EXPECT_EQ(test, (int)func_blend->type, (int)TF_TYPE_BYPASS); + KUNIT_EXPECT_EQ(test, (int)func_blend->tf, (int)TRANSFER_FUNCTION_LINEAR); +} + +/* ---- Tests for __set_colorop_in_tf_1d_curve ---- */ + +/** + * dm_test_set_colorop_in_tf_1d_curve_invalid_type - Non-1D colorop type must be rejected + * @test: KUnit test context + */ +static void dm_test_set_colorop_in_tf_1d_curve_invalid_type(struct kunit *test) +{ + struct dc_plane_state *dc_plane_state; + struct drm_colorop *colorop; + struct drm_colorop_state *colorop_state; + + dc_plane_state = kunit_kzalloc(test, sizeof(*dc_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_plane_state); + + colorop = kunit_kzalloc(test, sizeof(*colorop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorop); + + colorop_state = kunit_kzalloc(test, sizeof(*colorop_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorop_state); + + colorop->type = DRM_COLOROP_3D_LUT; + colorop_state->colorop = colorop; + colorop_state->curve_1d_type = DRM_COLOROP_1D_CURVE_SRGB_EOTF; + + KUNIT_EXPECT_EQ(test, + __set_colorop_in_tf_1d_curve(dc_plane_state, colorop_state), + -EINVAL); +} + +/** + * dm_test_set_colorop_in_tf_1d_curve_unsupported_curve - Unsupported 1D curve type must be rejected + * @test: KUnit test context + */ +static void dm_test_set_colorop_in_tf_1d_curve_unsupported_curve(struct kunit *test) +{ + struct dc_plane_state *dc_plane_state; + struct drm_colorop *colorop; + struct drm_colorop_state *colorop_state; + + dc_plane_state = kunit_kzalloc(test, sizeof(*dc_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_plane_state); + + colorop = kunit_kzalloc(test, sizeof(*colorop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorop); + + colorop_state = kunit_kzalloc(test, sizeof(*colorop_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorop_state); + + colorop->type = DRM_COLOROP_1D_CURVE; + colorop_state->colorop = colorop; + colorop_state->curve_1d_type = DRM_COLOROP_1D_CURVE_COUNT; + + KUNIT_EXPECT_EQ(test, + __set_colorop_in_tf_1d_curve(dc_plane_state, colorop_state), + -EINVAL); +} + +/** + * dm_test_set_colorop_in_tf_1d_curve_bypass - Bypass mode forces linear bypass transfer function + * @test: KUnit test context + */ +static void dm_test_set_colorop_in_tf_1d_curve_bypass(struct kunit *test) +{ + struct dc_plane_state *dc_plane_state; + struct drm_colorop *colorop; + struct drm_colorop_state *colorop_state; + + dc_plane_state = kunit_kzalloc(test, sizeof(*dc_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_plane_state); + + colorop = kunit_kzalloc(test, sizeof(*colorop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorop); + + colorop_state = kunit_kzalloc(test, sizeof(*colorop_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorop_state); + + colorop->type = DRM_COLOROP_1D_CURVE; + colorop_state->colorop = colorop; + colorop_state->curve_1d_type = DRM_COLOROP_1D_CURVE_SRGB_EOTF; + colorop_state->bypass = true; + + KUNIT_EXPECT_EQ(test, + __set_colorop_in_tf_1d_curve(dc_plane_state, colorop_state), + 0); + KUNIT_EXPECT_EQ(test, + (int)dc_plane_state->in_transfer_func.type, + (int)TF_TYPE_BYPASS); + KUNIT_EXPECT_EQ(test, + (int)dc_plane_state->in_transfer_func.tf, + (int)TRANSFER_FUNCTION_LINEAR); +} + +static struct kunit_case dm_color_test_cases[] = { + /* amdgpu_dm_fixpt_from_s3132 */ + KUNIT_CASE(dm_test_fixpt_from_s3132_zero), + KUNIT_CASE(dm_test_fixpt_from_s3132_one), + KUNIT_CASE(dm_test_fixpt_from_s3132_negative_one), + KUNIT_CASE(dm_test_fixpt_from_s3132_half), + KUNIT_CASE(dm_test_fixpt_from_s3132_neg_half), + /* __is_lut_linear */ + KUNIT_CASE(dm_test_is_lut_linear_with_linear_lut), + KUNIT_CASE(dm_test_is_lut_linear_with_nonlinear_lut), + KUNIT_CASE(dm_test_is_lut_linear_rgb_mismatch), + /* __drm_ctm_to_dc_matrix */ + KUNIT_CASE(dm_test_drm_ctm_to_dc_matrix_identity), + KUNIT_CASE(dm_test_drm_ctm_to_dc_matrix_negative), + KUNIT_CASE(dm_test_drm_ctm_to_dc_matrix_4th_col_zero), + /* __drm_ctm_3x4_to_dc_matrix */ + KUNIT_CASE(dm_test_drm_ctm_3x4_to_dc_matrix_identity), + KUNIT_CASE(dm_test_drm_ctm_3x4_to_dc_matrix_offset), + /* amdgpu_tf_to_dc_tf */ + KUNIT_CASE(dm_test_tf_to_dc_tf_default), + KUNIT_CASE(dm_test_tf_to_dc_tf_identity), + KUNIT_CASE(dm_test_tf_to_dc_tf_srgb), + KUNIT_CASE(dm_test_tf_to_dc_tf_bt709), + KUNIT_CASE(dm_test_tf_to_dc_tf_pq), + KUNIT_CASE(dm_test_tf_to_dc_tf_gamma22), + KUNIT_CASE(dm_test_tf_to_dc_tf_gamma24), + KUNIT_CASE(dm_test_tf_to_dc_tf_gamma26), + /* amdgpu_colorop_tf_to_dc_tf */ + KUNIT_CASE(dm_test_colorop_tf_to_dc_tf_srgb), + KUNIT_CASE(dm_test_colorop_tf_to_dc_tf_pq), + KUNIT_CASE(dm_test_colorop_tf_to_dc_tf_bt2020), + KUNIT_CASE(dm_test_colorop_tf_to_dc_tf_gamma22), + KUNIT_CASE(dm_test_colorop_tf_to_dc_tf_default), + /* __drm_lut_to_dc_gamma */ + KUNIT_CASE(dm_test_drm_lut_to_dc_gamma_legacy_zero), + KUNIT_CASE(dm_test_drm_lut_to_dc_gamma_legacy_max), + KUNIT_CASE(dm_test_drm_lut_to_dc_gamma_legacy_channels), + KUNIT_CASE(dm_test_drm_lut_to_dc_gamma_nonlegacy_zero), + KUNIT_CASE(dm_test_drm_lut_to_dc_gamma_nonlegacy_max), + /* __drm_lut32_to_dc_gamma */ + KUNIT_CASE(dm_test_drm_lut32_to_dc_gamma_zero), + KUNIT_CASE(dm_test_drm_lut32_to_dc_gamma_max), + KUNIT_CASE(dm_test_drm_lut32_to_dc_gamma_channels), + /* __extract_blob_lut */ + KUNIT_CASE(dm_test_extract_blob_lut_null), + KUNIT_CASE(dm_test_extract_blob_lut_valid), + /* __extract_blob_lut32 */ + KUNIT_CASE(dm_test_extract_blob_lut32_null), + KUNIT_CASE(dm_test_extract_blob_lut32_valid), + /* __to_dc_lut3d_color */ + KUNIT_CASE(dm_test_to_dc_lut3d_color_zero), + KUNIT_CASE(dm_test_to_dc_lut3d_color_max), + KUNIT_CASE(dm_test_to_dc_lut3d_color_channels), + /* __drm_3dlut_to_dc_3dlut */ + KUNIT_CASE(dm_test_3dlut_to_dc_3dlut_distribution), + KUNIT_CASE(dm_test_3dlut_to_dc_3dlut_tetrahedral_17), + KUNIT_CASE(dm_test_3dlut_to_dc_3dlut_green_blue), + /* __to_dc_lut3d_32_color */ + KUNIT_CASE(dm_test_to_dc_lut3d_32_color_zero), + KUNIT_CASE(dm_test_to_dc_lut3d_32_color_max), + KUNIT_CASE(dm_test_to_dc_lut3d_32_color_channels), + /* __drm_3dlut32_to_dc_3dlut */ + KUNIT_CASE(dm_test_3dlut32_to_dc_3dlut_distribution), + KUNIT_CASE(dm_test_3dlut32_to_dc_3dlut_tetrahedral_17), + KUNIT_CASE(dm_test_3dlut32_to_dc_3dlut_green_blue), + /* amdgpu_dm_verify_lut_sizes */ + KUNIT_CASE(dm_test_verify_lut_sizes_null_luts), + KUNIT_CASE(dm_test_verify_lut_sizes_valid_degamma), + KUNIT_CASE(dm_test_verify_lut_sizes_invalid_degamma), + KUNIT_CASE(dm_test_verify_lut_sizes_valid_gamma_atomic), + KUNIT_CASE(dm_test_verify_lut_sizes_valid_gamma_legacy), + KUNIT_CASE(dm_test_verify_lut_sizes_invalid_gamma), + KUNIT_CASE(dm_test_verify_lut_sizes_both_valid), + KUNIT_CASE(dm_test_verify_lut_sizes_invalid_degamma_valid_gamma), + /* amdgpu_dm_atomic_lut3d */ + KUNIT_CASE(dm_test_atomic_lut3d_zero_size), + KUNIT_CASE(dm_test_atomic_lut3d_nonzero_state_bits), + KUNIT_CASE(dm_test_atomic_lut3d_data_forwarded), + /* __set_colorop_3dlut */ + KUNIT_CASE(dm_test_set_colorop_3dlut_zero_size), + KUNIT_CASE(dm_test_set_colorop_3dlut_nonzero_state_bits), + KUNIT_CASE(dm_test_set_colorop_3dlut_data_forwarded), + /* __set_tf_bypass */ + KUNIT_CASE(dm_test_set_tf_bypass), + /* __set_tf_distributed_points */ + KUNIT_CASE(dm_test_set_tf_distributed_points_srgb), + KUNIT_CASE(dm_test_set_tf_distributed_points_pq), + /* amdgpu_dm_set_atomic_regamma */ + KUNIT_CASE(dm_test_set_atomic_regamma_bypass), + /* amdgpu_dm_atomic_shaper_lut */ + KUNIT_CASE(dm_test_atomic_shaper_lut_bypass), + /* amdgpu_dm_atomic_blend_lut */ + KUNIT_CASE(dm_test_atomic_blend_lut_bypass), + /* __set_colorop_in_tf_1d_curve */ + KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_invalid_type), + KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_unsupported_curve), + KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_bypass), + {} +}; + +static struct kunit_suite dm_color_test_suite = { + .name = "amdgpu_dm_color", + .test_cases = dm_color_test_cases, +}; + +kunit_test_suite(dm_color_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_color"); +MODULE_AUTHOR("AMD"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c new file mode 100644 index 000000000000..fa270ff28c6a --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_colorop.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include <kunit/test.h> +#include <drm/drm_colorop.h> +#include <drm/drm_kunit_helpers.h> + +#include "amdgpu_dm_colorop.h" + +/* Tests for amdgpu_dm_supported_degam_tfs */ + +static void dm_test_supported_degam_tfs_has_srgb_eotf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs & + BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF)); +} + +static void dm_test_supported_degam_tfs_has_pq125_eotf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs & + BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF)); +} + +static void dm_test_supported_degam_tfs_has_bt2020_inv_oetf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs & + BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF)); +} + +static void dm_test_supported_degam_tfs_has_gamma22(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs & + BIT(DRM_COLOROP_1D_CURVE_GAMMA22)); +} + +static void dm_test_supported_degam_tfs_no_extra_bits(struct kunit *test) +{ + u64 expected = BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) | + BIT(DRM_COLOROP_1D_CURVE_GAMMA22); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_degam_tfs, expected); +} + +/* Tests for amdgpu_dm_supported_shaper_tfs */ + +static void dm_test_supported_shaper_tfs_has_srgb_inv_eotf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs & + BIT(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF)); +} + +static void dm_test_supported_shaper_tfs_has_pq125_inv_eotf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs & + BIT(DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF)); +} + +static void dm_test_supported_shaper_tfs_has_bt2020_oetf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs & + BIT(DRM_COLOROP_1D_CURVE_BT2020_OETF)); +} + +static void dm_test_supported_shaper_tfs_has_gamma22_inv(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs & + BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV)); +} + +static void dm_test_supported_shaper_tfs_no_extra_bits(struct kunit *test) +{ + u64 expected = BIT(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_BT2020_OETF) | + BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_shaper_tfs, expected); +} + +/* Tests for amdgpu_dm_supported_blnd_tfs */ + +static void dm_test_supported_blnd_tfs_has_srgb_eotf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs & + BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF)); +} + +static void dm_test_supported_blnd_tfs_has_pq125_eotf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs & + BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF)); +} + +static void dm_test_supported_blnd_tfs_has_bt2020_inv_oetf(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs & + BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF)); +} + +static void dm_test_supported_blnd_tfs_has_gamma22(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs & + BIT(DRM_COLOROP_1D_CURVE_GAMMA22)); +} + +static void dm_test_supported_blnd_tfs_no_extra_bits(struct kunit *test) +{ + u64 expected = BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) | + BIT(DRM_COLOROP_1D_CURVE_GAMMA22); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_blnd_tfs, expected); +} + +/* degam and blnd should support the same set of EOTF curves */ +static void dm_test_degam_and_blnd_tfs_match(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_degam_tfs, + amdgpu_dm_supported_blnd_tfs); +} + +/* Tests for amdgpu_dm_initialize_default_pipeline */ + +static void kunit_colorop_pipeline_destroy(void *drm) +{ + drm_colorop_pipeline_destroy((struct drm_device *)drm); +} + +/** + * dm_test_initialize_default_pipeline() - Verify amdgpu_dm_build_default_pipeline() + * produces the expected colorop chain with all ops bypassable. + * @test: KUnit test context. + */ +static void dm_test_initialize_default_pipeline(struct kunit *test) +{ + static const enum drm_colorop_type expected[] = { + DRM_COLOROP_1D_CURVE, /* degam TF */ + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, /* shaper TF */ + DRM_COLOROP_1D_LUT, /* shaper LUT */ + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, /* blnd TF */ + DRM_COLOROP_1D_LUT, /* blnd LUT */ + }; + struct device *dev; + struct drm_device *drm; + struct drm_plane *plane; + struct drm_prop_enum_list list = {}; + struct drm_colorop *op, *first = NULL; + int i = 0; + int ret; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + /* + * Allocate a plain drm_device (not an amdgpu_device) — sufficient + * because amdgpu_dm_build_default_pipeline() only needs the DRM + * mode-config infrastructure, not the amdgpu device wrapper. + */ + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, 0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + plane = drm_kunit_helper_create_primary_plane(test, drm, + NULL, NULL, NULL, 0, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane); + + /* + * Destroy the pipeline before the DRM device is cleaned up so + * that the colorop objects (kzalloc'd inside the function) are + * freed while the device is still valid. + */ + kunit_add_action(test, kunit_colorop_pipeline_destroy, drm); + + ret = amdgpu_dm_build_default_pipeline(drm, plane, true, &list); + KUNIT_ASSERT_EQ(test, ret, 0); + kfree(list.name); + + drm_for_each_colorop(op, drm) { + if (op->base.id == (uint32_t)list.type) { + first = op; + break; + } + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, first); + + for (op = first; op; op = op->next, i++) { + KUNIT_ASSERT_LT(test, i, (int)ARRAY_SIZE(expected)); + KUNIT_EXPECT_EQ(test, op->type, expected[i]); + KUNIT_EXPECT_NOT_NULL(test, op->bypass_property); + } + KUNIT_EXPECT_EQ(test, i, (int)ARRAY_SIZE(expected)); +} + +static struct kunit_case dm_colorop_test_cases[] = { + /* degam TFs */ + KUNIT_CASE(dm_test_supported_degam_tfs_has_srgb_eotf), + KUNIT_CASE(dm_test_supported_degam_tfs_has_pq125_eotf), + KUNIT_CASE(dm_test_supported_degam_tfs_has_bt2020_inv_oetf), + KUNIT_CASE(dm_test_supported_degam_tfs_has_gamma22), + KUNIT_CASE(dm_test_supported_degam_tfs_no_extra_bits), + /* shaper TFs */ + KUNIT_CASE(dm_test_supported_shaper_tfs_has_srgb_inv_eotf), + KUNIT_CASE(dm_test_supported_shaper_tfs_has_pq125_inv_eotf), + KUNIT_CASE(dm_test_supported_shaper_tfs_has_bt2020_oetf), + KUNIT_CASE(dm_test_supported_shaper_tfs_has_gamma22_inv), + KUNIT_CASE(dm_test_supported_shaper_tfs_no_extra_bits), + /* blnd TFs */ + KUNIT_CASE(dm_test_supported_blnd_tfs_has_srgb_eotf), + KUNIT_CASE(dm_test_supported_blnd_tfs_has_pq125_eotf), + KUNIT_CASE(dm_test_supported_blnd_tfs_has_bt2020_inv_oetf), + KUNIT_CASE(dm_test_supported_blnd_tfs_has_gamma22), + KUNIT_CASE(dm_test_supported_blnd_tfs_no_extra_bits), + /* cross-check */ + KUNIT_CASE(dm_test_degam_and_blnd_tfs_match), + /* amdgpu_dm_initialize_default_pipeline */ + KUNIT_CASE(dm_test_initialize_default_pipeline), + {} +}; + +static struct kunit_suite dm_colorop_test_suite = { + .name = "amdgpu_dm_colorop", + .test_cases = dm_colorop_test_cases, +}; + +kunit_test_suite(dm_colorop_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_colorop"); +MODULE_AUTHOR("AMD"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c new file mode 100644 index 000000000000..bba8b1a8fa1c --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_crc.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include <kunit/test.h> + +#include "amdgpu_dm_crc.h" + +static void dm_test_parse_crc_source_none(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_NONE, dm_parse_crc_source("none")); + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_NONE, dm_parse_crc_source(NULL)); +} + +static void dm_test_parse_crc_source_crtc(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_CRTC, dm_parse_crc_source("crtc")); + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_CRTC, dm_parse_crc_source("auto")); +} + +static void dm_test_parse_crc_source_dprx(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, dm_parse_crc_source("dprx")); +} + +static void dm_test_parse_crc_source_crtc_dither(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER, + dm_parse_crc_source("crtc dither")); +} + +static void dm_test_parse_crc_source_dprx_dither(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER, + dm_parse_crc_source("dprx dither")); +} + +static void dm_test_parse_crc_source_invalid(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_INVALID, + dm_parse_crc_source("invalid")); + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_INVALID, + dm_parse_crc_source("unknown")); + KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_INVALID, + dm_parse_crc_source("")); +} + +static void dm_test_is_crc_source_crtc(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_TRUE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER)); + + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER)); + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID)); +} + +static void dm_test_is_crc_source_dprx(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + KUNIT_EXPECT_TRUE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER)); + + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER)); + KUNIT_EXPECT_FALSE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID)); +} + +static void dm_test_need_crc_dither(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_TRUE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER)); + KUNIT_EXPECT_TRUE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER)); + + KUNIT_EXPECT_FALSE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_FALSE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + KUNIT_EXPECT_FALSE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID)); +} + +static void dm_test_is_valid_crc_source(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER)); + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER)); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_MAX)); + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID)); +} + +static struct kunit_case dm_crc_test_cases[] = { + KUNIT_CASE(dm_test_parse_crc_source_none), + KUNIT_CASE(dm_test_parse_crc_source_crtc), + KUNIT_CASE(dm_test_parse_crc_source_dprx), + KUNIT_CASE(dm_test_parse_crc_source_crtc_dither), + KUNIT_CASE(dm_test_parse_crc_source_dprx_dither), + KUNIT_CASE(dm_test_parse_crc_source_invalid), + KUNIT_CASE(dm_test_is_crc_source_crtc), + KUNIT_CASE(dm_test_is_crc_source_dprx), + KUNIT_CASE(dm_test_need_crc_dither), + KUNIT_CASE(dm_test_is_valid_crc_source), + {} +}; + +static struct kunit_suite dm_crc_test_suite = { + .name = "amdgpu_dm_crc", + .test_cases = dm_crc_test_cases, +}; + +kunit_test_suite(dm_crc_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_crc"); +MODULE_AUTHOR("AMD"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c new file mode 100644 index 000000000000..d03b606d27bc --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_hdcp.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include <kunit/test.h> +#include <linux/workqueue.h> + +#include "amdgpu_dm_hdcp.h" + +static void dummy_work_fn(struct work_struct *work) {} + +/* Tests for process_output() */ + +/* + * Helper: allocate and initialise a minimal hdcp_workqueue sufficient for + * process_output() testing. Only the three delayed works accessed by + * process_output() are initialised; everything else is zeroed. + */ +static struct hdcp_workqueue *alloc_test_workqueue(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + + return work; +} + +/* + * process_output() always schedules property_validate_dwork with delay=0, + * which queues the work item directly (bypassing the timer). Use + * work_pending() rather than delayed_work_pending() to detect this. + */ +static void dm_test_process_output_property_validate_always_scheduled(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + /* No flags set: only property_validate_dwork should be enqueued */ + process_output(work); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work)); + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->callback_dwork)); + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* + * output.callback_needed=true must schedule callback_dwork. + */ +static void dm_test_process_output_callback_needed(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + work->output.callback_needed = true; + work->output.callback_delay = 500; + + process_output(work); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* + * output.callback_stop=true must cancel a previously scheduled callback_dwork. + */ +static void dm_test_process_output_callback_stop(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + /* Pre-schedule callback_dwork with a long delay so it won't fire. */ + schedule_delayed_work(&work->callback_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + + work->output.callback_stop = true; + + process_output(work); + + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->callback_dwork)); + + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* + * output.watchdog_timer_needed=true must schedule watchdog_timer_dwork. + */ +static void dm_test_process_output_watchdog_needed(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + work->output.watchdog_timer_needed = true; + work->output.watchdog_timer_delay = 1000; + + process_output(work); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* + * output.watchdog_timer_stop=true must cancel a previously scheduled + * watchdog_timer_dwork. + */ +static void dm_test_process_output_watchdog_stop(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + /* Pre-schedule watchdog_timer_dwork with a long delay. */ + schedule_delayed_work(&work->watchdog_timer_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + work->output.watchdog_timer_stop = true; + + process_output(work); + + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* + * Both callback_needed and watchdog_timer_needed set: both dworks are + * scheduled independently. + */ +static void dm_test_process_output_callback_and_watchdog_needed(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + work->output.callback_needed = true; + work->output.callback_delay = 200; + work->output.watchdog_timer_needed = true; + work->output.watchdog_timer_delay = 800; + + process_output(work); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} +/* End of tests for process_output() */ + +static struct kunit_case dm_hdcp_test_cases[] = { + KUNIT_CASE(dm_test_process_output_property_validate_always_scheduled), + KUNIT_CASE(dm_test_process_output_callback_needed), + KUNIT_CASE(dm_test_process_output_callback_stop), + KUNIT_CASE(dm_test_process_output_watchdog_needed), + KUNIT_CASE(dm_test_process_output_watchdog_stop), + KUNIT_CASE(dm_test_process_output_callback_and_watchdog_needed), + {} +}; + +static struct kunit_suite dm_hdcp_test_suite = { + .name = "amdgpu_dm_hdcp", + .test_cases = dm_hdcp_test_cases, +}; + +kunit_test_suite(dm_hdcp_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_hdcp"); +MODULE_AUTHOR("AMD"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c new file mode 100644 index 000000000000..f3b3f77aafd5 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c @@ -0,0 +1,938 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_ism.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include <kunit/test.h> + +#include "dc.h" +#include "amdgpu_dm_ism.h" + +/* + * Helper: allocate and zero-initialise a dc_stream_state for timing tests. + * Only the timing sub-struct is accessed by the functions under test. + */ +static struct dc_stream_state *alloc_test_stream(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + return stream; +} + +/* + * Helper: allocate and zero-initialise an ISM instance. + */ +static struct amdgpu_dm_ism *alloc_test_ism(struct kunit *test) +{ + struct amdgpu_dm_ism *ism; + + ism = kunit_kzalloc(test, sizeof(*ism), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ism); + + return ism; +} + +/* ===== Tests for dm_ism_next_state — FULL_POWER_RUNNING transitions ===== */ + +static void dm_test_ism_next_state_running_enter_idle(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_RUNNING, + DM_ISM_EVENT_ENTER_IDLE_REQUESTED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_WAITING); +} + +static void dm_test_ism_next_state_running_begin_cursor(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_RUNNING, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_BUSY); +} + +static void dm_test_ism_next_state_running_invalid(struct kunit *test) +{ + enum amdgpu_dm_ism_state next = DM_ISM_NUM_STATES; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_RUNNING, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next); + KUNIT_EXPECT_FALSE(test, ok); + /* next should remain untouched on invalid transition */ + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_NUM_STATES); +} + +/* ===== Tests for dm_ism_next_state — FULL_POWER_BUSY transitions ===== */ + +static void dm_test_ism_next_state_busy_enter_idle(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_BUSY, + DM_ISM_EVENT_ENTER_IDLE_REQUESTED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY); +} + +static void dm_test_ism_next_state_busy_end_cursor(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_BUSY, + DM_ISM_EVENT_END_CURSOR_UPDATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING); +} + +/* ===== Tests for dm_ism_next_state — HYSTERESIS_WAITING transitions ===== */ + +static void dm_test_ism_next_state_hyst_wait_exit_idle(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_TIMER_ABORTED); +} + +static void dm_test_ism_next_state_hyst_wait_begin_cursor(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY); +} + +static void dm_test_ism_next_state_hyst_wait_timer(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING, + DM_ISM_EVENT_TIMER_ELAPSED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE); +} + +static void dm_test_ism_next_state_hyst_wait_immediate(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING, + DM_ISM_EVENT_IMMEDIATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE); +} + +/* ===== Tests for dm_ism_next_state — HYSTERESIS_BUSY transitions ===== */ + +static void dm_test_ism_next_state_hyst_busy_exit_idle(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_BUSY, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_BUSY); +} + +static void dm_test_ism_next_state_hyst_busy_end_cursor(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_BUSY, + DM_ISM_EVENT_END_CURSOR_UPDATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_WAITING); +} + +/* ===== Tests for dm_ism_next_state — OPTIMIZED_IDLE transitions ===== */ + +static void dm_test_ism_next_state_opt_idle_exit(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING); +} + +static void dm_test_ism_next_state_opt_idle_begin_cursor(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY); +} + +static void dm_test_ism_next_state_opt_idle_sso_timer(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE, + DM_ISM_EVENT_SSO_TIMER_ELAPSED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE_SSO); +} + +static void dm_test_ism_next_state_opt_idle_immediate(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE, + DM_ISM_EVENT_IMMEDIATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE_SSO); +} + +/* ===== Tests for dm_ism_next_state — OPTIMIZED_IDLE_SSO transitions ===== */ + +static void dm_test_ism_next_state_opt_idle_sso_exit(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE_SSO, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING); +} + +static void dm_test_ism_next_state_opt_idle_sso_cursor(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE_SSO, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY); +} + +/* ===== Tests for dm_ism_next_state — TIMER_ABORTED transitions ===== */ + +static void dm_test_ism_next_state_aborted_immediate(struct kunit *test) +{ + enum amdgpu_dm_ism_state next; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_TIMER_ABORTED, + DM_ISM_EVENT_IMMEDIATE, &next); + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING); +} + +static void dm_test_ism_next_state_aborted_invalid(struct kunit *test) +{ + enum amdgpu_dm_ism_state next = DM_ISM_NUM_STATES; + bool ok; + + ok = dm_ism_next_state(DM_ISM_STATE_TIMER_ABORTED, + DM_ISM_EVENT_ENTER_IDLE_REQUESTED, &next); + KUNIT_EXPECT_FALSE(test, ok); + KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_NUM_STATES); +} + +/* ===== Tests for dm_ism_get_sso_delay ===== */ + +static void dm_test_ism_sso_delay_null_stream(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + + ism->config.sso_num_frames = 5; + + KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, NULL), (uint64_t)0); +} + +static void dm_test_ism_sso_delay_zero_frames(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + ism->config.sso_num_frames = 0; + + KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, stream), (uint64_t)0); +} + +static void dm_test_ism_sso_delay_1080p60_3frames(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t expected_one_frame_ns, expected; + + /* + * 1080p@60Hz: v_total=1125, h_total=2200, pix_clk=148.5MHz + * pix_clk_100hz = 1485000 + * one_frame_ns = (1125 * 2200 * 10000000) / 1485000 = 16666666 ns + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + ism->config.sso_num_frames = 3; + + expected_one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + expected = 3 * expected_one_frame_ns; + + KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, stream), expected); +} + +static void dm_test_ism_sso_delay_4k60_1frame(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t expected_one_frame_ns; + + /* + * 4K@60Hz: v_total=2250, h_total=4400, pix_clk=594MHz + * pix_clk_100hz = 5940000 + */ + stream->timing.v_total = 2250; + stream->timing.h_total = 4400; + stream->timing.pix_clk_100hz = 5940000; + ism->config.sso_num_frames = 1; + + expected_one_frame_ns = div64_u64((uint64_t)2250 * 4400 * 10000000ULL, + 5940000); + + KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, stream), + expected_one_frame_ns); +} + +/* ===== Tests for dm_ism_get_idle_allow_delay ===== */ + +static void dm_test_ism_idle_delay_null_stream(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 10; + + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, NULL), + (uint64_t)0); +} + +static void dm_test_ism_idle_delay_zero_filter_frames(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + ism->config.filter_num_frames = 0; + + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + (uint64_t)0); +} + +static void dm_test_ism_idle_delay_zero_entry_count(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 0; + + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + (uint64_t)0); +} + +static void dm_test_ism_idle_delay_zero_delay_frames(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 0; + + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + (uint64_t)0); +} + +static void dm_test_ism_idle_delay_no_short_idles(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t one_frame_ns; + + /* + * All history records have long durations (well above the + * short_idle_ns threshold), so no delay should be applied. + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 10; + ism->config.filter_history_size = 8; + ism->config.filter_old_history_threshold = 0; + + /* Fill history with long idle durations */ + for (int i = 0; i < 8; i++) { + ism->records[i].duration_ns = one_frame_ns * 100; + ism->records[i].timestamp_ns = 0; + } + ism->next_record_idx = 8; + + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + (uint64_t)0); +} + +static void dm_test_ism_idle_delay_enough_short_idles(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t one_frame_ns, expected; + + /* + * Fill history with short idle durations that meet the threshold. + * filter_entry_count=3, so 3 short idles should trigger the delay. + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 10; + ism->config.filter_history_size = 8; + ism->config.filter_old_history_threshold = 0; + + /* Fill history with short idle durations (1 frame each) */ + for (int i = 0; i < 8; i++) { + ism->records[i].duration_ns = one_frame_ns; + ism->records[i].timestamp_ns = 0; + } + ism->next_record_idx = 8; + + expected = 10 * one_frame_ns; + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + expected); +} + +static void dm_test_ism_idle_delay_wraps_around_buffer(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t one_frame_ns, expected; + + /* + * Test the circular buffer wraparound: next_record_idx at 2 means + * the most recent records are at indices 1, 0, 15, 14, ... + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 10; + ism->config.filter_history_size = 8; + ism->config.filter_old_history_threshold = 0; + + /* Fill entire buffer with short idles */ + for (int i = 0; i < AMDGPU_DM_IDLE_HIST_LEN; i++) { + ism->records[i].duration_ns = one_frame_ns; + ism->records[i].timestamp_ns = 0; + } + /* Position next_record_idx at 2 to test wraparound */ + ism->next_record_idx = 2; + + expected = 10 * one_frame_ns; + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + expected); +} + +static void dm_test_ism_idle_delay_old_history_cutoff(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t one_frame_ns; + + /* + * Test old_history_threshold: only recent entries within the + * threshold should be counted. Set up 2 recent short idles but + * require 3 — older entries are outside the threshold. + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 10; + ism->config.filter_history_size = 8; + /* Threshold: entries older than 20 frames are ignored */ + ism->config.filter_old_history_threshold = 20; + + ism->last_idle_timestamp_ns = one_frame_ns * 100; + + /* 2 recent short idles (within threshold) */ + ism->records[6].duration_ns = one_frame_ns; + ism->records[6].timestamp_ns = one_frame_ns * 95; + ism->records[7].duration_ns = one_frame_ns; + ism->records[7].timestamp_ns = one_frame_ns * 98; + + /* Older entries outside the threshold with long durations */ + for (int i = 0; i < 6; i++) { + ism->records[i].duration_ns = one_frame_ns * 100; + ism->records[i].timestamp_ns = one_frame_ns * 10; + } + ism->next_record_idx = 8; + + /* + * Only 2 short idles within threshold, but 3 required — + * should return 0 (no delay). + */ + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + (uint64_t)0); +} + +static void dm_test_ism_idle_delay_mixed_durations(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t one_frame_ns; + + /* + * Mix of short and long idle durations. Only 2 short idles + * in 8 entries, but filter_entry_count=3, so no delay. + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 3; + ism->config.activation_num_delay_frames = 10; + ism->config.filter_history_size = 8; + ism->config.filter_old_history_threshold = 0; + + /* 2 short idles, 6 long idles */ + for (int i = 0; i < 8; i++) { + if (i == 6 || i == 7) + ism->records[i].duration_ns = one_frame_ns; + else + ism->records[i].duration_ns = one_frame_ns * 100; + ism->records[i].timestamp_ns = 0; + } + ism->next_record_idx = 8; + + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), + (uint64_t)0); +} + +/** + * dm_test_ism_idle_delay_entry_count_exceeds_history_size - entry_count > history_size sets delay + * @test: KUnit test context + */ +static void dm_test_ism_idle_delay_entry_count_exceeds_history_size(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct dc_stream_state *stream = alloc_test_stream(test); + uint64_t one_frame_ns, expected; + + /* + * filter_entry_count (5) > filter_history_size (3), so history_size + * is determined by filter_entry_count via max(). All 5 records are + * short idles, triggering the delay. + */ + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, + 1485000); + + ism->config.filter_num_frames = 5; + ism->config.filter_entry_count = 5; + ism->config.filter_history_size = 3; + ism->config.activation_num_delay_frames = 10; + ism->config.filter_old_history_threshold = 0; + + for (int i = 0; i < 5; i++) { + ism->records[i].duration_ns = one_frame_ns; + ism->records[i].timestamp_ns = 0; + } + ism->next_record_idx = 5; + + expected = 10 * one_frame_ns; + KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), expected); +} + +/* ===== Tests for amdgpu_dm_ism_init ===== */ + +/** + * dm_test_ism_init_sets_initial_state - all ISM fields initialized to expected values + * @test: KUnit test context + */ +static void dm_test_ism_init_sets_initial_state(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct amdgpu_dm_ism_config config = { + .filter_num_frames = 5, + .filter_entry_count = 3, + .activation_num_delay_frames = 10, + .filter_history_size = 8, + .filter_old_history_threshold = 20, + .sso_num_frames = 2, + }; + + amdgpu_dm_ism_init(ism, &config); + + KUNIT_EXPECT_EQ(test, (int)ism->current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + KUNIT_EXPECT_EQ(test, (int)ism->previous_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + KUNIT_EXPECT_EQ(test, ism->next_record_idx, 0); + KUNIT_EXPECT_EQ(test, ism->last_idle_timestamp_ns, (uint64_t)0); + KUNIT_EXPECT_EQ(test, ism->config.filter_num_frames, + config.filter_num_frames); + KUNIT_EXPECT_EQ(test, ism->config.filter_entry_count, + config.filter_entry_count); + KUNIT_EXPECT_EQ(test, ism->config.activation_num_delay_frames, + config.activation_num_delay_frames); + KUNIT_EXPECT_EQ(test, ism->config.sso_num_frames, config.sso_num_frames); +} + +/* ===== Tests for amdgpu_dm_ism_fini ===== */ + +/** + * dm_test_ism_fini_after_init - fini cancels never-scheduled work without error + * @test: KUnit test context + */ +static void dm_test_ism_fini_after_init(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + struct amdgpu_dm_ism_config config = { + .filter_num_frames = 5, + .filter_entry_count = 3, + .activation_num_delay_frames = 10, + .sso_num_frames = 2, + }; + + amdgpu_dm_ism_init(ism, &config); + /* Work was never scheduled; cancel_delayed_work_sync is a no-op. */ + amdgpu_dm_ism_fini(ism); + + /* FSM state is untouched by fini */ + KUNIT_EXPECT_EQ(test, (int)ism->current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); +} + +/* ===== Tests for dm_ism_set_last_idle_ts ===== */ + +/** + * dm_test_ism_set_last_idle_ts_updates_timestamp - last_idle_timestamp_ns updated to current time + * @test: KUnit test context + */ +static void dm_test_ism_set_last_idle_ts_updates_timestamp(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + uint64_t before; + + ism->last_idle_timestamp_ns = 0; + before = ktime_get_ns(); + dm_ism_set_last_idle_ts(ism); + + KUNIT_EXPECT_GE(test, ism->last_idle_timestamp_ns, before); +} + +/* ===== Tests for dm_ism_insert_record ===== */ + +/** + * dm_test_ism_insert_record_basic - record inserted with correct index and duration + * @test: KUnit test context + */ +static void dm_test_ism_insert_record_basic(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + + ism->last_idle_timestamp_ns = 0; + ism->next_record_idx = 0; + + dm_ism_insert_record(ism); + + KUNIT_EXPECT_EQ(test, ism->next_record_idx, 1); + KUNIT_EXPECT_GT(test, ism->records[0].timestamp_ns, (uint64_t)0); + /* duration = timestamp - last_idle_timestamp_ns (0) */ + KUNIT_EXPECT_EQ(test, ism->records[0].duration_ns, + ism->records[0].timestamp_ns); +} + +/** + * dm_test_ism_insert_record_wraps_around - out-of-bounds index wraps to slot 0 + * @test: KUnit test context + */ +static void dm_test_ism_insert_record_wraps_around(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + + ism->last_idle_timestamp_ns = 0; + /* Out-of-bounds index triggers reset to 0 */ + ism->next_record_idx = AMDGPU_DM_IDLE_HIST_LEN; + + dm_ism_insert_record(ism); + + KUNIT_EXPECT_EQ(test, ism->next_record_idx, 1); + KUNIT_EXPECT_GT(test, ism->records[0].timestamp_ns, (uint64_t)0); +} + +/* ===== Tests for dm_ism_trigger_event ===== */ + +/** + * dm_test_ism_trigger_event_valid_transition - valid event advances current and previous state + * @test: KUnit test context + */ +static void dm_test_ism_trigger_event_valid_transition(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + bool ok; + + ism->current_state = DM_ISM_STATE_FULL_POWER_RUNNING; + ism->previous_state = DM_ISM_STATE_FULL_POWER_RUNNING; + + ok = dm_ism_trigger_event(ism, DM_ISM_EVENT_ENTER_IDLE_REQUESTED); + + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, (int)ism->current_state, + (int)DM_ISM_STATE_HYSTERESIS_WAITING); + KUNIT_EXPECT_EQ(test, (int)ism->previous_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); +} + +/** + * dm_test_ism_trigger_event_invalid_transition - invalid event leaves state unchanged + * @test: KUnit test context + */ +static void dm_test_ism_trigger_event_invalid_transition(struct kunit *test) +{ + struct amdgpu_dm_ism *ism = alloc_test_ism(test); + bool ok; + + ism->current_state = DM_ISM_STATE_FULL_POWER_RUNNING; + ism->previous_state = DM_ISM_STATE_FULL_POWER_RUNNING; + + /* EXIT_IDLE_REQUESTED is not valid from FULL_POWER_RUNNING */ + ok = dm_ism_trigger_event(ism, DM_ISM_EVENT_EXIT_IDLE_REQUESTED); + + KUNIT_EXPECT_FALSE(test, ok); + /* State must remain unchanged on invalid transition */ + KUNIT_EXPECT_EQ(test, (int)ism->current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + KUNIT_EXPECT_EQ(test, (int)ism->previous_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); +} + +/* ===== Tests for dm_ism_dispatch_next_event ===== */ + +/** + * dm_test_dispatch_next_event_hyst_wait_no_delay - zero delay_ns: IMMEDIATE in HYSTERESIS_WAITING + * @test: KUnit test context + */ +static void dm_test_dispatch_next_event_hyst_wait_no_delay(struct kunit *test) +{ + enum amdgpu_dm_ism_event result; + + result = dm_ism_dispatch_next_event(DM_ISM_STATE_HYSTERESIS_WAITING, + 0, 0); + KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_EVENT_IMMEDIATE); +} + +/** + * dm_test_dispatch_next_event_hyst_wait_with_delay - delay_ns > 0, no IMMEDIATE event returned + * @test: KUnit test context + */ +static void dm_test_dispatch_next_event_hyst_wait_with_delay(struct kunit *test) +{ + enum amdgpu_dm_ism_event result; + + result = dm_ism_dispatch_next_event(DM_ISM_STATE_HYSTERESIS_WAITING, + 1000000, 0); + KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS); +} + +/** + * dm_test_dispatch_next_event_opt_idle_no_sso_delay - sso_delay_ns == 0 triggers IMMEDIATE event + * @test: KUnit test context + */ +static void dm_test_dispatch_next_event_opt_idle_no_sso_delay(struct kunit *test) +{ + enum amdgpu_dm_ism_event result; + + result = dm_ism_dispatch_next_event(DM_ISM_STATE_OPTIMIZED_IDLE, + 0, 0); + KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_EVENT_IMMEDIATE); +} + +/** + * dm_test_dispatch_next_event_opt_idle_with_sso_delay - sso_delay_ns > 0, SSO timer, no IMMEDIATE + * @test: KUnit test context + */ +static void dm_test_dispatch_next_event_opt_idle_with_sso_delay(struct kunit *test) +{ + enum amdgpu_dm_ism_event result; + + result = dm_ism_dispatch_next_event(DM_ISM_STATE_OPTIMIZED_IDLE, + 0, 1000000); + KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS); +} + +/** + * dm_test_dispatch_next_event_timer_aborted - TIMER_ABORTED always returns IMMEDIATE + * @test: KUnit test context + */ +static void dm_test_dispatch_next_event_timer_aborted(struct kunit *test) +{ + enum amdgpu_dm_ism_event result; + + result = dm_ism_dispatch_next_event(DM_ISM_STATE_TIMER_ABORTED, + 0, 0); + KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_EVENT_IMMEDIATE); +} + +/** + * dm_test_dispatch_next_event_no_action_state - other states return DM_ISM_NUM_EVENTS + * @test: KUnit test context + */ +static void dm_test_dispatch_next_event_no_action_state(struct kunit *test) +{ + enum amdgpu_dm_ism_event result; + + result = dm_ism_dispatch_next_event(DM_ISM_STATE_FULL_POWER_RUNNING, + 0, 0); + KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS); +} + +static struct kunit_case dm_ism_test_cases[] = { + /* dm_ism_next_state — FULL_POWER_RUNNING */ + KUNIT_CASE(dm_test_ism_next_state_running_enter_idle), + KUNIT_CASE(dm_test_ism_next_state_running_begin_cursor), + KUNIT_CASE(dm_test_ism_next_state_running_invalid), + /* dm_ism_next_state — FULL_POWER_BUSY */ + KUNIT_CASE(dm_test_ism_next_state_busy_enter_idle), + KUNIT_CASE(dm_test_ism_next_state_busy_end_cursor), + /* dm_ism_next_state — HYSTERESIS_WAITING */ + KUNIT_CASE(dm_test_ism_next_state_hyst_wait_exit_idle), + KUNIT_CASE(dm_test_ism_next_state_hyst_wait_begin_cursor), + KUNIT_CASE(dm_test_ism_next_state_hyst_wait_timer), + KUNIT_CASE(dm_test_ism_next_state_hyst_wait_immediate), + /* dm_ism_next_state — HYSTERESIS_BUSY */ + KUNIT_CASE(dm_test_ism_next_state_hyst_busy_exit_idle), + KUNIT_CASE(dm_test_ism_next_state_hyst_busy_end_cursor), + /* dm_ism_next_state — OPTIMIZED_IDLE */ + KUNIT_CASE(dm_test_ism_next_state_opt_idle_exit), + KUNIT_CASE(dm_test_ism_next_state_opt_idle_begin_cursor), + KUNIT_CASE(dm_test_ism_next_state_opt_idle_sso_timer), + KUNIT_CASE(dm_test_ism_next_state_opt_idle_immediate), + /* dm_ism_next_state — OPTIMIZED_IDLE_SSO */ + KUNIT_CASE(dm_test_ism_next_state_opt_idle_sso_exit), + KUNIT_CASE(dm_test_ism_next_state_opt_idle_sso_cursor), + /* dm_ism_next_state — TIMER_ABORTED */ + KUNIT_CASE(dm_test_ism_next_state_aborted_immediate), + KUNIT_CASE(dm_test_ism_next_state_aborted_invalid), + /* dm_ism_get_sso_delay */ + KUNIT_CASE(dm_test_ism_sso_delay_null_stream), + KUNIT_CASE(dm_test_ism_sso_delay_zero_frames), + KUNIT_CASE(dm_test_ism_sso_delay_1080p60_3frames), + KUNIT_CASE(dm_test_ism_sso_delay_4k60_1frame), + /* dm_ism_get_idle_allow_delay */ + KUNIT_CASE(dm_test_ism_idle_delay_null_stream), + KUNIT_CASE(dm_test_ism_idle_delay_zero_filter_frames), + KUNIT_CASE(dm_test_ism_idle_delay_zero_entry_count), + KUNIT_CASE(dm_test_ism_idle_delay_zero_delay_frames), + KUNIT_CASE(dm_test_ism_idle_delay_no_short_idles), + KUNIT_CASE(dm_test_ism_idle_delay_enough_short_idles), + KUNIT_CASE(dm_test_ism_idle_delay_wraps_around_buffer), + KUNIT_CASE(dm_test_ism_idle_delay_old_history_cutoff), + KUNIT_CASE(dm_test_ism_idle_delay_mixed_durations), + KUNIT_CASE(dm_test_ism_idle_delay_entry_count_exceeds_history_size), + /* amdgpu_dm_ism_init */ + KUNIT_CASE(dm_test_ism_init_sets_initial_state), + /* amdgpu_dm_ism_fini */ + KUNIT_CASE(dm_test_ism_fini_after_init), + /* dm_ism_set_last_idle_ts */ + KUNIT_CASE(dm_test_ism_set_last_idle_ts_updates_timestamp), + /* dm_ism_insert_record */ + KUNIT_CASE(dm_test_ism_insert_record_basic), + KUNIT_CASE(dm_test_ism_insert_record_wraps_around), + /* dm_ism_trigger_event */ + KUNIT_CASE(dm_test_ism_trigger_event_valid_transition), + KUNIT_CASE(dm_test_ism_trigger_event_invalid_transition), + /* dm_ism_dispatch_next_event */ + KUNIT_CASE(dm_test_dispatch_next_event_hyst_wait_no_delay), + KUNIT_CASE(dm_test_dispatch_next_event_hyst_wait_with_delay), + KUNIT_CASE(dm_test_dispatch_next_event_opt_idle_no_sso_delay), + KUNIT_CASE(dm_test_dispatch_next_event_opt_idle_with_sso_delay), + KUNIT_CASE(dm_test_dispatch_next_event_timer_aborted), + KUNIT_CASE(dm_test_dispatch_next_event_no_action_state), + {} +}; + +static struct kunit_suite dm_ism_test_suite = { + .name = "amdgpu_dm_ism", + .test_cases = dm_ism_test_cases, +}; + +kunit_test_suite(dm_ism_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_ism"); +MODULE_AUTHOR("AMD"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c new file mode 100644 index 000000000000..09084f70a405 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_psr.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include <kunit/test.h> + +#include "amdgpu_dm_psr.h" + +/* + * Helper: allocate and zero-initialise a dc_link sufficient for + * amdgpu_dm_psr_fill_caps() testing. The function only accesses + * embedded members (dpcd_caps, psr_settings) so no pointer fields + * need to be wired up. + */ +static struct dc_link *alloc_test_link(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + return link; +} + +/* Tests for amdgpu_dm_psr_fill_caps() — PSR version mapping */ + +static void dm_test_psr_fill_caps_version_1(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + link->psr_settings.psr_version = DC_PSR_VERSION_1; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, (int)caps.psr_version, 1); +} + +static void dm_test_psr_fill_caps_version_su1(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + link->psr_settings.psr_version = DC_PSR_VERSION_SU_1; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, (int)caps.psr_version, 2); +} + +static void dm_test_psr_fill_caps_version_unsupported(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + link->psr_settings.psr_version = DC_PSR_VERSION_UNSUPPORTED; + + amdgpu_dm_psr_fill_caps(link, &caps); + + /* + * Neither DC_PSR_VERSION_1 nor DC_PSR_VERSION_SU_1, + * so psr_version stays at its zero-initialised value. + */ + KUNIT_EXPECT_EQ(test, (int)caps.psr_version, 0); +} + +/* Tests for amdgpu_dm_psr_fill_caps() — RFB setup time */ + +static void dm_test_psr_fill_caps_setup_time_zero(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + /* PSR_SETUP_TIME = 0 → (6 - 0) * 55 = 330 */ + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.PSR_SETUP_TIME = 0; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, caps.psr_rfb_setup_time, 330U); +} + +static void dm_test_psr_fill_caps_setup_time_mid(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + /* PSR_SETUP_TIME = 3 → (6 - 3) * 55 = 165 */ + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.PSR_SETUP_TIME = 3; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, caps.psr_rfb_setup_time, 165U); +} + +static void dm_test_psr_fill_caps_setup_time_max(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + /* PSR_SETUP_TIME = 6 → (6 - 6) * 55 = 0 */ + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.PSR_SETUP_TIME = 6; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, caps.psr_rfb_setup_time, 0U); +} + +/* Tests for amdgpu_dm_psr_fill_caps() — link training flag */ + +static void dm_test_psr_fill_caps_link_training_required(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.LINK_TRAINING_ON_EXIT_NOT_REQUIRED = 0; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_TRUE(test, caps.psr_exit_link_training_required); +} + +static void dm_test_psr_fill_caps_link_training_not_required(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.LINK_TRAINING_ON_EXIT_NOT_REQUIRED = 1; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_FALSE(test, caps.psr_exit_link_training_required); +} + +/* Tests for amdgpu_dm_psr_fill_caps() — DPCD field passthrough */ + +static void dm_test_psr_fill_caps_dpcd_fields(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + + link->dpcd_caps.edp_rev = 0x14; + link->dpcd_caps.psr_info.psr_version = 2; + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.SU_GRANULARITY_REQUIRED = 1; + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.Y_COORDINATE_REQUIRED = 1; + link->dpcd_caps.psr_info.psr2_su_y_granularity_cap = 4; + link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 1; + link->dpcd_caps.alpm_caps.bits.PM_STATE_2A_SUPPORT = 1; + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, (int)caps.edp_revision, 0x14); + KUNIT_EXPECT_EQ(test, (int)caps.support_ver, 2); + KUNIT_EXPECT_TRUE(test, caps.su_granularity_required); + KUNIT_EXPECT_TRUE(test, caps.y_coordinate_required); + KUNIT_EXPECT_EQ(test, (int)caps.su_y_granularity, 4); + KUNIT_EXPECT_TRUE(test, caps.alpm_cap); + KUNIT_EXPECT_TRUE(test, caps.standby_support); +} + +static void dm_test_psr_fill_caps_dpcd_fields_unset(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0xFF, sizeof(caps)); + + /* All dpcd_caps fields are zero from kzalloc */ + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, (int)caps.edp_revision, 0); + KUNIT_EXPECT_EQ(test, (int)caps.support_ver, 0); + KUNIT_EXPECT_FALSE(test, caps.su_granularity_required); + KUNIT_EXPECT_FALSE(test, caps.y_coordinate_required); + KUNIT_EXPECT_EQ(test, (int)caps.su_y_granularity, 0); + KUNIT_EXPECT_FALSE(test, caps.alpm_cap); + KUNIT_EXPECT_FALSE(test, caps.standby_support); +} + +/* Tests for amdgpu_dm_psr_fill_caps() — rate control and power opts */ + +static void dm_test_psr_fill_caps_rate_control_always_zero(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + /* Pre-fill caps with non-zero to verify overwrite */ + memset(&caps, 0xFF, sizeof(caps)); + + amdgpu_dm_psr_fill_caps(link, &caps); + + KUNIT_EXPECT_EQ(test, (int)caps.rate_control_caps, 0); +} + +static void dm_test_psr_fill_caps_power_opts_z10_always_set(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + + memset(&caps, 0, sizeof(caps)); + + amdgpu_dm_psr_fill_caps(link, &caps); + + /* + * psr_power_opt_z10_static_screen is always added to power_opts + * regardless of amdgpu_dc_feature_mask. + */ + KUNIT_EXPECT_TRUE(test, + (caps.psr_power_opt_flag & + psr_power_opt_z10_static_screen) != 0); +} +/* End of tests for amdgpu_dm_psr_fill_caps() */ + +/* Tests for amdgpu_dm_psr_set_event() — early-exit validation guards */ + +static void dm_test_psr_set_event_null_stream(struct kunit *test) +{ + /* NULL stream → immediate false, dm is not accessed */ + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_set_event(NULL, NULL, true, psr_event_vsync, false)); +} + +static void dm_test_psr_set_event_null_link(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + /* stream->link remains NULL from kzalloc */ + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_set_event(NULL, stream, true, psr_event_vsync, false)); +} + +static void dm_test_psr_set_event_psr_not_enabled(struct kunit *test) +{ + struct dc_stream_state *stream; + struct dc_link *link; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + stream->link = link; + /* link->psr_settings.psr_feature_enabled remains false from kzalloc */ + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_set_event(NULL, stream, true, psr_event_vsync, false)); +} +/* End of tests for amdgpu_dm_psr_set_event() */ + +static struct kunit_case dm_psr_test_cases[] = { + KUNIT_CASE(dm_test_psr_fill_caps_version_1), + KUNIT_CASE(dm_test_psr_fill_caps_version_su1), + KUNIT_CASE(dm_test_psr_fill_caps_version_unsupported), + KUNIT_CASE(dm_test_psr_fill_caps_setup_time_zero), + KUNIT_CASE(dm_test_psr_fill_caps_setup_time_mid), + KUNIT_CASE(dm_test_psr_fill_caps_setup_time_max), + KUNIT_CASE(dm_test_psr_fill_caps_link_training_required), + KUNIT_CASE(dm_test_psr_fill_caps_link_training_not_required), + KUNIT_CASE(dm_test_psr_fill_caps_dpcd_fields), + KUNIT_CASE(dm_test_psr_fill_caps_dpcd_fields_unset), + KUNIT_CASE(dm_test_psr_fill_caps_rate_control_always_zero), + KUNIT_CASE(dm_test_psr_fill_caps_power_opts_z10_always_set), + KUNIT_CASE(dm_test_psr_set_event_null_stream), + KUNIT_CASE(dm_test_psr_set_event_null_link), + KUNIT_CASE(dm_test_psr_set_event_psr_not_enabled), + {} +}; + +static struct kunit_suite dm_psr_test_suite = { + .name = "amdgpu_dm_psr", + .test_cases = dm_psr_test_cases, +}; + +kunit_test_suite(dm_psr_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_psr"); +MODULE_AUTHOR("AMD"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c new file mode 100644 index 000000000000..28ff8bbcc0f7 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_replay.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include <kunit/test.h> + +#include "dc.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" + +/* Extern declaration for the function under test */ +extern bool amdgpu_dm_link_supports_replay(struct dc_link *link, + struct amdgpu_dm_connector *aconnector); + +/* + * Helper: allocate a dc_link, amdgpu_dm_connector, and dm_connector_state + * wired up so that to_dm_connector_state(aconnector->base.state) works. + */ +struct replay_test_ctx { + struct dc_link *link; + struct amdgpu_dm_connector *aconnector; + struct dm_connector_state *dm_state; +}; + +static struct replay_test_ctx *alloc_replay_ctx(struct kunit *test) +{ + struct replay_test_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + + ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); + + /* Wire connector state so to_dm_connector_state() works */ + ctx->aconnector->base.state = &ctx->dm_state->base; + + return ctx; +} + +/* + * Helper: set all conditions for replay support to pass so individual + * tests can disable one condition at a time. + */ +static void set_all_replay_caps(struct replay_test_ctx *ctx) +{ + ctx->dm_state->freesync_capable = true; + ctx->aconnector->vsdb_info.replay_mode = true; + ctx->link->dpcd_caps.edp_rev = EDP_REVISION_13; + ctx->link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 1; + ctx->link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + ctx->link->dpcd_caps.pr_info.pixel_deviation_per_line = 1; + ctx->link->dpcd_caps.pr_info.max_deviation_line = 1; +} + +/* Tests for amdgpu_dm_link_supports_replay() — all caps met */ + +static void dm_test_replay_supports_all_caps(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — freesync not capable */ + +static void dm_test_replay_no_freesync(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->dm_state->freesync_capable = false; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — no replay mode in VSDB */ + +static void dm_test_replay_no_vsdb_replay_mode(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->aconnector->vsdb_info.replay_mode = false; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — eDP revision too low */ + +static void dm_test_replay_edp_rev_too_low(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->dpcd_caps.edp_rev = EDP_REVISION_12; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — no ALPM AUX wake cap */ + +static void dm_test_replay_no_alpm_aux_wake(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 0; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — no adaptive sync SDP */ + +static void dm_test_replay_no_adaptive_sync_sdp(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 0; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — zero pixel deviation */ + +static void dm_test_replay_zero_pixel_deviation(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->dpcd_caps.pr_info.pixel_deviation_per_line = 0; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — zero max deviation line */ + +static void dm_test_replay_zero_max_deviation_line(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->dpcd_caps.pr_info.max_deviation_line = 0; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* Tests for amdgpu_dm_link_supports_replay() — both deviation fields zero */ + +static void dm_test_replay_both_deviations_zero(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->dpcd_caps.pr_info.pixel_deviation_per_line = 0; + ctx->link->dpcd_caps.pr_info.max_deviation_line = 0; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector)); +} + +/* End of tests for amdgpu_dm_link_supports_replay() */ + +static struct kunit_case dm_replay_test_cases[] = { + KUNIT_CASE(dm_test_replay_supports_all_caps), + KUNIT_CASE(dm_test_replay_no_freesync), + KUNIT_CASE(dm_test_replay_no_vsdb_replay_mode), + KUNIT_CASE(dm_test_replay_edp_rev_too_low), + KUNIT_CASE(dm_test_replay_no_alpm_aux_wake), + KUNIT_CASE(dm_test_replay_no_adaptive_sync_sdp), + KUNIT_CASE(dm_test_replay_zero_pixel_deviation), + KUNIT_CASE(dm_test_replay_zero_max_deviation_line), + KUNIT_CASE(dm_test_replay_both_deviations_zero), + {} +}; + +static struct kunit_suite dm_replay_test_suite = { + .name = "amdgpu_dm_replay", + .test_cases = dm_replay_test_cases, +}; + +kunit_test_suite(dm_replay_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_replay"); +MODULE_AUTHOR("AMD"); |
