summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Hung <alex.hung@amd.com>2026-04-29 21:17:43 -0600
committerAlex Deucher <alexander.deucher@amd.com>2026-06-17 16:16:08 -0400
commitc71a6dc1cf01cb2c03c9cbb00bfb761cd65a4543 (patch)
treeb8a669cd0450d3d73bfd0fab61b9b12b325a0ae2
parent179b22085ef0177fa3c78afa2858100d59f5a991 (diff)
drm/amd/display: Add KUnit tests for amdgpu_dm_audio
Add KUnit tests for amdgpu_dm_audio.c. Tests cover: - amdgpu_dm_audio_init(): early exit when audio is disabled - amdgpu_dm_audio_fini(): early exit when audio is not enabled - fill_audio_info(): manufacturer and product ID propagation, display name copy, speaker allocation flags, CEA revision gating of audio mode copying (including the zero-mode case), and latency field propagation - amdgpu_dm_audio_component_bind()/unbind(): component ops, device, and audio_component pointer are wired up on bind and cleared on unbind - amdgpu_dm_audio_eld_notify(): callback is forwarded with the correct port and audio pointer, and the no-op guard paths for a missing component, audio_ops, or pin_eld_notify callback Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com> Signed-off-by: Alex Hung <alex.hung@amd.com> Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com> Tested-by: Daniel Wheeler <daniel.wheeler@amd.com> Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c27
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h12
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile1
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c490
4 files changed, 527 insertions, 3 deletions
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
index a15b7c0c9075..13c9a9d145ba 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
@@ -26,6 +26,7 @@
#include "amdgpu.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_audio.h"
+#include "amdgpu_dm_kunit_helpers.h"
#include "dc.h"
#include <linux/component.h>
@@ -83,7 +84,7 @@ static const struct drm_audio_component_ops amdgpu_dm_audio_component_ops = {
.get_eld = amdgpu_dm_audio_component_get_eld,
};
-static int amdgpu_dm_audio_component_bind(struct device *kdev,
+STATIC_IFN_KUNIT int amdgpu_dm_audio_component_bind(struct device *kdev,
struct device *hda_kdev, void *data)
{
struct drm_device *dev = dev_get_drvdata(kdev);
@@ -96,8 +97,9 @@ static int amdgpu_dm_audio_component_bind(struct device *kdev,
return 0;
}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_component_bind);
-static void amdgpu_dm_audio_component_unbind(struct device *kdev,
+STATIC_IFN_KUNIT void amdgpu_dm_audio_component_unbind(struct device *kdev,
struct device *hda_kdev, void *data)
{
struct amdgpu_device *adev = drm_to_adev(dev_get_drvdata(kdev));
@@ -107,6 +109,7 @@ static void amdgpu_dm_audio_component_unbind(struct device *kdev,
acomp->dev = NULL;
adev->dm.audio_component = NULL;
}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_component_unbind);
static const struct component_ops amdgpu_dm_audio_component_bind_ops = {
.bind = amdgpu_dm_audio_component_bind,
@@ -144,6 +147,7 @@ int amdgpu_dm_audio_init(struct amdgpu_device *adev)
return 0;
}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_init);
void amdgpu_dm_audio_fini(struct amdgpu_device *adev)
{
@@ -162,8 +166,9 @@ void amdgpu_dm_audio_fini(struct amdgpu_device *adev)
adev->mode_info.audio.enabled = false;
}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_fini);
-static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin)
+STATIC_IFN_KUNIT void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin)
{
struct drm_audio_component *acomp = adev->dm.audio_component;
@@ -174,6 +179,7 @@ static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin)
pin, -1);
}
}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_eld_notify);
void amdgpu_dm_fill_audio_info(struct audio_info *audio_info,
const struct drm_connector *drm_connector,
@@ -219,6 +225,7 @@ void amdgpu_dm_fill_audio_info(struct audio_info *audio_info,
/* TODO: For DP, video and audio latency should be calculated from DPCD caps */
}
+EXPORT_IF_KUNIT(amdgpu_dm_fill_audio_info);
void amdgpu_dm_commit_audio(struct drm_device *dev,
struct drm_atomic_commit *state)
@@ -300,3 +307,17 @@ notify:
amdgpu_dm_audio_eld_notify(adev, inst);
}
}
+
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+int amdgpu_dm_audio_get_param(void)
+{
+ return amdgpu_audio;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_get_param);
+
+void amdgpu_dm_audio_set_param(int val)
+{
+ amdgpu_audio = val;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_set_param);
+#endif
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h
index 58cce1f79ffd..7acfc5ef69b3 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h
@@ -41,4 +41,16 @@ void amdgpu_dm_fill_audio_info(struct audio_info *audio_info,
const struct drm_connector *drm_connector,
const struct dc_sink *dc_sink);
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+struct device;
+
+int amdgpu_dm_audio_component_bind(struct device *kdev,
+ struct device *hda_kdev, void *data);
+void amdgpu_dm_audio_component_unbind(struct device *kdev,
+ struct device *hda_kdev, void *data);
+void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin);
+int amdgpu_dm_audio_get_param(void);
+void amdgpu_dm_audio_set_param(int val);
+#endif
+
#endif /* __AMDGPU_DM_AUDIO_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index ddd9fce66232..5bb43b3bc439 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -13,6 +13,7 @@ ccflags-y += -I$(src)/../../../include
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_audio_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_backlight_test.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
new file mode 100644
index 000000000000..79ff5d9b3fa5
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_audio.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+
+#include <drm/drm_audio_component.h>
+
+#include "dc.h"
+#include "amdgpu.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_audio.h"
+
+/* Tests for amdgpu_dm_audio_init() */
+
+/**
+ * dm_test_audio_init_disabled - Test audio init exits when audio is disabled
+ * @test: The KUnit test context
+ */
+static void dm_test_audio_init_disabled(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ int saved_audio = amdgpu_dm_audio_get_param();
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ amdgpu_dm_audio_set_param(0);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_audio_init(adev), 0);
+ KUNIT_EXPECT_FALSE(test, adev->mode_info.audio.enabled);
+ KUNIT_EXPECT_FALSE(test, adev->dm.audio_registered);
+
+ amdgpu_dm_audio_set_param(saved_audio);
+}
+
+/* Tests for amdgpu_dm_audio_fini() */
+
+/**
+ * dm_test_audio_fini_without_enabled_audio - Test fini exits when audio is not enabled
+ * @test: The KUnit test context
+ */
+static void dm_test_audio_fini_without_enabled_audio(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ int saved_audio = amdgpu_dm_audio_get_param();
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ amdgpu_dm_audio_set_param(1);
+ adev->mode_info.audio.enabled = false;
+ adev->dm.audio_registered = true;
+
+ amdgpu_dm_audio_fini(adev);
+
+ KUNIT_EXPECT_FALSE(test, adev->mode_info.audio.enabled);
+ KUNIT_EXPECT_TRUE(test, adev->dm.audio_registered);
+
+ amdgpu_dm_audio_set_param(saved_audio);
+}
+
+/* Tests for amdgpu_dm_fill_audio_info() */
+
+/**
+ * dm_test_fill_audio_info_ids_name_flags - Test Fill audio info ids name flags
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_audio_info_ids_name_flags(struct kunit *test)
+{
+ struct audio_info *audio_info;
+ struct drm_connector *connector;
+ struct dc_sink *dc_sink;
+ const char *name = "DM-AUDIO-PANEL";
+
+ audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL);
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink);
+
+ dc_sink->edid_caps.manufacturer_id = 0x1234;
+ dc_sink->edid_caps.product_id = 0xABCD;
+ dc_sink->edid_caps.speaker_flags = 0x5;
+ strscpy(dc_sink->edid_caps.display_name, name,
+ AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS);
+
+ connector->display_info.cea_rev = 1;
+
+ amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink);
+
+ KUNIT_EXPECT_EQ(test, audio_info->manufacture_id, 0x1234U);
+ KUNIT_EXPECT_EQ(test, audio_info->product_id, 0xABCDU);
+ KUNIT_EXPECT_EQ(test, audio_info->flags.all, 0x5U);
+ KUNIT_EXPECT_STREQ(test, audio_info->display_name, name);
+}
+
+/**
+ * dm_test_fill_audio_info_cea_lt_3_skips_modes - Test Fill audio info cea lt 3 skips modes
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_audio_info_cea_lt_3_skips_modes(struct kunit *test)
+{
+ struct audio_info *audio_info;
+ struct drm_connector *connector;
+ struct dc_sink *dc_sink;
+
+ audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL);
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink);
+
+ connector->display_info.cea_rev = 2;
+ dc_sink->edid_caps.audio_mode_count = 2;
+ dc_sink->edid_caps.audio_modes[0].format_code = 1;
+ dc_sink->edid_caps.audio_modes[0].channel_count = 2;
+ dc_sink->edid_caps.audio_modes[0].sample_rate = 0x07;
+ dc_sink->edid_caps.audio_modes[0].sample_size = 16;
+
+ amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink);
+
+ KUNIT_EXPECT_EQ(test, audio_info->mode_count, 0U);
+}
+
+/**
+ * dm_test_fill_audio_info_cea_ge_3_copies_modes - Test Fill audio info cea ge 3 copies modes
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_audio_info_cea_ge_3_copies_modes(struct kunit *test)
+{
+ struct audio_info *audio_info;
+ struct drm_connector *connector;
+ struct dc_sink *dc_sink;
+
+ audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL);
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink);
+
+ connector->display_info.cea_rev = 3;
+ dc_sink->edid_caps.audio_mode_count = 2;
+
+ dc_sink->edid_caps.audio_modes[0].format_code = 1;
+ dc_sink->edid_caps.audio_modes[0].channel_count = 2;
+ dc_sink->edid_caps.audio_modes[0].sample_rate = 0x07;
+ dc_sink->edid_caps.audio_modes[0].sample_size = 16;
+
+ dc_sink->edid_caps.audio_modes[1].format_code = 11;
+ dc_sink->edid_caps.audio_modes[1].channel_count = 6;
+ dc_sink->edid_caps.audio_modes[1].sample_rate = 0x1F;
+ dc_sink->edid_caps.audio_modes[1].sample_size = 24;
+
+ amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink);
+
+ KUNIT_EXPECT_EQ(test, audio_info->mode_count, 2U);
+
+ KUNIT_EXPECT_EQ(test, (int)audio_info->modes[0].format_code, 1);
+ KUNIT_EXPECT_EQ(test, audio_info->modes[0].channel_count, 2);
+ KUNIT_EXPECT_EQ(test, audio_info->modes[0].sample_rates.all, 0x07U);
+ KUNIT_EXPECT_EQ(test, audio_info->modes[0].sample_size, 16);
+
+ KUNIT_EXPECT_EQ(test, (int)audio_info->modes[1].format_code, 11);
+ KUNIT_EXPECT_EQ(test, audio_info->modes[1].channel_count, 6);
+ KUNIT_EXPECT_EQ(test, audio_info->modes[1].sample_rates.all, 0x1FU);
+ KUNIT_EXPECT_EQ(test, audio_info->modes[1].sample_size, 24);
+}
+
+/**
+ * dm_test_fill_audio_info_latency_present - Test Fill audio info latency present
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_audio_info_latency_present(struct kunit *test)
+{
+ struct audio_info *audio_info;
+ struct drm_connector *connector;
+ struct dc_sink *dc_sink;
+
+ audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL);
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink);
+
+ connector->display_info.cea_rev = 3;
+ connector->latency_present[0] = true;
+ connector->video_latency[0] = 11;
+ connector->audio_latency[0] = 22;
+
+ amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink);
+
+ KUNIT_EXPECT_EQ(test, audio_info->video_latency, 11U);
+ KUNIT_EXPECT_EQ(test, audio_info->audio_latency, 22U);
+}
+
+/**
+ * dm_test_fill_audio_info_latency_absent_keeps_zero - Test Fill audio info latency absent keeps zero
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_audio_info_latency_absent_keeps_zero(struct kunit *test)
+{
+ struct audio_info *audio_info;
+ struct drm_connector *connector;
+ struct dc_sink *dc_sink;
+
+ audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL);
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink);
+
+ connector->display_info.cea_rev = 3;
+ connector->latency_present[0] = false;
+ connector->video_latency[0] = 99;
+ connector->audio_latency[0] = 88;
+
+ amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink);
+
+ KUNIT_EXPECT_EQ(test, audio_info->video_latency, 0U);
+ KUNIT_EXPECT_EQ(test, audio_info->audio_latency, 0U);
+}
+
+/**
+ * dm_test_fill_audio_info_cea_ge_3_zero_modes - Test cea >= 3 with zero modes
+ * @test: The KUnit test context
+ *
+ * When cea_rev >= 3 but the sink reports no audio modes, mode_count must be
+ * copied as 0 and no mode entries should be populated.
+ */
+static void dm_test_fill_audio_info_cea_ge_3_zero_modes(struct kunit *test)
+{
+ struct audio_info *audio_info;
+ struct drm_connector *connector;
+ struct dc_sink *dc_sink;
+
+ audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL);
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink);
+
+ connector->display_info.cea_rev = 3;
+ dc_sink->edid_caps.audio_mode_count = 0;
+
+ amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink);
+
+ KUNIT_EXPECT_EQ(test, audio_info->mode_count, 0U);
+ KUNIT_EXPECT_EQ(test, (int)audio_info->modes[0].format_code, 0);
+}
+
+/* Tests for amdgpu_dm_audio_component_bind()/unbind() */
+
+/**
+ * dm_test_audio_component_bind_sets_fields - Test bind wires up audio component
+ * @test: The KUnit test context
+ *
+ * Binding must publish the DRM audio component ops, record the kernel device,
+ * and store the component pointer in the display manager.
+ */
+static void dm_test_audio_component_bind_sets_fields(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct device *kdev;
+ struct drm_audio_component *acomp;
+ int ret;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ kdev = kunit_kzalloc(test, sizeof(*kdev), GFP_KERNEL);
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, kdev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp);
+
+ dev_set_drvdata(kdev, &adev->ddev);
+
+ ret = amdgpu_dm_audio_component_bind(kdev, NULL, acomp);
+
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_NOT_NULL(test, acomp->ops);
+ KUNIT_EXPECT_PTR_EQ(test, acomp->dev, kdev);
+ KUNIT_EXPECT_PTR_EQ(test, adev->dm.audio_component, acomp);
+}
+
+/**
+ * dm_test_audio_component_unbind_clears_fields - Test unbind tears down component
+ * @test: The KUnit test context
+ *
+ * Unbinding must clear the component ops, the kernel device, and the display
+ * manager's stored component pointer.
+ */
+static void dm_test_audio_component_unbind_clears_fields(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct device *kdev;
+ struct drm_audio_component *acomp;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ kdev = kunit_kzalloc(test, sizeof(*kdev), GFP_KERNEL);
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, kdev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp);
+
+ dev_set_drvdata(kdev, &adev->ddev);
+
+ /* Pretend a prior bind already happened. */
+ acomp->dev = kdev;
+ adev->dm.audio_component = acomp;
+
+ amdgpu_dm_audio_component_unbind(kdev, NULL, acomp);
+
+ KUNIT_EXPECT_NULL(test, acomp->ops);
+ KUNIT_EXPECT_NULL(test, acomp->dev);
+ KUNIT_EXPECT_NULL(test, adev->dm.audio_component);
+}
+
+/* Tests for amdgpu_dm_audio_eld_notify() */
+
+static int dm_test_eld_notify_count;
+static int dm_test_eld_notify_port;
+static void *dm_test_eld_notify_ptr;
+
+static void dm_test_pin_eld_notify(void *audio_ptr, int port, int pipe)
+{
+ dm_test_eld_notify_count++;
+ dm_test_eld_notify_port = port;
+ dm_test_eld_notify_ptr = audio_ptr;
+}
+
+/**
+ * dm_test_eld_notify_invokes_callback - Test ELD notify forwards to hda driver
+ * @test: The KUnit test context
+ *
+ * When a component with a pin_eld_notify callback is registered, the notify
+ * helper must invoke it with the audio pointer and the requested pin.
+ */
+static void dm_test_eld_notify_invokes_callback(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct drm_audio_component *acomp;
+ struct drm_audio_component_audio_ops *audio_ops;
+ int marker = 0;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ audio_ops = kunit_kzalloc(test, sizeof(*audio_ops), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_ops);
+
+ audio_ops->audio_ptr = &marker;
+ audio_ops->pin_eld_notify = dm_test_pin_eld_notify;
+ acomp->audio_ops = audio_ops;
+ adev->dm.audio_component = acomp;
+
+ dm_test_eld_notify_count = 0;
+ dm_test_eld_notify_port = -100;
+ dm_test_eld_notify_ptr = NULL;
+
+ amdgpu_dm_audio_eld_notify(adev, 7);
+
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 7);
+ KUNIT_EXPECT_PTR_EQ(test, dm_test_eld_notify_ptr, (void *)&marker);
+}
+
+/**
+ * dm_test_eld_notify_no_component - Test ELD notify is a no-op without component
+ * @test: The KUnit test context
+ *
+ * With no registered audio component, the notify helper must return without
+ * invoking any callback.
+ */
+static void dm_test_eld_notify_no_component(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->dm.audio_component = NULL;
+
+ dm_test_eld_notify_count = 0;
+
+ amdgpu_dm_audio_eld_notify(adev, 3);
+
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0);
+}
+
+/**
+ * dm_test_eld_notify_null_audio_ops - Test ELD notify is a no-op without audio_ops
+ * @test: The KUnit test context
+ *
+ * A component without audio_ops must not trigger any callback.
+ */
+static void dm_test_eld_notify_null_audio_ops(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct drm_audio_component *acomp;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp);
+
+ acomp->audio_ops = NULL;
+ adev->dm.audio_component = acomp;
+
+ dm_test_eld_notify_count = 0;
+
+ amdgpu_dm_audio_eld_notify(adev, 3);
+
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0);
+}
+
+/**
+ * dm_test_eld_notify_null_callback - Test ELD notify is a no-op without callback
+ * @test: The KUnit test context
+ *
+ * audio_ops present but with a NULL pin_eld_notify must not crash or call
+ * anything.
+ */
+static void dm_test_eld_notify_null_callback(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct drm_audio_component *acomp;
+ struct drm_audio_component_audio_ops *audio_ops;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ audio_ops = kunit_kzalloc(test, sizeof(*audio_ops), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_ops);
+
+ audio_ops->pin_eld_notify = NULL;
+ acomp->audio_ops = audio_ops;
+ adev->dm.audio_component = acomp;
+
+ dm_test_eld_notify_count = 0;
+
+ amdgpu_dm_audio_eld_notify(adev, 3);
+
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0);
+}
+
+static struct kunit_case dm_audio_test_cases[] = {
+ /* amdgpu_dm_audio_init */
+ KUNIT_CASE(dm_test_audio_init_disabled),
+ /* amdgpu_dm_audio_fini */
+ KUNIT_CASE(dm_test_audio_fini_without_enabled_audio),
+ /* amdgpu_dm_fill_audio_info */
+ KUNIT_CASE(dm_test_fill_audio_info_ids_name_flags),
+ KUNIT_CASE(dm_test_fill_audio_info_cea_lt_3_skips_modes),
+ KUNIT_CASE(dm_test_fill_audio_info_cea_ge_3_copies_modes),
+ KUNIT_CASE(dm_test_fill_audio_info_cea_ge_3_zero_modes),
+ KUNIT_CASE(dm_test_fill_audio_info_latency_present),
+ KUNIT_CASE(dm_test_fill_audio_info_latency_absent_keeps_zero),
+ /* amdgpu_dm_audio_component_bind/unbind */
+ KUNIT_CASE(dm_test_audio_component_bind_sets_fields),
+ KUNIT_CASE(dm_test_audio_component_unbind_clears_fields),
+ /* amdgpu_dm_audio_eld_notify */
+ KUNIT_CASE(dm_test_eld_notify_invokes_callback),
+ KUNIT_CASE(dm_test_eld_notify_no_component),
+ KUNIT_CASE(dm_test_eld_notify_null_audio_ops),
+ KUNIT_CASE(dm_test_eld_notify_null_callback),
+ {}
+};
+
+static struct kunit_suite dm_audio_test_suite = {
+ .name = "amdgpu_dm_audio",
+ .test_cases = dm_audio_test_cases,
+};
+
+kunit_test_suite(dm_audio_test_suite);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_audio");
+MODULE_AUTHOR("AMD");