aboutsummaryrefslogtreecommitdiffstats
diff options
authorMark Brown <broonie@kernel.org>2026-05-29 22:42:09 +0100
committerMark Brown <broonie@kernel.org>2026-05-29 22:42:09 +0100
commitc7c18ef008859bbe0017e164f8522aef5bac3e7b (patch)
tree034d71b9449d89e5094a04b8d6300f80dcf59ee2
parent343e69a2a8b629f8da9062811faa1260bddb17ed (diff)
parent470d1ae31d29f90b8998c5c08ee0b267a05fe378 (diff)
downloadlinux-next-history-c7c18ef008859bbe0017e164f8522aef5bac3e7b.tar.gz
Merge branch 'drm-next' of https://gitlab.freedesktop.org/agd5f/linux.git
-rw-r--r--drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c2
-rw-r--r--drivers/gpu/drm/amd/amdgpu/amdgpu_device.c44
-rw-r--r--drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c20
-rw-r--r--drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c20
-rw-r--r--drivers/gpu/drm/amd/amdgpu/umc_v12_0.c3
-rw-r--r--drivers/gpu/drm/amd/amdkfd/kfd_crat.c6
-rw-r--r--drivers/gpu/drm/amd/amdkfd/kfd_topology.c2
-rw-r--r--drivers/gpu/drm/amd/display/Kconfig12
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/Makefile5
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c2
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h2
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c65
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h89
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c4
-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_hdcp.c5
-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_ism.c26
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h10
-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_pp_smu.c55
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c6
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h5
-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/.kunitconfig14
-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.c1071
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c161
-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.c636
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c255
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c206
-rw-r--r--drivers/gpu/drm/amd/display/dc/bios/bios_parser.c36
-rw-r--r--drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c18
-rw-r--r--drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c144
-rw-r--r--drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.h3
-rw-r--r--drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c25
-rw-r--r--drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c41
-rw-r--r--drivers/gpu/drm/amd/display/dc/clk_mgr/dce120/dce120_clk_mgr.c16
-rw-r--r--drivers/gpu/drm/amd/display/dc/core/dc_debug.c4
-rw-r--r--drivers/gpu/drm/amd/display/dc/dm_services.h8
-rw-r--r--drivers/gpu/drm/amd/display/dc/dm_services_types.h30
-rw-r--r--drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h10
-rw-r--r--drivers/gpu/drm/amd/display/include/grph_object_ctrl_defs.h9
-rw-r--r--drivers/gpu/drm/amd/include/dm_pp_interface.h19
-rw-r--r--drivers/gpu/drm/amd/include/kgd_pp_interface.h4
-rw-r--r--drivers/gpu/drm/amd/pm/amdgpu_dpm.c8
-rw-r--r--drivers/gpu/drm/amd/pm/legacy-dpm/kv_dpm.c6
-rw-r--r--drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c46
-rw-r--r--drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c34
-rw-r--r--drivers/gpu/drm/amd/pm/powerplay/hwmgr/hardwaremanager.c10
-rw-r--r--drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c1
-rw-r--r--drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c47
-rw-r--r--drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c15
-rw-r--r--drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c63
-rw-r--r--drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c18
-rw-r--r--drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c16
-rw-r--r--drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c17
-rw-r--r--drivers/gpu/drm/amd/pm/powerplay/inc/hardwaremanager.h3
-rw-r--r--drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h5
-rw-r--r--drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_virt_ras_cmd.c16
-rw-r--r--drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h9
-rw-r--r--drivers/gpu/drm/amd/ras/rascore/ras_umc.c35
-rw-r--r--drivers/gpu/drm/amd/ras/rascore/ras_umc.h12
-rw-r--r--drivers/gpu/drm/radeon/radeon_audio.c4
-rw-r--r--drivers/gpu/drm/radeon/radeon_combios.c4
-rw-r--r--drivers/gpu/drm/radeon/radeon_connectors.c60
-rw-r--r--drivers/gpu/drm/radeon/radeon_mode.h4
70 files changed, 3159 insertions, 743 deletions
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c
index d386bc775d03c..0811593fca7f9 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c
@@ -586,7 +586,7 @@ void amdgpu_coredump(struct amdgpu_device *adev, bool skip_vram_check,
*/
adev->coredump = coredump;
/* Kick off coredump formatting to a worker thread. */
- queue_work(system_unbound_wq, &adev->coredump_work);
+ queue_work(system_dfl_wq, &adev->coredump_work);
drm_info(dev, "AMDGPU device coredump file has been created\n");
drm_info(dev, "Check your /sys/class/drm/card%d/device/devcoredump/data\n",
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
index 21a3fb574d53e..480eeb8510f87 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
@@ -1913,6 +1913,19 @@ static void amdgpu_uid_fini(struct amdgpu_device *adev)
adev->uid_info = NULL;
}
+static struct pci_dev *amdgpu_device_find_parent(struct amdgpu_device *adev)
+{
+ struct pci_dev *parent = adev->pdev;
+
+ /* skip upstream/downstream switches internal to dGPU */
+ while ((parent = pci_upstream_bridge(parent))) {
+ if (parent->vendor == PCI_VENDOR_ID_ATI)
+ continue;
+ }
+
+ return parent;
+}
+
/**
* amdgpu_device_ip_early_init - run early init for hardware IPs
*
@@ -5921,8 +5934,6 @@ static void amdgpu_device_partner_bandwidth(struct amdgpu_device *adev,
enum pci_bus_speed *speed,
enum pcie_link_width *width)
{
- struct pci_dev *parent = adev->pdev;
-
if (!speed || !width)
return;
@@ -5930,13 +5941,11 @@ static void amdgpu_device_partner_bandwidth(struct amdgpu_device *adev,
*width = PCIE_LNK_WIDTH_UNKNOWN;
if (amdgpu_device_pcie_dynamic_switching_supported(adev)) {
- while ((parent = pci_upstream_bridge(parent))) {
- /* skip upstream/downstream switches internal to dGPU*/
- if (parent->vendor == PCI_VENDOR_ID_ATI)
- continue;
+ struct pci_dev *parent = amdgpu_device_find_parent(adev);
+
+ if (parent) {
*speed = pcie_get_speed_cap(parent);
*width = pcie_get_width_cap(parent);
- break;
}
} else {
/* use the current speeds rather than max if switching is not supported */
@@ -5963,22 +5972,15 @@ static void amdgpu_device_gpu_bandwidth(struct amdgpu_device *adev,
if (!speed || !width)
return;
- parent = pci_upstream_bridge(parent);
- if (parent && parent->vendor == PCI_VENDOR_ID_ATI) {
- /* use the upstream/downstream switches internal to dGPU */
+ /* use the device itself */
+ *speed = pcie_get_speed_cap(adev->pdev);
+ *width = pcie_get_width_cap(adev->pdev);
+
+ /* use the link outside the device */
+ parent = amdgpu_device_find_parent(adev);
+ if (parent) {
*speed = pcie_get_speed_cap(parent);
*width = pcie_get_width_cap(parent);
- while ((parent = pci_upstream_bridge(parent))) {
- if (parent->vendor == PCI_VENDOR_ID_ATI) {
- /* use the upstream/downstream switches internal to dGPU */
- *speed = pcie_get_speed_cap(parent);
- *width = pcie_get_width_cap(parent);
- }
- }
- } else {
- /* use the device itself */
- *speed = pcie_get_speed_cap(adev->pdev);
- *width = pcie_get_width_cap(adev->pdev);
}
}
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c
index 3fcbd722ee3b4..229eb6219de01 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c
@@ -1804,13 +1804,15 @@ amdgpu_virt_write_cpers_to_ring(struct amdgpu_device *adev,
struct amd_sriov_ras_cper_dump *cper_dump = NULL;
struct cper_hdr *entry = NULL;
struct amdgpu_ring *ring = &adev->cper.ring_buf;
- uint32_t checksum, used_size, i;
+ uint32_t checksum, used_size;
+ u64 remaining, cnt, i;
int ret = 0;
checksum = host_telemetry->header.checksum;
used_size = host_telemetry->header.used_size;
- if (used_size > (AMD_SRIOV_MSG_RAS_TELEMETRY_SIZE_KB_V1 << 10))
+ if (used_size < offsetof(struct amd_sriov_ras_cper_dump, buf) ||
+ used_size > (AMD_SRIOV_MSG_RAS_TELEMETRY_SIZE_KB_V1 << 10))
return -EINVAL;
cper_dump = kmemdup(&host_telemetry->body.cper_dump, used_size, GFP_KERNEL);
@@ -1835,11 +1837,19 @@ amdgpu_virt_write_cpers_to_ring(struct amdgpu_device *adev,
}
entry = (struct cper_hdr *)&cper_dump->buf[0];
+ remaining = (u64)used_size - offsetof(struct amd_sriov_ras_cper_dump, buf);
+ cnt = min_t(u64, cper_dump->count, CPER_MAX_ALLOWED_COUNT);
+
+ for (i = 0; i < cnt; i++) {
+ if (entry->record_length < sizeof(struct cper_hdr) ||
+ entry->record_length > remaining) {
+ ret = -EINVAL;
+ goto out;
+ }
- for (i = 0; i < cper_dump->count; i++) {
amdgpu_cper_ring_write(ring, entry, entry->record_length);
- entry = (struct cper_hdr *)((char *)entry +
- entry->record_length);
+ remaining -= entry->record_length;
+ entry = (struct cper_hdr *)((char *)entry + entry->record_length);
}
if (cper_dump->overflow_count)
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
index 76f7a78c5e9e9..4558a5663c66e 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
@@ -266,12 +266,23 @@ static void amdgpu_vm_bo_idle(struct amdgpu_vm_bo_base *vm_bo)
*/
static void amdgpu_vm_bo_reset_state_machine(struct amdgpu_vm *vm)
{
+ struct amdgpu_vm_bo_base *vm_bo, *tmp;
+
+ /*
+ * Don't use list splice here, we need the special handling for the root
+ * PD and set the moved flag appropriately.
+ */
amdgpu_vm_assert_locked(vm);
- list_splice_init(&vm->kernel.idle, &vm->kernel.moved);
- list_splice_init(&vm->always_valid.idle, &vm->always_valid.moved);
+ list_for_each_entry_safe(vm_bo, tmp, &vm->kernel.idle, vm_status)
+ amdgpu_vm_bo_moved(vm_bo);
+ list_for_each_entry_safe(vm_bo, tmp, &vm->always_valid.idle, vm_status)
+ amdgpu_vm_bo_moved(vm_bo);
spin_lock(&vm->individual_lock);
- list_splice_init(&vm->individual.idle, &vm->individual.moved);
+ list_for_each_entry_safe(vm_bo, tmp, &vm->individual.idle, vm_status) {
+ vm_bo->moved = true;
+ list_move(&vm_bo->vm_status, &vm->individual.moved);
+ }
spin_unlock(&vm->individual_lock);
}
@@ -1616,7 +1627,8 @@ int amdgpu_vm_handle_moved(struct amdgpu_device *adev,
while (!list_empty(&vm->individual.moved)) {
bo_va = list_first_entry(&vm->individual.moved,
typeof(*bo_va), base.vm_status);
- resv = bo_va->base.bo->tbo.base.resv;
+ bo = bo_va->base.bo;
+ resv = bo->tbo.base.resv;
spin_unlock(&vm->individual_lock);
/* Try to reserve the BO to avoid clearing its ptes */
diff --git a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c
index db505ab32fa0f..14092150336a5 100644
--- a/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c
+++ b/drivers/gpu/drm/amd/amdgpu/umc_v12_0.c
@@ -285,7 +285,8 @@ static int umc_v12_0_convert_error_address(struct amdgpu_device *adev,
struct ta_ras_query_address_output *addr_out,
bool dump_addr)
{
- uint32_t col, col_lower, row, row_lower, row_high, bank;
+ uint32_t row = 0, row_lower = 0, row_high = 0;
+ uint32_t col = 0, col_lower = 0, bank = 0;
uint32_t channel_index = 0, umc_inst = 0;
uint32_t i, bit_num, retire_unit, *flip_bits;
uint64_t soc_pa, column, err_addr;
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c
index a1087c13f2419..cf7b1b038d5f8 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c
@@ -1821,7 +1821,7 @@ static int kfd_fill_mem_info_for_cpu(int numa_node_id, int *avail_size,
return 0;
}
-#ifdef CONFIG_X86_64
+#if defined(CONFIG_X86_64) && !defined(CONFIG_UML)
static int kfd_fill_iolink_info_for_cpu(int numa_node_id, int *avail_size,
uint32_t *num_entries,
struct crat_subtype_iolink *sub_type_hdr)
@@ -1880,7 +1880,7 @@ static int kfd_create_vcrat_image_cpu(void *pcrat_image, size_t *size)
struct crat_subtype_generic *sub_type_hdr;
int avail_size = *size;
int numa_node_id;
-#ifdef CONFIG_X86_64
+#if defined(CONFIG_X86_64) && !defined(CONFIG_UML)
uint32_t entries = 0;
#endif
int ret = 0;
@@ -1945,7 +1945,7 @@ static int kfd_create_vcrat_image_cpu(void *pcrat_image, size_t *size)
sub_type_hdr->length);
/* Fill in Subtype: IO Link */
-#ifdef CONFIG_X86_64
+#if defined(CONFIG_X86_64) && !defined(CONFIG_UML)
ret = kfd_fill_iolink_info_for_cpu(numa_node_id, &avail_size,
&entries,
(struct crat_subtype_iolink *)sub_type_hdr);
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c
index 630f46091dc75..dc1c6bd1252f1 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c
@@ -2350,7 +2350,7 @@ static int kfd_cpumask_to_apic_id(const struct cpumask *cpumask)
first_cpu_of_numa_node = cpumask_first(cpumask);
if (first_cpu_of_numa_node >= nr_cpu_ids)
return -1;
-#ifdef CONFIG_X86_64
+#if defined(CONFIG_X86_64) && !defined(CONFIG_UML)
return cpu_data(first_cpu_of_numa_node).topo.apicid;
#else
return first_cpu_of_numa_node;
diff --git a/drivers/gpu/drm/amd/display/Kconfig b/drivers/gpu/drm/amd/display/Kconfig
index abd3b6564373a..38323e574c6bb 100644
--- a/drivers/gpu/drm/amd/display/Kconfig
+++ b/drivers/gpu/drm/amd/display/Kconfig
@@ -56,4 +56,16 @@ config DRM_AMD_SECURE_DISPLAY
This option enables the calculation of crc of specific region via
debugfs. Cooperate with specific DMCU FW.
+config DRM_AMD_DC_KUNIT_TEST
+ tristate "KUnit tests for the AMD DC display driver" if !KUNIT_ALL_TESTS
+ depends on DRM_AMD_DC && KUNIT && DEBUG_FS
+ default KUNIT_ALL_TESTS
+ help
+ This option enables KUnit tests for the AMD Display Core driver.
+ These tests validate core functionality like CRC source parsing,
+ color management, and other utility functions.
+
+ For more information on KUnit and unit tests in general, please
+ refer to the KUnit documentation in Documentation/dev-tools/kunit/.
+
endmenu
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
index 89350aa9ca7ec..914f89af047c1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
@@ -59,3 +59,8 @@ 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/
+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 ba7f98a87808c..c5a5e6ca4958a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -7473,7 +7473,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;
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 74f700fbeb6f1..c4672ade22d57 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
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 fa6883ae4dfb8..4339e84360a58 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
@@ -851,7 +868,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 +897,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 +920,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 +931,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 +977,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 +988,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 +1034,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
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 0000000000000..19d3a13572f55
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h
@@ -0,0 +1,89 @@
+/* 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_property_blob;
+struct dc_gamma;
+struct dc_rgb;
+struct fixed31_32;
+struct tetrahedral_params;
+
+#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);
+#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 7ee051cb3c056..ab20e3f41d604 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
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 5adbb0f6a0c88..88f7cfea56240 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 8538513ea8799..c9aa0c82038fe 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_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c
index eb73bbf8f411f..adc5cd1007f29 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)
{
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 4faa344f196e7..90b18c450ca61 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_ism.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c
index 7dcc587c45e91..06594fbfceee0 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,6 +224,7 @@ 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
@@ -375,7 +383,7 @@ static enum amdgpu_dm_ism_event dm_ism_dispatch_power_state(
}
/* Schedule worker */
- mod_delayed_work(system_unbound_wq, &ism->delayed_work,
+ mod_delayed_work(system_dfl_wq, &ism->delayed_work,
nsecs_to_jiffies(delay_ns));
break;
@@ -391,14 +399,14 @@ 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,
+ mod_delayed_work(system_dfl_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,
+ mod_delayed_work(system_dfl_wq,
&ism->sso_delayed_work,
nsecs_to_jiffies(sso_delay_ns));
}
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 964408cd9a839..72e2dac49e55a 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,14 @@ 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);
+#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 0000000000000..4b2864375105b
--- /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_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c
index 11b2ea6edf953..2cdb8fea504a0 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 dc5913a6456e2..4b823bba43921 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
@@ -29,6 +29,8 @@
#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 +60,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 +89,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
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 16d535806ad63..9f3e22520ca02 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
@@ -39,4 +39,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 297125d1db708..22aa4305d2af9 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 0000000000000..36676326ade43
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig
@@ -0,0 +1,14 @@
+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_KMS_HELPER=y
+CONFIG_DRM_TTM=y
+CONFIG_HWMON=y
+CONFIG_I2C=y
+CONFIG_POWER_SUPPLY=y
+CONFIG_CRC16=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 0000000000000..768f9bbc50e14
--- /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 0000000000000..377fa4342ca1f
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c
@@ -0,0 +1,1071 @@
+// 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));
+}
+
+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),
+ {}
+};
+
+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 0000000000000..6c77a7159188e
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c
@@ -0,0 +1,161 @@
+// 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 "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_inv(struct kunit *test)
+{
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs &
+ BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV));
+}
+
+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_INV);
+
+ 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(struct kunit *test)
+{
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs &
+ BIT(DRM_COLOROP_1D_CURVE_GAMMA22));
+}
+
+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);
+
+ 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_inv(struct kunit *test)
+{
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs &
+ BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV));
+}
+
+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_INV);
+
+ 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);
+}
+
+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_inv),
+ 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),
+ 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_inv),
+ KUNIT_CASE(dm_test_supported_blnd_tfs_no_extra_bits),
+ /* cross-check */
+ KUNIT_CASE(dm_test_degam_and_blnd_tfs_match),
+ {}
+};
+
+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 0000000000000..bba8b1a8fa1c0
--- /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 0000000000000..d03b606d27bc7
--- /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 0000000000000..e761105e19952
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c
@@ -0,0 +1,636 @@
+// 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);
+}
+
+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),
+ {}
+};
+
+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 0000000000000..61a4167898cbd
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c
@@ -0,0 +1,255 @@
+// 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 "dc.h"
+
+/* Extern declaration for the function under test */
+extern void amdgpu_dm_psr_fill_caps(struct dc_link *link,
+ struct psr_caps *caps);
+
+/*
+ * 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() */
+
+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),
+ {}
+};
+
+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 0000000000000..28ff8bbcc0f75
--- /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");
diff --git a/drivers/gpu/drm/amd/display/dc/bios/bios_parser.c b/drivers/gpu/drm/amd/display/dc/bios/bios_parser.c
index 30567ef74e5c8..91df37c4c8da3 100644
--- a/drivers/gpu/drm/amd/display/dc/bios/bios_parser.c
+++ b/drivers/gpu/drm/amd/display/dc/bios/bios_parser.c
@@ -2419,15 +2419,6 @@ static enum bp_result get_integrated_info_v8(
info->dentist_vco_freq = le32_to_cpu(info_v8->ulDentistVCOFreq) * 10;
info->boot_up_uma_clock = le32_to_cpu(info_v8->ulBootUpUMAClock) * 10;
- for (i = 0; i < NUMBER_OF_DISP_CLK_VOLTAGE; ++i) {
- /* Convert [10KHz] into [KHz] */
- info->disp_clk_voltage[i].max_supported_clk =
- le32_to_cpu(info_v8->sDISPCLK_Voltage[i].
- ulMaximumSupportedCLK) * 10;
- info->disp_clk_voltage[i].voltage_index =
- le32_to_cpu(info_v8->sDISPCLK_Voltage[i].ulVoltageIndex);
- }
-
info->boot_up_req_display_vector =
le32_to_cpu(info_v8->ulBootUpReqDisplayVector);
info->gpu_cap_info =
@@ -2570,14 +2561,6 @@ static enum bp_result get_integrated_info_v9(
info->dentist_vco_freq = le32_to_cpu(info_v9->ulDentistVCOFreq) * 10;
info->boot_up_uma_clock = le32_to_cpu(info_v9->ulBootUpUMAClock) * 10;
- for (i = 0; i < NUMBER_OF_DISP_CLK_VOLTAGE; ++i) {
- /* Convert [10KHz] into [KHz] */
- info->disp_clk_voltage[i].max_supported_clk =
- le32_to_cpu(info_v9->sDISPCLK_Voltage[i].ulMaximumSupportedCLK) * 10;
- info->disp_clk_voltage[i].voltage_index =
- le32_to_cpu(info_v9->sDISPCLK_Voltage[i].ulVoltageIndex);
- }
-
info->boot_up_req_display_vector =
le32_to_cpu(info_v9->ulBootUpReqDisplayVector);
info->gpu_cap_info = le32_to_cpu(info_v9->ulGPUCapInfo);
@@ -2719,25 +2702,6 @@ static enum bp_result construct_integrated_info(
}
}
- /* Sort voltage table from low to high*/
- if (result == BP_RESULT_OK) {
- int32_t i;
- int32_t j;
-
- for (i = 1; i < NUMBER_OF_DISP_CLK_VOLTAGE; ++i) {
- for (j = i; j > 0; --j) {
- if (
- info->disp_clk_voltage[j].max_supported_clk <
- info->disp_clk_voltage[j-1].max_supported_clk) {
- /* swap j and j - 1*/
- swap(info->disp_clk_voltage[j - 1],
- info->disp_clk_voltage[j]);
- }
- }
- }
-
- }
-
return result;
}
diff --git a/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c b/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c
index ee6d52b87b029..06bab03a8579f 100644
--- a/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c
+++ b/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c
@@ -2671,15 +2671,6 @@ static enum bp_result get_integrated_info_v11(
info->dentist_vco_freq = le32_to_cpu(info_v11->ulDentistVCOFreq) * 10;
info->boot_up_uma_clock = le32_to_cpu(info_v8->ulBootUpUMAClock) * 10;
- for (i = 0; i < NUMBER_OF_DISP_CLK_VOLTAGE; ++i) {
- /* Convert [10KHz] into [KHz] */
- info->disp_clk_voltage[i].max_supported_clk =
- le32_to_cpu(info_v11->sDISPCLK_Voltage[i].
- ulMaximumSupportedCLK) * 10;
- info->disp_clk_voltage[i].voltage_index =
- le32_to_cpu(info_v11->sDISPCLK_Voltage[i].ulVoltageIndex);
- }
-
info->boot_up_req_display_vector =
le32_to_cpu(info_v11->ulBootUpReqDisplayVector);
info->boot_up_nb_voltage =
@@ -3032,7 +3023,6 @@ static enum bp_result construct_integrated_info(
struct atom_data_revision revision;
int32_t i;
- int32_t j;
if (!info)
return result;
@@ -3134,14 +3124,6 @@ static enum bp_result construct_integrated_info(
DC_LOG_BIOS("driver forced fixdpvoltageswing = %d\n", info->ext_disp_conn_info.fixdpvoltageswing);
}
}
- /* Sort voltage table from low to high*/
- for (i = 1; i < NUMBER_OF_DISP_CLK_VOLTAGE; ++i) {
- for (j = i; j > 0; --j) {
- if (info->disp_clk_voltage[j].max_supported_clk <
- info->disp_clk_voltage[j-1].max_supported_clk)
- swap(info->disp_clk_voltage[j-1], info->disp_clk_voltage[j]);
- }
- }
return result;
}
diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c
index 808e24f0e88fd..d891b3bfe2a1f 100644
--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c
+++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.c
@@ -62,32 +62,6 @@ static const struct clk_mgr_mask disp_clk_mask = {
CLK_COMMON_MASK_SH_LIST_DCE_COMMON_BASE(_MASK)
};
-/* Max clock values for each state indexed by "enum clocks_state": */
-static const struct state_dependent_clocks dce60_max_clks_by_state[] = {
-/* ClocksStateInvalid - should not be used */
-{ .display_clk_khz = 0, .pixel_clk_khz = 0 },
-/* ClocksStateUltraLow - not expected to be used for DCE 6.0 */
-{ .display_clk_khz = 0, .pixel_clk_khz = 0 },
-/* ClocksStateLow */
-{ .display_clk_khz = 352000, .pixel_clk_khz = 330000},
-/* ClocksStateNominal */
-{ .display_clk_khz = 600000, .pixel_clk_khz = 400000 },
-/* ClocksStatePerformance */
-{ .display_clk_khz = 600000, .pixel_clk_khz = 400000 } };
-
-/* Max clock values for each state indexed by "enum clocks_state": */
-static const struct state_dependent_clocks dce80_max_clks_by_state[] = {
-/* ClocksStateInvalid - should not be used */
-{ .display_clk_khz = 0, .pixel_clk_khz = 0 },
-/* ClocksStateUltraLow - not expected to be used for DCE 8.0 */
-{ .display_clk_khz = 0, .pixel_clk_khz = 0 },
-/* ClocksStateLow */
-{ .display_clk_khz = 352000, .pixel_clk_khz = 330000},
-/* ClocksStateNominal */
-{ .display_clk_khz = 625000, .pixel_clk_khz = 400000 },
-/* ClocksStatePerformance */
-{ .display_clk_khz = 625000, .pixel_clk_khz = 400000 } };
-
unsigned int dentist_get_divider_from_did(unsigned int did)
{
if (did < DENTIST_BASE_DID_1)
@@ -220,40 +194,6 @@ uint32_t dce_get_max_pixel_clock_for_all_paths(struct dc_state *context)
return max_pix_clk;
}
-enum dm_pp_clocks_state dce_get_required_clocks_state(
- struct clk_mgr *clk_mgr_base,
- struct dc_state *context)
-{
- struct clk_mgr_internal *clk_mgr_dce = TO_CLK_MGR_INTERNAL(clk_mgr_base);
- int i;
- enum dm_pp_clocks_state low_req_clk;
- int max_pix_clk = dce_get_max_pixel_clock_for_all_paths(context);
-
- /* Iterate from highest supported to lowest valid state, and update
- * lowest RequiredState with the lowest state that satisfies
- * all required clocks
- */
- for (i = clk_mgr_dce->max_clks_state; i >= DM_PP_CLOCKS_STATE_ULTRA_LOW; i--)
- if (context->bw_ctx.bw.dce.dispclk_khz >
- clk_mgr_dce->max_clks_by_state[i].display_clk_khz
- || max_pix_clk >
- clk_mgr_dce->max_clks_by_state[i].pixel_clk_khz)
- break;
-
- low_req_clk = i + 1;
- if (low_req_clk > clk_mgr_dce->max_clks_state) {
- /* set max clock state for high phyclock, invalid on exceeding display clock */
- if (clk_mgr_dce->max_clks_by_state[clk_mgr_dce->max_clks_state].display_clk_khz
- < context->bw_ctx.bw.dce.dispclk_khz)
- low_req_clk = DM_PP_CLOCKS_STATE_INVALID;
- else
- low_req_clk = clk_mgr_dce->max_clks_state;
- }
-
- return low_req_clk;
-}
-
-
/* TODO: remove use the two broken down functions */
int dce_set_clock(
struct clk_mgr *clk_mgr_base,
@@ -291,11 +231,6 @@ int dce_set_clock(
actual_clock = pxl_clk_params.dfs_bypass_display_clock;
}
- /* from power down, we need mark the clock state as ClocksStateNominal
- * from HWReset, so when resume we will call pplib voltage regulator.*/
- if (requested_clk_khz == 0)
- clk_mgr_dce->cur_min_clks_state = DM_PP_CLOCKS_STATE_NOMINAL;
-
if (dmcu && dmcu->funcs->is_dmcu_initialized(dmcu))
dmcu->funcs->set_psr_wait_loop(dmcu, actual_clock / 1000 / 7);
@@ -307,7 +242,6 @@ static void dce_clock_read_integrated_info(struct clk_mgr_internal *clk_mgr_dce)
{
struct dc_debug_options *debug = &clk_mgr_dce->base.ctx->dc->debug;
struct dc_bios *bp = clk_mgr_dce->base.ctx->dc_bios;
- int i;
if (bp->integrated_info)
clk_mgr_dce->base.dentist_vco_freq_khz = bp->integrated_info->dentist_vco_freq;
@@ -317,40 +251,6 @@ static void dce_clock_read_integrated_info(struct clk_mgr_internal *clk_mgr_dce)
clk_mgr_dce->base.dentist_vco_freq_khz = 3600000;
}
- /*update the maximum display clock for each power state*/
- for (i = 0; i < NUMBER_OF_DISP_CLK_VOLTAGE; ++i) {
- enum dm_pp_clocks_state clk_state = DM_PP_CLOCKS_STATE_INVALID;
-
- switch (i) {
- case 0:
- clk_state = DM_PP_CLOCKS_STATE_ULTRA_LOW;
- break;
-
- case 1:
- clk_state = DM_PP_CLOCKS_STATE_LOW;
- break;
-
- case 2:
- clk_state = DM_PP_CLOCKS_STATE_NOMINAL;
- break;
-
- case 3:
- clk_state = DM_PP_CLOCKS_STATE_PERFORMANCE;
- break;
-
- default:
- clk_state = DM_PP_CLOCKS_STATE_INVALID;
- break;
- }
-
- /*Do not allow bad VBIOS/SBIOS to override with invalid values,
- * check for > 100MHz*/
- if (bp->integrated_info)
- if (bp->integrated_info->disp_clk_voltage[i].max_supported_clk >= 100000)
- clk_mgr_dce->max_clks_by_state[clk_state].display_clk_khz =
- bp->integrated_info->disp_clk_voltage[i].max_supported_clk;
- }
-
if (!debug->disable_dfs_bypass && bp->integrated_info)
if (bp->integrated_info->gpu_cap_info & DFS_BYPASS_ENABLE)
clk_mgr_dce->dfs_bypass_enabled = true;
@@ -430,20 +330,9 @@ static void dce_update_clocks(struct clk_mgr *clk_mgr_base,
struct dc_state *context,
bool safe_to_lower)
{
- struct clk_mgr_internal *clk_mgr_dce = TO_CLK_MGR_INTERNAL(clk_mgr_base);
- struct dm_pp_power_level_change_request level_change_req;
- const int max_disp_clk =
- clk_mgr_dce->max_clks_by_state[DM_PP_CLOCKS_STATE_PERFORMANCE].display_clk_khz;
+ const int max_disp_clk = clk_mgr_base->clks.max_supported_dispclk_khz;
int patched_disp_clk = MIN(max_disp_clk, context->bw_ctx.bw.dce.dispclk_khz);
- level_change_req.power_level = dce_get_required_clocks_state(clk_mgr_base, context);
- /* get max clock state from PPLIB */
- if ((level_change_req.power_level < clk_mgr_dce->cur_min_clks_state && safe_to_lower)
- || level_change_req.power_level > clk_mgr_dce->cur_min_clks_state) {
- if (dm_pp_apply_power_level_change_request(clk_mgr_base->ctx, &level_change_req))
- clk_mgr_dce->cur_min_clks_state = level_change_req.power_level;
- }
-
if (should_set_clock(safe_to_lower, patched_disp_clk, clk_mgr_base->clks.dispclk_khz)) {
patched_disp_clk = dce_set_clock(clk_mgr_base, patched_disp_clk);
clk_mgr_base->clks.dispclk_khz = patched_disp_clk;
@@ -468,17 +357,6 @@ void dce_clk_mgr_construct(
struct clk_mgr_internal *clk_mgr)
{
struct clk_mgr *base = &clk_mgr->base;
- struct dm_pp_static_clock_info static_clk_info = {0};
-
- if (ctx->dce_version <= DCE_VERSION_6_4)
- memcpy(clk_mgr->max_clks_by_state,
- dce60_max_clks_by_state,
- sizeof(dce60_max_clks_by_state));
- else
- memcpy(clk_mgr->max_clks_by_state,
- dce80_max_clks_by_state,
- sizeof(dce80_max_clks_by_state));
-
base->ctx = ctx;
base->funcs = &dce_funcs;
@@ -494,16 +372,16 @@ void dce_clk_mgr_construct(
clk_mgr->dprefclk_ss_divider = 1000;
clk_mgr->ss_on_dprefclk = false;
- if (ctx->dce_version >= DCE_VERSION_8_0) {
- if (dm_pp_get_static_clocks(ctx, &static_clk_info))
- clk_mgr->max_clks_state = static_clk_info.max_clocks_state;
- else
- clk_mgr->max_clks_state = DM_PP_CLOCKS_STATE_NOMINAL;
- clk_mgr->cur_min_clks_state = DM_PP_CLOCKS_STATE_INVALID;
- }
-
- base->clks.max_supported_dispclk_khz =
- clk_mgr->max_clks_by_state[DM_PP_CLOCKS_STATE_PERFORMANCE].display_clk_khz;
+ if (ctx->dce_version >= DCE_VERSION_12_0)
+ base->clks.max_supported_dispclk_khz = 1133000;
+ else if (ctx->dce_version >= DCE_VERSION_11_2)
+ base->clks.max_supported_dispclk_khz = 1132000;
+ else if (ctx->dce_version >= DCE_VERSION_11_0)
+ base->clks.max_supported_dispclk_khz = 643000;
+ else if (ctx->dce_version >= DCE_VERSION_8_0)
+ base->clks.max_supported_dispclk_khz = 625000;
+ else
+ base->clks.max_supported_dispclk_khz = 600000;
dce_clock_read_integrated_info(clk_mgr);
dce_clock_read_ss_info(clk_mgr);
diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.h b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.h
index 9ea1b0a9923da..e1c6a92d7957f 100644
--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.h
+++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce100/dce_clk_mgr.h
@@ -32,9 +32,6 @@
/* functions shared by other dce clk mgrs */
int dce_adjust_dp_ref_freq_for_ss(struct clk_mgr_internal *clk_mgr_dce, int dp_ref_clk_khz);
int dce_get_dp_ref_freq_khz(struct clk_mgr *clk_mgr_base);
-enum dm_pp_clocks_state dce_get_required_clocks_state(
- struct clk_mgr *clk_mgr_base,
- struct dc_state *context);
uint32_t dce_get_max_pixel_clock_for_all_paths(struct dc_state *context);
diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c
index b35a44976477a..d52789ba26137 100644
--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c
+++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce110/dce110_clk_mgr.c
@@ -51,18 +51,6 @@ static const struct clk_mgr_mask disp_clk_mask = {
CLK_COMMON_MASK_SH_LIST_DCE_COMMON_BASE(_MASK)
};
-static const struct state_dependent_clocks dce110_max_clks_by_state[] = {
-/*ClocksStateInvalid - should not be used*/
-{ .display_clk_khz = 0, .pixel_clk_khz = 0 },
-/*ClocksStateUltraLow - currently by HW design team not supposed to be used*/
-{ .display_clk_khz = 352000, .pixel_clk_khz = 330000 },
-/*ClocksStateLow*/
-{ .display_clk_khz = 352000, .pixel_clk_khz = 330000 },
-/*ClocksStateNominal*/
-{ .display_clk_khz = 467000, .pixel_clk_khz = 400000 },
-/*ClocksStatePerformance*/
-{ .display_clk_khz = 643000, .pixel_clk_khz = 400000 } };
-
static uint32_t determine_sclk_from_bounding_box(
const struct dc *dc,
uint32_t required_sclk)
@@ -257,21 +245,12 @@ static void dce11_update_clocks(struct clk_mgr *clk_mgr_base,
bool safe_to_lower)
{
struct clk_mgr_internal *clk_mgr_dce = TO_CLK_MGR_INTERNAL(clk_mgr_base);
- struct dm_pp_power_level_change_request level_change_req;
int patched_disp_clk = context->bw_ctx.bw.dce.dispclk_khz;
/*TODO: W/A for dal3 linux, investigate why this works */
if (!clk_mgr_dce->dfs_bypass_active)
patched_disp_clk = patched_disp_clk * 115 / 100;
- level_change_req.power_level = dce_get_required_clocks_state(clk_mgr_base, context);
- /* get max clock state from PPLIB */
- if ((level_change_req.power_level < clk_mgr_dce->cur_min_clks_state && safe_to_lower)
- || level_change_req.power_level > clk_mgr_dce->cur_min_clks_state) {
- if (dm_pp_apply_power_level_change_request(clk_mgr_base->ctx, &level_change_req))
- clk_mgr_dce->cur_min_clks_state = level_change_req.power_level;
- }
-
if (should_set_clock(safe_to_lower, patched_disp_clk, clk_mgr_base->clks.dispclk_khz)) {
context->bw_ctx.bw.dce.dispclk_khz = dce_set_clock(clk_mgr_base, patched_disp_clk);
clk_mgr_base->clks.dispclk_khz = patched_disp_clk;
@@ -290,10 +269,6 @@ void dce110_clk_mgr_construct(
{
dce_clk_mgr_construct(ctx, clk_mgr);
- memcpy(clk_mgr->max_clks_by_state,
- dce110_max_clks_by_state,
- sizeof(dce110_max_clks_by_state));
-
clk_mgr->regs = &disp_clk_regs;
clk_mgr->clk_mgr_shift = &disp_clk_shift;
clk_mgr->clk_mgr_mask = &disp_clk_mask;
diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c
index 1f36ad8a7de46..08ed6f88025fa 100644
--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c
+++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce112/dce112_clk_mgr.c
@@ -53,19 +53,6 @@ static const struct clk_mgr_mask disp_clk_mask = {
CLK_COMMON_MASK_SH_LIST_DCE_COMMON_BASE(_MASK)
};
-static const struct state_dependent_clocks dce112_max_clks_by_state[] = {
-/*ClocksStateInvalid - should not be used*/
-{ .display_clk_khz = 0, .pixel_clk_khz = 0 },
-/*ClocksStateUltraLow - currently by HW design team not supposed to be used*/
-{ .display_clk_khz = 389189, .pixel_clk_khz = 346672 },
-/*ClocksStateLow*/
-{ .display_clk_khz = 459000, .pixel_clk_khz = 400000 },
-/*ClocksStateNominal*/
-{ .display_clk_khz = 667000, .pixel_clk_khz = 600000 },
-/*ClocksStatePerformance*/
-{ .display_clk_khz = 1132000, .pixel_clk_khz = 600000 } };
-
-
//TODO: remove use the two broken down functions
int dce112_set_clock(struct clk_mgr *clk_mgr_base, int requested_clk_khz)
{
@@ -89,13 +76,6 @@ int dce112_set_clock(struct clk_mgr *clk_mgr_base, int requested_clk_khz)
bp->funcs->set_dce_clock(bp, &dce_clk_params);
actual_clock = dce_clk_params.target_clock_frequency;
- /*
- * from power down, we need mark the clock state as ClocksStateNominal
- * from HWReset, so when resume we will call pplib voltage regulator.
- */
- if (requested_clk_khz == 0)
- clk_mgr_dce->cur_min_clks_state = DM_PP_CLOCKS_STATE_NOMINAL;
-
/*Program DP ref Clock*/
/*VBIOS will determine DPREFCLK frequency, so we don't set it*/
dce_clk_params.target_clock_frequency = 0;
@@ -143,14 +123,6 @@ int dce112_set_dispclk(struct clk_mgr_internal *clk_mgr, int requested_clk_khz)
bp->funcs->set_dce_clock(bp, &dce_clk_params);
actual_clock = dce_clk_params.target_clock_frequency;
- /*
- * from power down, we need mark the clock state as ClocksStateNominal
- * from HWReset, so when resume we will call pplib voltage regulator.
- */
- if (requested_clk_khz == 0)
- clk_mgr->cur_min_clks_state = DM_PP_CLOCKS_STATE_NOMINAL;
-
-
if (dmcu && dmcu->funcs->is_dmcu_initialized(dmcu)) {
if (clk_mgr->dfs_bypass_disp_clk != actual_clock)
dmcu->funcs->set_psr_wait_loop(dmcu,
@@ -193,21 +165,12 @@ static void dce112_update_clocks(struct clk_mgr *clk_mgr_base,
bool safe_to_lower)
{
struct clk_mgr_internal *clk_mgr_dce = TO_CLK_MGR_INTERNAL(clk_mgr_base);
- struct dm_pp_power_level_change_request level_change_req;
int patched_disp_clk = context->bw_ctx.bw.dce.dispclk_khz;
/*TODO: W/A for dal3 linux, investigate why this works */
if (!clk_mgr_dce->dfs_bypass_active)
patched_disp_clk = patched_disp_clk * 115 / 100;
- level_change_req.power_level = dce_get_required_clocks_state(clk_mgr_base, context);
- /* get max clock state from PPLIB */
- if ((level_change_req.power_level < clk_mgr_dce->cur_min_clks_state && safe_to_lower)
- || level_change_req.power_level > clk_mgr_dce->cur_min_clks_state) {
- if (dm_pp_apply_power_level_change_request(clk_mgr_base->ctx, &level_change_req))
- clk_mgr_dce->cur_min_clks_state = level_change_req.power_level;
- }
-
if (should_set_clock(safe_to_lower, patched_disp_clk, clk_mgr_base->clks.dispclk_khz)) {
patched_disp_clk = dce112_set_clock(clk_mgr_base, patched_disp_clk);
clk_mgr_base->clks.dispclk_khz = patched_disp_clk;
@@ -226,10 +189,6 @@ void dce112_clk_mgr_construct(
{
dce_clk_mgr_construct(ctx, clk_mgr);
- memcpy(clk_mgr->max_clks_by_state,
- dce112_max_clks_by_state,
- sizeof(dce112_max_clks_by_state));
-
clk_mgr->regs = &disp_clk_regs;
clk_mgr->clk_mgr_shift = &disp_clk_shift;
clk_mgr->clk_mgr_mask = &disp_clk_mask;
diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce120/dce120_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce120/dce120_clk_mgr.c
index c9ba7b3fd2c32..f8ef3a4710fc2 100644
--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dce120/dce120_clk_mgr.c
+++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dce120/dce120_clk_mgr.c
@@ -32,18 +32,6 @@
#include "dce100/dce_clk_mgr.h"
#include "dce120/dce120_hwseq.h"
-static const struct state_dependent_clocks dce120_max_clks_by_state[] = {
-/*ClocksStateInvalid - should not be used*/
-{ .display_clk_khz = 0, .pixel_clk_khz = 0 },
-/*ClocksStateUltraLow - currently by HW design team not supposed to be used*/
-{ .display_clk_khz = 0, .pixel_clk_khz = 0 },
-/*ClocksStateLow*/
-{ .display_clk_khz = 460000, .pixel_clk_khz = 400000 },
-/*ClocksStateNominal*/
-{ .display_clk_khz = 670000, .pixel_clk_khz = 600000 },
-/*ClocksStatePerformance*/
-{ .display_clk_khz = 1133000, .pixel_clk_khz = 600000 } };
-
/**
* dce121_clock_patch_xgmi_ss_info() - Save XGMI spread spectrum info
* @clk_mgr_dce: clock manager internal structure
@@ -129,10 +117,6 @@ void dce120_clk_mgr_construct(struct dc_context *ctx, struct clk_mgr_internal *c
{
dce_clk_mgr_construct(ctx, clk_mgr);
- memcpy(clk_mgr->max_clks_by_state,
- dce120_max_clks_by_state,
- sizeof(dce120_max_clks_by_state));
-
clk_mgr->base.dprefclk_khz = 600000;
clk_mgr->base.funcs = &dce120_funcs;
}
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_debug.c b/drivers/gpu/drm/amd/display/dc/core/dc_debug.c
index bbce751b485fd..44028ba88f809 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc_debug.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_debug.c
@@ -272,9 +272,9 @@ char *dc_status_to_str(enum dc_status status)
return "Fail DP Tunnel BW validation";
case DC_ERROR_UNEXPECTED:
return "Unexpected error";
+ default:
+ return "Unexpected status error";
}
-
- return "Unexpected status error";
}
char *dc_pixel_encoding_to_str(enum dc_pixel_encoding pixel_encoding)
diff --git a/drivers/gpu/drm/amd/display/dc/dm_services.h b/drivers/gpu/drm/amd/display/dc/dm_services.h
index fbbf9c757b3c3..8b062b011fc65 100644
--- a/drivers/gpu/drm/amd/display/dc/dm_services.h
+++ b/drivers/gpu/drm/amd/display/dc/dm_services.h
@@ -224,18 +224,10 @@ bool dm_pp_apply_display_requirements(
const struct dc_context *ctx,
const struct dm_pp_display_configuration *pp_display_cfg);
-bool dm_pp_apply_power_level_change_request(
- const struct dc_context *ctx,
- struct dm_pp_power_level_change_request *level_change_req);
-
bool dm_pp_apply_clock_for_voltage_request(
const struct dc_context *ctx,
struct dm_pp_clock_for_voltage_req *clock_for_voltage_req);
-bool dm_pp_get_static_clocks(
- const struct dc_context *ctx,
- struct dm_pp_static_clock_info *static_clk_info);
-
/****** end of PP interfaces ******/
struct persistent_data_flag {
diff --git a/drivers/gpu/drm/amd/display/dc/dm_services_types.h b/drivers/gpu/drm/amd/display/dc/dm_services_types.h
index 3b093b8699abd..b3505d93503fd 100644
--- a/drivers/gpu/drm/amd/display/dc/dm_services_types.h
+++ b/drivers/gpu/drm/amd/display/dc/dm_services_types.h
@@ -36,30 +36,7 @@ struct dm_pp_clock_range {
int max_khz;
};
-enum dm_pp_clocks_state {
- DM_PP_CLOCKS_STATE_INVALID,
- DM_PP_CLOCKS_STATE_ULTRA_LOW,
- DM_PP_CLOCKS_STATE_LOW,
- DM_PP_CLOCKS_STATE_NOMINAL,
- DM_PP_CLOCKS_STATE_PERFORMANCE,
-
- /* Starting from DCE11, Max 8 levels of DPM state supported. */
- DM_PP_CLOCKS_DPM_STATE_LEVEL_INVALID = DM_PP_CLOCKS_STATE_INVALID,
- DM_PP_CLOCKS_DPM_STATE_LEVEL_0,
- DM_PP_CLOCKS_DPM_STATE_LEVEL_1,
- DM_PP_CLOCKS_DPM_STATE_LEVEL_2,
- /* to be backward compatible */
- DM_PP_CLOCKS_DPM_STATE_LEVEL_3,
- DM_PP_CLOCKS_DPM_STATE_LEVEL_4,
- DM_PP_CLOCKS_DPM_STATE_LEVEL_5,
- DM_PP_CLOCKS_DPM_STATE_LEVEL_6,
- DM_PP_CLOCKS_DPM_STATE_LEVEL_7,
-
- DM_PP_CLOCKS_MAX_STATES
-};
-
struct dm_pp_gpu_clock_range {
- enum dm_pp_clocks_state clock_state;
struct dm_pp_clock_range sclk;
struct dm_pp_clock_range mclk;
struct dm_pp_clock_range eclk;
@@ -246,10 +223,6 @@ enum dm_acpi_display_type {
AcpiDisplayType_DFP6 = 12
};
-struct dm_pp_power_level_change_request {
- enum dm_pp_clocks_state power_level;
-};
-
struct dm_pp_clock_for_voltage_req {
enum dm_pp_clock_type clk_type;
uint32_t clocks_in_khz;
@@ -258,9 +231,6 @@ struct dm_pp_clock_for_voltage_req {
struct dm_pp_static_clock_info {
uint32_t max_sclk_khz;
uint32_t max_mclk_khz;
-
- /* max possible display block clocks state */
- enum dm_pp_clocks_state max_clocks_state;
};
struct dtn_min_clk_info {
diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h
index c69ccfcebeb5a..450bce3d8e02c 100644
--- a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h
+++ b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr_internal.h
@@ -405,12 +405,6 @@ enum clock_type {
clock_type_dtbclk,
};
-
-struct state_dependent_clocks {
- int display_clk_khz;
- int pixel_clk_khz;
-};
-
struct clk_mgr_internal {
struct clk_mgr base;
int smu_ver;
@@ -429,8 +423,6 @@ struct clk_mgr_internal {
const struct clk_mgr_shift *clk_mgr_shift;
const struct clk_mgr_mask *clk_mgr_mask;
- struct state_dependent_clocks max_clks_by_state[DM_PP_CLOCKS_MAX_STATES];
-
/*TODO: figure out which of the below fields should be here vs in asic specific portion */
/* Cache the status of DFS-bypass feature*/
bool dfs_bypass_enabled;
@@ -477,8 +469,6 @@ struct clk_mgr_internal {
*/
int dprefclk_ss_divider;
- enum dm_pp_clocks_state max_clks_state;
- enum dm_pp_clocks_state cur_min_clks_state;
bool periodic_retraining_disabled;
unsigned int cur_phyclk_req_table[MAX_LINKS];
diff --git a/drivers/gpu/drm/amd/display/include/grph_object_ctrl_defs.h b/drivers/gpu/drm/amd/display/include/grph_object_ctrl_defs.h
index a0f03fb67605e..3e4e8d55dd0fd 100644
--- a/drivers/gpu/drm/amd/display/include/grph_object_ctrl_defs.h
+++ b/drivers/gpu/drm/amd/display/include/grph_object_ctrl_defs.h
@@ -273,7 +273,6 @@ struct transmitter_configuration {
#define NUMBER_OF_UCHAR_FOR_GUID 16
#define MAX_NUMBER_OF_EXT_DISPLAY_PATH 7
#define NUMBER_OF_CSR_M3_ARB 10
-#define NUMBER_OF_DISP_CLK_VOLTAGE 4
#define NUMBER_OF_AVAILABLE_SCLK 5
struct i2c_reg_info {
@@ -302,14 +301,6 @@ struct edp_info {
/* V6 */
struct integrated_info {
- struct clock_voltage_caps {
- /* The Voltage Index indicated by FUSE, same voltage index
- shared with SCLK DPM fuse table */
- uint32_t voltage_index;
- /* Maximum clock supported with specified voltage index */
- uint32_t max_supported_clk; /* in KHz */
- } disp_clk_voltage[NUMBER_OF_DISP_CLK_VOLTAGE];
-
struct display_connection_info {
struct external_display_path {
/* A bit vector to show what devices are supported */
diff --git a/drivers/gpu/drm/amd/include/dm_pp_interface.h b/drivers/gpu/drm/amd/include/dm_pp_interface.h
index 349544504c93c..e3d40fb371039 100644
--- a/drivers/gpu/drm/amd/include/dm_pp_interface.h
+++ b/drivers/gpu/drm/amd/include/dm_pp_interface.h
@@ -113,24 +113,6 @@ struct amd_pp_display_configuration {
struct amd_pp_simple_clock_info {
uint32_t engine_max_clock;
uint32_t memory_max_clock;
- uint32_t level;
-};
-
-enum PP_DAL_POWERLEVEL {
- PP_DAL_POWERLEVEL_INVALID = 0,
- PP_DAL_POWERLEVEL_ULTRALOW,
- PP_DAL_POWERLEVEL_LOW,
- PP_DAL_POWERLEVEL_NOMINAL,
- PP_DAL_POWERLEVEL_PERFORMANCE,
-
- PP_DAL_POWERLEVEL_0 = PP_DAL_POWERLEVEL_ULTRALOW,
- PP_DAL_POWERLEVEL_1 = PP_DAL_POWERLEVEL_LOW,
- PP_DAL_POWERLEVEL_2 = PP_DAL_POWERLEVEL_NOMINAL,
- PP_DAL_POWERLEVEL_3 = PP_DAL_POWERLEVEL_PERFORMANCE,
- PP_DAL_POWERLEVEL_4 = PP_DAL_POWERLEVEL_3+1,
- PP_DAL_POWERLEVEL_5 = PP_DAL_POWERLEVEL_4+1,
- PP_DAL_POWERLEVEL_6 = PP_DAL_POWERLEVEL_5+1,
- PP_DAL_POWERLEVEL_7 = PP_DAL_POWERLEVEL_6+1,
};
struct amd_pp_clock_info {
@@ -142,7 +124,6 @@ struct amd_pp_clock_info {
uint32_t max_bus_bandwidth;
uint32_t max_engine_clock_in_sr;
uint32_t min_engine_clock_in_sr;
- enum PP_DAL_POWERLEVEL max_clocks_state;
};
enum amd_pp_clock_type {
diff --git a/drivers/gpu/drm/amd/include/kgd_pp_interface.h b/drivers/gpu/drm/amd/include/kgd_pp_interface.h
index 1bbf531de5ed7..6371f292f2d80 100644
--- a/drivers/gpu/drm/amd/include/kgd_pp_interface.h
+++ b/drivers/gpu/drm/amd/include/kgd_pp_interface.h
@@ -417,7 +417,7 @@ struct amd_pm_funcs {
void (*display_configuration_changed)(void *handle);
void (*print_power_state)(void *handle, void *ps);
bool (*vblank_too_short)(void *handle);
- void (*enable_bapm)(void *handle, bool enable);
+ void (*notify_ac_dc)(void *handle);
int (*check_state_equal)(void *handle,
void *cps,
void *rps,
@@ -476,8 +476,6 @@ struct amd_pm_funcs {
u32 (*get_mclk)(void *handle, bool low);
int (*display_configuration_change)(void *handle,
const struct amd_pp_display_configuration *input);
- int (*get_display_power_level)(void *handle,
- struct amd_pp_simple_clock_info *output);
int (*get_current_clocks)(void *handle,
struct amd_pp_clock_info *clocks);
int (*get_clock_by_type)(void *handle,
diff --git a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c
index feadf604b4749..f76ba67535519 100644
--- a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c
+++ b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c
@@ -33,8 +33,8 @@
#include <linux/power_supply.h>
#include "amdgpu_smu.h"
-#define amdgpu_dpm_enable_bapm(adev, e) \
- ((adev)->powerplay.pp_funcs->enable_bapm((adev)->powerplay.pp_handle, (e)))
+#define amdgpu_dpm_notify_ac_dc(adev) \
+ ((adev)->powerplay.pp_funcs->notify_ac_dc((adev)->powerplay.pp_handle))
#define amdgpu_dpm_is_legacy_dpm(adev) ((adev)->powerplay.pp_handle == (adev))
@@ -504,8 +504,8 @@ void amdgpu_pm_acpi_event_handler(struct amdgpu_device *adev)
adev->pm.ac_power = false;
if (adev->powerplay.pp_funcs &&
- adev->powerplay.pp_funcs->enable_bapm)
- amdgpu_dpm_enable_bapm(adev, adev->pm.ac_power);
+ adev->powerplay.pp_funcs->notify_ac_dc)
+ amdgpu_dpm_notify_ac_dc(adev);
if (is_support_sw_smu(adev))
smu_set_ac_dc(adev->powerplay.pp_handle);
diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/kv_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/kv_dpm.c
index cdf3f8e6ec2fd..b763fdf43c603 100644
--- a/drivers/gpu/drm/amd/pm/legacy-dpm/kv_dpm.c
+++ b/drivers/gpu/drm/amd/pm/legacy-dpm/kv_dpm.c
@@ -1233,14 +1233,14 @@ static void kv_update_requested_ps(struct amdgpu_device *adev,
adev->pm.dpm.requested_ps = &pi->requested_rps;
}
-static void kv_dpm_enable_bapm(void *handle, bool enable)
+static void kv_dpm_enable_bapm(void *handle)
{
struct amdgpu_device *adev = (struct amdgpu_device *)handle;
struct kv_power_info *pi = kv_get_pi(adev);
int ret;
if (pi->bapm_enable) {
- ret = amdgpu_kv_smc_bapm_enable(adev, enable);
+ ret = amdgpu_kv_smc_bapm_enable(adev, adev->pm.ac_power);
if (ret)
drm_err(adev_to_drm(adev), "amdgpu_kv_smc_bapm_enable failed\n");
}
@@ -3341,7 +3341,7 @@ static const struct amd_pm_funcs kv_dpm_funcs = {
.debugfs_print_current_performance_level = &kv_dpm_debugfs_print_current_performance_level,
.force_performance_level = &kv_dpm_force_performance_level,
.set_powergating_by_smu = kv_set_powergating_by_smu,
- .enable_bapm = &kv_dpm_enable_bapm,
+ .notify_ac_dc = &kv_dpm_enable_bapm,
.get_vce_clock_state = amdgpu_get_vce_clock_state,
.check_state_equal = kv_check_state_equal,
.read_sensor = &kv_dpm_read_sensor,
diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c
index c3aff5d0c53dc..8079da7c53350 100644
--- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c
+++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c
@@ -3888,16 +3888,18 @@ static void si_notify_hardware_vpu_recovery_event(struct amdgpu_device *adev)
}
#endif
-#if 0
-static int si_notify_hw_of_powersource(struct amdgpu_device *adev, bool ac_power)
+static void si_notify_hw_of_powersource(void *handle)
{
- if (ac_power)
- return (amdgpu_si_send_msg_to_smc(adev, PPSMC_MSG_RunningOnAC) == PPSMC_Result_OK) ?
- 0 : -EINVAL;
+ struct amdgpu_device *adev = (struct amdgpu_device *)handle;
- return 0;
+ /* Check if the platform already manages the AC/DC switch via dedicated GPIO. */
+ if (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_HARDWAREDC)
+ return;
+
+ /* The SMU automatically notices DC, but needs to be notified when switching to AC. */
+ if (adev->pm.ac_power)
+ amdgpu_si_send_msg_to_smc(adev, PPSMC_MSG_RunningOnAC);
}
-#endif
static PPSMC_Result si_send_msg_to_smc_with_parameter(struct amdgpu_device *adev,
PPSMC_Msg msg, u32 parameter)
@@ -7240,6 +7242,7 @@ static void si_parse_pplib_clock_info(struct amdgpu_device *adev,
struct evergreen_power_info *eg_pi = evergreen_get_pi(adev);
struct si_power_info *si_pi = si_get_pi(adev);
struct si_ps *ps = si_get_ps(rps);
+ struct amdgpu_clock_and_voltage_limits *limits;
u16 leakage_voltage;
struct rv7xx_pl *pl = &ps->performance_levels[index];
int ret;
@@ -7299,12 +7302,30 @@ static void si_parse_pplib_clock_info(struct amdgpu_device *adev,
si_pi->mvdd_bootup_value = mvdd;
}
+ /*
+ * Update maximum allowed clock limits.
+ * VBIOS can contain conflicting values between:
+ * - the maximum allowed clocks and voltages on AC or DC
+ * - the clocks and voltages in power states on AC or DC
+ */
if ((rps->class & ATOM_PPLIB_CLASSIFICATION_UI_MASK) ==
- ATOM_PPLIB_CLASSIFICATION_UI_PERFORMANCE) {
- adev->pm.dpm.dyn_state.max_clock_voltage_on_ac.sclk = pl->sclk;
- adev->pm.dpm.dyn_state.max_clock_voltage_on_ac.mclk = pl->mclk;
- adev->pm.dpm.dyn_state.max_clock_voltage_on_ac.vddc = pl->vddc;
- adev->pm.dpm.dyn_state.max_clock_voltage_on_ac.vddci = pl->vddci;
+ ATOM_PPLIB_CLASSIFICATION_UI_PERFORMANCE)
+ limits = &adev->pm.dpm.dyn_state.max_clock_voltage_on_ac;
+ else if ((rps->class & ATOM_PPLIB_CLASSIFICATION_UI_MASK) ==
+ ATOM_PPLIB_CLASSIFICATION_UI_BATTERY)
+ limits = &adev->pm.dpm.dyn_state.max_clock_voltage_on_dc;
+ else
+ limits = NULL;
+
+ if (limits) {
+ if (pl->sclk > limits->sclk)
+ limits->sclk = pl->sclk;
+ if (pl->mclk > limits->mclk)
+ limits->mclk = pl->mclk;
+ if (pl->vddc > limits->vddc)
+ limits->vddc = pl->vddc;
+ if (pl->vddci > limits->vddci)
+ limits->vddci = pl->vddci;
}
}
@@ -8144,6 +8165,7 @@ static const struct amd_pm_funcs si_dpm_funcs = {
.get_vce_clock_state = amdgpu_get_vce_clock_state,
.read_sensor = &si_dpm_read_sensor,
.pm_compute_clocks = amdgpu_legacy_dpm_compute_clocks,
+ .notify_ac_dc = si_notify_hw_of_powersource,
};
static const struct amdgpu_irq_src_funcs si_dpm_irq_funcs = {
diff --git a/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c b/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c
index e558b81b25c91..5700bcc7ad9ab 100644
--- a/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c
+++ b/drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c
@@ -1020,21 +1020,9 @@ static int pp_display_configuration_change(void *handle,
return 0;
}
-static int pp_get_display_power_level(void *handle,
- struct amd_pp_simple_clock_info *output)
-{
- struct pp_hwmgr *hwmgr = handle;
-
- if (!hwmgr || !hwmgr->pm_en || !output)
- return -EINVAL;
-
- return phm_get_dal_power_level(hwmgr, output);
-}
-
static int pp_get_current_clocks(void *handle,
struct amd_pp_clock_info *clocks)
{
- struct amd_pp_simple_clock_info simple_clocks = { 0 };
struct pp_clock_info hw_clocks;
struct pp_hwmgr *hwmgr = handle;
int ret = 0;
@@ -1042,8 +1030,6 @@ static int pp_get_current_clocks(void *handle,
if (!hwmgr || !hwmgr->pm_en)
return -EINVAL;
- phm_get_dal_power_level(hwmgr, &simple_clocks);
-
if (phm_cap_enabled(hwmgr->platform_descriptor.platformCaps,
PHM_PlatformCaps_PowerContainment))
ret = phm_get_clock_info(hwmgr, &hwmgr->current_ps->hardware,
@@ -1068,11 +1054,6 @@ static int pp_get_current_clocks(void *handle,
clocks->max_engine_clock_in_sr = hw_clocks.max_eng_clk;
clocks->min_engine_clock_in_sr = hw_clocks.min_eng_clk;
- if (simple_clocks.level == 0)
- clocks->max_clocks_state = PP_DAL_POWERLEVEL_7;
- else
- clocks->max_clocks_state = simple_clocks.level;
-
if (0 == phm_get_current_shallow_sleep_clocks(hwmgr, &hwmgr->current_ps->hardware, &hw_clocks)) {
clocks->max_engine_clock_in_sr = hw_clocks.max_eng_clk;
clocks->min_engine_clock_in_sr = hw_clocks.min_eng_clk;
@@ -1149,8 +1130,6 @@ static int pp_get_display_mode_validation_clocks(void *handle,
if (!hwmgr || !hwmgr->pm_en || !clocks)
return -EINVAL;
- clocks->level = PP_DAL_POWERLEVEL_7;
-
if (phm_cap_enabled(hwmgr->platform_descriptor.platformCaps, PHM_PlatformCaps_DynamicPatchPowerState))
ret = phm_get_max_high_clocks(hwmgr, clocks);
@@ -1550,6 +1529,17 @@ static void pp_pm_compute_clocks(void *handle)
NULL);
}
+static void pp_dpm_notify_ac_dc(void *handle)
+{
+ struct pp_hwmgr *hwmgr = handle;
+
+ if (!hwmgr || !hwmgr->pm_en)
+ return;
+
+ if (hwmgr->hwmgr_func->notify_ac_dc)
+ hwmgr->hwmgr_func->notify_ac_dc(hwmgr);
+}
+
static const struct amd_pm_funcs pp_dpm_funcs = {
.load_firmware = pp_dpm_load_fw,
.wait_for_fw_loading_complete = pp_dpm_fw_loading_complete,
@@ -1588,7 +1578,6 @@ static const struct amd_pm_funcs pp_dpm_funcs = {
.get_sclk = pp_dpm_get_sclk,
.get_mclk = pp_dpm_get_mclk,
.display_configuration_change = pp_display_configuration_change,
- .get_display_power_level = pp_get_display_power_level,
.get_current_clocks = pp_get_current_clocks,
.get_clock_by_type = pp_get_clock_by_type,
.get_clock_by_type_with_latency = pp_get_clock_by_type_with_latency,
@@ -1615,4 +1604,5 @@ static const struct amd_pm_funcs pp_dpm_funcs = {
.gfx_state_change_set = pp_gfx_state_change_set,
.get_smu_prv_buf_details = pp_get_prv_buffer_details,
.pm_compute_clocks = pp_pm_compute_clocks,
+ .notify_ac_dc = pp_dpm_notify_ac_dc,
};
diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hardwaremanager.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hardwaremanager.c
index a59677cf8dfc8..72c2d3b69a038 100644
--- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hardwaremanager.c
+++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hardwaremanager.c
@@ -328,16 +328,6 @@ int phm_store_dal_configuration_data(struct pp_hwmgr *hwmgr,
return 0;
}
-int phm_get_dal_power_level(struct pp_hwmgr *hwmgr,
- struct amd_pp_simple_clock_info *info)
-{
- PHM_FUNC_CHECK(hwmgr);
-
- if (info == NULL || hwmgr->hwmgr_func->get_dal_power_level == NULL)
- return -EINVAL;
- return hwmgr->hwmgr_func->get_dal_power_level(hwmgr, info);
-}
-
int phm_set_cpu_power_state(struct pp_hwmgr *hwmgr)
{
PHM_FUNC_CHECK(hwmgr);
diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c
index 59af3314ffc40..bfd8fbb0b49dd 100644
--- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c
+++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/processpptables.c
@@ -1319,7 +1319,6 @@ static int init_clock_voltage_dependency(struct pp_hwmgr *hwmgr,
hwmgr->dyn_state.vddc_dependency_on_sclk = NULL;
hwmgr->dyn_state.vddci_dependency_on_mclk = NULL;
hwmgr->dyn_state.vddc_dependency_on_mclk = NULL;
- hwmgr->dyn_state.vddc_dep_on_dal_pwrl = NULL;
hwmgr->dyn_state.mvdd_dependency_on_mclk = NULL;
hwmgr->dyn_state.vce_clock_voltage_dependency_table = NULL;
hwmgr->dyn_state.uvd_clock_voltage_dependency_table = NULL;
diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c
index 2e671b45f1740..15456c1c3614d 100644
--- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c
+++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu10_hwmgr.c
@@ -127,41 +127,6 @@ static int smu10_construct_max_power_limits_table(struct pp_hwmgr *hwmgr,
return 0;
}
-static int smu10_init_dynamic_state_adjustment_rule_settings(
- struct pp_hwmgr *hwmgr)
-{
- int count = 8;
- struct phm_clock_voltage_dependency_table *table_clk_vlt;
-
- table_clk_vlt = kzalloc_flex(*table_clk_vlt, entries, count);
-
- if (NULL == table_clk_vlt) {
- pr_err("Can not allocate memory!\n");
- return -ENOMEM;
- }
-
- table_clk_vlt->count = count;
- table_clk_vlt->entries[0].clk = PP_DAL_POWERLEVEL_0;
- table_clk_vlt->entries[0].v = 0;
- table_clk_vlt->entries[1].clk = PP_DAL_POWERLEVEL_1;
- table_clk_vlt->entries[1].v = 1;
- table_clk_vlt->entries[2].clk = PP_DAL_POWERLEVEL_2;
- table_clk_vlt->entries[2].v = 2;
- table_clk_vlt->entries[3].clk = PP_DAL_POWERLEVEL_3;
- table_clk_vlt->entries[3].v = 3;
- table_clk_vlt->entries[4].clk = PP_DAL_POWERLEVEL_4;
- table_clk_vlt->entries[4].v = 4;
- table_clk_vlt->entries[5].clk = PP_DAL_POWERLEVEL_5;
- table_clk_vlt->entries[5].v = 5;
- table_clk_vlt->entries[6].clk = PP_DAL_POWERLEVEL_6;
- table_clk_vlt->entries[6].v = 6;
- table_clk_vlt->entries[7].clk = PP_DAL_POWERLEVEL_7;
- table_clk_vlt->entries[7].v = 7;
- hwmgr->dyn_state.vddc_dep_on_dal_pwrl = table_clk_vlt;
-
- return 0;
-}
-
static int smu10_get_system_info_data(struct pp_hwmgr *hwmgr)
{
struct smu10_hwmgr *smu10_data = (struct smu10_hwmgr *)hwmgr->backend;
@@ -175,8 +140,6 @@ static int smu10_get_system_info_data(struct pp_hwmgr *hwmgr)
smu10_construct_max_power_limits_table (hwmgr,
&hwmgr->dyn_state.max_clock_voltage_on_ac);
- smu10_init_dynamic_state_adjustment_rule_settings(hwmgr);
-
return 0;
}
@@ -611,9 +574,6 @@ static int smu10_hwmgr_backend_fini(struct pp_hwmgr *hwmgr)
kfree(pinfo->vdd_dep_on_phyclk);
pinfo->vdd_dep_on_phyclk = NULL;
- kfree(hwmgr->dyn_state.vddc_dep_on_dal_pwrl);
- hwmgr->dyn_state.vddc_dep_on_dal_pwrl = NULL;
-
kfree(hwmgr->backend);
hwmgr->backend = NULL;
@@ -962,12 +922,6 @@ static int smu10_store_cc6_data(struct pp_hwmgr *hwmgr, uint32_t separation_time
return 0;
}
-static int smu10_get_dal_power_level(struct pp_hwmgr *hwmgr,
- struct amd_pp_simple_clock_info *info)
-{
- return -EINVAL;
-}
-
static int smu10_force_clock_level(struct pp_hwmgr *hwmgr,
enum pp_clock_type type, uint32_t mask)
{
@@ -1663,7 +1617,6 @@ static const struct pp_hwmgr_func smu10_hwmgr_funcs = {
.store_cc6_data = smu10_store_cc6_data,
.force_clock_level = smu10_force_clock_level,
.emit_clock_levels = smu10_emit_clock_levels,
- .get_dal_power_level = smu10_get_dal_power_level,
.get_performance_level = smu10_get_performance_level,
.get_current_shallow_sleep_clocks = smu10_get_current_shallow_sleep_clocks,
.get_clock_by_type_with_latency = smu10_get_clock_by_type_with_latency,
diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c
index 55e2375e1dad8..95bf187f02a5e 100644
--- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c
+++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c
@@ -5854,6 +5854,20 @@ static int smu7_power_off_asic(struct pp_hwmgr *hwmgr)
return result;
}
+static void smu7_notify_ac_dc(struct pp_hwmgr *hwmgr)
+{
+ struct amdgpu_device *adev = (struct amdgpu_device *)(hwmgr->adev);
+
+ /* Check if the platform already manages the AC/DC switch via dedicated GPIO. */
+ if (phm_cap_enabled(hwmgr->platform_descriptor.platformCaps,
+ PHM_PlatformCaps_AutomaticDCTransition))
+ return;
+
+ /* The SMU automatically notices DC, but needs to be notified when switching to AC. */
+ if (adev->pm.ac_power)
+ smum_send_msg_to_smc(hwmgr, PPSMC_MSG_RunningOnAC, NULL);
+}
+
static const struct pp_hwmgr_func smu7_hwmgr_funcs = {
.backend_init = &smu7_hwmgr_backend_init,
.backend_fini = &smu7_hwmgr_backend_fini,
@@ -5916,6 +5930,7 @@ static const struct pp_hwmgr_func smu7_hwmgr_funcs = {
.get_asic_baco_state = smu7_baco_get_state,
.set_asic_baco_state = smu7_baco_set_state,
.power_off_asic = smu7_power_off_asic,
+ .notify_ac_dc = smu7_notify_ac_dc,
};
uint8_t smu7_get_sleep_divider_id_from_clock(uint32_t clock,
diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c
index 5ad6ab3d2d375..8f82d7c07e8c8 100644
--- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c
+++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu8_hwmgr.c
@@ -270,41 +270,6 @@ static int smu8_construct_max_power_limits_table(struct pp_hwmgr *hwmgr,
return 0;
}
-static int smu8_init_dynamic_state_adjustment_rule_settings(
- struct pp_hwmgr *hwmgr,
- ATOM_CLK_VOLT_CAPABILITY *disp_voltage_table)
-{
- struct phm_clock_voltage_dependency_table *table_clk_vlt;
-
- table_clk_vlt = kzalloc_flex(*table_clk_vlt, entries, 8);
-
- if (NULL == table_clk_vlt) {
- pr_err("Can not allocate memory!\n");
- return -ENOMEM;
- }
-
- table_clk_vlt->count = 8;
- table_clk_vlt->entries[0].clk = PP_DAL_POWERLEVEL_0;
- table_clk_vlt->entries[0].v = 0;
- table_clk_vlt->entries[1].clk = PP_DAL_POWERLEVEL_1;
- table_clk_vlt->entries[1].v = 1;
- table_clk_vlt->entries[2].clk = PP_DAL_POWERLEVEL_2;
- table_clk_vlt->entries[2].v = 2;
- table_clk_vlt->entries[3].clk = PP_DAL_POWERLEVEL_3;
- table_clk_vlt->entries[3].v = 3;
- table_clk_vlt->entries[4].clk = PP_DAL_POWERLEVEL_4;
- table_clk_vlt->entries[4].v = 4;
- table_clk_vlt->entries[5].clk = PP_DAL_POWERLEVEL_5;
- table_clk_vlt->entries[5].v = 5;
- table_clk_vlt->entries[6].clk = PP_DAL_POWERLEVEL_6;
- table_clk_vlt->entries[6].v = 6;
- table_clk_vlt->entries[7].clk = PP_DAL_POWERLEVEL_7;
- table_clk_vlt->entries[7].v = 7;
- hwmgr->dyn_state.vddc_dep_on_dal_pwrl = table_clk_vlt;
-
- return 0;
-}
-
static int smu8_get_system_info_data(struct pp_hwmgr *hwmgr)
{
struct smu8_hwmgr *data = hwmgr->backend;
@@ -403,9 +368,6 @@ static int smu8_get_system_info_data(struct pp_hwmgr *hwmgr)
smu8_construct_max_power_limits_table (hwmgr,
&hwmgr->dyn_state.max_clock_voltage_on_ac);
- smu8_init_dynamic_state_adjustment_rule_settings(hwmgr,
- &info->sDISPCLK_Voltage[0]);
-
return result;
}
@@ -1149,9 +1111,6 @@ static int smu8_hwmgr_backend_init(struct pp_hwmgr *hwmgr)
static int smu8_hwmgr_backend_fini(struct pp_hwmgr *hwmgr)
{
if (hwmgr != NULL) {
- kfree(hwmgr->dyn_state.vddc_dep_on_dal_pwrl);
- hwmgr->dyn_state.vddc_dep_on_dal_pwrl = NULL;
-
kfree(hwmgr->backend);
hwmgr->backend = NULL;
}
@@ -1521,27 +1480,6 @@ static int smu8_store_cc6_data(struct pp_hwmgr *hwmgr, uint32_t separation_time,
return 0;
}
-static int smu8_get_dal_power_level(struct pp_hwmgr *hwmgr,
- struct amd_pp_simple_clock_info *info)
-{
- uint32_t i;
- const struct phm_clock_voltage_dependency_table *table =
- hwmgr->dyn_state.vddc_dep_on_dal_pwrl;
- const struct phm_clock_and_voltage_limits *limits =
- &hwmgr->dyn_state.max_clock_voltage_on_ac;
-
- info->engine_max_clock = limits->sclk;
- info->memory_max_clock = limits->mclk;
-
- for (i = table->count - 1; i > 0; i--) {
- if (limits->vddc >= table->entries[i].v) {
- info->level = table->entries[i].clk;
- return 0;
- }
- }
- return -EINVAL;
-}
-
static int smu8_force_clock_level(struct pp_hwmgr *hwmgr,
enum pp_clock_type type, uint32_t mask)
{
@@ -2062,7 +2000,6 @@ static const struct pp_hwmgr_func smu8_hwmgr_funcs = {
.store_cc6_data = smu8_store_cc6_data,
.force_clock_level = smu8_force_clock_level,
.emit_clock_levels = smu8_emit_clock_levels,
- .get_dal_power_level = smu8_get_dal_power_level,
.get_performance_level = smu8_get_performance_level,
.get_current_shallow_sleep_clocks = smu8_get_current_shallow_sleep_clocks,
.get_clock_by_type = smu8_get_clock_by_type,
diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c
index d9899cf7020b6..4b92b52aba2b8 100644
--- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c
+++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c
@@ -814,9 +814,6 @@ static int vega10_set_private_data_based_on_pptable(struct pp_hwmgr *hwmgr)
static int vega10_hwmgr_backend_fini(struct pp_hwmgr *hwmgr)
{
- kfree(hwmgr->dyn_state.vddc_dep_on_dal_pwrl);
- hwmgr->dyn_state.vddc_dep_on_dal_pwrl = NULL;
-
kfree(hwmgr->backend);
hwmgr->backend = NULL;
@@ -4386,20 +4383,6 @@ static uint32_t vega10_get_fan_control_mode(struct pp_hwmgr *hwmgr)
return AMD_FAN_CTRL_AUTO;
}
-static int vega10_get_dal_power_level(struct pp_hwmgr *hwmgr,
- struct amd_pp_simple_clock_info *info)
-{
- struct phm_ppt_v2_information *table_info =
- (struct phm_ppt_v2_information *)hwmgr->pptable;
- struct phm_clock_and_voltage_limits *max_limits =
- &table_info->max_clock_voltage_on_ac;
-
- info->engine_max_clock = max_limits->sclk;
- info->memory_max_clock = max_limits->mclk;
-
- return 0;
-}
-
static void vega10_get_sclks(struct pp_hwmgr *hwmgr,
struct pp_clock_levels_with_latency *clocks)
{
@@ -5644,7 +5627,6 @@ static const struct pp_hwmgr_func vega10_hwmgr_funcs = {
.set_fan_control_mode = vega10_set_fan_control_mode,
.get_fan_control_mode = vega10_get_fan_control_mode,
.read_sensor = vega10_read_sensor,
- .get_dal_power_level = vega10_get_dal_power_level,
.get_clock_by_type_with_latency = vega10_get_clock_by_type_with_latency,
.get_clock_by_type_with_voltage = vega10_get_clock_by_type_with_voltage,
.set_watermarks_for_clocks_ranges = vega10_set_watermarks_for_clocks_ranges,
diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c
index a9a85fd639b28..69a9074058ceb 100644
--- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c
+++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c
@@ -1822,21 +1822,6 @@ static uint32_t vega12_get_fan_control_mode(struct pp_hwmgr *hwmgr)
return AMD_FAN_CTRL_AUTO;
}
-static int vega12_get_dal_power_level(struct pp_hwmgr *hwmgr,
- struct amd_pp_simple_clock_info *info)
-{
-#if 0
- struct phm_ppt_v2_information *table_info =
- (struct phm_ppt_v2_information *)hwmgr->pptable;
- struct phm_clock_and_voltage_limits *max_limits =
- &table_info->max_clock_voltage_on_ac;
-
- info->engine_max_clock = max_limits->sclk;
- info->memory_max_clock = max_limits->mclk;
-#endif
- return 0;
-}
-
static int vega12_get_clock_ranges(struct pp_hwmgr *hwmgr,
uint32_t *clock,
PPCLK_e clock_select,
@@ -2963,7 +2948,6 @@ static const struct pp_hwmgr_func vega12_hwmgr_funcs = {
.set_fan_control_mode = vega12_set_fan_control_mode,
.get_fan_control_mode = vega12_get_fan_control_mode,
.read_sensor = vega12_read_sensor,
- .get_dal_power_level = vega12_get_dal_power_level,
.get_clock_by_type_with_latency = vega12_get_clock_by_type_with_latency,
.get_clock_by_type_with_voltage = vega12_get_clock_by_type_with_voltage,
.set_watermarks_for_clocks_ranges = vega12_set_watermarks_for_clocks_ranges,
diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c
index dab9b78a9fc86..7b8f4c1b80eb0 100644
--- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c
+++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c
@@ -2796,22 +2796,6 @@ static void vega20_set_fan_control_mode(struct pp_hwmgr *hwmgr, uint32_t mode)
}
}
-static int vega20_get_dal_power_level(struct pp_hwmgr *hwmgr,
- struct amd_pp_simple_clock_info *info)
-{
-#if 0
- struct phm_ppt_v2_information *table_info =
- (struct phm_ppt_v2_information *)hwmgr->pptable;
- struct phm_clock_and_voltage_limits *max_limits =
- &table_info->max_clock_voltage_on_ac;
-
- info->engine_max_clock = max_limits->sclk;
- info->memory_max_clock = max_limits->mclk;
-#endif
- return 0;
-}
-
-
static int vega20_get_sclks(struct pp_hwmgr *hwmgr,
struct pp_clock_levels_with_latency *clocks)
{
@@ -4446,7 +4430,6 @@ static const struct pp_hwmgr_func vega20_hwmgr_funcs = {
/* export to DAL */
.get_sclk = vega20_dpm_get_sclk,
.get_mclk = vega20_dpm_get_mclk,
- .get_dal_power_level = vega20_get_dal_power_level,
.get_clock_by_type_with_latency = vega20_get_clock_by_type_with_latency,
.get_clock_by_type_with_voltage = vega20_get_clock_by_type_with_voltage,
.set_watermarks_for_clocks_ranges = vega20_set_watermarks_for_clocks_ranges,
diff --git a/drivers/gpu/drm/amd/pm/powerplay/inc/hardwaremanager.h b/drivers/gpu/drm/amd/pm/powerplay/inc/hardwaremanager.h
index 915f1b8e4dbad..36dcad065faeb 100644
--- a/drivers/gpu/drm/amd/pm/powerplay/inc/hardwaremanager.h
+++ b/drivers/gpu/drm/amd/pm/powerplay/inc/hardwaremanager.h
@@ -426,9 +426,6 @@ extern int phm_check_states_equal(struct pp_hwmgr *hwmgr,
extern int phm_store_dal_configuration_data(struct pp_hwmgr *hwmgr,
const struct amd_pp_display_configuration *display_config);
-extern int phm_get_dal_power_level(struct pp_hwmgr *hwmgr,
- struct amd_pp_simple_clock_info *info);
-
extern int phm_set_cpu_power_state(struct pp_hwmgr *hwmgr);
extern int phm_power_down_asic(struct pp_hwmgr *hwmgr);
diff --git a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h
index 3ae45eac0c5ca..ca71efaa16561 100644
--- a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h
+++ b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h
@@ -292,8 +292,6 @@ struct pp_hwmgr_func {
int (*store_cc6_data)(struct pp_hwmgr *hwmgr, uint32_t separation_time,
bool cc6_disable, bool pstate_disable,
bool pstate_switch_disable);
- int (*get_dal_power_level)(struct pp_hwmgr *hwmgr,
- struct amd_pp_simple_clock_info *info);
int (*get_performance_level)(struct pp_hwmgr *, const struct pp_hw_power_state *,
PHM_PerformanceLevelDesignation, uint32_t, PHM_PerformanceLevel *);
int (*get_current_shallow_sleep_clocks)(struct pp_hwmgr *hwmgr,
@@ -364,6 +362,7 @@ struct pp_hwmgr_func {
bool disable);
ssize_t (*get_gpu_metrics)(struct pp_hwmgr *hwmgr, void **table);
int (*gfx_state_change)(struct pp_hwmgr *hwmgr, uint32_t state);
+ void (*notify_ac_dc)(struct pp_hwmgr *hwmgr);
};
struct pp_table_func {
@@ -540,7 +539,6 @@ struct phm_ppt_v1_information {
struct phm_clock_array *valid_dcefclk_values;
struct phm_clock_and_voltage_limits max_clock_voltage_on_dc;
struct phm_clock_and_voltage_limits max_clock_voltage_on_ac;
- struct phm_clock_voltage_dependency_table *vddc_dep_on_dal_pwrl;
struct phm_ppm_table *ppm_parameter_table;
struct phm_cac_tdp_table *cac_dtp_table;
struct phm_tdp_table *tdp_table;
@@ -632,7 +630,6 @@ struct phm_dynamic_state_info {
struct phm_clock_voltage_dependency_table *vddc_dependency_on_mclk;
struct phm_clock_voltage_dependency_table *mvdd_dependency_on_mclk;
struct phm_clock_voltage_dependency_table *vddc_dependency_on_display_clock;
- struct phm_clock_voltage_dependency_table *vddc_dep_on_dal_pwrl;
struct phm_clock_array *valid_sclk_values;
struct phm_clock_array *valid_mclk_values;
struct phm_clock_and_voltage_limits max_clock_voltage_on_dc;
diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_virt_ras_cmd.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_virt_ras_cmd.c
index c2761f3d06a0a..571b81cb04750 100644
--- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_virt_ras_cmd.c
+++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_virt_ras_cmd.c
@@ -108,15 +108,19 @@ static int amdgpu_virt_ras_remote_ioctl_cmd(struct ras_core_context *ras_core,
ret = amdgpu_virt_send_remote_ras_cmd(ras_core->dev,
shared_mem.gpa, mem_len);
if (!ret) {
- if (rcmd->cmd_res) {
- ret = rcmd->cmd_res;
+ uint32_t cmd_res = READ_ONCE(rcmd->cmd_res);
+ uint32_t osz;
+
+ if (cmd_res) {
+ ret = cmd_res;
goto out;
}
- cmd->cmd_res = rcmd->cmd_res;
- cmd->output_size = rcmd->output_size;
- if (rcmd->output_size && (rcmd->output_size <= output_size) && output_data)
- memcpy(output_data, rcmd->output_buff_raw, rcmd->output_size);
+ osz = READ_ONCE(rcmd->output_size);
+ cmd->cmd_res = cmd_res;
+ cmd->output_size = osz;
+ if (osz && osz <= output_size && output_data)
+ memcpy(output_data, rcmd->output_buff_raw, osz);
}
out:
diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h b/drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h
index 8156531a7b637..f34dda7ce87b1 100644
--- a/drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h
+++ b/drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h
@@ -46,6 +46,15 @@
printk(KERN_WARNING fmt, ##__VA_ARGS__); \
} while (0)
+#define RAS_DEV_WARN_RATELIMITED(device, fmt, ...) \
+ do { \
+ if (device) \
+ dev_warn_ratelimited(((struct amdgpu_device *)device)->dev, \
+ fmt, ##__VA_ARGS__); \
+ else \
+ printk_ratelimited(KERN_WARNING fmt, ##__VA_ARGS__); \
+ } while (0)
+
#define RAS_DEV_INFO(device, fmt, ...) \
do { \
if (device) \
diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc.c b/drivers/gpu/drm/amd/ras/rascore/ras_umc.c
index 91dd730de3cec..f32ee2fecf536 100644
--- a/drivers/gpu/drm/amd/ras/rascore/ras_umc.c
+++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc.c
@@ -193,12 +193,29 @@ static void ras_umc_reserve_eeprom_record(struct ras_core_context *ras_core,
}
/* When gpu reset is ongoing, ecc logging operations will be pended.
+ *
+ * The pending list is bounded by RAS_UMC_PENDING_ECC_MAX so that an ECC
+ * storm or repeated UMC error injection cannot make this list (and the
+ * kernel allocations behind it) grow without bound. Once the limit is
+ * reached, additional events are dropped and counted in
+ * pending_ecc_dropped, with a rate-limited warning emitted.
*/
int ras_umc_log_bad_bank_pending(struct ras_core_context *ras_core, struct ras_bank_ecc *bank)
{
struct ras_umc *ras_umc = &ras_core->ras_umc;
struct ras_bank_ecc_node *ecc_node;
+ mutex_lock(&ras_umc->pending_ecc_lock);
+ if (ras_umc->pending_ecc_count >= RAS_UMC_PENDING_ECC_MAX) {
+ ras_umc->pending_ecc_dropped++;
+ mutex_unlock(&ras_umc->pending_ecc_lock);
+ RAS_DEV_WARN_RATELIMITED(ras_core->dev,
+ "pending ECC list full (%u), dropping bad bank event (total dropped:%u)\n",
+ RAS_UMC_PENDING_ECC_MAX, ras_umc->pending_ecc_dropped);
+ return -ENOSPC;
+ }
+ mutex_unlock(&ras_umc->pending_ecc_lock);
+
ecc_node = kzalloc_obj(*ecc_node);
if (!ecc_node)
return -ENOMEM;
@@ -206,7 +223,15 @@ int ras_umc_log_bad_bank_pending(struct ras_core_context *ras_core, struct ras_b
memcpy(&ecc_node->ecc, bank, sizeof(ecc_node->ecc));
mutex_lock(&ras_umc->pending_ecc_lock);
+ /* re-check under the lock to honor the cap across concurrent callers */
+ if (ras_umc->pending_ecc_count >= RAS_UMC_PENDING_ECC_MAX) {
+ ras_umc->pending_ecc_dropped++;
+ mutex_unlock(&ras_umc->pending_ecc_lock);
+ kfree(ecc_node);
+ return -ENOSPC;
+ }
list_add_tail(&ecc_node->node, &ras_umc->pending_ecc_list);
+ ras_umc->pending_ecc_count++;
mutex_unlock(&ras_umc->pending_ecc_lock);
return 0;
@@ -225,8 +250,16 @@ int ras_umc_log_pending_bad_bank(struct ras_core_context *ras_core)
if (!ras_umc_log_bad_bank(ras_core, &ecc_node->ecc)) {
list_del(&ecc_node->node);
kfree(ecc_node);
+ if (ras_umc->pending_ecc_count)
+ ras_umc->pending_ecc_count--;
}
}
+ if (ras_umc->pending_ecc_dropped) {
+ RAS_DEV_WARN(ras_core->dev,
+ "%u pending ECC bad-bank events were dropped during GPU reset\n",
+ ras_umc->pending_ecc_dropped);
+ ras_umc->pending_ecc_dropped = 0;
+ }
mutex_unlock(&ras_umc->pending_ecc_lock);
return 0;
@@ -609,6 +642,8 @@ int ras_umc_sw_fini(struct ras_core_context *ras_core)
list_del(&ecc_node->node);
kfree(ecc_node);
}
+ ras_umc->pending_ecc_count = 0;
+ ras_umc->pending_ecc_dropped = 0;
mutex_unlock(&ras_umc->pending_ecc_lock);
mutex_destroy(&ras_umc->tree_lock);
diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc.h b/drivers/gpu/drm/amd/ras/rascore/ras_umc.h
index 1d3026be509b8..237525b46b9bb 100644
--- a/drivers/gpu/drm/amd/ras/rascore/ras_umc.h
+++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc.h
@@ -139,8 +139,20 @@ struct ras_umc {
struct mutex pending_ecc_lock;
struct ras_umc_err_data umc_err_data;
struct list_head pending_ecc_list;
+ /* number of entries currently queued on pending_ecc_list */
+ u32 pending_ecc_count;
+ /* number of entries dropped because pending_ecc_list was full */
+ u32 pending_ecc_dropped;
};
+/*
+ * Upper bound on entries that can be queued on pending_ecc_list while a
+ * GPU reset is in progress. Beyond this, new ECC events are dropped to
+ * prevent unbounded kernel memory growth in case of an ECC storm or
+ * malicious/repeated UMC error injection.
+ */
+#define RAS_UMC_PENDING_ECC_MAX 8192
+
int ras_umc_sw_init(struct ras_core_context *ras);
int ras_umc_sw_fini(struct ras_core_context *ras);
int ras_umc_hw_init(struct ras_core_context *ras);
diff --git a/drivers/gpu/drm/radeon/radeon_audio.c b/drivers/gpu/drm/radeon/radeon_audio.c
index 8d64ba18572ec..52d0049738b17 100644
--- a/drivers/gpu/drm/radeon/radeon_audio.c
+++ b/drivers/gpu/drm/radeon/radeon_audio.c
@@ -311,7 +311,7 @@ static void radeon_audio_write_sad_regs(struct drm_encoder *encoder)
if (!connector)
return;
- sad_count = drm_edid_to_sad(radeon_connector->edid, &sads);
+ sad_count = drm_edid_to_sad(drm_edid_raw(radeon_connector->edid), &sads);
if (sad_count < 0)
DRM_ERROR("Couldn't read SADs: %d\n", sad_count);
if (sad_count <= 0)
@@ -335,7 +335,7 @@ static void radeon_audio_write_speaker_allocation(struct drm_encoder *encoder)
if (!connector)
return;
- sad_count = drm_edid_to_speaker_allocation(radeon_connector->edid, &sadb);
+ sad_count = drm_edid_to_speaker_allocation(drm_edid_raw(radeon_connector->edid), &sadb);
if (sad_count < 0) {
DRM_DEBUG("Couldn't read Speaker Allocation Data Block: %d\n",
sad_count);
diff --git a/drivers/gpu/drm/radeon/radeon_combios.c b/drivers/gpu/drm/radeon/radeon_combios.c
index 6a4e268ffd995..b3e7a747f11e3 100644
--- a/drivers/gpu/drm/radeon/radeon_combios.c
+++ b/drivers/gpu/drm/radeon/radeon_combios.c
@@ -390,10 +390,10 @@ bool radeon_combios_check_hardcoded_edid(struct radeon_device *rdev)
}
/* this is used for atom LCDs as well */
-struct edid *
+const struct drm_edid *
radeon_bios_get_hardcoded_edid(struct radeon_device *rdev)
{
- return drm_edid_duplicate(drm_edid_raw(rdev->mode_info.bios_hardcoded_edid));
+ return drm_edid_dup(rdev->mode_info.bios_hardcoded_edid);
}
static struct radeon_i2c_bus_rec combios_setup_i2c_bus(struct radeon_device *rdev,
diff --git a/drivers/gpu/drm/radeon/radeon_connectors.c b/drivers/gpu/drm/radeon/radeon_connectors.c
index 373f08a125ffe..16b048f58c9be 100644
--- a/drivers/gpu/drm/radeon/radeon_connectors.c
+++ b/drivers/gpu/drm/radeon/radeon_connectors.c
@@ -271,8 +271,8 @@ static void radeon_connector_get_edid(struct drm_connector *connector)
if ((radeon_connector_encoder_get_dp_bridge_encoder_id(connector) !=
ENCODER_OBJECT_ID_NONE) &&
radeon_connector->ddc_bus->has_aux) {
- radeon_connector->edid = drm_get_edid(connector,
- &radeon_connector->ddc_bus->aux.ddc);
+ radeon_connector->edid = drm_edid_read_ddc(connector,
+ &radeon_connector->ddc_bus->aux.ddc);
} else if ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) ||
(connector->connector_type == DRM_MODE_CONNECTOR_eDP)) {
struct radeon_connector_atom_dig *dig = radeon_connector->con_priv;
@@ -280,19 +280,19 @@ static void radeon_connector_get_edid(struct drm_connector *connector)
if ((dig->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT ||
dig->dp_sink_type == CONNECTOR_OBJECT_ID_eDP) &&
radeon_connector->ddc_bus->has_aux)
- radeon_connector->edid = drm_get_edid(&radeon_connector->base,
- &radeon_connector->ddc_bus->aux.ddc);
+ radeon_connector->edid = drm_edid_read_ddc(&radeon_connector->base,
+ &radeon_connector->ddc_bus->aux.ddc);
else if (radeon_connector->ddc_bus)
- radeon_connector->edid = drm_get_edid(&radeon_connector->base,
- &radeon_connector->ddc_bus->adapter);
+ radeon_connector->edid = drm_edid_read_ddc(&radeon_connector->base,
+ &radeon_connector->ddc_bus->adapter);
} else if (vga_switcheroo_handler_flags() & VGA_SWITCHEROO_CAN_SWITCH_DDC &&
connector->connector_type == DRM_MODE_CONNECTOR_LVDS &&
radeon_connector->ddc_bus) {
- radeon_connector->edid = drm_get_edid_switcheroo(&radeon_connector->base,
- &radeon_connector->ddc_bus->adapter);
+ radeon_connector->edid = drm_edid_read_switcheroo(&radeon_connector->base,
+ &radeon_connector->ddc_bus->adapter);
} else if (radeon_connector->ddc_bus) {
- radeon_connector->edid = drm_get_edid(&radeon_connector->base,
- &radeon_connector->ddc_bus->adapter);
+ radeon_connector->edid = drm_edid_read_ddc(&radeon_connector->base,
+ &radeon_connector->ddc_bus->adapter);
}
if (!radeon_connector->edid) {
@@ -314,25 +314,17 @@ static void radeon_connector_get_edid(struct drm_connector *connector)
}
}
-static void radeon_connector_free_edid(struct drm_connector *connector)
-{
- struct radeon_connector *radeon_connector = to_radeon_connector(connector);
-
- kfree(radeon_connector->edid);
- radeon_connector->edid = NULL;
-}
-
static int radeon_ddc_get_modes(struct drm_connector *connector)
{
struct radeon_connector *radeon_connector = to_radeon_connector(connector);
int ret;
if (radeon_connector->edid) {
- drm_connector_update_edid_property(connector, radeon_connector->edid);
- ret = drm_add_edid_modes(connector, radeon_connector->edid);
+ drm_edid_connector_update(connector, radeon_connector->edid);
+ ret = drm_edid_connector_add_modes(connector);
return ret;
}
- drm_connector_update_edid_property(connector, NULL);
+ drm_edid_connector_update(connector, NULL);
return 0;
}
@@ -895,7 +887,9 @@ static void radeon_connector_destroy(struct drm_connector *connector)
{
struct radeon_connector *radeon_connector = to_radeon_connector(connector);
- radeon_connector_free_edid(connector);
+ drm_edid_free(radeon_connector->edid);
+ radeon_connector->edid = NULL;
+
kfree(radeon_connector->con_priv);
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
@@ -1007,7 +1001,8 @@ radeon_vga_detect(struct drm_connector *connector, bool force)
dret = radeon_ddc_probe(radeon_connector, false);
if (dret) {
radeon_connector->detected_by_load = false;
- radeon_connector_free_edid(connector);
+ drm_edid_free(radeon_connector->edid);
+ radeon_connector->edid = NULL;
radeon_connector_get_edid(connector);
if (!radeon_connector->edid) {
@@ -1016,13 +1011,14 @@ radeon_vga_detect(struct drm_connector *connector, bool force)
ret = connector_status_connected;
} else {
radeon_connector->use_digital =
- !!(radeon_connector->edid->input & DRM_EDID_INPUT_DIGITAL);
+ drm_edid_is_digital(radeon_connector->edid);
/* some oems have boards with separate digital and analog connectors
* with a shared ddc line (often vga + hdmi)
*/
if (radeon_connector->use_digital && radeon_connector->shared_ddc) {
- radeon_connector_free_edid(connector);
+ drm_edid_free(radeon_connector->edid);
+ radeon_connector->edid = NULL;
ret = connector_status_disconnected;
} else {
ret = connector_status_connected;
@@ -1251,7 +1247,8 @@ radeon_dvi_detect(struct drm_connector *connector, bool force)
}
if (dret) {
radeon_connector->detected_by_load = false;
- radeon_connector_free_edid(connector);
+ drm_edid_free(radeon_connector->edid);
+ radeon_connector->edid = NULL;
radeon_connector_get_edid(connector);
if (!radeon_connector->edid) {
@@ -1271,13 +1268,14 @@ radeon_dvi_detect(struct drm_connector *connector, bool force)
}
} else {
radeon_connector->use_digital =
- !!(radeon_connector->edid->input & DRM_EDID_INPUT_DIGITAL);
+ drm_edid_is_digital(radeon_connector->edid);
/* some oems have boards with separate digital and analog connectors
* with a shared ddc line (often vga + hdmi)
*/
if ((!radeon_connector->use_digital) && radeon_connector->shared_ddc) {
- radeon_connector_free_edid(connector);
+ drm_edid_free(radeon_connector->edid);
+ radeon_connector->edid = NULL;
ret = connector_status_disconnected;
} else {
ret = connector_status_connected;
@@ -1301,7 +1299,8 @@ radeon_dvi_detect(struct drm_connector *connector, bool force)
if (list_connector->connector_type != DRM_MODE_CONNECTOR_VGA) {
/* hpd is our only option in this case */
if (!radeon_hpd_sense(rdev, radeon_connector->hpd.hpd)) {
- radeon_connector_free_edid(connector);
+ drm_edid_free(radeon_connector->edid);
+ radeon_connector->edid = NULL;
ret = connector_status_disconnected;
}
}
@@ -1635,7 +1634,8 @@ radeon_dp_detect(struct drm_connector *connector, bool force)
goto out;
}
- radeon_connector_free_edid(connector);
+ drm_edid_free(radeon_connector->edid);
+ radeon_connector->edid = NULL;
if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) ||
(connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) {
diff --git a/drivers/gpu/drm/radeon/radeon_mode.h b/drivers/gpu/drm/radeon/radeon_mode.h
index ae1ecdc2e1894..031a3bf6fe0a4 100644
--- a/drivers/gpu/drm/radeon/radeon_mode.h
+++ b/drivers/gpu/drm/radeon/radeon_mode.h
@@ -525,7 +525,7 @@ struct radeon_connector {
bool use_digital;
/* we need to mind the EDID between detect
and get modes due to analog/digital/tvencoder */
- struct edid *edid;
+ const struct drm_edid *edid;
void *con_priv;
bool dac_load_detect;
bool detected_by_load; /* if the connection status was determined by load */
@@ -839,7 +839,7 @@ radeon_get_crtc_scanout_position(struct drm_crtc *crtc, bool in_vblank_irq,
const struct drm_display_mode *mode);
extern bool radeon_combios_check_hardcoded_edid(struct radeon_device *rdev);
-extern struct edid *
+extern const struct drm_edid *
radeon_bios_get_hardcoded_edid(struct radeon_device *rdev);
extern bool radeon_atom_get_clock_info(struct drm_device *dev);
extern bool radeon_combios_get_clock_info(struct drm_device *dev);