summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/amd/display/amdgpu_dm
diff options
context:
space:
mode:
authorThomas Zimmermann <tzimmermann@suse.de>2026-06-30 10:16:00 +0200
committerThomas Zimmermann <tzimmermann@suse.de>2026-06-30 10:16:00 +0200
commit827b9aabd3ea3e96d5e48abed9f44dbd1e550d4e (patch)
tree845a97a83f20fe0585840a66d9eed8fcf1286772 /drivers/gpu/drm/amd/display/amdgpu_dm
parent3b3bce4a692ac60d9f4a341e6b597dd1fd0a28f9 (diff)
parentdc59e4fea9d83f03bad6bddf3fa2e52491777482 (diff)
downloadlinux-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')
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/Makefile8
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c212
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h15
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c134
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h118
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c26
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.h5
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c17
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h10
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c33
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c7
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h13
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c89
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c107
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h17
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h19
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c79
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c1
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c55
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c27
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h9
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c3
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig22
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile18
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c1639
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c239
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c121
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c175
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c938
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c291
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c206
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, &params));
+ 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, &params[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");