* [PATCH 00/32] DC Patches Jun 15 2026
@ 2026-06-10 9:44 Chenyu Chen
2026-06-10 9:44 ` [PATCH 01/32] drm/amd/display: Increase dcn42b uclk value Chenyu Chen
` (32 more replies)
0 siblings, 33 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Chenyu Chen
This DC patchset brings improvements in multiple areas. In summary, we highlight:
- Major amdgpu_dm refactoring into modular components
(backlight, audio, DMUB, IRQ, connector)
- Enhancements in clock management and DCN4 pstate handling
- HDMI pipeline cleanup and compliance automation support
- Introduction of dc_plane_cm and surface update refactor
- Backlight handling improvements without ACPI dependency
- Addition of KUnit tests to improve test coverage
- Firmware update to version 0.1.63.0
Cc: Daniel Wheeler <daniel.wheeler@amd.com>
Alex Hung (19):
drm/amd/display: Extract backlight code to amdgpu_dm_backlight
drm/amd/display: Extract audio code to amdgpu_dm_audio
drm/amd/display: Extract DMUB code to amdgpu_dm_dmub
drm/amd/display: Move HPD and IRQ handler code to amdgpu_dm_irq
drm/amd/display: Extract connector and encoder code to
amdgpu_dm_connector
drm/amd/display: Fix conflicting types for dc_plane_cm functions
drm/amd/display: Add KUnit tests for amdgpu_dm
drm/amd/display: Add KUnit tests for amdgpu_dm_backlight
drm/amd/display: Add KUnit tests for amdgpu_dm_audio
drm/amd/display: Add KUnit tests for amdgpu_dm_dmub
drm/amd/display: Add KUnit tests for amdgpu_dm_connector
drm/amd/display: Add KUnit tests for amdgpu_dm_irq
drm/amd/display: Add KUnit tests for amdgpu_dm_crtc
drm/amd/display: Add KUnit tests for amdgpu_dm_services
drm/amd/display: Add KUnit tests for amdgpu_dm_helpers
drm/amd/display: Add KUnit tests for amdgpu_dm_quirks
drm/amd/display: Add more KUnit tests for amdgpu_dm_pp_smu
drm/amd/display: Add more KUnit tests for amdgpu_dm_mst_types
drm/amd/display: Move backlight macros to backlight header
Fangzhi Zuo (1):
drm/amd/display: Add Support for HDMI Compliance Automation
Gabe Teeger (2):
drm/amd/display: Increase dcn42b uclk value
drm/amd/display: Enable pstate for DCN4 non-emulation builds
Mario Limonciello (1):
drm/amd/display: Set default backlight without ACPI support
Nicholas Kazlauskas (1):
drm/amd/display: Add a new interface to set idle opts in clock manager
Rafal Ostrowski (2):
drm/amd/display: Introduce dc_plane_cm and migrate surface update
color path
drm/amd/display: Refactor surface_update_flags to flat struct with
helpers
Robin Chen (1):
drm/amd/display: Add PSR Active VTotal Control capability
Taimur Hassan (2):
drm/amd/display: [FW Promotion] Release 0.1.63.0
drm/amd/display: Promote DC to 3.2.386
Wenjing Liu (3):
drm/amd/display: Add utm_qos_model pointer to clk_bw_params
drm/amd/display: Remove get_utm_qos_model from soc_and_ip_translator
drm/amd/display: Remove unused project_id from DML2 core instance
.../gpu/drm/amd/display/amdgpu_dm/Makefile | 6 +-
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 11981 ++++------------
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 79 +-
.../amd/display/amdgpu_dm/amdgpu_dm_audio.c | 323 +
.../amd/display/amdgpu_dm/amdgpu_dm_audio.h | 56 +
.../display/amdgpu_dm/amdgpu_dm_backlight.c | 704 +
.../display/amdgpu_dm/amdgpu_dm_backlight.h | 67 +
.../amd/display/amdgpu_dm/amdgpu_dm_color.c | 69 +-
.../amd/display/amdgpu_dm/amdgpu_dm_color.h | 8 +-
.../display/amdgpu_dm/amdgpu_dm_connector.c | 3581 +++++
.../display/amdgpu_dm/amdgpu_dm_connector.h | 162 +
.../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 14 +-
.../amd/display/amdgpu_dm/amdgpu_dm_crtc.h | 6 +
.../amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 69 +-
.../amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 934 ++
.../amd/display/amdgpu_dm/amdgpu_dm_dmub.h | 68 +
.../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 64 +-
.../amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 20 +
.../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 1511 +-
.../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 27 +
.../amdgpu_dm/amdgpu_dm_kunit_helpers.h | 1 +
.../display/amdgpu_dm/amdgpu_dm_mst_types.c | 145 +-
.../display/amdgpu_dm/amdgpu_dm_mst_types.h | 12 +
.../amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 234 +-
.../amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 22 +
.../amd/display/amdgpu_dm/amdgpu_dm_quirks.c | 2 +
.../display/amdgpu_dm/amdgpu_dm_services.c | 7 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 11 +
.../amdgpu_dm/tests/amdgpu_dm_audio_test.c | 490 +
.../tests/amdgpu_dm_backlight_test.c | 1128 ++
.../amdgpu_dm/tests/amdgpu_dm_color_test.c | 64 +-
.../tests/amdgpu_dm_connector_test.c | 2142 +++
.../amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 532 +
.../amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 600 +
.../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 645 +
.../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 934 ++
.../tests/amdgpu_dm_mst_types_test.c | 385 +
.../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 736 +
.../amdgpu_dm/tests/amdgpu_dm_quirks_test.c | 103 +
.../amdgpu_dm/tests/amdgpu_dm_services_test.c | 313 +
.../display/amdgpu_dm/tests/amdgpu_dm_test.c | 929 ++
drivers/gpu/drm/amd/display/dc/core/dc.c | 287 +-
.../drm/amd/display/dc/core/dc_hw_sequencer.c | 20 +-
.../gpu/drm/amd/display/dc/core/dc_surface.c | 44 +-
drivers/gpu/drm/amd/display/dc/dc.h | 227 +-
drivers/gpu/drm/amd/display/dc/dc_dp_types.h | 1 +
drivers/gpu/drm/amd/display/dc/dc_stream.h | 29 +
drivers/gpu/drm/amd/display/dc/dc_types.h | 66 +-
.../drm/amd/display/dc/dml/calcs/dcn_calcs.c | 2 +-
.../dml21/inc/bounding_boxes/dcn42b_soc_bb.h | 2 +-
.../dml21/src/dml2_core/dml2_core_factory.c | 2 -
.../src/dml2_core/dml2_core_shared_types.h | 2 -
.../src/inc/dml2_internal_shared_types.h | 1 -
.../amd/display/dc/hubp/dcn401/dcn401_hubp.c | 2 +-
.../amd/display/dc/hwss/dce110/dce110_hwseq.c | 8 +-
.../amd/display/dc/hwss/dce60/dce60_hwseq.c | 8 +-
.../amd/display/dc/hwss/dcn10/dcn10_hwseq.c | 62 +-
.../amd/display/dc/hwss/dcn20/dcn20_hwseq.c | 76 +-
.../amd/display/dc/hwss/dcn201/dcn201_hwseq.c | 2 +-
.../amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 10 +-
.../amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 33 +-
.../amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 244 +-
.../amd/display/dc/hwss/dcn401/dcn401_hwseq.h | 2 +-
.../amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 318 +-
.../amd/display/dc/hwss/dcn42/dcn42_hwseq.h | 5 +-
.../display/dc/hwss/hw_sequencer_private.h | 3 +-
.../gpu/drm/amd/display/dc/inc/hw/clk_mgr.h | 4 +
drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h | 2 +-
.../display/dc/inc/soc_and_ip_translator.h | 14 -
.../dc/link/protocols/link_dp_capability.c | 6 +-
.../dc/resource/dcn42b/dcn42b_resource.c | 2 +-
.../gpu/drm/amd/display/dmub/inc/dmub_cmd.h | 178 +-
.../amd/display/include/ddc_service_types.h | 1 +
73 files changed, 20422 insertions(+), 10425 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
--
2.43.0
^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 01/32] drm/amd/display: Increase dcn42b uclk value
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 02/32] drm/amd/display: Add a new interface to set idle opts in clock manager Chenyu Chen
` (31 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Gabe Teeger, Dillon Varone,
Chenyu Chen
From: Gabe Teeger <gabe.teeger@amd.com>
Increase uclk value in order to enable UHBR20.
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Gabe Teeger <gabe.teeger@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h
index ce4025591b87..eae4a37b0984 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h
@@ -75,7 +75,7 @@ static const struct dml2_soc_bb dml2_socbb_dcn42b = {
.clk_values_khz = {2},
},
.uclk = {
- .clk_values_khz = {400000},
+ .clk_values_khz = {2400000},
.num_clk_values = 1,
},
.fclk = {
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 02/32] drm/amd/display: Add a new interface to set idle opts in clock manager
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
2026-06-10 9:44 ` [PATCH 01/32] drm/amd/display: Increase dcn42b uclk value Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 03/32] drm/amd/display: Add utm_qos_model pointer to clk_bw_params Chenyu Chen
` (30 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Nicholas Kazlauskas,
Dillon Varone, Chenyu Chen
From: Nicholas Kazlauskas <nicholas.kazlauskas@amd.com>
[Why & How]
For future use in migrating the idle optimizations message to PMFW to
DC core.
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Nicholas Kazlauskas <nicholas.kazlauskas@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h
index f829ce3f70e5..9b5bdcddfa7a 100644
--- a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h
+++ b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h
@@ -337,6 +337,8 @@ struct clk_mgr_funcs {
void (*exit_low_power_state)(struct clk_mgr *clk_mgr);
bool (*is_ips_supported)(struct clk_mgr *clk_mgr);
+ void (*set_idle_power_optimizations)(struct clk_mgr *clk_mgr, bool enable);
+
void (*init_clocks)(struct clk_mgr *clk_mgr);
void (*dump_clk_registers)(struct clk_state_registers_and_bypass *regs_and_bypass,
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 03/32] drm/amd/display: Add utm_qos_model pointer to clk_bw_params
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
2026-06-10 9:44 ` [PATCH 01/32] drm/amd/display: Increase dcn42b uclk value Chenyu Chen
2026-06-10 9:44 ` [PATCH 02/32] drm/amd/display: Add a new interface to set idle opts in clock manager Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 04/32] drm/amd/display: Remove get_utm_qos_model from soc_and_ip_translator Chenyu Chen
` (29 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Wenjing Liu, Dillon Varone,
Chenyu Chen
From: Wenjing Liu <wenjing.liu@amd.com>
[Why]
Add support for passing QoS model data from clock manager
to bandwidth calculation consumers.
[How]
- Add forward declaration and const pointer for utm_qos_model
in clk_bw_params
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Wenjing Liu <wenjing.liu@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h
index 9b5bdcddfa7a..69c4a49a40fc 100644
--- a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h
+++ b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h
@@ -30,6 +30,7 @@
#include "dc.h"
#include "core_types.h"
#include "dm_pp_smu.h"
+struct utm_qos_model;
/* Constants */
#define DDR4_DRAM_WIDTH 64
@@ -311,6 +312,7 @@ struct clk_bw_params {
struct wm_table wm_table;
struct dummy_pstate_entry dummy_pstate_table[4];
struct clk_limit_table_entry dc_mode_limit;
+ const struct utm_qos_model *utm_qos_model;
};
/* Public interfaces */
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 04/32] drm/amd/display: Remove get_utm_qos_model from soc_and_ip_translator
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (2 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 03/32] drm/amd/display: Add utm_qos_model pointer to clk_bw_params Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 05/32] drm/amd/display: Remove unused project_id from DML2 core instance Chenyu Chen
` (28 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Wenjing Liu, Dillon Varone,
Chenyu Chen
From: Wenjing Liu <wenjing.liu@amd.com>
[Why]
The QoS model is now populated directly in clock manager
from firmware data. The translator function pointer is no
longer needed.
[How]
- Remove get_utm_qos_model function pointer from
soc_and_ip_translator_funcs
- Remove associated forward declarations from
soc_and_ip_translator.h
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Wenjing Liu <wenjing.liu@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../drm/amd/display/dc/inc/soc_and_ip_translator.h | 14 --------------
1 file changed, 14 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/dc/inc/soc_and_ip_translator.h b/drivers/gpu/drm/amd/display/dc/inc/soc_and_ip_translator.h
index 6a97a3e28bd2..5dcb9f8f4daf 100644
--- a/drivers/gpu/drm/amd/display/dc/inc/soc_and_ip_translator.h
+++ b/drivers/gpu/drm/amd/display/dc/inc/soc_and_ip_translator.h
@@ -8,26 +8,12 @@
#include "dc.h"
#include "dml_top_soc_parameter_types.h"
-/* Forward declarations — callers that dereference these structs must include
- * the full UTM model headers themselves. */
-struct utm_qos_model;
-struct utm_qos_model_dchub_v2;
-
struct soc_and_ip_translator_funcs {
void (*get_soc_bb)(
struct dml2_soc_bb *soc_bb,
const struct dc *dc,
const struct dml2_configuration_options *config);
void (*get_ip_caps)(struct dml2_ip_capabilities *dml_ip_caps);
- /**
- * get_utm_qos_model - Return the static UTM QoS model for this DCN
- * generation. Caller provides storage for @qos_model and @dchub.
- * @qos_model: output — populated with SoC bounding box and SOP table
- * @dchub: output — populated with DCHUB client extension data
- */
- void (*get_utm_qos_model)(
- struct utm_qos_model *qos_model,
- struct utm_qos_model_dchub_v2 *dchub);
};
struct soc_and_ip_translator {
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 05/32] drm/amd/display: Remove unused project_id from DML2 core instance
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (3 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 04/32] drm/amd/display: Remove get_utm_qos_model from soc_and_ip_translator Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 06/32] drm/amd/display: Introduce dc_plane_cm and migrate surface update color path Chenyu Chen
` (27 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Wenjing Liu, Austin Zheng,
Chenyu Chen
From: Wenjing Liu <wenjing.liu@amd.com>
[Why]
The project_id field stored in dml2_core_instance and related
context structs was not consumed after initial setup and
represents unnecessary coupling between the core layer and
project-specific identifiers.
[How]
- Remove project_id field from dml2_core_instance
- Remove the corresponding assignment in dml2_core_create
Reviewed-by: Austin Zheng <austin.zheng@amd.com>
Signed-off-by: Wenjing Liu <wenjing.liu@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../display/dc/dml2_0/dml21/src/dml2_core/dml2_core_factory.c | 2 --
.../dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h | 2 --
.../dc/dml2_0/dml21/src/inc/dml2_internal_shared_types.h | 1 -
3 files changed, 5 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_factory.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_factory.c
index 67e307fa4310..9f1222f5a835 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_factory.c
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_factory.c
@@ -15,8 +15,6 @@ bool dml2_core_create(enum dml2_project_id project_id, struct dml2_core_instance
memset(out, 0, sizeof(struct dml2_core_instance));
- out->project_id = project_id;
-
switch (project_id) {
case dml2_project_dcn4x_stage1:
result = false;
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h
index 11e295253f72..e9f970794488 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h
@@ -2329,7 +2329,6 @@ struct dml2_core_calcs_mode_support_ex {
const struct dml2_display_cfg *in_display_cfg;
const struct dml2_mcg_min_clock_table *min_clk_table;
int min_clk_index;
- enum dml2_project_id project_id;
//unsigned int in_state_index;
struct dml2_core_internal_mode_support_info *out_evaluation_info;
};
@@ -2342,7 +2341,6 @@ struct dml2_core_calcs_mode_programming_ex {
const struct dml2_mcg_min_clock_table *min_clk_table;
const struct core_display_cfg_support_info *cfg_support_info;
int min_clk_index;
- enum dml2_project_id project_id;
struct dml2_display_cfg_programming *programming;
};
diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/inc/dml2_internal_shared_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/inc/dml2_internal_shared_types.h
index d328d92240b4..3ae817ea2aad 100644
--- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/inc/dml2_internal_shared_types.h
+++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/inc/dml2_internal_shared_types.h
@@ -489,7 +489,6 @@ struct dml2_core_scratch {
};
struct dml2_core_instance {
- enum dml2_project_id project_id;
struct dml2_mcg_min_clock_table *minimum_clock_table;
struct dml2_core_internal_state_inputs inputs;
struct dml2_core_internal_state_intermediates intermediates;
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 06/32] drm/amd/display: Introduce dc_plane_cm and migrate surface update color path
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (4 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 05/32] drm/amd/display: Remove unused project_id from DML2 core instance Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 07/32] drm/amd/display: Extract backlight code to amdgpu_dm_backlight Chenyu Chen
` (26 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Rafal Ostrowski, Dillon Varone,
Chenyu Chen
From: Rafal Ostrowski <rafal.ostrowski@amd.com>
[Why]
Begin convergence with upstream Color Manager refactor
(fda768acb2a1 "drm/amd/display: Sync dcn42 with DC 3.2.373") by
consolidating fragmented per-plane CM state (shaper, 3DLUT, blend,
CM2) into a single dc_plane_cm structure shared by dc_plane_state
and dc_surface_update. Legacy fields are gated behind TRIM_CM2 so
that it keeps compatibility with other repositories.
[How]
Refactored to use newer structures.
No functional behavior change intended. Under !TRIM_CM2 the legacy
fields are still populated for compatibility with other repositories.
Reviewed-by: Dillon Varone <dillon.varone@amd.com>
Signed-off-by: Rafal Ostrowski <rafal.ostrowski@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 +-
.../amd/display/amdgpu_dm/amdgpu_dm_color.c | 69 ++--
drivers/gpu/drm/amd/display/dc/core/dc.c | 130 ++++---
.../gpu/drm/amd/display/dc/core/dc_surface.c | 44 ++-
drivers/gpu/drm/amd/display/dc/dc.h | 72 +++-
drivers/gpu/drm/amd/display/dc/dc_types.h | 66 +++-
.../amd/display/dc/hubp/dcn401/dcn401_hubp.c | 2 +-
.../amd/display/dc/hwss/dcn20/dcn20_hwseq.c | 20 +-
.../amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 10 +-
.../amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 29 +-
.../amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 172 +++++-----
.../amd/display/dc/hwss/dcn401/dcn401_hwseq.h | 2 +-
.../amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 316 +++++++++---------
.../amd/display/dc/hwss/dcn42/dcn42_hwseq.h | 5 +-
.../display/dc/hwss/hw_sequencer_private.h | 3 +-
drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h | 2 +-
16 files changed, 552 insertions(+), 394 deletions(-)
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 5838c1d77d34..231825249c48 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -10317,9 +10317,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
bundle->surface_updates[planes_count].in_transfer_func = &dc_plane->in_transfer_func;
bundle->surface_updates[planes_count].gamut_remap_matrix = &dc_plane->gamut_remap_matrix;
bundle->surface_updates[planes_count].hdr_mult = dc_plane->hdr_mult;
- bundle->surface_updates[planes_count].func_shaper = &dc_plane->in_shaper_func;
- bundle->surface_updates[planes_count].lut3d_func = &dc_plane->lut3d_func;
- bundle->surface_updates[planes_count].blend_tf = &dc_plane->blend_tf;
+ bundle->surface_updates[planes_count].cm = &dc_plane->cm;
}
amdgpu_dm_plane_fill_dc_scaling_info(dm->adev, new_plane_state,
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 cc03e9adf7a1..a9a741c21663 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
@@ -1051,26 +1051,28 @@ EXPORT_IF_KUNIT(__drm_3dlut32_to_dc_3dlut);
/* amdgpu_dm_atomic_lut3d - set DRM 3D LUT to DC stream
* @drm_lut3d: user 3D LUT
* @drm_lut3d_size: size of 3D LUT
- * @lut3d: DC 3D LUT
+ * @cm: DC Color Manager (includes 3D LUT)
*
* Map user 3D LUT data to DC 3D LUT and all necessary bits to program it
* on DCN accordingly.
*/
STATIC_IFN_KUNIT void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut3d,
uint32_t drm_lut3d_size,
- struct dc_3dlut *lut)
+ struct dc_plane_cm *cm)
{
if (!drm_lut3d_size) {
- lut->state.bits.initialized = 0;
+ cm->lut3d_func.state.bits.initialized = 0;
+ cm->flags.bits.lut3d_enable = 0;
} else {
/* Stride and bit depth are not programmable by API yet.
* Therefore, only supports 17x17x17 3D LUT (12-bit).
*/
- lut->lut_3d.use_tetrahedral_9 = false;
- lut->lut_3d.use_12bits = true;
- lut->state.bits.initialized = 1;
- __drm_3dlut_to_dc_3dlut(drm_lut3d, drm_lut3d_size, &lut->lut_3d,
- lut->lut_3d.use_tetrahedral_9,
+ cm->lut3d_func.lut_3d.use_tetrahedral_9 = false;
+ cm->lut3d_func.lut_3d.use_12bits = true;
+ cm->lut3d_func.state.bits.initialized = 1;
+ cm->flags.bits.lut3d_enable = 1;
+ __drm_3dlut_to_dc_3dlut(drm_lut3d, drm_lut3d_size, &cm->lut3d_func.lut_3d,
+ cm->lut3d_func.lut_3d.use_tetrahedral_9,
MAX_COLOR_3DLUT_BITDEPTH);
}
}
@@ -1080,7 +1082,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *sha
bool has_rom,
enum dc_transfer_func_predefined tf,
uint32_t shaper_size,
- struct dc_transfer_func *func_shaper)
+ struct dc_plane_cm *cm)
{
int ret = 0;
@@ -1089,10 +1091,13 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *sha
* If user shaper LUT is set, we assume a linear color space
* (linearized by degamma 1D LUT or not).
*/
- __set_tf_distributed_points(func_shaper, tf);
- ret = __set_output_tf(func_shaper, shaper_lut, shaper_size, has_rom);
+ __set_tf_distributed_points(&cm->shaper_func, tf);
+ cm->flags.bits.shaper_enable = 1;
+
+ ret = __set_output_tf(&cm->shaper_func, shaper_lut, shaper_size, has_rom);
} else {
- __set_tf_bypass(func_shaper);
+ __set_tf_bypass(&cm->shaper_func);
+ cm->flags.bits.shaper_enable = 0;
}
return ret;
@@ -1103,7 +1108,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blen
bool has_rom,
enum dc_transfer_func_predefined tf,
uint32_t blend_size,
- struct dc_transfer_func *func_blend)
+ struct dc_plane_cm *cm)
{
int ret = 0;
@@ -1115,10 +1120,13 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blen
* module to fill the parameters that will be translated to HW
* points.
*/
- __set_tf_distributed_points(func_blend, tf);
- ret = __set_input_tf(NULL, func_blend, blend_lut, blend_size);
+ __set_tf_distributed_points(&cm->blend_func, tf);
+ cm->flags.bits.blend_enable = 1;
+
+ ret = __set_input_tf(NULL, &cm->blend_func, blend_lut, blend_size);
} else {
- __set_tf_bypass(func_blend);
+ __set_tf_bypass(&cm->blend_func);
+ cm->flags.bits.blend_enable = 0;
}
return ret;
@@ -1635,7 +1643,7 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state,
struct drm_colorop_state *colorop_state = NULL, *new_colorop_state;
struct drm_atomic_state *state = plane_state->state;
enum dc_transfer_func_predefined default_tf = TRANSFER_FUNCTION_LINEAR;
- struct dc_transfer_func *tf = &dc_plane_state->in_shaper_func;
+ struct dc_transfer_func *tf = &dc_plane_state->cm.shaper_func;
const struct drm_color_lut32 *shaper_lut;
struct drm_device *dev = colorop->dev;
bool enabled = false;
@@ -1696,8 +1704,12 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state,
}
}
- if (!enabled)
+ if (!enabled) {
tf->type = TF_TYPE_BYPASS;
+ dc_plane_state->cm.flags.bits.shaper_enable = 0;
+ } else {
+ dc_plane_state->cm.flags.bits.shaper_enable = 1;
+ }
return 0;
}
@@ -1741,7 +1753,7 @@ __set_dm_plane_colorop_3dlut(struct drm_plane_state *plane_state,
{
struct drm_colorop *old_colorop;
struct drm_colorop_state *colorop_state = NULL, *new_colorop_state;
- struct dc_transfer_func *tf = &dc_plane_state->in_shaper_func;
+ struct dc_transfer_func *tf = &dc_plane_state->cm.shaper_func;
struct drm_atomic_state *state = plane_state->state;
const struct amdgpu_device *adev = drm_to_adev(colorop->dev);
bool has_3dlut = adev->dm.dc->caps.color.dpp.hw_3d_lut || adev->dm.dc->caps.color.mpc.preblend;
@@ -1769,13 +1781,15 @@ __set_dm_plane_colorop_3dlut(struct drm_plane_state *plane_state,
drm_dbg(dev, "3D LUT colorop with ID: %d\n", colorop->base.id);
lut3d = __extract_blob_lut32(colorop_state->data, &lut3d_size);
lut3d_size = lut3d != NULL ? lut3d_size : 0;
- ret = __set_colorop_3dlut(lut3d, lut3d_size, &dc_plane_state->lut3d_func);
+ ret = __set_colorop_3dlut(lut3d, lut3d_size, &dc_plane_state->cm.lut3d_func);
if (ret) {
drm_dbg(dev, "3D LUT colorop with ID: %d has LUT size = %d\n",
colorop->base.id, lut3d_size);
return ret;
}
+ dc_plane_state->cm.flags.bits.lut3d_enable = 1;
+
/* 3D LUT requires shaper. If shaper colorop is bypassed, enable shaper curve
* with TRANSFER_FUNCTION_LINEAR
*/
@@ -1785,6 +1799,8 @@ __set_dm_plane_colorop_3dlut(struct drm_plane_state *plane_state,
tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE;
ret = __set_output_tf_32(tf, NULL, 0, false);
}
+ } else {
+ dc_plane_state->cm.flags.bits.lut3d_enable = 0;
}
return ret;
@@ -1799,12 +1815,14 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state,
struct drm_colorop_state *colorop_state = NULL, *new_colorop_state;
struct drm_atomic_state *state = plane_state->state;
enum dc_transfer_func_predefined default_tf = TRANSFER_FUNCTION_LINEAR;
- struct dc_transfer_func *tf = &dc_plane_state->blend_tf;
+ struct dc_transfer_func *tf = &dc_plane_state->cm.blend_func;
const struct drm_color_lut32 *blend_lut = NULL;
struct drm_device *dev = colorop->dev;
uint32_t blend_size = 0;
int i = 0;
+ dc_plane_state->cm.flags.bits.blend_enable = 0;
+
/* 1D Curve - BLND TF */
old_colorop = colorop;
for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) {
@@ -1821,6 +1839,7 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state,
tf->type = TF_TYPE_DISTRIBUTED_POINTS;
tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(colorop_state->curve_1d_type);
tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE;
+ dc_plane_state->cm.flags.bits.blend_enable = 1;
__set_input_tf_32(NULL, tf, blend_lut, blend_size);
}
@@ -1846,6 +1865,7 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state,
tf->type = TF_TYPE_DISTRIBUTED_POINTS;
tf->tf = default_tf;
tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE;
+ dc_plane_state->cm.flags.bits.blend_enable = 1;
blend_lut = __extract_blob_lut32(colorop_state->data, &blend_size);
blend_size = blend_lut != NULL ? blend_size : 0;
@@ -1876,11 +1896,11 @@ amdgpu_dm_plane_set_color_properties(struct drm_plane_state *plane_state,
lut3d = __extract_blob_lut(dm_plane_state->lut3d, &lut3d_size);
lut3d_size = lut3d != NULL ? lut3d_size : 0;
- amdgpu_dm_atomic_lut3d(lut3d, lut3d_size, &dc_plane_state->lut3d_func);
+ amdgpu_dm_atomic_lut3d(lut3d, lut3d_size, &dc_plane_state->cm);
ret = amdgpu_dm_atomic_shaper_lut(shaper_lut, false,
amdgpu_tf_to_dc_tf(shaper_tf),
shaper_size,
- &dc_plane_state->in_shaper_func);
+ &dc_plane_state->cm);
if (ret) {
drm_dbg_kms(plane_state->plane->dev,
"setting plane %d shaper LUT failed.\n",
@@ -1895,7 +1915,8 @@ amdgpu_dm_plane_set_color_properties(struct drm_plane_state *plane_state,
ret = amdgpu_dm_atomic_blend_lut(blend_lut, false,
amdgpu_tf_to_dc_tf(blend_tf),
- blend_size, &dc_plane_state->blend_tf);
+ blend_size, &dc_plane_state->cm);
+
if (ret) {
drm_dbg_kms(plane_state->plane->dev,
"setting plane %d gamma lut failed.\n",
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c
index 0e5227a796f0..1e7269246cea 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc.c
@@ -2965,16 +2965,28 @@ static struct surface_update_descriptor det_surface_update(
elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
}
- if (u->blend_tf || (u->gamma && dce_use_lut(u->plane_info ? u->plane_info->format : u->surface->format))) {
+ if ((u->cm && u->cm->flags.bits.blend_enable) ||
+ (u->gamma && dce_use_lut(u->plane_info ? u->plane_info->format : u->surface->format))) {
update_flags->bits.gamma_change = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
}
- if (u->lut3d_func || u->func_shaper) {
+ if (u->cm && (u->cm->flags.bits.lut3d_enable || u->cm->flags.bits.shaper_enable)) {
update_flags->bits.lut_3d = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
}
+ if (u->cm && u->cm->flags.bits.lut3d_dma_enable != u->surface->cm.flags.bits.lut3d_dma_enable &&
+ u->cm->flags.bits.lut3d_enable && u->surface->cm.flags.bits.lut3d_enable) {
+ /* Toggling 3DLUT loading between DMA and Host is illegal */
+ BREAK_TO_DEBUGGER();
+ }
+
+ if (u->cm && u->cm->flags.bits.lut3d_enable && !u->cm->flags.bits.lut3d_dma_enable) {
+ /* Host loading 3DLUT requires full update but only stream lock */
+ elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_STREAM);
+ }
+
if (u->hdr_mult.value)
if (u->hdr_mult.value != u->surface->hdr_mult.value) {
// TODO: Should be fast?
@@ -2993,17 +3005,30 @@ static struct surface_update_descriptor det_surface_update(
update_flags->bits.cm_hist_change = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
}
- if (u->cm2_params) {
- if (u->cm2_params->component_settings.shaper_3dlut_setting != u->surface->mcm_shaper_3dlut_setting
- || u->cm2_params->component_settings.lut1d_enable != u->surface->mcm_lut1d_enable
- || u->cm2_params->cm2_luts.lut3d_data.lut3d_src != u->surface->mcm_luts.lut3d_data.lut3d_src) {
+
+ if (u->cm) {
+ const union dc_plane_cm_flags blend_only_flags = {
+ .bits = {
+ .blend_enable = 1,
+ }
+ };
+
+ if (u->cm->flags.bits.shaper_enable != u->surface->cm.flags.bits.shaper_enable
+ || u->cm->flags.bits.blend_enable != u->surface->cm.flags.bits.blend_enable
+ || u->cm->flags.bits.lut3d_enable != u->surface->cm.flags.bits.lut3d_enable
+ || u->cm->flags.bits.lut3d_dma_enable != u->surface->cm.flags.bits.lut3d_dma_enable) {
update_flags->bits.mcm_transfer_function_enable_change = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
+
+ if ((u->cm->flags.all != blend_only_flags.all && u->cm->flags.all != 0) ||
+ (u->surface->cm.flags.all != blend_only_flags.all && u->surface->cm.flags.all != 0)) {
+ elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
+ }
}
if (update_flags->bits.lut_3d &&
- u->surface->mcm_luts.lut3d_data.lut3d_src != DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM) {
+ !u->surface->cm.flags.bits.lut3d_dma_enable) {
elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
@@ -3305,24 +3330,55 @@ static void copy_surface_update_to_plane(
sizeof(struct dc_transfer_func_distributed_points));
}
- if (srf_update->cm2_params) {
- surface->mcm_shaper_3dlut_setting = srf_update->cm2_params->component_settings.shaper_3dlut_setting;
- surface->mcm_lut1d_enable = srf_update->cm2_params->component_settings.lut1d_enable;
- surface->mcm_luts = srf_update->cm2_params->cm2_luts;
- }
-
- if (srf_update->func_shaper) {
- memcpy(&surface->in_shaper_func, srf_update->func_shaper,
- sizeof(surface->in_shaper_func));
+ /* Shaper, 3DLUT, 1DLUT */
+ if (srf_update->cm) {
+ struct kref refcount = surface->cm.refcount;
+
+ memcpy(&surface->cm, srf_update->cm, sizeof(surface->cm));
+ surface->cm.refcount = refcount;
+
+#ifndef TRIM_CM2
+ /* Populate mcm_luts from cm for legacy consumers (dml2, hwseq) */
+ surface->mcm_luts.lut1d_func = &surface->cm.blend_func;
+ surface->mcm_luts.shaper = &surface->cm.shaper_func;
+ if (srf_update->cm->flags.bits.lut3d_dma_enable) {
+ surface->mcm_luts.lut3d_data.lut3d_src = DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM;
+ surface->mcm_luts.lut3d_data.gpu_mem_params.addr = surface->cm.lut3d_dma.addr;
+ surface->mcm_luts.lut3d_data.gpu_mem_params.layout =
+ (surface->cm.lut3d_dma.swizzle == CM_LUT_3D_SWIZZLE_LINEAR_RGB) ?
+ DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB :
+ (surface->cm.lut3d_dma.swizzle == CM_LUT_3D_SWIZZLE_LINEAR_BGR) ?
+ DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR :
+ DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR;
+ surface->mcm_luts.lut3d_data.gpu_mem_params.format_params.format =
+ (surface->cm.lut3d_dma.format == CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB) ?
+ DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB :
+ (surface->cm.lut3d_dma.format == CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB) ?
+ DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB :
+ DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10;
+ surface->mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias =
+ surface->cm.lut3d_dma.bias;
+ surface->mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale =
+ surface->cm.lut3d_dma.scale;
+ surface->mcm_luts.lut3d_data.gpu_mem_params.component_order =
+ DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_RGBA;
+ surface->mcm_luts.lut3d_data.gpu_mem_params.size = DC_CM2_GPU_MEM_SIZE_TRANSFORMED;
+ surface->mcm_luts.lut3d_data.mpc_3dlut_enable = (srf_update->cm->flags.bits.lut3d_enable != 0);
+ } else {
+ surface->mcm_luts.lut3d_data.lut3d_src = DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM;
+ surface->mcm_luts.lut3d_data.lut3d_func = &surface->cm.lut3d_func;
+ }
- if (surface->mcm_shaper_3dlut_setting >= DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER)
- surface->mcm_luts.shaper = &surface->in_shaper_func;
+ if (srf_update->cm->flags.bits.shaper_enable &&
+ srf_update->cm->flags.bits.lut3d_enable)
+ surface->mcm_shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT;
+ else if (srf_update->cm->flags.bits.shaper_enable)
+ surface->mcm_shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER;
+ else
+ surface->mcm_shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL;
+#endif /* TRIM_CM2 */
}
- if (srf_update->lut3d_func)
- memcpy(&surface->lut3d_func, srf_update->lut3d_func,
- sizeof(surface->lut3d_func));
-
if (srf_update->hdr_mult.value)
surface->hdr_mult =
srf_update->hdr_mult;
@@ -3331,15 +3387,10 @@ static void copy_surface_update_to_plane(
surface->sdr_white_level_nits =
srf_update->sdr_white_level_nits;
- if (srf_update->blend_tf) {
- memcpy(&surface->blend_tf, srf_update->blend_tf,
- sizeof(surface->blend_tf));
-
- if (surface->mcm_lut1d_enable)
- surface->mcm_luts.lut1d_func = &surface->blend_tf;
- }
-
- if (srf_update->cm2_params || srf_update->blend_tf)
+ if (srf_update->cm &&
+ (srf_update->cm->flags.bits.blend_enable ||
+ srf_update->cm->flags.bits.shaper_enable ||
+ srf_update->cm->flags.bits.lut3d_enable))
surface->lut_bank_a = !surface->lut_bank_a;
if (srf_update->input_csc_color_matrix)
@@ -5074,11 +5125,9 @@ static void commit_planes_for_stream(struct dc *dc,
if (!should_update_pipe_for_plane(context, pipe_ctx, plane_state))
continue;
- if (srf_updates[i].cm2_params &&
- srf_updates[i].cm2_params->cm2_luts.lut3d_data.lut3d_src ==
- DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM &&
- srf_updates[i].cm2_params->component_settings.shaper_3dlut_setting ==
- DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT &&
+ if (srf_updates[i].cm &&
+ srf_updates[i].cm->flags.bits.lut3d_enable &&
+ srf_updates[i].cm->flags.bits.lut3d_dma_enable &&
dc->hwss.trigger_3dlut_dma_load)
dc->hwss.trigger_3dlut_dma_load(dc, pipe_ctx);
@@ -5793,14 +5842,9 @@ static bool full_update_required(
(srf_updates[i].sdr_white_level_nits &&
srf_updates[i].sdr_white_level_nits != srf_updates->surface->sdr_white_level_nits) ||
srf_updates[i].in_transfer_func ||
- srf_updates[i].func_shaper ||
- srf_updates[i].lut3d_func ||
srf_updates[i].surface->force_full_update ||
(srf_updates[i].flip_addr &&
- srf_updates[i].flip_addr->address.tmz_surface != srf_updates[i].surface->address.tmz_surface) ||
- (srf_updates[i].cm2_params &&
- (srf_updates[i].cm2_params->component_settings.shaper_3dlut_setting != srf_updates[i].surface->mcm_shaper_3dlut_setting ||
- srf_updates[i].cm2_params->component_settings.lut1d_enable != srf_updates[i].surface->mcm_lut1d_enable))))
+ srf_updates[i].flip_addr->address.tmz_surface != srf_updates[i].surface->address.tmz_surface)))
return true;
}
@@ -7543,7 +7587,7 @@ bool dc_capture_register_software_state(struct dc *dc, struct dc_register_softwa
struct dc_plane_state *plane_state = pipe_ctx->plane_state;
/* MPCC blending tree and mode control - capture actual blend configuration */
- state->mpc.mpcc_mode[i] = (plane_state->blend_tf.type != TF_TYPE_BYPASS) ? 1 : 0;
+ state->mpc.mpcc_mode[i] = (plane_state->cm.blend_func.type != TF_TYPE_BYPASS) ? 1 : 0;
state->mpc.mpcc_alpha_blend_mode[i] = plane_state->per_pixel_alpha ? 1 : 0;
state->mpc.mpcc_alpha_multiplied_mode[i] = plane_state->pre_multiplied_alpha ? 1 : 0;
state->mpc.mpcc_blnd_active_overlap_only[i] = 0; /* Default - no overlap restriction */
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_surface.c b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c
index b03c8fc2f7ce..6e66aacd42b1 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc_surface.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c
@@ -45,14 +45,13 @@ void dc_plane_construct(struct dc_context *ctx, struct dc_plane_state *plane_sta
plane_state->in_transfer_func.type = TF_TYPE_BYPASS;
- plane_state->in_shaper_func.type = TF_TYPE_BYPASS;
-
- plane_state->lut3d_func.state.raw = 0;
-
- plane_state->blend_tf.type = TF_TYPE_BYPASS;
-
plane_state->pre_multiplied_alpha = true;
+ /* CM */
+ plane_state->cm.shaper_func.type = TF_TYPE_BYPASS;
+ plane_state->cm.blend_func.type = TF_TYPE_BYPASS;
+ plane_state->cm.lut3d_func.state.raw = 0;
+ plane_state->cm.flags.all = 0;
}
void dc_plane_destruct(struct dc_plane_state *plane_state)
@@ -282,6 +281,39 @@ void dc_3dlut_func_retain(struct dc_3dlut *lut)
kref_get(&lut->refcount);
}
+static void dc_plane_cm_free(struct kref *kref)
+{
+ struct dc_plane_cm *cm = container_of(kref, struct dc_plane_cm, refcount);
+
+ kvfree(cm);
+}
+
+struct dc_plane_cm *dc_plane_cm_create(void)
+{
+ struct dc_plane_cm *cm = kvzalloc(sizeof(*cm), GFP_KERNEL);
+
+ if (cm == NULL)
+ goto alloc_fail;
+
+ kref_init(&cm->refcount);
+
+ return cm;
+
+alloc_fail:
+ return NULL;
+
+}
+
+void dc_plane_cm_release(struct dc_plane_cm *cm)
+{
+ kref_put(&cm->refcount, dc_plane_cm_free);
+}
+
+void dc_plane_cm_retain(struct dc_plane_cm *cm)
+{
+ kref_get(&cm->refcount);
+}
+
void dc_plane_force_dcc_and_tiling_disable(struct dc_plane_state *plane_state,
bool clear_tiling)
{
diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h
index b8ac462a676a..2a47d7ddf53b 100644
--- a/drivers/gpu/drm/amd/display/dc/dc.h
+++ b/drivers/gpu/drm/amd/display/dc/dc.h
@@ -1486,6 +1486,47 @@ struct dc_3dlut {
struct fixed31_32 hdr_multiplier;
union dc_3dlut_state state;
};
+
+/* 3DLUT DMA (Fast Load) params */
+struct dc_3dlut_dma {
+ struct dc_plane_address addr;
+ enum dc_cm_lut_swizzle swizzle;
+ enum dc_cm_lut_pixel_format format;
+ uint16_t bias; /* FP1.5.10 */
+ uint16_t scale; /* FP1.5.10 */
+ enum dc_cm_lut_size size;
+};
+
+/* color manager */
+union dc_plane_cm_flags {
+ unsigned int all;
+ struct {
+ unsigned int shaper_enable : 1;
+ unsigned int lut3d_enable : 1;
+ unsigned int blend_enable : 1;
+ /* whether legacy (lut3d_func) or DMA is valid */
+ unsigned int lut3d_dma_enable : 1;
+#if defined(CONFIG_DRM_AMD_DC_DCN4_2)
+ /* RMCM lut to be used instead of MCM */
+ unsigned int rmcm_enable : 1;
+ unsigned int reserved: 27;
+#else
+ unsigned int reserved: 28;
+#endif
+ } bits;
+};
+
+struct dc_plane_cm {
+ struct kref refcount;
+ struct dc_transfer_func shaper_func;
+ union {
+ struct dc_3dlut lut3d_func;
+ struct dc_3dlut_dma lut3d_dma;
+ };
+ struct dc_transfer_func blend_func;
+ union dc_plane_cm_flags flags;
+};
+
/*
* This structure is filled in by dc_surface_get_status and contains
* the last requested address and the currently active address so the called
@@ -1564,14 +1605,22 @@ struct dc_plane_state {
struct fixed31_32 hdr_mult;
struct colorspace_transform gamut_remap_matrix;
+ enum dc_color_space color_space;
+
+#ifndef TRIM_CM2
// TODO: No longer used, remove
struct dc_hdr_static_metadata hdr_static_ctx;
- enum dc_color_space color_space;
-
struct dc_3dlut lut3d_func;
struct dc_transfer_func in_shaper_func;
struct dc_transfer_func blend_tf;
+ enum dc_cm2_shaper_3dlut_setting mcm_shaper_3dlut_setting;
+ bool mcm_lut1d_enable;
+ struct dc_cm2_func_luts mcm_luts;
+#endif /* TRIM_CM2 */
+ bool lut_bank_a;
+ enum mpcc_movable_cm_location mcm_location;
+ struct dc_plane_cm cm;
struct dc_transfer_func *gamcor_tf;
enum surface_pixel_format format;
@@ -1608,11 +1657,6 @@ struct dc_plane_state {
bool is_statically_allocated;
enum chroma_cositing cositing;
- enum dc_cm2_shaper_3dlut_setting mcm_shaper_3dlut_setting;
- bool mcm_lut1d_enable;
- struct dc_cm2_func_luts mcm_luts;
- bool lut_bank_a;
- enum mpcc_movable_cm_location mcm_location;
struct dc_csc_transform cursor_csc_color_matrix;
bool adaptive_sharpness_en;
int adaptive_sharpness_policy;
@@ -1976,17 +2020,7 @@ struct dc_surface_update {
const struct dc_csc_transform *input_csc_color_matrix;
const struct fixed31_32 *coeff_reduction_factor;
- const struct dc_transfer_func *func_shaper;
- const struct dc_3dlut *lut3d_func;
- const struct dc_transfer_func *blend_tf;
const struct colorspace_transform *gamut_remap_matrix;
- /*
- * Color Transformations for pre-blend MCM (Shaper, 3DLUT, 1DLUT)
- *
- * change cm2_params.component_settings: Full update
- * change cm2_params.cm2_luts: Fast update
- */
- const struct dc_cm2_parameters *cm2_params;
const struct dc_plane_cm *cm;
const struct dc_csc_transform *cursor_csc_color_matrix;
unsigned int sdr_white_level_nits;
@@ -2032,6 +2066,10 @@ struct dc_3dlut *dc_create_3dlut_func(void);
void dc_3dlut_func_release(struct dc_3dlut *lut);
void dc_3dlut_func_retain(struct dc_3dlut *lut);
+struct dc_plane_cm *dc_plane_cm_create(void);
+void dc_plane_cm_release(struct dc_plane_cm *cm);
+void dc_plane_cm_retain(struct dc_plane_cm *cm);
+
void dc_post_update_surfaces_to_stream(
struct dc *dc);
diff --git a/drivers/gpu/drm/amd/display/dc/dc_types.h b/drivers/gpu/drm/amd/display/dc/dc_types.h
index 4ed1efa17270..db6a89d938b6 100644
--- a/drivers/gpu/drm/amd/display/dc/dc_types.h
+++ b/drivers/gpu/drm/amd/display/dc/dc_types.h
@@ -1397,6 +1397,39 @@ enum dc_hpd_enable_select {
HPD_EN_FOR_SECONDARY_EDP_ONLY,
};
+enum dc_cm_lut_swizzle {
+ CM_LUT_3D_SWIZZLE_LINEAR_RGB,
+ CM_LUT_3D_SWIZZLE_LINEAR_BGR,
+ CM_LUT_1D_PACKED_LINEAR
+};
+
+enum dc_cm_lut_pixel_format {
+ CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB,
+#if defined(CONFIG_DRM_AMD_DC_DCN4_2)
+ CM_LUT_PIXEL_FORMAT_BGRA16161616_UNORM_12MSB,
+#endif
+ CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB,
+#if defined(CONFIG_DRM_AMD_DC_DCN4_2)
+ CM_LUT_PIXEL_FORMAT_BGRA16161616_UNORM_12LSB,
+#endif
+ CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10,
+#if defined(CONFIG_DRM_AMD_DC_DCN4_2)
+ CM_LUT_PIXEL_FORMAT_BGRA16161616_FLOAT_FP1_5_10
+#endif
+};
+
+enum dc_cm_lut_size {
+ CM_LUT_SIZE_NONE,
+ CM_LUT_SIZE_999,
+ CM_LUT_SIZE_171717,
+#if defined(CONFIG_DRM_AMD_DC_DCN4_2)
+ CM_LUT_SIZE_333333,
+ CM_LUT_SIZE_454545,
+ CM_LUT_SIZE_656565,
+#endif
+};
+
+#ifndef TRIM_CM2
enum dc_cm2_shaper_3dlut_setting {
DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL,
DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER,
@@ -1421,6 +1454,16 @@ enum dc_cm2_gpu_mem_format {
DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10
};
+enum dc_cm2_gpu_mem_size {
+ DC_CM2_GPU_MEM_SIZE_171717,
+ DC_CM2_GPU_MEM_SIZE_333333,
+ DC_CM2_GPU_MEM_SIZE_454545,
+ DC_CM2_GPU_MEM_SIZE_656565,
+ DC_CM2_GPU_MEM_SIZE_TRANSFORMED,
+};
+#endif /* TRIM_CM2 */
+
+#ifndef TRIM_CM2
struct dc_cm2_gpu_mem_format_parameters {
enum dc_cm2_gpu_mem_format format;
union {
@@ -1432,14 +1475,6 @@ struct dc_cm2_gpu_mem_format_parameters {
};
};
-enum dc_cm2_gpu_mem_size {
- DC_CM2_GPU_MEM_SIZE_171717,
- DC_CM2_GPU_MEM_SIZE_333333,
- DC_CM2_GPU_MEM_SIZE_454545,
- DC_CM2_GPU_MEM_SIZE_656565,
- DC_CM2_GPU_MEM_SIZE_TRANSFORMED,
-};
-
struct dc_cm2_gpu_mem_parameters {
struct dc_plane_address addr;
enum dc_cm2_gpu_mem_layout layout;
@@ -1448,17 +1483,16 @@ struct dc_cm2_gpu_mem_parameters {
enum dc_cm2_gpu_mem_size size;
uint16_t bit_depth;
};
+#endif /* TRIM_CM2 */
+#ifndef TRIM_CM2
enum dc_cm2_transfer_func_source {
DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM,
DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM
};
+#endif /* TRIM_CM2 */
-struct dc_cm2_component_settings {
- enum dc_cm2_shaper_3dlut_setting shaper_3dlut_setting;
- bool lut1d_enable;
-};
-
+#ifndef TRIM_CM2
/*
* All pointers in this struct must remain valid for as long as the 3DLUTs are used
*/
@@ -1478,11 +1512,7 @@ struct dc_cm2_func_luts {
} lut3d_data;
const struct dc_transfer_func *lut1d_func;
};
-
-struct dc_cm2_parameters {
- struct dc_cm2_component_settings component_settings;
- struct dc_cm2_func_luts cm2_luts;
-};
+#endif /* TRIM_CM2 */
enum mall_stream_type {
SUBVP_NONE, // subvp not in use
diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c b/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c
index 302515128358..9965cf572354 100644
--- a/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c
+++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c
@@ -136,7 +136,7 @@ void hubp401_program_3dlut_fl_config(
uint32_t mpc_width = {(cfg->width == 17) ? 0 : 1};
uint32_t width = {cfg->width};
- if (cfg->layout == DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR)
+ if (cfg->layout == CM_LUT_1D_PACKED_LINEAR)
width = (cfg->width == 17) ? 4916 : 35940;
REG_UPDATE_2(_3DLUT_FL_CONFIG,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c
index e6a8206f8ce0..50d039b3fb43 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c
@@ -1066,11 +1066,11 @@ bool dcn20_set_blend_lut(
bool result = true;
const struct pwl_params *blend_lut = NULL;
- if (plane_state->blend_tf.type == TF_TYPE_HWPWL)
- blend_lut = &plane_state->blend_tf.pwl;
- else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL)
+ blend_lut = &plane_state->cm.blend_func.pwl;
+ else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
cm_helper_translate_curve_to_hw_format(plane_state->ctx,
- &plane_state->blend_tf,
+ &plane_state->cm.blend_func,
&dpp_base->regamma_params, false);
blend_lut = &dpp_base->regamma_params;
}
@@ -1086,19 +1086,19 @@ bool dcn20_set_shaper_3dlut(
bool result = true;
const struct pwl_params *shaper_lut = NULL;
- if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL)
- shaper_lut = &plane_state->in_shaper_func.pwl;
- else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (plane_state->cm.shaper_func.type == TF_TYPE_HWPWL)
+ shaper_lut = &plane_state->cm.shaper_func.pwl;
+ else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
cm_helper_translate_curve_to_hw_format(plane_state->ctx,
- &plane_state->in_shaper_func,
+ &plane_state->cm.shaper_func,
&dpp_base->shaper_params, true);
shaper_lut = &dpp_base->shaper_params;
}
result = dpp_base->funcs->dpp_program_shaper_lut(dpp_base, shaper_lut);
- if (plane_state->lut3d_func.state.bits.initialized == 1)
+ if (plane_state->cm.lut3d_func.state.bits.initialized == 1)
result = dpp_base->funcs->dpp_program_3dlut(dpp_base,
- &plane_state->lut3d_func.lut_3d);
+ &plane_state->cm.lut3d_func.lut_3d);
else
result = dpp_base->funcs->dpp_program_3dlut(dpp_base, NULL);
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c
index a7c85a2302ab..aed9d06ec538 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c
@@ -239,11 +239,13 @@ bool dcn30_set_blend_lut(
bool result = true;
const struct pwl_params *blend_lut = NULL;
- if (plane_state->blend_tf.type == TF_TYPE_HWPWL)
- blend_lut = &plane_state->blend_tf.pwl;
- else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL)
+ blend_lut = &plane_state->cm.blend_func.pwl;
+ else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
result = cm3_helper_translate_curve_to_hw_format(plane_state->ctx,
- &plane_state->blend_tf, &dpp_base->regamma_params, false);
+ &plane_state->cm.blend_func,
+ &dpp_base->regamma_params,
+ false);
if (!result)
return result;
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c
index a3242e7521a4..34cbd90b2283 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c
@@ -490,12 +490,14 @@ bool dcn32_set_mcm_luts(
const struct pwl_params *lut_params = NULL;
// 1D LUT
- if (plane_state->blend_tf.type == TF_TYPE_HWPWL)
- lut_params = &plane_state->blend_tf.pwl;
- else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) {
- result = cm3_helper_translate_curve_to_hw_format(plane_state->ctx,
- &plane_state->blend_tf,
- &dpp_base->regamma_params, false);
+ if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL)
+ lut_params = &plane_state->cm.blend_func.pwl;
+ else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
+ result = cm3_helper_translate_curve_to_hw_format(
+ plane_state->ctx,
+ &plane_state->cm.blend_func,
+ &dpp_base->regamma_params,
+ false);
if (!result)
return result;
@@ -505,21 +507,22 @@ bool dcn32_set_mcm_luts(
lut_params = NULL;
// Shaper
- if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL)
- lut_params = &plane_state->in_shaper_func.pwl;
- else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (plane_state->cm.shaper_func.type == TF_TYPE_HWPWL)
+ lut_params = &plane_state->cm.shaper_func.pwl;
+ else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
// TODO: dpp_base replace
rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx,
- &plane_state->in_shaper_func,
- &dpp_base->shaper_params, true);
+ &plane_state->cm.shaper_func,
+ &dpp_base->shaper_params,
+ true);
lut_params = rval ? &dpp_base->shaper_params : NULL;
}
mpc->funcs->program_shaper(mpc, lut_params, mpcc_id);
// 3D
- if (plane_state->lut3d_func.state.bits.initialized == 1)
- result = mpc->funcs->program_3dlut(mpc, &plane_state->lut3d_func.lut_3d, mpcc_id);
+ if (plane_state->cm.lut3d_func.state.bits.initialized == 1)
+ result = mpc->funcs->program_3dlut(mpc, &plane_state->cm.lut3d_func.lut_3d, mpcc_id);
else
result = mpc->funcs->program_3dlut(mpc, NULL, mpcc_id);
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c
index 7adba2acfd40..5da81adf02d1 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c
@@ -410,37 +410,27 @@ static void dcn401_get_mcm_lut_xable_from_pipe_ctx(struct dc *dc, struct pipe_ct
enum MCM_LUT_XABLE *lut3d_xable,
enum MCM_LUT_XABLE *lut1d_xable)
{
- enum dc_cm2_shaper_3dlut_setting shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL;
- bool lut1d_enable = false;
struct mpc *mpc = dc->res_pool->mpc;
int mpcc_id = pipe_ctx->plane_res.hubp->inst;
if (!pipe_ctx->plane_state)
return;
- shaper_3dlut_setting = pipe_ctx->plane_state->mcm_shaper_3dlut_setting;
- lut1d_enable = pipe_ctx->plane_state->mcm_lut1d_enable;
+
mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id);
pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE;
- *lut1d_xable = lut1d_enable ? MCM_LUT_ENABLE : MCM_LUT_DISABLE;
-
- switch (shaper_3dlut_setting) {
- case DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL:
- *lut3d_xable = *shaper_xable = MCM_LUT_DISABLE;
- break;
- case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER:
- *lut3d_xable = MCM_LUT_DISABLE;
- *shaper_xable = MCM_LUT_ENABLE;
- break;
- case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT:
- *lut3d_xable = *shaper_xable = MCM_LUT_ENABLE;
- break;
- }
+ *lut1d_xable = pipe_ctx->plane_state->cm.flags.bits.blend_enable ?
+ MCM_LUT_ENABLE : MCM_LUT_DISABLE;
+ *shaper_xable = pipe_ctx->plane_state->cm.flags.bits.shaper_enable ?
+ MCM_LUT_ENABLE : MCM_LUT_DISABLE;
+ *lut3d_xable = (pipe_ctx->plane_state->cm.flags.bits.shaper_enable &&
+ pipe_ctx->plane_state->cm.flags.bits.lut3d_enable) ?
+ MCM_LUT_ENABLE : MCM_LUT_DISABLE;
}
void dcn401_populate_mcm_luts(struct dc *dc,
struct pipe_ctx *pipe_ctx,
- struct dc_cm2_func_luts mcm_luts,
+ const struct dc_plane_cm *cm,
bool lut_bank_a)
{
struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
@@ -448,14 +438,17 @@ void dcn401_populate_mcm_luts(struct dc *dc,
int mpcc_id = hubp->inst;
struct mpc *mpc = dc->res_pool->mpc;
union mcm_lut_params m_lut_params;
- enum dc_cm2_transfer_func_source lut3d_src = mcm_luts.lut3d_data.lut3d_src;
+ const bool lut3d_dma = !!cm->flags.bits.lut3d_dma_enable;
enum hubp_3dlut_fl_format format = 0;
enum hubp_3dlut_fl_mode mode;
- enum hubp_3dlut_fl_width width = 0;
+ /* Width was previously hard-coded to TRANSFORMED via local_mcm build,
+ * preserve identical behavior.
+ */
+ enum hubp_3dlut_fl_width width = hubp_3dlut_fl_width_transformed;
enum hubp_3dlut_fl_addressing_mode addr_mode;
- enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g = 0;
- enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b = 0;
- enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r = 0;
+ enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g;
+ enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b;
+ enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r;
enum MCM_LUT_XABLE shaper_xable = MCM_LUT_DISABLE;
enum MCM_LUT_XABLE lut3d_xable = MCM_LUT_DISABLE;
enum MCM_LUT_XABLE lut1d_xable = MCM_LUT_DISABLE;
@@ -464,13 +457,13 @@ void dcn401_populate_mcm_luts(struct dc *dc,
dcn401_get_mcm_lut_xable_from_pipe_ctx(dc, pipe_ctx, &shaper_xable, &lut3d_xable, &lut1d_xable);
/* 1D LUT */
- if (mcm_luts.lut1d_func) {
+ {
memset(&m_lut_params, 0, sizeof(m_lut_params));
- if (mcm_luts.lut1d_func->type == TF_TYPE_HWPWL)
- m_lut_params.pwl = &mcm_luts.lut1d_func->pwl;
- else if (mcm_luts.lut1d_func->type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (cm->blend_func.type == TF_TYPE_HWPWL)
+ m_lut_params.pwl = &cm->blend_func.pwl;
+ else if (cm->blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx,
- mcm_luts.lut1d_func,
+ &cm->blend_func,
&dpp_base->regamma_params, false);
m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL;
}
@@ -483,14 +476,14 @@ void dcn401_populate_mcm_luts(struct dc *dc,
}
/* Shaper */
- if (mcm_luts.shaper && mcm_luts.lut3d_data.mpc_3dlut_enable) {
+ if (cm->flags.bits.lut3d_enable) {
memset(&m_lut_params, 0, sizeof(m_lut_params));
- if (mcm_luts.shaper->type == TF_TYPE_HWPWL)
- m_lut_params.pwl = &mcm_luts.shaper->pwl;
- else if (mcm_luts.shaper->type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (cm->shaper_func.type == TF_TYPE_HWPWL)
+ m_lut_params.pwl = &cm->shaper_func.pwl;
+ else if (cm->shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
ASSERT(false);
rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx,
- mcm_luts.shaper,
+ &cm->shaper_func,
&dpp_base->regamma_params, true);
m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL;
}
@@ -503,42 +496,43 @@ void dcn401_populate_mcm_luts(struct dc *dc,
}
/* 3DLUT */
- switch (lut3d_src) {
- case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM:
+ if (!lut3d_dma) {
+ /* SYSMEM (legacy lut3d_func) */
memset(&m_lut_params, 0, sizeof(m_lut_params));
if (hubp->funcs->hubp_enable_3dlut_fl)
hubp->funcs->hubp_enable_3dlut_fl(hubp, false);
- if (mcm_luts.lut3d_data.lut3d_func && mcm_luts.lut3d_data.lut3d_func->state.bits.initialized) {
- m_lut_params.lut3d = &mcm_luts.lut3d_data.lut3d_func->lut_3d;
+ if (cm->lut3d_func.state.bits.initialized) {
+ m_lut_params.lut3d = &cm->lut3d_func.lut_3d;
if (mpc->funcs->populate_lut)
mpc->funcs->populate_lut(mpc, MCM_LUT_3DLUT, m_lut_params, lut_bank_a, mpcc_id);
if (mpc->funcs->program_lut_mode)
mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a,
mpcc_id);
}
- break;
- case DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM:
- switch (mcm_luts.lut3d_data.gpu_mem_params.size) {
- case DC_CM2_GPU_MEM_SIZE_333333:
+ } else {
+ /* VIDMEM (3DLUT DMA Fast Load) */
+
+ /* Select width based on the requested LUT size */
+ switch (cm->lut3d_dma.size) {
+#if defined(CONFIG_DRM_AMD_DC_DCN4_2)
+ case CM_LUT_SIZE_333333:
if (dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_33)
width = hubp_3dlut_fl_width_33;
break;
- case DC_CM2_GPU_MEM_SIZE_171717:
+#endif // CONFIG_DRM_AMD_DC_DCN4_2
+ case CM_LUT_SIZE_171717:
width = hubp_3dlut_fl_width_17;
break;
- case DC_CM2_GPU_MEM_SIZE_TRANSFORMED:
- width = hubp_3dlut_fl_width_transformed;
- break;
default:
- //TODO: handle default case
+ /* keep default hubp_3dlut_fl_width_transformed */
break;
}
//check for support
if (mpc->funcs->mcm.is_config_supported &&
!mpc->funcs->mcm.is_config_supported(width))
- break;
+ return;
if (mpc->funcs->program_lut_read_write_control)
mpc->funcs->program_lut_read_write_control(mpc, MCM_LUT_3DLUT, lut_bank_a, mpcc_id);
@@ -546,21 +540,24 @@ void dcn401_populate_mcm_luts(struct dc *dc,
mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a, mpcc_id);
if (hubp->funcs->hubp_program_3dlut_fl_addr)
- hubp->funcs->hubp_program_3dlut_fl_addr(hubp, mcm_luts.lut3d_data.gpu_mem_params.addr);
+ hubp->funcs->hubp_program_3dlut_fl_addr(hubp, cm->lut3d_dma.addr);
+ /* bit_depth was previously zero-initialized in local_mcm,
+ * preserve identical behavior.
+ */
if (mpc->funcs->mcm.program_bit_depth)
- mpc->funcs->mcm.program_bit_depth(mpc, mcm_luts.lut3d_data.gpu_mem_params.bit_depth, mpcc_id);
+ mpc->funcs->mcm.program_bit_depth(mpc, 0, mpcc_id);
- switch (mcm_luts.lut3d_data.gpu_mem_params.layout) {
- case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB:
+ switch (cm->lut3d_dma.swizzle) {
+ case CM_LUT_3D_SWIZZLE_LINEAR_RGB:
mode = hubp_3dlut_fl_mode_native_1;
addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear;
break;
- case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR:
+ case CM_LUT_3D_SWIZZLE_LINEAR_BGR:
mode = hubp_3dlut_fl_mode_native_2;
addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear;
break;
- case DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR:
+ case CM_LUT_1D_PACKED_LINEAR:
mode = hubp_3dlut_fl_mode_transform;
addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear;
break;
@@ -575,40 +572,38 @@ void dcn401_populate_mcm_luts(struct dc *dc,
if (hubp->funcs->hubp_program_3dlut_fl_addressing_mode)
hubp->funcs->hubp_program_3dlut_fl_addressing_mode(hubp, addr_mode);
- switch (mcm_luts.lut3d_data.gpu_mem_params.format_params.format) {
- case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB:
+ switch (cm->lut3d_dma.format) {
+ case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB:
format = hubp_3dlut_fl_format_unorm_12msb_bitslice;
break;
- case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB:
+ case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB:
format = hubp_3dlut_fl_format_unorm_12lsb_bitslice;
break;
- case DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10:
+ case CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10:
format = hubp_3dlut_fl_format_float_fp1_5_10;
break;
+ default:
+ break;
}
if (hubp->funcs->hubp_program_3dlut_fl_format)
hubp->funcs->hubp_program_3dlut_fl_format(hubp, format);
if (hubp->funcs->hubp_update_3dlut_fl_bias_scale &&
mpc->funcs->mcm.program_bias_scale) {
mpc->funcs->mcm.program_bias_scale(mpc,
- mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias,
- mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale,
+ cm->lut3d_dma.bias,
+ cm->lut3d_dma.scale,
mpcc_id);
hubp->funcs->hubp_update_3dlut_fl_bias_scale(hubp,
- mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias,
- mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale);
+ cm->lut3d_dma.bias,
+ cm->lut3d_dma.scale);
}
- //navi 4x has a bug and r and blue are swapped and need to be worked around here in
- //TODO: need to make a method for get_xbar per asic OR do the workaround in program_crossbar for 4x
- switch (mcm_luts.lut3d_data.gpu_mem_params.component_order) {
- case DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_RGBA:
- default:
- crossbar_bit_slice_cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15;
- crossbar_bit_slice_y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31;
- crossbar_bit_slice_cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47;
- break;
- }
+ /* component_order was previously hard-coded to RGBA in local_mcm,
+ * preserve identical behavior.
+ */
+ crossbar_bit_slice_cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15;
+ crossbar_bit_slice_y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31;
+ crossbar_bit_slice_cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47;
if (hubp->funcs->hubp_program_3dlut_fl_crossbar)
hubp->funcs->hubp_program_3dlut_fl_crossbar(hubp,
@@ -634,8 +629,6 @@ void dcn401_populate_mcm_luts(struct dc *dc,
mpc->funcs->program_lut_mode(mpc, MCM_LUT_1DLUT, MCM_LUT_DISABLE, lut_bank_a, mpcc_id);
}
}
- break;
-
}
}
@@ -660,19 +653,19 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx,
const struct pwl_params *lut_params = NULL;
bool rval;
- if (plane_state->mcm_luts.lut3d_data.lut3d_src == DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM) {
- dcn401_populate_mcm_luts(dc, pipe_ctx, plane_state->mcm_luts, plane_state->lut_bank_a);
+ if (plane_state->cm.flags.bits.lut3d_dma_enable) {
+ dcn401_populate_mcm_luts(dc, pipe_ctx, &plane_state->cm, plane_state->lut_bank_a);
return true;
}
mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id);
pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE;
// 1D LUT
- if (plane_state->blend_tf.type == TF_TYPE_HWPWL)
- lut_params = &plane_state->blend_tf.pwl;
- else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL)
+ lut_params = &plane_state->cm.blend_func.pwl;
+ else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx,
- &plane_state->blend_tf,
+ &plane_state->cm.blend_func,
&dpp_base->regamma_params, false);
lut_params = rval ? &dpp_base->regamma_params : NULL;
}
@@ -680,12 +673,12 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx,
lut_params = NULL;
// Shaper
- if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL)
- lut_params = &plane_state->in_shaper_func.pwl;
- else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (plane_state->cm.shaper_func.type == TF_TYPE_HWPWL)
+ lut_params = &plane_state->cm.shaper_func.pwl;
+ else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
// TODO: dpp_base replace
rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx,
- &plane_state->in_shaper_func,
+ &plane_state->cm.shaper_func,
&dpp_base->shaper_params, true);
lut_params = rval ? &dpp_base->shaper_params : NULL;
}
@@ -693,8 +686,8 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx,
// 3D
if (mpc->funcs->program_3dlut) {
- if (plane_state->lut3d_func.state.bits.initialized == 1)
- result &= mpc->funcs->program_3dlut(mpc, &plane_state->lut3d_func.lut_3d, mpcc_id);
+ if (plane_state->cm.lut3d_func.state.bits.initialized == 1)
+ result &= mpc->funcs->program_3dlut(mpc, &plane_state->cm.lut3d_func.lut_3d, mpcc_id);
else
result &= mpc->funcs->program_3dlut(mpc, NULL, mpcc_id);
}
@@ -1999,10 +1992,9 @@ void dcn401_perform_3dlut_wa_unlock(struct pipe_ctx *pipe_ctx)
for (odm_pipe = pipe_ctx; odm_pipe != NULL; odm_pipe = odm_pipe->next_odm_pipe) {
for (mpc_pipe = odm_pipe; mpc_pipe != NULL; mpc_pipe = mpc_pipe->bottom_pipe) {
- if (mpc_pipe->plane_state && mpc_pipe->plane_state->mcm_luts.lut3d_data.lut3d_src
- == DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM
- && mpc_pipe->plane_state->mcm_shaper_3dlut_setting
- == DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT) {
+ if (mpc_pipe->plane_state &&
+ mpc_pipe->plane_state->cm.flags.bits.lut3d_enable &&
+ mpc_pipe->plane_state->cm.flags.bits.lut3d_dma_enable) {
wa_pipes[wa_pipe_ct++] = mpc_pipe;
}
}
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h
index f78162ab859b..2afeafc902c7 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h
@@ -52,7 +52,7 @@ enum dc_status dcn401_enable_stream_timing(
void dcn401_enable_stream(struct pipe_ctx *pipe_ctx);
void dcn401_populate_mcm_luts(struct dc *dc,
struct pipe_ctx *pipe_ctx,
- struct dc_cm2_func_luts mcm_luts,
+ const struct dc_plane_cm *cm,
bool lut_bank_a);
void dcn401_setup_hpo_hw_control(const struct dce_hwseq *hws, bool enable);
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c
index 96e0133880e1..9cf8b379cb34 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c
@@ -401,40 +401,33 @@ void dcn42_program_cm_hist(
}
static void dc_get_lut_xbar(
- enum dc_cm2_gpu_mem_pixel_component_order order,
enum hubp_3dlut_fl_crossbar_bit_slice *cr_r,
enum hubp_3dlut_fl_crossbar_bit_slice *y_g,
enum hubp_3dlut_fl_crossbar_bit_slice *cb_b)
{
- switch (order) {
- case DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_RGBA:
- *cr_r = hubp_3dlut_fl_crossbar_bit_slice_32_47;
- *y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31;
- *cb_b = hubp_3dlut_fl_crossbar_bit_slice_0_15;
- break;
- case DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_BGRA:
- *cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15;
- *y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31;
- *cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47;
- break;
- }
+ /* component_order was previously hard-coded to RGBA in local_mcm,
+ * preserve identical behavior.
+ */
+ *cr_r = hubp_3dlut_fl_crossbar_bit_slice_32_47;
+ *y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31;
+ *cb_b = hubp_3dlut_fl_crossbar_bit_slice_0_15;
}
static void dc_get_lut_mode(
- enum dc_cm2_gpu_mem_layout layout,
+ enum dc_cm_lut_swizzle swizzle,
enum hubp_3dlut_fl_mode *mode,
enum hubp_3dlut_fl_addressing_mode *addr_mode)
{
- switch (layout) {
- case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB:
+ switch (swizzle) {
+ case CM_LUT_3D_SWIZZLE_LINEAR_RGB:
*mode = hubp_3dlut_fl_mode_native_1;
*addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear;
break;
- case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR:
+ case CM_LUT_3D_SWIZZLE_LINEAR_BGR:
*mode = hubp_3dlut_fl_mode_native_2;
*addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear;
break;
- case DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR:
+ case CM_LUT_1D_PACKED_LINEAR:
*mode = hubp_3dlut_fl_mode_transform;
*addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear;
break;
@@ -446,19 +439,22 @@ static void dc_get_lut_mode(
}
static void dc_get_lut_format(
- enum dc_cm2_gpu_mem_format dc_format,
+ enum dc_cm_lut_pixel_format dc_format,
enum hubp_3dlut_fl_format *format)
{
switch (dc_format) {
- case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB:
+ case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB:
*format = hubp_3dlut_fl_format_unorm_12msb_bitslice;
break;
- case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB:
+ case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB:
*format = hubp_3dlut_fl_format_unorm_12lsb_bitslice;
break;
- case DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10:
+ case CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10:
*format = hubp_3dlut_fl_format_float_fp1_5_10;
break;
+ default:
+ *format = hubp_3dlut_fl_format_unorm_12msb_bitslice;
+ break;
}
}
@@ -472,16 +468,17 @@ static bool dc_is_rmcm_3dlut_supported(struct hubp *hubp, struct mpc *mpc)
return false;
}
-static bool is_rmcm_3dlut_fl_supported(struct dc *dc, enum dc_cm2_gpu_mem_size size)
+#if defined(CONFIG_DRM_AMD_DC_DCN4_2)
+static bool is_rmcm_3dlut_fl_supported(struct dc *dc)
{
+ /* size was previously hard-coded to TRANSFORMED in local_mcm,
+ * which mapped to dim_17. Preserve identical behavior.
+ */
if (!dc->caps.color.mpc.rmcm_3d_lut_caps.dma_3d_lut)
return false;
- if (size == DC_CM2_GPU_MEM_SIZE_171717)
- return dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_17 != 0u;
- else if (size == DC_CM2_GPU_MEM_SIZE_333333)
- return dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_33 != 0u;
- return false;
+ return dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_17 != 0u;
}
+#endif
static void dcn42_set_mcm_location_post_blend(struct dc *dc, struct pipe_ctx *pipe_ctx, bool bPostBlend)
{
@@ -502,56 +499,45 @@ static void dcn42_get_mcm_lut_xable_from_pipe_ctx(struct dc *dc, struct pipe_ctx
enum MCM_LUT_XABLE *lut3d_xable,
enum MCM_LUT_XABLE *lut1d_xable)
{
- enum dc_cm2_shaper_3dlut_setting shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL;
- bool lut1d_enable = false;
struct mpc *mpc = dc->res_pool->mpc;
int mpcc_id = pipe_ctx->plane_res.hubp->inst;
if (!pipe_ctx->plane_state)
return;
- shaper_3dlut_setting = pipe_ctx->plane_state->mcm_shaper_3dlut_setting;
- lut1d_enable = pipe_ctx->plane_state->mcm_lut1d_enable;
+
mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id);
pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE;
- *lut1d_xable = lut1d_enable ? MCM_LUT_ENABLE : MCM_LUT_DISABLE;
-
- switch (shaper_3dlut_setting) {
- case DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL:
- *lut3d_xable = *shaper_xable = MCM_LUT_DISABLE;
- break;
- case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER:
- *lut3d_xable = MCM_LUT_DISABLE;
- *shaper_xable = MCM_LUT_ENABLE;
- break;
- case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT:
- *lut3d_xable = *shaper_xable = MCM_LUT_ENABLE;
- break;
- }
+ *lut1d_xable = pipe_ctx->plane_state->cm.flags.bits.blend_enable ?
+ MCM_LUT_ENABLE : MCM_LUT_DISABLE;
+ *shaper_xable = pipe_ctx->plane_state->cm.flags.bits.shaper_enable ?
+ MCM_LUT_ENABLE : MCM_LUT_DISABLE;
+ *lut3d_xable = (pipe_ctx->plane_state->cm.flags.bits.shaper_enable &&
+ pipe_ctx->plane_state->cm.flags.bits.lut3d_enable) ?
+ MCM_LUT_ENABLE : MCM_LUT_DISABLE;
}
static void fl_get_lut_mode(
- enum dc_cm2_gpu_mem_layout layout,
- enum dc_cm2_gpu_mem_size size,
+ enum dc_cm_lut_swizzle swizzle,
enum hubp_3dlut_fl_mode *mode,
enum hubp_3dlut_fl_addressing_mode *addr_mode,
enum hubp_3dlut_fl_width *width)
{
+ /* size was previously hard-coded to TRANSFORMED in local_mcm,
+ * preserve identical behavior (transformed width).
+ */
*width = hubp_3dlut_fl_width_17;
- if (size == DC_CM2_GPU_MEM_SIZE_333333)
- *width = hubp_3dlut_fl_width_33;
-
- switch (layout) {
- case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB:
+ switch (swizzle) {
+ case CM_LUT_3D_SWIZZLE_LINEAR_RGB:
*mode = hubp_3dlut_fl_mode_native_1;
*addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear;
break;
- case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR:
+ case CM_LUT_3D_SWIZZLE_LINEAR_BGR:
*mode = hubp_3dlut_fl_mode_native_2;
*addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear;
break;
- case DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR:
+ case CM_LUT_1D_PACKED_LINEAR:
*mode = hubp_3dlut_fl_mode_transform;
*addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear;
break;
@@ -565,8 +551,7 @@ static void fl_get_lut_mode(
bool dcn42_program_rmcm_luts(
struct hubp *hubp,
struct pipe_ctx *pipe_ctx,
- enum dc_cm2_transfer_func_source lut3d_src,
- struct dc_cm2_func_luts *mcm_luts,
+ const struct dc_plane_cm *cm,
struct mpc *mpc,
bool lut_bank_a,
int mpcc_id)
@@ -596,21 +581,24 @@ bool dcn42_program_rmcm_luts(
if (!rmcm_3dlut)
return false;
- rmcm_3dlut->protection_bits = mcm_luts->lut3d_data.rmcm_tmz;
+ /* rmcm_tmz was previously zero-initialized in local_mcm,
+ * preserve identical behavior.
+ */
+ rmcm_3dlut->protection_bits = 0;
dcn42_get_mcm_lut_xable_from_pipe_ctx(dc, pipe_ctx, &shaper_xable, &lut3d_xable, &lut1d_xable);
/* Shaper */
- if (mcm_luts->shaper) {
+ {
memset(&m_lut_params, 0, sizeof(m_lut_params));
- if (mcm_luts->shaper->type == TF_TYPE_HWPWL) {
- m_lut_params.pwl = &mcm_luts->shaper->pwl;
- } else if (mcm_luts->shaper->type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (cm->shaper_func.type == TF_TYPE_HWPWL) {
+ m_lut_params.pwl = &cm->shaper_func.pwl;
+ } else if (cm->shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
ASSERT(false);
cm_helper_translate_curve_to_hw_format(
dc->ctx,
- mcm_luts->shaper,
+ &cm->shaper_func,
&dpp_base->shaper_params, true);
m_lut_params.pwl = &dpp_base->shaper_params;
}
@@ -626,15 +614,16 @@ bool dcn42_program_rmcm_luts(
}
/* 3DLUT */
- switch (lut3d_src) {
- case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM:
+ if (!cm->flags.bits.lut3d_dma_enable) {
+ /* SYSMEM path — no DMA 3DLUT available.
+ * Previously this was treated as a no-op for the DMA/VIDMEM
+ * programming, preserve identical behavior.
+ */
memset(&m_lut_params, 0, sizeof(m_lut_params));
- // Don't know what to do in this case.
- //case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM:
- break;
- case DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM:
- fl_get_lut_mode(mcm_luts->lut3d_data.gpu_mem_params.layout,
- mcm_luts->lut3d_data.gpu_mem_params.size,
+ } else {
+ /* VIDMEM (3DLUT DMA Fast Load) */
+
+ fl_get_lut_mode(cm->lut3d_dma.swizzle,
&mode,
&addr_mode,
&width);
@@ -646,20 +635,19 @@ bool dcn42_program_rmcm_luts(
return false;
// setting native or transformed mode,
- dc_get_lut_mode(mcm_luts->lut3d_data.gpu_mem_params.layout, &mode, &addr_mode);
+ dc_get_lut_mode(cm->lut3d_dma.swizzle, &mode, &addr_mode);
//seems to be only for the MCM
- dc_get_lut_format(mcm_luts->lut3d_data.gpu_mem_params.format_params.format, &format);
+ dc_get_lut_format(cm->lut3d_dma.format, &format);
dc_get_lut_xbar(
- mcm_luts->lut3d_data.gpu_mem_params.component_order,
&crossbar_bit_slice_cr_r,
&crossbar_bit_slice_y_g,
&crossbar_bit_slice_cb_b);
fl_config.mode = mode;
fl_config.enabled = lut3d_xable != MCM_LUT_DISABLE;
- fl_config.address = mcm_luts->lut3d_data.gpu_mem_params.addr;
+ fl_config.address = cm->lut3d_dma.addr;
fl_config.format = format;
fl_config.crossbar_bit_slice_y_g = crossbar_bit_slice_y_g;
fl_config.crossbar_bit_slice_cb_b = crossbar_bit_slice_cb_b;
@@ -667,17 +655,20 @@ bool dcn42_program_rmcm_luts(
fl_config.width = width;
fl_config.protection_bits = rmcm_3dlut->protection_bits;
fl_config.addr_mode = addr_mode;
- fl_config.layout = mcm_luts->lut3d_data.gpu_mem_params.layout;
- fl_config.bias = mcm_luts->lut3d_data.gpu_mem_params.format_params.float_params.bias;
- fl_config.scale = mcm_luts->lut3d_data.gpu_mem_params.format_params.float_params.scale;
+ fl_config.layout = cm->lut3d_dma.swizzle;
+ fl_config.bias = cm->lut3d_dma.bias;
+ fl_config.scale = cm->lut3d_dma.scale;
mpc_fl_config.enabled = fl_config.enabled;
mpc_fl_config.width = width;
mpc_fl_config.select_lut_bank_a = lut_bank_a;
- mpc_fl_config.bit_depth = mcm_luts->lut3d_data.gpu_mem_params.bit_depth;
+ /* bit_depth was previously zero-initialized in local_mcm,
+ * preserve identical behavior.
+ */
+ mpc_fl_config.bit_depth = 0;
mpc_fl_config.hubp_index = hubp->inst;
- mpc_fl_config.bias = mcm_luts->lut3d_data.gpu_mem_params.format_params.float_params.bias;
- mpc_fl_config.scale = mcm_luts->lut3d_data.gpu_mem_params.format_params.float_params.scale;
+ mpc_fl_config.bias = cm->lut3d_dma.bias;
+ mpc_fl_config.scale = cm->lut3d_dma.scale;
//1. power down the block
mpc->funcs->rmcm.power_on_shaper_3dlut(mpc, mpcc_id, false);
@@ -689,10 +680,6 @@ bool dcn42_program_rmcm_luts(
//3. power on the block
mpc->funcs->rmcm.power_on_shaper_3dlut(mpc, mpcc_id, true);
-
- break;
- default:
- return false;
}
return true;
@@ -700,7 +687,7 @@ bool dcn42_program_rmcm_luts(
void dcn42_populate_mcm_luts(struct dc *dc,
struct pipe_ctx *pipe_ctx,
- struct dc_cm2_func_luts mcm_luts,
+ const struct dc_plane_cm *cm,
bool lut_bank_a)
{
struct dpp *dpp_base = pipe_ctx->plane_res.dpp;
@@ -708,14 +695,17 @@ void dcn42_populate_mcm_luts(struct dc *dc,
int mpcc_id = hubp->inst;
struct mpc *mpc = dc->res_pool->mpc;
union mcm_lut_params m_lut_params;
- enum dc_cm2_transfer_func_source lut3d_src = mcm_luts.lut3d_data.lut3d_src;
+ const bool lut3d_dma = !!cm->flags.bits.lut3d_dma_enable;
enum hubp_3dlut_fl_format format = 0;
enum hubp_3dlut_fl_mode mode;
- enum hubp_3dlut_fl_width width = 0;
+ /* Width was previously hard-coded to TRANSFORMED via local_mcm build,
+ * preserve identical behavior.
+ */
+ enum hubp_3dlut_fl_width width = hubp_3dlut_fl_width_transformed;
enum hubp_3dlut_fl_addressing_mode addr_mode;
- enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g = 0;
- enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b = 0;
- enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r = 0;
+ enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g;
+ enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b;
+ enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r;
enum MCM_LUT_XABLE shaper_xable = MCM_LUT_DISABLE;
enum MCM_LUT_XABLE lut3d_xable = MCM_LUT_DISABLE;
enum MCM_LUT_XABLE lut1d_xable = MCM_LUT_DISABLE;
@@ -724,33 +714,35 @@ void dcn42_populate_mcm_luts(struct dc *dc,
dcn42_get_mcm_lut_xable_from_pipe_ctx(dc, pipe_ctx, &shaper_xable, &lut3d_xable, &lut1d_xable);
//MCM - setting its location (Before/After) blender
- //set to post blend (true)
+ //mpc_mcm_post_blend was previously zero-initialized in local_mcm,
+ //preserve identical behavior.
dcn42_set_mcm_location_post_blend(
dc,
pipe_ctx,
- mcm_luts.lut3d_data.mpc_mcm_post_blend);
+ false);
//RMCM - 3dLUT+Shaper
- if (mcm_luts.lut3d_data.rmcm_3dlut_enable &&
- is_rmcm_3dlut_fl_supported(dc, mcm_luts.lut3d_data.gpu_mem_params.size)) {
+#if defined(CONFIG_DRM_AMD_DC_DCN4_2)
+ if (cm->flags.bits.rmcm_enable &&
+ is_rmcm_3dlut_fl_supported(dc)) {
dcn42_program_rmcm_luts(
hubp,
pipe_ctx,
- lut3d_src,
- &mcm_luts,
+ cm,
mpc,
lut_bank_a,
mpcc_id);
}
+#endif /* CONFIG_DRM_AMD_DC_DCN4_2 */
/* 1D LUT */
- if (mcm_luts.lut1d_func) {
+ {
memset(&m_lut_params, 0, sizeof(m_lut_params));
- if (mcm_luts.lut1d_func->type == TF_TYPE_HWPWL)
- m_lut_params.pwl = &mcm_luts.lut1d_func->pwl;
- else if (mcm_luts.lut1d_func->type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (cm->blend_func.type == TF_TYPE_HWPWL)
+ m_lut_params.pwl = &cm->blend_func.pwl;
+ else if (cm->blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx,
- mcm_luts.lut1d_func,
+ &cm->blend_func,
&dpp_base->regamma_params, false);
m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL;
}
@@ -763,14 +755,14 @@ void dcn42_populate_mcm_luts(struct dc *dc,
}
/* Shaper */
- if (mcm_luts.shaper && mcm_luts.lut3d_data.mpc_3dlut_enable) {
+ if (cm->flags.bits.lut3d_enable) {
memset(&m_lut_params, 0, sizeof(m_lut_params));
- if (mcm_luts.shaper->type == TF_TYPE_HWPWL)
- m_lut_params.pwl = &mcm_luts.shaper->pwl;
- else if (mcm_luts.shaper->type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (cm->shaper_func.type == TF_TYPE_HWPWL)
+ m_lut_params.pwl = &cm->shaper_func.pwl;
+ else if (cm->shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
ASSERT(false);
rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx,
- mcm_luts.shaper,
+ &cm->shaper_func,
&dpp_base->regamma_params, true);
m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL;
}
@@ -783,41 +775,27 @@ void dcn42_populate_mcm_luts(struct dc *dc,
}
/* 3DLUT */
- switch (lut3d_src) {
- case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM:
+ if (!lut3d_dma) {
+ /* SYSMEM (legacy lut3d_func) */
memset(&m_lut_params, 0, sizeof(m_lut_params));
if (hubp->funcs->hubp_enable_3dlut_fl)
hubp->funcs->hubp_enable_3dlut_fl(hubp, false);
- if (mcm_luts.lut3d_data.lut3d_func && mcm_luts.lut3d_data.lut3d_func->state.bits.initialized) {
- m_lut_params.lut3d = &mcm_luts.lut3d_data.lut3d_func->lut_3d;
+ if (cm->lut3d_func.state.bits.initialized) {
+ m_lut_params.lut3d = &cm->lut3d_func.lut_3d;
if (mpc->funcs->populate_lut)
mpc->funcs->populate_lut(mpc, MCM_LUT_3DLUT, m_lut_params, lut_bank_a, mpcc_id);
if (mpc->funcs->program_lut_mode)
mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a,
mpcc_id);
}
- break;
- case DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM:
- switch (mcm_luts.lut3d_data.gpu_mem_params.size) {
- case DC_CM2_GPU_MEM_SIZE_333333:
- width = hubp_3dlut_fl_width_33;
- break;
- case DC_CM2_GPU_MEM_SIZE_171717:
- width = hubp_3dlut_fl_width_17;
- break;
- case DC_CM2_GPU_MEM_SIZE_TRANSFORMED:
- width = hubp_3dlut_fl_width_transformed;
- break;
- default:
- //TODO: Handle default case
- break;
- }
+ } else {
+ /* VIDMEM (3DLUT DMA Fast Load) */
//check for support
if (mpc->funcs->mcm.is_config_supported &&
!mpc->funcs->mcm.is_config_supported(width))
- break;
+ return;
if (mpc->funcs->program_lut_read_write_control)
mpc->funcs->program_lut_read_write_control(mpc, MCM_LUT_3DLUT, lut_bank_a, mpcc_id);
@@ -825,49 +803,70 @@ void dcn42_populate_mcm_luts(struct dc *dc,
mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a, mpcc_id);
if (hubp->funcs->hubp_program_3dlut_fl_addr)
- hubp->funcs->hubp_program_3dlut_fl_addr(hubp, mcm_luts.lut3d_data.gpu_mem_params.addr);
+ hubp->funcs->hubp_program_3dlut_fl_addr(hubp, cm->lut3d_dma.addr);
+ /* bit_depth was previously zero-initialized in local_mcm,
+ * preserve identical behavior.
+ */
if (mpc->funcs->mcm.program_bit_depth)
- mpc->funcs->mcm.program_bit_depth(mpc, mcm_luts.lut3d_data.gpu_mem_params.bit_depth, mpcc_id);
+ mpc->funcs->mcm.program_bit_depth(mpc, 0, mpcc_id);
- dc_get_lut_mode(mcm_luts.lut3d_data.gpu_mem_params.layout, &mode, &addr_mode);
+ switch (cm->lut3d_dma.swizzle) {
+ case CM_LUT_3D_SWIZZLE_LINEAR_RGB:
+ mode = hubp_3dlut_fl_mode_native_1;
+ addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear;
+ break;
+ case CM_LUT_3D_SWIZZLE_LINEAR_BGR:
+ mode = hubp_3dlut_fl_mode_native_2;
+ addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear;
+ break;
+ case CM_LUT_1D_PACKED_LINEAR:
+ mode = hubp_3dlut_fl_mode_transform;
+ addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear;
+ break;
+ default:
+ mode = hubp_3dlut_fl_mode_disable;
+ addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear;
+ break;
+ }
if (hubp->funcs->hubp_program_3dlut_fl_mode)
hubp->funcs->hubp_program_3dlut_fl_mode(hubp, mode);
if (hubp->funcs->hubp_program_3dlut_fl_addressing_mode)
hubp->funcs->hubp_program_3dlut_fl_addressing_mode(hubp, addr_mode);
- switch (mcm_luts.lut3d_data.gpu_mem_params.format_params.format) {
- case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB:
+ switch (cm->lut3d_dma.format) {
+ case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB:
format = hubp_3dlut_fl_format_unorm_12msb_bitslice;
break;
- case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB:
+ case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB:
format = hubp_3dlut_fl_format_unorm_12lsb_bitslice;
break;
- case DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10:
+ case CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10:
format = hubp_3dlut_fl_format_float_fp1_5_10;
break;
+ default:
+ break;
}
if (hubp->funcs->hubp_program_3dlut_fl_format)
hubp->funcs->hubp_program_3dlut_fl_format(hubp, format);
if (hubp->funcs->hubp_update_3dlut_fl_bias_scale &&
mpc->funcs->mcm.program_bias_scale) {
mpc->funcs->mcm.program_bias_scale(mpc,
- mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias,
- mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale,
+ cm->lut3d_dma.bias,
+ cm->lut3d_dma.scale,
mpcc_id);
hubp->funcs->hubp_update_3dlut_fl_bias_scale(hubp,
- mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias,
- mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale);
+ cm->lut3d_dma.bias,
+ cm->lut3d_dma.scale);
}
- //navi 4x has a bug and r and blue are swapped and need to be worked around here in
- //TODO: need to make a method for get_xbar per asic OR do the workaround in program_crossbar for 4x
- dc_get_lut_xbar(
- mcm_luts.lut3d_data.gpu_mem_params.component_order,
- &crossbar_bit_slice_cr_r,
- &crossbar_bit_slice_y_g,
- &crossbar_bit_slice_cb_b);
+ /* component_order was previously hard-coded to RGBA in local_mcm,
+ * preserve identical behavior.
+ */
+ crossbar_bit_slice_cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15;
+ crossbar_bit_slice_y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31;
+ crossbar_bit_slice_cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47;
if (hubp->funcs->hubp_program_3dlut_fl_crossbar)
hubp->funcs->hubp_program_3dlut_fl_crossbar(hubp,
@@ -893,7 +892,6 @@ void dcn42_populate_mcm_luts(struct dc *dc,
mpc->funcs->program_lut_mode(mpc, MCM_LUT_1DLUT, MCM_LUT_DISABLE, lut_bank_a, mpcc_id);
}
}
- break;
}
}
@@ -908,19 +906,19 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx,
const struct pwl_params *lut_params = NULL;
bool rval;
- if (plane_state->mcm_luts.lut3d_data.lut3d_src == DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM) {
- dcn42_populate_mcm_luts(dc, pipe_ctx, plane_state->mcm_luts, plane_state->lut_bank_a);
+ if (plane_state->cm.flags.bits.lut3d_dma_enable) {
+ dcn42_populate_mcm_luts(dc, pipe_ctx, &plane_state->cm, plane_state->lut_bank_a);
return true;
}
mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id);
pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE;
// 1D LUT
- if (plane_state->blend_tf.type == TF_TYPE_HWPWL)
- lut_params = &plane_state->blend_tf.pwl;
- else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL)
+ lut_params = &plane_state->cm.blend_func.pwl;
+ else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx,
- &plane_state->blend_tf,
+ &plane_state->cm.blend_func,
&dpp_base->regamma_params, false);
lut_params = rval ? &dpp_base->regamma_params : NULL;
}
@@ -928,12 +926,12 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx,
lut_params = NULL;
// Shaper
- if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL)
- lut_params = &plane_state->in_shaper_func.pwl;
- else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
+ if (plane_state->cm.shaper_func.type == TF_TYPE_HWPWL)
+ lut_params = &plane_state->cm.shaper_func.pwl;
+ else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) {
// TODO: dpp_base replace
rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx,
- &plane_state->in_shaper_func,
+ &plane_state->cm.shaper_func,
&dpp_base->shaper_params, true);
lut_params = rval ? &dpp_base->shaper_params : NULL;
}
@@ -941,8 +939,8 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx,
// 3D
if (mpc->funcs->program_3dlut) {
- if (plane_state->lut3d_func.state.bits.initialized == 1)
- result &= mpc->funcs->program_3dlut(mpc, &plane_state->lut3d_func.lut_3d, mpcc_id);
+ if (plane_state->cm.lut3d_func.state.bits.initialized == 1)
+ result &= mpc->funcs->program_3dlut(mpc, &plane_state->cm.lut3d_func.lut_3d, mpcc_id);
else
result &= mpc->funcs->program_3dlut(mpc, NULL, mpcc_id);
}
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h
index 0539ee0ffaee..c469e7535114 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h
@@ -20,14 +20,13 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx,
void dcn42_populate_mcm_luts(struct dc *dc,
struct pipe_ctx *pipe_ctx,
- struct dc_cm2_func_luts mcm_luts,
+ const struct dc_plane_cm *cm,
bool lut_bank_a);
bool dcn42_program_rmcm_luts(
struct hubp *hubp,
struct pipe_ctx *pipe_ctx,
- enum dc_cm2_transfer_func_source lut3d_src,
- struct dc_cm2_func_luts *mcm_luts,
+ const struct dc_plane_cm *cm,
struct mpc *mpc,
bool lut_bank_a,
int mpcc_id);
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h
index 63c6c841c681..b4956893ae9a 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h
+++ b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h
@@ -58,6 +58,7 @@ struct dc_state;
struct dc_stream_status;
struct dc_writeback_info;
struct dchub_init_data;
+struct dc_plane_cm;
struct dc_static_screen_params;
struct resource_pool;
struct resource_context;
@@ -219,7 +220,7 @@ struct hwseq_private_funcs {
struct dc_state *context);
void (*populate_mcm_luts)(struct dc *dc,
struct pipe_ctx *pipe_ctx,
- struct dc_cm2_func_luts mcm_luts,
+ const struct dc_plane_cm *cm,
bool lut_bank_a);
void (*perform_3dlut_wa_unlock)(struct pipe_ctx *pipe_ctx);
void (*wait_for_pipe_update_if_needed)(struct dc *dc, struct pipe_ctx *pipe_ctx, bool is_surface_update_only);
diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h b/drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h
index 1c18898aa475..6d6eda0e7e9d 100644
--- a/drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h
+++ b/drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h
@@ -108,7 +108,7 @@ struct hubp_fl_3dlut_config {
uint16_t scale;
struct dc_plane_address address;
enum hubp_3dlut_fl_addressing_mode addr_mode;
- enum dc_cm2_gpu_mem_layout layout;
+ enum dc_cm_lut_swizzle layout;
uint8_t protection_bits;
enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g;
enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b;
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 07/32] drm/amd/display: Extract backlight code to amdgpu_dm_backlight
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (5 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 06/32] drm/amd/display: Introduce dc_plane_cm and migrate surface update color path Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 08/32] drm/amd/display: Extract audio code to amdgpu_dm_audio Chenyu Chen
` (25 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Move backlight-related functions from amdgpu_dm.c into a new
amdgpu_dm_backlight.c file to improve code organization and
reduce the size of the monolithic amdgpu_dm.c.
No functional change intended.
Assisted-by: Copilot:Claude-Opus-4.6
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +-
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 620 +---------------
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 1 -
.../display/amdgpu_dm/amdgpu_dm_backlight.c | 660 ++++++++++++++++++
.../display/amdgpu_dm/amdgpu_dm_backlight.h | 44 ++
.../display/amdgpu_dm/amdgpu_dm_services.c | 1 +
6 files changed, 710 insertions(+), 619 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
index 54a93e4255b3..2953c59d85e7 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
@@ -41,7 +41,8 @@ AMDGPUDM = \
amdgpu_dm_quirks.o \
amdgpu_dm_wb.o \
amdgpu_dm_colorop.o \
- amdgpu_dm_ism.o
+ amdgpu_dm_ism.o \
+ amdgpu_dm_backlight.o
ifdef CONFIG_DRM_AMD_DC_FP
AMDGPUDM += dc_fpu.o
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 231825249c48..100e1fb572b1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -66,6 +66,7 @@
#endif
#include "amdgpu_dm_psr.h"
#include "amdgpu_dm_replay.h"
+#include "amdgpu_dm_backlight.h"
#include "ivsrcid/ivsrcid_vislands30.h"
@@ -246,10 +247,6 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector,
enum dc_detect_reason reason);
static void handle_hpd_rx_irq(void *param);
-static void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
- int bl_idx,
- u32 user_brightness);
-
static bool
is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state,
struct drm_crtc_state *new_crtc_state);
@@ -4047,74 +4044,6 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector)
drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type);
}
-static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector)
-{
- const struct drm_panel_backlight_quirk *panel_backlight_quirk;
- struct amdgpu_dm_backlight_caps *caps;
- struct drm_connector *conn_base;
- struct amdgpu_device *adev;
- struct drm_luminance_range_info *luminance_range;
- struct drm_device *drm;
-
- if (aconnector->bl_idx == -1 ||
- aconnector->dc_link->connector_signal != SIGNAL_TYPE_EDP)
- return;
-
- conn_base = &aconnector->base;
- drm = conn_base->dev;
- adev = drm_to_adev(drm);
-
- caps = &adev->dm.backlight_caps[aconnector->bl_idx];
- caps->ext_caps = &aconnector->dc_link->dpcd_sink_ext_caps;
- caps->aux_support = false;
-
- if (caps->ext_caps->bits.oled == 1
- /*
- * ||
- * caps->ext_caps->bits.sdr_aux_backlight_control == 1 ||
- * caps->ext_caps->bits.hdr_aux_backlight_control == 1
- */)
- caps->aux_support = true;
-
- if (amdgpu_backlight == 0)
- caps->aux_support = false;
- else if (amdgpu_backlight == 1)
- caps->aux_support = true;
- if (caps->aux_support)
- aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX;
-
- luminance_range = &conn_base->display_info.luminance_range;
-
- if (luminance_range->max_luminance)
- caps->aux_max_input_signal = luminance_range->max_luminance;
- else
- caps->aux_max_input_signal = 512;
-
- if (luminance_range->min_luminance)
- caps->aux_min_input_signal = luminance_range->min_luminance;
- else
- caps->aux_min_input_signal = 1;
-
- panel_backlight_quirk =
- drm_get_panel_backlight_quirk(aconnector->drm_edid);
- if (!IS_ERR_OR_NULL(panel_backlight_quirk)) {
- if (panel_backlight_quirk->min_brightness) {
- caps->min_input_signal =
- panel_backlight_quirk->min_brightness - 1;
- drm_info(drm,
- "Applying panel backlight quirk, min_brightness: %d\n",
- caps->min_input_signal);
- }
- if (panel_backlight_quirk->brightness_mask) {
- drm_info(drm,
- "Applying panel backlight quirk, brightness_mask: 0x%X\n",
- panel_backlight_quirk->brightness_mask);
- caps->brightness_mask =
- panel_backlight_quirk->brightness_mask;
- }
- }
-}
-
DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T))
void amdgpu_dm_update_connector_after_detect(
@@ -4240,7 +4169,7 @@ void amdgpu_dm_update_connector_after_detect(
}
amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true);
- update_connector_ext_caps(aconnector);
+ amdgpu_dm_update_connector_ext_caps(aconnector);
dm_set_panel_type(aconnector);
} else {
hdmi_cec_unset_edid(aconnector);
@@ -5156,420 +5085,6 @@ static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev)
return 0;
}
-#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12
-#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255
-#define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2)
-#define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50
-
-void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm,
- int bl_idx)
-{
- struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[bl_idx];
-
- if (caps->caps_valid)
- return;
-
-#if defined(CONFIG_ACPI)
- amdgpu_acpi_get_backlight_caps(caps);
-
- /* validate the firmware value is sane */
- if (caps->caps_valid) {
- int spread = caps->max_input_signal - caps->min_input_signal;
-
- if (caps->max_input_signal > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT ||
- caps->min_input_signal < 0 ||
- spread > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT ||
- spread < AMDGPU_DM_MIN_SPREAD) {
- drm_dbg_kms(adev_to_drm(dm->adev), "DM: Invalid backlight caps: min=%d, max=%d\n",
- caps->min_input_signal, caps->max_input_signal);
- caps->caps_valid = false;
- }
- }
-
- if (!caps->caps_valid) {
- caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
- caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- caps->caps_valid = true;
- }
-#else
- if (caps->aux_support)
- return;
-
- caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
- caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- caps->caps_valid = true;
-#endif
-}
-
-static int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,
- unsigned int *min, unsigned int *max)
-{
- if (!caps)
- return 0;
-
- if (caps->aux_support) {
- // Firmware limits are in nits, DC API wants millinits.
- *max = 1000 * caps->aux_max_input_signal;
- *min = 1000 * caps->aux_min_input_signal;
- } else {
- // Firmware limits are 8-bit, PWM control is 16-bit.
- *max = 0x101 * caps->max_input_signal;
- *min = 0x101 * caps->min_input_signal;
- }
- return 1;
-}
-
-/* Rescale from [min..max] to [0..AMDGPU_MAX_BL_LEVEL] */
-static inline u32 scale_input_to_fw(int min, int max, u64 input)
-{
- return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max - min);
-}
-
-/* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [min..max] */
-static inline u32 scale_fw_to_input(int min, int max, u64 input)
-{
- return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL);
-}
-
-static void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
- unsigned int min, unsigned int max,
- uint32_t *user_brightness)
-{
- u32 brightness = scale_input_to_fw(min, max, *user_brightness);
- u8 lower_signal, upper_signal, upper_lum, lower_lum, lum;
- int left, right;
-
- if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)
- return;
-
- if (!caps->data_points)
- return;
-
- /*
- * Handle the case where brightness is below the first data point
- * Interpolate between (0,0) and (first_signal, first_lum)
- */
- if (brightness < caps->luminance_data[0].input_signal) {
- lum = DIV_ROUND_CLOSEST(caps->luminance_data[0].luminance * brightness,
- caps->luminance_data[0].input_signal);
- goto scale;
- }
-
- left = 0;
- right = caps->data_points - 1;
- while (left <= right) {
- int mid = left + (right - left) / 2;
- u8 signal = caps->luminance_data[mid].input_signal;
-
- /* Exact match found */
- if (signal == brightness) {
- lum = caps->luminance_data[mid].luminance;
- goto scale;
- }
-
- if (signal < brightness)
- left = mid + 1;
- else
- right = mid - 1;
- }
-
- /* verify bound */
- if (left >= caps->data_points)
- left = caps->data_points - 1;
-
- /* At this point, left > right */
- lower_signal = caps->luminance_data[right].input_signal;
- upper_signal = caps->luminance_data[left].input_signal;
- lower_lum = caps->luminance_data[right].luminance;
- upper_lum = caps->luminance_data[left].luminance;
-
- /* interpolate */
- if (right == left || !lower_lum)
- lum = upper_lum;
- else
- lum = lower_lum + DIV_ROUND_CLOSEST((upper_lum - lower_lum) *
- (brightness - lower_signal),
- upper_signal - lower_signal);
-scale:
- *user_brightness = scale_fw_to_input(min, max,
- DIV_ROUND_CLOSEST(lum * brightness, 101));
-}
-
-static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
- uint32_t brightness)
-{
- unsigned int min, max;
-
- if (!get_brightness_range(caps, &min, &max))
- return brightness;
-
- convert_custom_brightness(caps, min, max, &brightness);
-
- // Rescale 0..max to min..max
- return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness, max);
-}
-
-static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,
- uint32_t brightness)
-{
- unsigned int min, max;
-
- if (!get_brightness_range(caps, &min, &max))
- return brightness;
-
- if (brightness < min)
- return 0;
- // Rescale min..max to 0..max
- return DIV_ROUND_CLOSEST_ULL((u64)max * (brightness - min),
- max - min);
-}
-
-static struct dc_stream_state *dm_find_stream_with_link(
- struct amdgpu_display_manager *dm,
- struct dc_link *link)
-{
- struct dc_state *cur_dc_state = dm->dc->current_state;
- struct dc_stream_state *stream = NULL;
- int i;
-
- for (i = 0; i < cur_dc_state->stream_count; i++) {
- stream = cur_dc_state->streams[i];
- if (stream->link == link)
- return stream;
- }
-
- return NULL;
-}
-
-static void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
- int bl_idx,
- u32 user_brightness)
-{
- struct amdgpu_dm_backlight_caps *caps;
- struct dc_link *link;
- u32 brightness = 0;
- bool rc = false, reallow_idle = false;
- struct drm_connector *connector;
- struct dc_stream_state *stream;
- unsigned int min, max;
-
- list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, head) {
- struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
-
- if (aconnector->bl_idx != bl_idx)
- continue;
-
- /* if connector is off, save the brightness for next time it's on */
- if (!aconnector->base.encoder) {
- dm->brightness[bl_idx] = user_brightness;
- dm->actual_brightness[bl_idx] = 0;
- return;
- }
- }
-
- amdgpu_dm_update_backlight_caps(dm, bl_idx);
- caps = &dm->backlight_caps[bl_idx];
-
- dm->brightness[bl_idx] = user_brightness;
- /* update scratch register */
- if (bl_idx == 0)
- amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]);
- brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]);
- link = (struct dc_link *)dm->backlight_link[bl_idx];
-
- /* Apply brightness quirk */
- if (caps->brightness_mask)
- brightness |= caps->brightness_mask;
-
- if (trace_amdgpu_dm_brightness_enabled()) {
- trace_amdgpu_dm_brightness(__builtin_return_address(0),
- user_brightness,
- brightness,
- caps->aux_support,
- power_supply_is_system_supplied() > 0);
- }
-
- stream = dm_find_stream_with_link(dm, link);
- if (!stream)
- return;
-
- mutex_lock(&dm->dc_lock);
- if (dm->dc->caps.ips_support && dm->dc->ctx->dmub_srv->idle_allowed) {
- dc_allow_idle_optimizations(dm->dc, false);
- reallow_idle = true;
- }
-
- if (caps->aux_support) {
- rc = mod_power_set_backlight_nits(dm->power_module, stream, brightness,
- AUX_BL_DEFAULT_TRANSITION_TIME_MS, false, true);
- } else {
- /* power module uses millipercent */
- get_brightness_range(caps, &min, &max);
- brightness = DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000;
- rc = mod_power_set_backlight_percent(dm->power_module, stream,
- brightness, 0, false);
- }
-
- /*
- * Some kms clients create a ramped backlight transition effect
- * by rapidly changing the backlight. Yet we must wait on dmcub
- * fw to exit psr/replay before programming backlight. To
- * prevent lag, keep disable psr/replay and let the next atomic
- * flip clear the event.
- *
- * ToDo: use ISM to handle rapidly backlight change
- *
- * Rapidly backlight change is similar to rapidly cursor events,
- * which is now handled by ISM. ISM can delay the event until system
- * is really idle, so we may use ISM to handle backlight change as well.
- */
- amdgpu_dm_psr_set_event(dm, stream, true,
- psr_event_hw_programming, true);
- amdgpu_dm_replay_set_event(dm, stream, true,
- replay_event_hw_programming, true);
-
- if (dm->dc->caps.ips_support && reallow_idle)
- dc_allow_idle_optimizations(dm->dc, true);
-
- mutex_unlock(&dm->dc_lock);
-
- if (rc)
- dm->actual_brightness[bl_idx] = user_brightness;
-}
-
-static int amdgpu_dm_backlight_update_status(struct backlight_device *bd)
-{
- struct amdgpu_display_manager *dm = bl_get_data(bd);
- int i;
-
- for (i = 0; i < dm->num_of_edps; i++) {
- if (bd == dm->backlight_dev[i])
- break;
- }
- if (i >= AMDGPU_DM_MAX_NUM_EDP)
- i = 0;
- amdgpu_dm_backlight_set_level(dm, i, bd->props.brightness);
-
- return 0;
-}
-
-static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm,
- int bl_idx)
-{
- int ret;
- struct amdgpu_dm_backlight_caps caps;
- struct dc_link *link = (struct dc_link *)dm->backlight_link[bl_idx];
-
- amdgpu_dm_update_backlight_caps(dm, bl_idx);
- caps = dm->backlight_caps[bl_idx];
-
- if (caps.aux_support) {
- u32 avg, peak;
-
- if (!dc_link_get_backlight_level_nits(link, &avg, &peak))
- return dm->brightness[bl_idx];
- return convert_brightness_to_user(&caps, avg);
- }
-
- ret = dc_link_get_backlight_level(link);
-
- if (ret == DC_ERROR_UNEXPECTED)
- return dm->brightness[bl_idx];
-
- return convert_brightness_to_user(&caps, ret);
-}
-
-static int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd)
-{
- struct amdgpu_display_manager *dm = bl_get_data(bd);
- int i;
-
- for (i = 0; i < dm->num_of_edps; i++) {
- if (bd == dm->backlight_dev[i])
- break;
- }
- if (i >= AMDGPU_DM_MAX_NUM_EDP)
- i = 0;
- return amdgpu_dm_backlight_get_level(dm, i);
-}
-
-static const struct backlight_ops amdgpu_dm_backlight_ops = {
- .options = BL_CORE_SUSPENDRESUME,
- .get_brightness = amdgpu_dm_backlight_get_brightness,
- .update_status = amdgpu_dm_backlight_update_status,
-};
-
-static void
-amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector)
-{
- struct drm_device *drm = aconnector->base.dev;
- struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm;
- struct backlight_properties props = { 0 };
- struct amdgpu_dm_backlight_caps *caps;
- char bl_name[16];
- int min, max;
- int real_brightness;
- int init_brightness;
-
- if (aconnector->bl_idx == -1)
- return;
-
- if (!acpi_video_backlight_use_native()) {
- drm_info(drm, "Skipping amdgpu DM backlight registration\n");
- /* Try registering an ACPI video backlight device instead. */
- acpi_video_register_backlight();
- return;
- }
-
- caps = &dm->backlight_caps[aconnector->bl_idx];
- if (get_brightness_range(caps, &min, &max)) {
- if (power_supply_is_system_supplied() > 0)
- props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, 100);
- else
- props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, 100);
- /* min is zero, so max needs to be adjusted */
- props.max_brightness = max - min;
- drm_dbg(drm, "Backlight caps: min: %d, max: %d, ac %d, dc %d\n", min, max,
- caps->ac_level, caps->dc_level);
- } else
- props.brightness = props.max_brightness = MAX_BACKLIGHT_LEVEL;
-
- init_brightness = props.brightness;
-
- if (caps->data_points && !(amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)) {
- drm_info(drm, "Using custom brightness curve\n");
- props.scale = BACKLIGHT_SCALE_NON_LINEAR;
- } else
- props.scale = BACKLIGHT_SCALE_LINEAR;
- props.type = BACKLIGHT_RAW;
-
- snprintf(bl_name, sizeof(bl_name), "amdgpu_bl%d",
- drm->primary->index + aconnector->bl_idx);
-
- dm->backlight_dev[aconnector->bl_idx] =
- backlight_device_register(bl_name, aconnector->base.kdev, dm,
- &amdgpu_dm_backlight_ops, &props);
- dm->brightness[aconnector->bl_idx] = props.brightness;
-
- if (IS_ERR(dm->backlight_dev[aconnector->bl_idx])) {
- drm_err(drm, "DM: Backlight registration failed!\n");
- dm->backlight_dev[aconnector->bl_idx] = NULL;
- } else {
- /*
- * dm->brightness[x] can be inconsistent just after startup until
- * ops.get_brightness is called.
- */
- real_brightness =
- amdgpu_dm_backlight_ops.get_brightness(dm->backlight_dev[aconnector->bl_idx]);
-
- if (real_brightness != init_brightness) {
- dm->actual_brightness[aconnector->bl_idx] = real_brightness;
- dm->brightness[aconnector->bl_idx] = real_brightness;
- }
- drm_dbg_driver(drm, "DM: Registered Backlight device: %s\n", bl_name);
- }
-}
-
static int initialize_plane(struct amdgpu_display_manager *dm,
struct amdgpu_mode_info *mode_info, int plane_id,
enum drm_plane_type plane_type,
@@ -5611,38 +5126,6 @@ static int initialize_plane(struct amdgpu_display_manager *dm,
}
-static void setup_backlight_device(struct amdgpu_display_manager *dm,
- struct amdgpu_dm_connector *aconnector)
-{
- struct amdgpu_dm_backlight_caps *caps;
- struct dc_link *link = aconnector->dc_link;
- int bl_idx = dm->num_of_edps;
-
- if (!(link->connector_signal & (SIGNAL_TYPE_EDP | SIGNAL_TYPE_LVDS)) ||
- link->type == dc_connection_none)
- return;
-
- if (dm->num_of_edps >= AMDGPU_DM_MAX_NUM_EDP) {
- drm_warn(adev_to_drm(dm->adev), "Too much eDP connections, skipping backlight setup for additional eDPs\n");
- return;
- }
-
- aconnector->bl_idx = bl_idx;
-
- amdgpu_dm_update_backlight_caps(dm, bl_idx);
- dm->backlight_link[bl_idx] = link;
- dm->num_of_edps++;
-
- update_connector_ext_caps(aconnector);
- caps = &dm->backlight_caps[aconnector->bl_idx];
-
- /* Only offer ABM property when non-OLED and user didn't turn off by module parameter */
- if (caps->ext_caps && !caps->ext_caps->bits.oled && amdgpu_dm_abm_level < 0)
- drm_object_attach_property(&aconnector->base.base,
- dm->adev->mode_info.abm_level_property,
- ABM_SYSFS_CONTROL);
-}
-
static void amdgpu_set_panel_orientation(struct drm_connector *connector);
@@ -5883,7 +5366,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev)
if (ret) {
amdgpu_dm_update_connector_after_detect(aconnector);
- setup_backlight_device(dm, aconnector);
+ amdgpu_dm_setup_backlight_device(dm, aconnector);
/* Disable PSR if Replay can be enabled */
if (replay_feature_enabled)
@@ -7960,103 +7443,6 @@ int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector,
return ret;
}
-/**
- * DOC: panel power savings
- *
- * The display manager allows you to set your desired **panel power savings**
- * level (between 0-4, with 0 representing off), e.g. using the following::
- *
- * # echo 3 > /sys/class/drm/card0-eDP-1/amdgpu/panel_power_savings
- *
- * Modifying this value can have implications on color accuracy, so tread
- * carefully.
- */
-
-static ssize_t panel_power_savings_show(struct device *device,
- struct device_attribute *attr,
- char *buf)
-{
- struct drm_connector *connector = dev_get_drvdata(device);
- struct drm_device *dev = connector->dev;
- u8 val;
-
- drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
- val = to_dm_connector_state(connector->state)->abm_level ==
- ABM_LEVEL_IMMEDIATE_DISABLE ? 0 :
- to_dm_connector_state(connector->state)->abm_level;
- drm_modeset_unlock(&dev->mode_config.connection_mutex);
-
- return sysfs_emit(buf, "%u\n", val);
-}
-
-static ssize_t panel_power_savings_store(struct device *device,
- struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct drm_connector *connector = dev_get_drvdata(device);
- struct drm_device *dev = connector->dev;
- long val;
- int ret;
-
- ret = kstrtol(buf, 0, &val);
-
- if (ret)
- return ret;
-
- if (val < 0 || val > 4)
- return -EINVAL;
-
- drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
- if (to_dm_connector_state(connector->state)->abm_sysfs_forbidden)
- ret = -EBUSY;
- else
- to_dm_connector_state(connector->state)->abm_level = val ?:
- ABM_LEVEL_IMMEDIATE_DISABLE;
- drm_modeset_unlock(&dev->mode_config.connection_mutex);
-
- if (ret)
- return ret;
-
- drm_kms_helper_hotplug_event(dev);
-
- return count;
-}
-
-static DEVICE_ATTR_RW(panel_power_savings);
-
-static struct attribute *amdgpu_attrs[] = {
- &dev_attr_panel_power_savings.attr,
- NULL
-};
-
-static const struct attribute_group amdgpu_group = {
- .name = "amdgpu",
- .attrs = amdgpu_attrs
-};
-
-static bool
-amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *amdgpu_dm_connector)
-{
- if (amdgpu_dm_abm_level >= 0)
- return false;
-
- if (amdgpu_dm_connector->base.connector_type != DRM_MODE_CONNECTOR_eDP)
- return false;
-
- /* check for OLED panels */
- if (amdgpu_dm_connector->bl_idx >= 0) {
- struct drm_device *drm = amdgpu_dm_connector->base.dev;
- struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm;
- struct amdgpu_dm_backlight_caps *caps;
-
- caps = &dm->backlight_caps[amdgpu_dm_connector->bl_idx];
- if (caps->aux_support)
- return false;
- }
-
- return true;
-}
-
static void amdgpu_dm_connector_unregister(struct drm_connector *connector)
{
struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector);
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 7d37c1612131..01f614e6da75 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1167,5 +1167,4 @@ int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector);
void retrieve_dmi_info(struct amdgpu_display_manager *dm);
-void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, int bl_idx);
#endif /* __AMDGPU_DM_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
new file mode 100644
index 000000000000..3770e8dafdbf
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
@@ -0,0 +1,660 @@
+// 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
+ */
+
+#include "dc.h"
+#include "dc/dc_dmub_srv.h"
+#include "dc/dc_state.h"
+#include "dc/dc_stat.h"
+
+#include "amdgpu.h"
+#include "amdgpu_display.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_backlight.h"
+#include "amdgpu_dm_psr.h"
+#include "amdgpu_dm_replay.h"
+#include "amdgpu_atombios.h"
+
+#include "modules/inc/mod_power.h"
+
+#include <linux/backlight.h>
+#include <linux/power_supply.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_utils.h>
+
+#include <acpi/video.h>
+
+#include "amdgpu_dm_trace.h"
+#include "amd_shared.h"
+
+#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12
+#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255
+#define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2)
+#define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50
+
+void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm,
+ int bl_idx)
+{
+ struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[bl_idx];
+
+ if (caps->caps_valid)
+ return;
+
+#if defined(CONFIG_ACPI)
+ amdgpu_acpi_get_backlight_caps(caps);
+
+ /* validate the firmware value is sane */
+ if (caps->caps_valid) {
+ int spread = caps->max_input_signal - caps->min_input_signal;
+
+ if (caps->max_input_signal > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT ||
+ caps->min_input_signal < 0 ||
+ spread > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT ||
+ spread < AMDGPU_DM_MIN_SPREAD) {
+ drm_dbg_kms(adev_to_drm(dm->adev), "DM: Invalid backlight caps: min=%d, max=%d\n",
+ caps->min_input_signal, caps->max_input_signal);
+ caps->caps_valid = false;
+ }
+ }
+
+ if (!caps->caps_valid) {
+ caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
+ caps->caps_valid = true;
+ }
+#else
+ if (caps->aux_support)
+ return;
+
+ caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
+ caps->caps_valid = true;
+#endif
+}
+
+static int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,
+ unsigned int *min, unsigned int *max)
+{
+ if (!caps)
+ return 0;
+
+ if (caps->aux_support) {
+ /* Firmware limits are in nits, DC API wants millinits. */
+ *max = 1000 * caps->aux_max_input_signal;
+ *min = 1000 * caps->aux_min_input_signal;
+ } else {
+ /* Firmware limits are 8-bit, PWM control is 16-bit. */
+ *max = 0x101 * caps->max_input_signal;
+ *min = 0x101 * caps->min_input_signal;
+ }
+ return 1;
+}
+
+/* Rescale from [min..max] to [0..AMDGPU_MAX_BL_LEVEL] */
+static inline u32 scale_input_to_fw(int min, int max, u64 input)
+{
+ return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max - min);
+}
+
+/* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [min..max] */
+static inline u32 scale_fw_to_input(int min, int max, u64 input)
+{
+ return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL);
+}
+
+static void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
+ unsigned int min, unsigned int max,
+ uint32_t *user_brightness)
+{
+ u32 brightness = scale_input_to_fw(min, max, *user_brightness);
+ u8 lower_signal, upper_signal, upper_lum, lower_lum, lum;
+ int left, right;
+
+ if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)
+ return;
+
+ if (!caps->data_points)
+ return;
+
+ /*
+ * Handle the case where brightness is below the first data point
+ * Interpolate between (0,0) and (first_signal, first_lum)
+ */
+ if (brightness < caps->luminance_data[0].input_signal) {
+ lum = DIV_ROUND_CLOSEST(caps->luminance_data[0].luminance * brightness,
+ caps->luminance_data[0].input_signal);
+ goto scale;
+ }
+
+ left = 0;
+ right = caps->data_points - 1;
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ u8 signal = caps->luminance_data[mid].input_signal;
+
+ /* Exact match found */
+ if (signal == brightness) {
+ lum = caps->luminance_data[mid].luminance;
+ goto scale;
+ }
+
+ if (signal < brightness)
+ left = mid + 1;
+ else
+ right = mid - 1;
+ }
+
+ /* verify bound */
+ if (left >= caps->data_points)
+ left = caps->data_points - 1;
+
+ /* At this point, left > right */
+ lower_signal = caps->luminance_data[right].input_signal;
+ upper_signal = caps->luminance_data[left].input_signal;
+ lower_lum = caps->luminance_data[right].luminance;
+ upper_lum = caps->luminance_data[left].luminance;
+
+ /* interpolate */
+ if (right == left || !lower_lum)
+ lum = upper_lum;
+ else
+ lum = lower_lum + DIV_ROUND_CLOSEST((upper_lum - lower_lum) *
+ (brightness - lower_signal),
+ upper_signal - lower_signal);
+scale:
+ *user_brightness = scale_fw_to_input(min, max,
+ DIV_ROUND_CLOSEST(lum * brightness, 101));
+}
+
+static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
+ uint32_t brightness)
+{
+ unsigned int min, max;
+
+ if (!get_brightness_range(caps, &min, &max))
+ return brightness;
+
+ convert_custom_brightness(caps, min, max, &brightness);
+
+ /* Rescale 0..max to min..max */
+ return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness, max);
+}
+
+static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,
+ uint32_t brightness)
+{
+ unsigned int min, max;
+
+ if (!get_brightness_range(caps, &min, &max))
+ return brightness;
+
+ if (brightness < min)
+ return 0;
+ /* Rescale min..max to 0..max */
+ return DIV_ROUND_CLOSEST_ULL((u64)max * (brightness - min),
+ max - min);
+}
+
+static struct dc_stream_state *dm_find_stream_with_link(
+ struct amdgpu_display_manager *dm,
+ struct dc_link *link)
+{
+ struct dc_state *cur_dc_state = dm->dc->current_state;
+ struct dc_stream_state *stream = NULL;
+ int i;
+
+ for (i = 0; i < cur_dc_state->stream_count; i++) {
+ stream = cur_dc_state->streams[i];
+ if (stream->link == link)
+ return stream;
+ }
+
+ return NULL;
+}
+
+void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
+ int bl_idx,
+ u32 user_brightness)
+{
+ struct amdgpu_dm_backlight_caps *caps;
+ struct dc_link *link;
+ u32 brightness = 0;
+ bool rc = false, reallow_idle = false;
+ struct drm_connector *connector;
+ struct dc_stream_state *stream;
+ unsigned int min, max;
+
+ list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, head) {
+ struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+
+ if (aconnector->bl_idx != bl_idx)
+ continue;
+
+ /* if connector is off, save the brightness for next time it's on */
+ if (!aconnector->base.encoder) {
+ dm->brightness[bl_idx] = user_brightness;
+ dm->actual_brightness[bl_idx] = 0;
+ return;
+ }
+ }
+
+ amdgpu_dm_update_backlight_caps(dm, bl_idx);
+ caps = &dm->backlight_caps[bl_idx];
+
+ dm->brightness[bl_idx] = user_brightness;
+ /* update scratch register */
+ if (bl_idx == 0)
+ amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]);
+ brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]);
+ link = (struct dc_link *)dm->backlight_link[bl_idx];
+
+ /* Apply brightness quirk */
+ if (caps->brightness_mask)
+ brightness |= caps->brightness_mask;
+
+ if (trace_amdgpu_dm_brightness_enabled()) {
+ trace_amdgpu_dm_brightness(__builtin_return_address(0),
+ user_brightness,
+ brightness,
+ caps->aux_support,
+ power_supply_is_system_supplied() > 0);
+ }
+
+ stream = dm_find_stream_with_link(dm, link);
+ if (!stream)
+ return;
+
+ mutex_lock(&dm->dc_lock);
+ if (dm->dc->caps.ips_support && dm->dc->ctx->dmub_srv->idle_allowed) {
+ dc_allow_idle_optimizations(dm->dc, false);
+ reallow_idle = true;
+ }
+
+ if (caps->aux_support) {
+ rc = mod_power_set_backlight_nits(dm->power_module, stream, brightness,
+ AUX_BL_DEFAULT_TRANSITION_TIME_MS, false, true);
+ } else {
+ /* power module uses millipercent */
+ get_brightness_range(caps, &min, &max);
+ brightness = DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000;
+ rc = mod_power_set_backlight_percent(dm->power_module, stream,
+ brightness, 0, false);
+ }
+
+ /*
+ * Some kms clients create a ramped backlight transition effect
+ * by rapidly changing the backlight. Yet we must wait on dmcub
+ * fw to exit psr/replay before programming backlight. To
+ * prevent lag, keep disable psr/replay and let the next atomic
+ * flip clear the event.
+ *
+ * ToDo: use ISM to handle rapidly backlight change
+ *
+ * Rapidly backlight change is similar to rapidly cursor events,
+ * which is now handled by ISM. ISM can delay the event until system
+ * is really idle, so we may use ISM to handle backlight change as well.
+ */
+ amdgpu_dm_psr_set_event(dm, stream, true,
+ psr_event_hw_programming, true);
+ amdgpu_dm_replay_set_event(dm, stream, true,
+ replay_event_hw_programming, true);
+
+ if (dm->dc->caps.ips_support && reallow_idle)
+ dc_allow_idle_optimizations(dm->dc, true);
+
+ mutex_unlock(&dm->dc_lock);
+
+ if (rc)
+ dm->actual_brightness[bl_idx] = user_brightness;
+}
+
+static int amdgpu_dm_backlight_update_status(struct backlight_device *bd)
+{
+ struct amdgpu_display_manager *dm = bl_get_data(bd);
+ int i;
+
+ for (i = 0; i < dm->num_of_edps; i++) {
+ if (bd == dm->backlight_dev[i])
+ break;
+ }
+ if (i >= AMDGPU_DM_MAX_NUM_EDP)
+ i = 0;
+ amdgpu_dm_backlight_set_level(dm, i, bd->props.brightness);
+
+ return 0;
+}
+
+static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm,
+ int bl_idx)
+{
+ int ret;
+ struct amdgpu_dm_backlight_caps caps;
+ struct dc_link *link = (struct dc_link *)dm->backlight_link[bl_idx];
+
+ amdgpu_dm_update_backlight_caps(dm, bl_idx);
+ caps = dm->backlight_caps[bl_idx];
+
+ if (caps.aux_support) {
+ u32 avg, peak;
+
+ if (!dc_link_get_backlight_level_nits(link, &avg, &peak))
+ return dm->brightness[bl_idx];
+ return convert_brightness_to_user(&caps, avg);
+ }
+
+ ret = dc_link_get_backlight_level(link);
+
+ if (ret == DC_ERROR_UNEXPECTED)
+ return dm->brightness[bl_idx];
+
+ return convert_brightness_to_user(&caps, ret);
+}
+
+static int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd)
+{
+ struct amdgpu_display_manager *dm = bl_get_data(bd);
+ int i;
+
+ for (i = 0; i < dm->num_of_edps; i++) {
+ if (bd == dm->backlight_dev[i])
+ break;
+ }
+ if (i >= AMDGPU_DM_MAX_NUM_EDP)
+ i = 0;
+ return amdgpu_dm_backlight_get_level(dm, i);
+}
+
+static const struct backlight_ops amdgpu_dm_backlight_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .get_brightness = amdgpu_dm_backlight_get_brightness,
+ .update_status = amdgpu_dm_backlight_update_status,
+};
+
+void
+amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector)
+{
+ struct drm_device *drm = aconnector->base.dev;
+ struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm;
+ struct backlight_properties props = { 0 };
+ struct amdgpu_dm_backlight_caps *caps;
+ char bl_name[16];
+ int min, max;
+ int real_brightness;
+ int init_brightness;
+
+ if (aconnector->bl_idx == -1)
+ return;
+
+ if (!acpi_video_backlight_use_native()) {
+ drm_info(drm, "Skipping amdgpu DM backlight registration\n");
+ /* Try registering an ACPI video backlight device instead. */
+ acpi_video_register_backlight();
+ return;
+ }
+
+ caps = &dm->backlight_caps[aconnector->bl_idx];
+ if (get_brightness_range(caps, &min, &max)) {
+ if (power_supply_is_system_supplied() > 0)
+ props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, 100);
+ else
+ props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, 100);
+ /* min is zero, so max needs to be adjusted */
+ props.max_brightness = max - min;
+ drm_dbg(drm, "Backlight caps: min: %d, max: %d, ac %d, dc %d\n", min, max,
+ caps->ac_level, caps->dc_level);
+ } else
+ props.brightness = props.max_brightness = MAX_BACKLIGHT_LEVEL;
+
+ init_brightness = props.brightness;
+
+ if (caps->data_points && !(amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)) {
+ drm_info(drm, "Using custom brightness curve\n");
+ props.scale = BACKLIGHT_SCALE_NON_LINEAR;
+ } else
+ props.scale = BACKLIGHT_SCALE_LINEAR;
+ props.type = BACKLIGHT_RAW;
+
+ snprintf(bl_name, sizeof(bl_name), "amdgpu_bl%d",
+ drm->primary->index + aconnector->bl_idx);
+
+ dm->backlight_dev[aconnector->bl_idx] =
+ backlight_device_register(bl_name, aconnector->base.kdev, dm,
+ &amdgpu_dm_backlight_ops, &props);
+ dm->brightness[aconnector->bl_idx] = props.brightness;
+
+ if (IS_ERR(dm->backlight_dev[aconnector->bl_idx])) {
+ drm_err(drm, "DM: Backlight registration failed!\n");
+ dm->backlight_dev[aconnector->bl_idx] = NULL;
+ } else {
+ /*
+ * dm->brightness[x] can be inconsistent just after startup until
+ * ops.get_brightness is called.
+ */
+ real_brightness =
+ amdgpu_dm_backlight_ops.get_brightness(dm->backlight_dev[aconnector->bl_idx]);
+
+ if (real_brightness != init_brightness) {
+ dm->actual_brightness[aconnector->bl_idx] = real_brightness;
+ dm->brightness[aconnector->bl_idx] = real_brightness;
+ }
+ drm_dbg_driver(drm, "DM: Registered Backlight device: %s\n", bl_name);
+ }
+}
+
+void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector)
+{
+ const struct drm_panel_backlight_quirk *panel_backlight_quirk;
+ struct amdgpu_dm_backlight_caps *caps;
+ struct drm_connector *conn_base;
+ struct amdgpu_device *adev;
+ struct drm_luminance_range_info *luminance_range;
+ struct drm_device *drm;
+
+ if (aconnector->bl_idx == -1 ||
+ aconnector->dc_link->connector_signal != SIGNAL_TYPE_EDP)
+ return;
+
+ conn_base = &aconnector->base;
+ drm = conn_base->dev;
+ adev = drm_to_adev(drm);
+
+ caps = &adev->dm.backlight_caps[aconnector->bl_idx];
+ caps->ext_caps = &aconnector->dc_link->dpcd_sink_ext_caps;
+ caps->aux_support = false;
+
+ if (caps->ext_caps->bits.oled == 1
+ /*
+ * ||
+ * caps->ext_caps->bits.sdr_aux_backlight_control == 1 ||
+ * caps->ext_caps->bits.hdr_aux_backlight_control == 1
+ */)
+ caps->aux_support = true;
+
+ if (amdgpu_backlight == 0)
+ caps->aux_support = false;
+ else if (amdgpu_backlight == 1)
+ caps->aux_support = true;
+ if (caps->aux_support)
+ aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX;
+
+ luminance_range = &conn_base->display_info.luminance_range;
+
+ if (luminance_range->max_luminance)
+ caps->aux_max_input_signal = luminance_range->max_luminance;
+ else
+ caps->aux_max_input_signal = 512;
+
+ if (luminance_range->min_luminance)
+ caps->aux_min_input_signal = luminance_range->min_luminance;
+ else
+ caps->aux_min_input_signal = 1;
+
+ panel_backlight_quirk =
+ drm_get_panel_backlight_quirk(aconnector->drm_edid);
+ if (!IS_ERR_OR_NULL(panel_backlight_quirk)) {
+ if (panel_backlight_quirk->min_brightness) {
+ caps->min_input_signal =
+ panel_backlight_quirk->min_brightness - 1;
+ drm_info(drm,
+ "Applying panel backlight quirk, min_brightness: %d\n",
+ caps->min_input_signal);
+ }
+ if (panel_backlight_quirk->brightness_mask) {
+ drm_info(drm,
+ "Applying panel backlight quirk, brightness_mask: 0x%X\n",
+ panel_backlight_quirk->brightness_mask);
+ caps->brightness_mask =
+ panel_backlight_quirk->brightness_mask;
+ }
+ }
+}
+
+void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm,
+ struct amdgpu_dm_connector *aconnector)
+{
+ struct amdgpu_dm_backlight_caps *caps;
+ struct dc_link *link = aconnector->dc_link;
+ int bl_idx = dm->num_of_edps;
+
+ if (!(link->connector_signal & (SIGNAL_TYPE_EDP | SIGNAL_TYPE_LVDS)) ||
+ link->type == dc_connection_none)
+ return;
+
+ if (dm->num_of_edps >= AMDGPU_DM_MAX_NUM_EDP) {
+ drm_warn(adev_to_drm(dm->adev), "Too much eDP connections, skipping backlight setup for additional eDPs\n");
+ return;
+ }
+
+ aconnector->bl_idx = bl_idx;
+
+ amdgpu_dm_update_backlight_caps(dm, bl_idx);
+ dm->backlight_link[bl_idx] = link;
+ dm->num_of_edps++;
+
+ amdgpu_dm_update_connector_ext_caps(aconnector);
+ caps = &dm->backlight_caps[aconnector->bl_idx];
+
+ /* Only offer ABM property when non-OLED and user didn't turn off by module parameter */
+ if (caps->ext_caps && !caps->ext_caps->bits.oled && amdgpu_dm_abm_level < 0)
+ drm_object_attach_property(&aconnector->base.base,
+ dm->adev->mode_info.abm_level_property,
+ ABM_SYSFS_CONTROL);
+}
+
+/**
+ * DOC: panel power savings
+ *
+ * The display manager allows you to set your desired **panel power savings**
+ * level (between 0-4, with 0 representing off), e.g. using the following::
+ *
+ * # echo 3 > /sys/class/drm/card0-eDP-1/amdgpu/panel_power_savings
+ *
+ * Modifying this value can have implications on color accuracy, so tread
+ * carefully.
+ */
+
+static ssize_t panel_power_savings_show(struct device *device,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct drm_connector *connector = dev_get_drvdata(device);
+ struct drm_device *dev = connector->dev;
+ u8 val;
+
+ drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
+ val = to_dm_connector_state(connector->state)->abm_level ==
+ ABM_LEVEL_IMMEDIATE_DISABLE ? 0 :
+ to_dm_connector_state(connector->state)->abm_level;
+ drm_modeset_unlock(&dev->mode_config.connection_mutex);
+
+ return sysfs_emit(buf, "%u\n", val);
+}
+
+static ssize_t panel_power_savings_store(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct drm_connector *connector = dev_get_drvdata(device);
+ struct drm_device *dev = connector->dev;
+ long val;
+ int ret;
+
+ ret = kstrtol(buf, 0, &val);
+
+ if (ret)
+ return ret;
+
+ if (val < 0 || val > 4)
+ return -EINVAL;
+
+ drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
+ if (to_dm_connector_state(connector->state)->abm_sysfs_forbidden)
+ ret = -EBUSY;
+ else
+ to_dm_connector_state(connector->state)->abm_level = val ?:
+ ABM_LEVEL_IMMEDIATE_DISABLE;
+ drm_modeset_unlock(&dev->mode_config.connection_mutex);
+
+ if (ret)
+ return ret;
+
+ drm_kms_helper_hotplug_event(dev);
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(panel_power_savings);
+
+static struct attribute *amdgpu_attrs[] = {
+ &dev_attr_panel_power_savings.attr,
+ NULL
+};
+
+const struct attribute_group amdgpu_group = {
+ .name = "amdgpu",
+ .attrs = amdgpu_attrs
+};
+
+bool
+amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *amdgpu_dm_connector)
+{
+ if (amdgpu_dm_abm_level >= 0)
+ return false;
+
+ if (amdgpu_dm_connector->base.connector_type != DRM_MODE_CONNECTOR_eDP)
+ return false;
+
+ /* check for OLED panels */
+ if (amdgpu_dm_connector->bl_idx >= 0) {
+ struct drm_device *drm = amdgpu_dm_connector->base.dev;
+ struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm;
+ struct amdgpu_dm_backlight_caps *caps;
+
+ caps = &dm->backlight_caps[amdgpu_dm_connector->bl_idx];
+ if (caps->aux_support)
+ return false;
+ }
+
+ return true;
+}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
new file mode 100644
index 000000000000..acff23f9feef
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
@@ -0,0 +1,44 @@
+/* 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.
+ */
+
+#ifndef __AMDGPU_DM_BACKLIGHT_H__
+#define __AMDGPU_DM_BACKLIGHT_H__
+
+struct amdgpu_display_manager;
+struct amdgpu_dm_connector;
+struct drm_connector;
+struct attribute_group;
+
+void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm,
+ int bl_idx);
+void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
+ int bl_idx, u32 user_brightness);
+void amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector);
+void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm,
+ struct amdgpu_dm_connector *aconnector);
+void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector);
+bool amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *aconnector);
+
+extern const struct attribute_group amdgpu_group;
+
+#endif /* __AMDGPU_DM_BACKLIGHT_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c
index 84dcb573d98f..0fdcf70256cc 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c
@@ -32,6 +32,7 @@
#include "dm_services.h"
#include "amdgpu.h"
#include "amdgpu_dm.h"
+#include "amdgpu_dm_backlight.h"
#include "amdgpu_dm_irq.h"
#include "amdgpu_pm.h"
#include "amdgpu_dm_trace.h"
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 08/32] drm/amd/display: Extract audio code to amdgpu_dm_audio
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (6 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 07/32] drm/amd/display: Extract backlight code to amdgpu_dm_backlight Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 09/32] drm/amd/display: Extract DMUB code to amdgpu_dm_dmub Chenyu Chen
` (24 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Move audio component, init/fini, ELD notification,
fill_audio_info, and commit_audio functions from
amdgpu_dm.c into a dedicated amdgpu_dm_audio.c file
with its own header.
No functional change intended.
Assisted-by: Copilot:Claude-Opus-4.6
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +-
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 268 +---------------
.../amd/display/amdgpu_dm/amdgpu_dm_audio.c | 302 ++++++++++++++++++
.../amd/display/amdgpu_dm/amdgpu_dm_audio.h | 44 +++
4 files changed, 350 insertions(+), 267 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
index 2953c59d85e7..83a7d03a0348 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
@@ -42,7 +42,8 @@ AMDGPUDM = \
amdgpu_dm_wb.o \
amdgpu_dm_colorop.o \
amdgpu_dm_ism.o \
- amdgpu_dm_backlight.o
+ amdgpu_dm_backlight.o \
+ amdgpu_dm_audio.o
ifdef CONFIG_DRM_AMD_DC_FP
AMDGPUDM += dc_fpu.o
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 100e1fb572b1..a07820ba5759 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -67,6 +67,7 @@
#include "amdgpu_dm_psr.h"
#include "amdgpu_dm_replay.h"
#include "amdgpu_dm_backlight.h"
+#include "amdgpu_dm_audio.h"
#include "ivsrcid/ivsrcid_vislands30.h"
@@ -95,7 +96,6 @@
#include <drm/drm_mode.h>
#include <drm/drm_utils.h>
#include <drm/drm_vblank.h>
-#include <drm/drm_audio_component.h>
#include <drm/drm_colorop.h>
#include <drm/drm_gem_atomic_helper.h>
@@ -1107,144 +1107,6 @@ static void amdgpu_dm_fbc_init(struct drm_connector *connector)
}
-static int amdgpu_dm_audio_component_get_eld(struct device *kdev, int port,
- int pipe, bool *enabled,
- unsigned char *buf, int max_bytes)
-{
- struct drm_device *dev = dev_get_drvdata(kdev);
- struct amdgpu_device *adev = drm_to_adev(dev);
- struct drm_connector *connector;
- struct drm_connector_list_iter conn_iter;
- struct amdgpu_dm_connector *aconnector;
- int ret = 0;
-
- *enabled = false;
-
- mutex_lock(&adev->dm.audio_lock);
-
- drm_connector_list_iter_begin(dev, &conn_iter);
- drm_for_each_connector_iter(connector, &conn_iter) {
-
- if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
- continue;
-
- aconnector = to_amdgpu_dm_connector(connector);
- if (aconnector->audio_inst != port)
- continue;
-
- *enabled = true;
- mutex_lock(&connector->eld_mutex);
- ret = drm_eld_size(connector->eld);
- memcpy(buf, connector->eld, min(max_bytes, ret));
- mutex_unlock(&connector->eld_mutex);
-
- break;
- }
- drm_connector_list_iter_end(&conn_iter);
-
- mutex_unlock(&adev->dm.audio_lock);
-
- drm_dbg_kms(adev_to_drm(adev), "Get ELD : idx=%d ret=%d en=%d\n", port, ret, *enabled);
-
- return ret;
-}
-
-static const struct drm_audio_component_ops amdgpu_dm_audio_component_ops = {
- .get_eld = amdgpu_dm_audio_component_get_eld,
-};
-
-static int amdgpu_dm_audio_component_bind(struct device *kdev,
- struct device *hda_kdev, void *data)
-{
- struct drm_device *dev = dev_get_drvdata(kdev);
- struct amdgpu_device *adev = drm_to_adev(dev);
- struct drm_audio_component *acomp = data;
-
- acomp->ops = &amdgpu_dm_audio_component_ops;
- acomp->dev = kdev;
- adev->dm.audio_component = acomp;
-
- return 0;
-}
-
-static void amdgpu_dm_audio_component_unbind(struct device *kdev,
- struct device *hda_kdev, void *data)
-{
- struct amdgpu_device *adev = drm_to_adev(dev_get_drvdata(kdev));
- struct drm_audio_component *acomp = data;
-
- acomp->ops = NULL;
- acomp->dev = NULL;
- adev->dm.audio_component = NULL;
-}
-
-static const struct component_ops amdgpu_dm_audio_component_bind_ops = {
- .bind = amdgpu_dm_audio_component_bind,
- .unbind = amdgpu_dm_audio_component_unbind,
-};
-
-static int amdgpu_dm_audio_init(struct amdgpu_device *adev)
-{
- int i, ret;
-
- if (!amdgpu_audio)
- return 0;
-
- adev->mode_info.audio.enabled = true;
-
- adev->mode_info.audio.num_pins = adev->dm.dc->res_pool->audio_count;
-
- for (i = 0; i < adev->mode_info.audio.num_pins; i++) {
- adev->mode_info.audio.pin[i].channels = -1;
- adev->mode_info.audio.pin[i].rate = -1;
- adev->mode_info.audio.pin[i].bits_per_sample = -1;
- adev->mode_info.audio.pin[i].status_bits = 0;
- adev->mode_info.audio.pin[i].category_code = 0;
- adev->mode_info.audio.pin[i].connected = false;
- adev->mode_info.audio.pin[i].id =
- adev->dm.dc->res_pool->audios[i]->inst;
- adev->mode_info.audio.pin[i].offset = 0;
- }
-
- ret = component_add(adev->dev, &amdgpu_dm_audio_component_bind_ops);
- if (ret < 0)
- return ret;
-
- adev->dm.audio_registered = true;
-
- return 0;
-}
-
-static void amdgpu_dm_audio_fini(struct amdgpu_device *adev)
-{
- if (!amdgpu_audio)
- return;
-
- if (!adev->mode_info.audio.enabled)
- return;
-
- if (adev->dm.audio_registered) {
- component_del(adev->dev, &amdgpu_dm_audio_component_bind_ops);
- adev->dm.audio_registered = false;
- }
-
- /* TODO: Disable audio? */
-
- adev->mode_info.audio.enabled = false;
-}
-
-static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin)
-{
- struct drm_audio_component *acomp = adev->dm.audio_component;
-
- if (acomp && acomp->audio_ops && acomp->audio_ops->pin_eld_notify) {
- drm_dbg_kms(adev_to_drm(adev), "Notify ELD: %d\n", pin);
-
- acomp->audio_ops->pin_eld_notify(acomp->audio_ops->audio_ptr,
- pin, -1);
- }
-}
-
static int dm_dmub_hw_init(struct amdgpu_device *adev)
{
const struct dmcub_firmware_header_v1_0 *hdr;
@@ -6526,51 +6388,6 @@ static void fill_stream_properties_from_drm_display_mode(
stream->content_type = get_output_content_type(connector_state);
}
-static void fill_audio_info(struct audio_info *audio_info,
- const struct drm_connector *drm_connector,
- const struct dc_sink *dc_sink)
-{
- int i = 0;
- int cea_revision = 0;
- const struct dc_edid_caps *edid_caps = &dc_sink->edid_caps;
-
- audio_info->manufacture_id = edid_caps->manufacturer_id;
- audio_info->product_id = edid_caps->product_id;
-
- cea_revision = drm_connector->display_info.cea_rev;
-
- strscpy(audio_info->display_name,
- edid_caps->display_name,
- AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS);
-
- if (cea_revision >= 3) {
- audio_info->mode_count = edid_caps->audio_mode_count;
-
- for (i = 0; i < audio_info->mode_count; ++i) {
- audio_info->modes[i].format_code =
- (enum audio_format_code)
- (edid_caps->audio_modes[i].format_code);
- audio_info->modes[i].channel_count =
- edid_caps->audio_modes[i].channel_count;
- audio_info->modes[i].sample_rates.all =
- edid_caps->audio_modes[i].sample_rate;
- audio_info->modes[i].sample_size =
- edid_caps->audio_modes[i].sample_size;
- }
- }
-
- audio_info->flags.all = edid_caps->speaker_flags;
-
- /* TODO: We only check for the progressive mode, check for interlace mode too */
- if (drm_connector->latency_present[0]) {
- audio_info->video_latency = drm_connector->video_latency[0];
- audio_info->audio_latency = drm_connector->audio_latency[0];
- }
-
- /* TODO: For DP, video and audio latency should be calculated from DPCD caps */
-
-}
-
static void
copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode,
struct drm_display_mode *dst_mode)
@@ -7172,7 +6989,7 @@ create_stream_for_sink(struct drm_connector *connector,
update_stream_scaling_settings(dev, &mode, dm_state, stream);
- fill_audio_info(
+ amdgpu_dm_fill_audio_info(
&stream->audio_info,
connector,
sink);
@@ -9980,87 +9797,6 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
kfree(bundle);
}
-static void amdgpu_dm_commit_audio(struct drm_device *dev,
- struct drm_atomic_state *state)
-{
- struct amdgpu_device *adev = drm_to_adev(dev);
- struct amdgpu_dm_connector *aconnector;
- struct drm_connector *connector;
- struct drm_connector_state *old_con_state, *new_con_state;
- struct drm_crtc_state *new_crtc_state;
- struct dm_crtc_state *new_dm_crtc_state;
- const struct dc_stream_status *status;
- int i, inst;
-
- /* Notify device removals. */
- for_each_oldnew_connector_in_state(state, connector, old_con_state, new_con_state, i) {
- if (old_con_state->crtc != new_con_state->crtc) {
- /* CRTC changes require notification. */
- goto notify;
- }
-
- if (!new_con_state->crtc)
- continue;
-
- new_crtc_state = drm_atomic_get_new_crtc_state(
- state, new_con_state->crtc);
-
- if (!new_crtc_state)
- continue;
-
- if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
- continue;
-
-notify:
- if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
- continue;
-
- aconnector = to_amdgpu_dm_connector(connector);
-
- mutex_lock(&adev->dm.audio_lock);
- inst = aconnector->audio_inst;
- aconnector->audio_inst = -1;
- mutex_unlock(&adev->dm.audio_lock);
-
- amdgpu_dm_audio_eld_notify(adev, inst);
- }
-
- /* Notify audio device additions. */
- for_each_new_connector_in_state(state, connector, new_con_state, i) {
- if (!new_con_state->crtc)
- continue;
-
- new_crtc_state = drm_atomic_get_new_crtc_state(
- state, new_con_state->crtc);
-
- if (!new_crtc_state)
- continue;
-
- if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
- continue;
-
- new_dm_crtc_state = to_dm_crtc_state(new_crtc_state);
- if (!new_dm_crtc_state->stream)
- continue;
-
- status = dc_stream_get_status(new_dm_crtc_state->stream);
- if (!status)
- continue;
-
- if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
- continue;
-
- aconnector = to_amdgpu_dm_connector(connector);
-
- mutex_lock(&adev->dm.audio_lock);
- inst = status->audio_inst;
- aconnector->audio_inst = inst;
- mutex_unlock(&adev->dm.audio_lock);
-
- amdgpu_dm_audio_eld_notify(adev, inst);
- }
-}
-
/*
* amdgpu_dm_crtc_copy_transient_flags - copy mirrored flags from DRM to DC
* @crtc_state: the DRM CRTC state
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
new file mode 100644
index 000000000000..70b7dbded275
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
@@ -0,0 +1,302 @@
+// 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
+ */
+
+#include "amdgpu.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_audio.h"
+#include "dc.h"
+
+#include <linux/component.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_audio_component.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_eld.h>
+
+#include "dc/inc/core_types.h"
+
+static int amdgpu_dm_audio_component_get_eld(struct device *kdev, int port,
+ int pipe, bool *enabled,
+ unsigned char *buf, int max_bytes)
+{
+ struct drm_device *dev = dev_get_drvdata(kdev);
+ struct amdgpu_device *adev = drm_to_adev(dev);
+ struct drm_connector *connector;
+ struct drm_connector_list_iter conn_iter;
+ struct amdgpu_dm_connector *aconnector;
+ int ret = 0;
+
+ *enabled = false;
+
+ mutex_lock(&adev->dm.audio_lock);
+
+ drm_connector_list_iter_begin(dev, &conn_iter);
+ drm_for_each_connector_iter(connector, &conn_iter) {
+
+ if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ continue;
+
+ aconnector = to_amdgpu_dm_connector(connector);
+ if (aconnector->audio_inst != port)
+ continue;
+
+ *enabled = true;
+ mutex_lock(&connector->eld_mutex);
+ ret = drm_eld_size(connector->eld);
+ memcpy(buf, connector->eld, min(max_bytes, ret));
+ mutex_unlock(&connector->eld_mutex);
+
+ break;
+ }
+ drm_connector_list_iter_end(&conn_iter);
+
+ mutex_unlock(&adev->dm.audio_lock);
+
+ drm_dbg_kms(adev_to_drm(adev), "Get ELD : idx=%d ret=%d en=%d\n", port, ret, *enabled);
+
+ return ret;
+}
+
+static const struct drm_audio_component_ops amdgpu_dm_audio_component_ops = {
+ .get_eld = amdgpu_dm_audio_component_get_eld,
+};
+
+static int amdgpu_dm_audio_component_bind(struct device *kdev,
+ struct device *hda_kdev, void *data)
+{
+ struct drm_device *dev = dev_get_drvdata(kdev);
+ struct amdgpu_device *adev = drm_to_adev(dev);
+ struct drm_audio_component *acomp = data;
+
+ acomp->ops = &amdgpu_dm_audio_component_ops;
+ acomp->dev = kdev;
+ adev->dm.audio_component = acomp;
+
+ return 0;
+}
+
+static void amdgpu_dm_audio_component_unbind(struct device *kdev,
+ struct device *hda_kdev, void *data)
+{
+ struct amdgpu_device *adev = drm_to_adev(dev_get_drvdata(kdev));
+ struct drm_audio_component *acomp = data;
+
+ acomp->ops = NULL;
+ acomp->dev = NULL;
+ adev->dm.audio_component = NULL;
+}
+
+static const struct component_ops amdgpu_dm_audio_component_bind_ops = {
+ .bind = amdgpu_dm_audio_component_bind,
+ .unbind = amdgpu_dm_audio_component_unbind,
+};
+
+int amdgpu_dm_audio_init(struct amdgpu_device *adev)
+{
+ int i, ret;
+
+ if (!amdgpu_audio)
+ return 0;
+
+ adev->mode_info.audio.enabled = true;
+
+ adev->mode_info.audio.num_pins = adev->dm.dc->res_pool->audio_count;
+
+ for (i = 0; i < adev->mode_info.audio.num_pins; i++) {
+ adev->mode_info.audio.pin[i].channels = -1;
+ adev->mode_info.audio.pin[i].rate = -1;
+ adev->mode_info.audio.pin[i].bits_per_sample = -1;
+ adev->mode_info.audio.pin[i].status_bits = 0;
+ adev->mode_info.audio.pin[i].category_code = 0;
+ adev->mode_info.audio.pin[i].connected = false;
+ adev->mode_info.audio.pin[i].id =
+ adev->dm.dc->res_pool->audios[i]->inst;
+ adev->mode_info.audio.pin[i].offset = 0;
+ }
+
+ ret = component_add(adev->dev, &amdgpu_dm_audio_component_bind_ops);
+ if (ret < 0)
+ return ret;
+
+ adev->dm.audio_registered = true;
+
+ return 0;
+}
+
+void amdgpu_dm_audio_fini(struct amdgpu_device *adev)
+{
+ if (!amdgpu_audio)
+ return;
+
+ if (!adev->mode_info.audio.enabled)
+ return;
+
+ if (adev->dm.audio_registered) {
+ component_del(adev->dev, &amdgpu_dm_audio_component_bind_ops);
+ adev->dm.audio_registered = false;
+ }
+
+ /* TODO: Disable audio? */
+
+ adev->mode_info.audio.enabled = false;
+}
+
+static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin)
+{
+ struct drm_audio_component *acomp = adev->dm.audio_component;
+
+ if (acomp && acomp->audio_ops && acomp->audio_ops->pin_eld_notify) {
+ drm_dbg_kms(adev_to_drm(adev), "Notify ELD: %d\n", pin);
+
+ acomp->audio_ops->pin_eld_notify(acomp->audio_ops->audio_ptr,
+ pin, -1);
+ }
+}
+
+void amdgpu_dm_fill_audio_info(struct audio_info *audio_info,
+ const struct drm_connector *drm_connector,
+ const struct dc_sink *dc_sink)
+{
+ int i = 0;
+ int cea_revision = 0;
+ const struct dc_edid_caps *edid_caps = &dc_sink->edid_caps;
+
+ audio_info->manufacture_id = edid_caps->manufacturer_id;
+ audio_info->product_id = edid_caps->product_id;
+
+ cea_revision = drm_connector->display_info.cea_rev;
+
+ strscpy(audio_info->display_name,
+ edid_caps->display_name,
+ AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS);
+
+ if (cea_revision >= 3) {
+ audio_info->mode_count = edid_caps->audio_mode_count;
+
+ for (i = 0; i < audio_info->mode_count; ++i) {
+ audio_info->modes[i].format_code =
+ (enum audio_format_code)
+ (edid_caps->audio_modes[i].format_code);
+ audio_info->modes[i].channel_count =
+ edid_caps->audio_modes[i].channel_count;
+ audio_info->modes[i].sample_rates.all =
+ edid_caps->audio_modes[i].sample_rate;
+ audio_info->modes[i].sample_size =
+ edid_caps->audio_modes[i].sample_size;
+ }
+ }
+
+ audio_info->flags.all = edid_caps->speaker_flags;
+
+ /* TODO: We only check for the progressive mode, check for interlace mode too */
+ if (drm_connector->latency_present[0]) {
+ audio_info->video_latency = drm_connector->video_latency[0];
+ audio_info->audio_latency = drm_connector->audio_latency[0];
+ }
+
+ /* TODO: For DP, video and audio latency should be calculated from DPCD caps */
+
+}
+
+void amdgpu_dm_commit_audio(struct drm_device *dev,
+ struct drm_atomic_state *state)
+{
+ struct amdgpu_device *adev = drm_to_adev(dev);
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector *connector;
+ struct drm_connector_state *old_con_state, *new_con_state;
+ struct drm_crtc_state *new_crtc_state;
+ struct dm_crtc_state *new_dm_crtc_state;
+ const struct dc_stream_status *status;
+ int i, inst;
+
+ /* Notify device removals. */
+ for_each_oldnew_connector_in_state(state, connector, old_con_state, new_con_state, i) {
+ if (old_con_state->crtc != new_con_state->crtc) {
+ /* CRTC changes require notification. */
+ goto notify;
+ }
+
+ if (!new_con_state->crtc)
+ continue;
+
+ new_crtc_state = drm_atomic_get_new_crtc_state(
+ state, new_con_state->crtc);
+
+ if (!new_crtc_state)
+ continue;
+
+ if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
+ continue;
+
+notify:
+ if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ continue;
+
+ aconnector = to_amdgpu_dm_connector(connector);
+
+ mutex_lock(&adev->dm.audio_lock);
+ inst = aconnector->audio_inst;
+ aconnector->audio_inst = -1;
+ mutex_unlock(&adev->dm.audio_lock);
+
+ amdgpu_dm_audio_eld_notify(adev, inst);
+ }
+
+ /* Notify audio device additions. */
+ for_each_new_connector_in_state(state, connector, new_con_state, i) {
+ if (!new_con_state->crtc)
+ continue;
+
+ new_crtc_state = drm_atomic_get_new_crtc_state(
+ state, new_con_state->crtc);
+
+ if (!new_crtc_state)
+ continue;
+
+ if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
+ continue;
+
+ new_dm_crtc_state = to_dm_crtc_state(new_crtc_state);
+ if (!new_dm_crtc_state->stream)
+ continue;
+
+ status = dc_stream_get_status(new_dm_crtc_state->stream);
+ if (!status)
+ continue;
+
+ if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ continue;
+
+ aconnector = to_amdgpu_dm_connector(connector);
+
+ mutex_lock(&adev->dm.audio_lock);
+ inst = status->audio_inst;
+ aconnector->audio_inst = inst;
+ mutex_unlock(&adev->dm.audio_lock);
+
+ amdgpu_dm_audio_eld_notify(adev, inst);
+ }
+}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h
new file mode 100644
index 000000000000..efd412bba178
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h
@@ -0,0 +1,44 @@
+/* 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_AUDIO_H__
+#define __AMDGPU_DM_AUDIO_H__
+
+struct amdgpu_device;
+struct drm_device;
+struct drm_atomic_state;
+struct drm_connector;
+struct audio_info;
+struct dc_sink;
+
+int amdgpu_dm_audio_init(struct amdgpu_device *adev);
+void amdgpu_dm_audio_fini(struct amdgpu_device *adev);
+void amdgpu_dm_commit_audio(struct drm_device *dev,
+ struct drm_atomic_state *state);
+void amdgpu_dm_fill_audio_info(struct audio_info *audio_info,
+ const struct drm_connector *drm_connector,
+ const struct dc_sink *dc_sink);
+
+#endif /* __AMDGPU_DM_AUDIO_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 09/32] drm/amd/display: Extract DMUB code to amdgpu_dm_dmub
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (7 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 08/32] drm/amd/display: Extract audio code to amdgpu_dm_audio Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 10/32] drm/amd/display: Move HPD and IRQ handler code to amdgpu_dm_irq Chenyu Chen
` (23 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Move DMUB-related functions and firmware defines from amdgpu_dm.c
into new amdgpu_dm_dmub.c and amdgpu_dm_dmub.h files to reduce
the size of amdgpu_dm.c and improve code organization.
No functional change intended.
Assisted-by: Copilot:Claude-Opus-4.6
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +-
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 928 +-----------------
.../amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 925 +++++++++++++++++
.../amd/display/amdgpu_dm/amdgpu_dm_dmub.h | 68 ++
4 files changed, 1003 insertions(+), 921 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
index 83a7d03a0348..a6408da05583 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
@@ -43,7 +43,8 @@ AMDGPUDM = \
amdgpu_dm_colorop.o \
amdgpu_dm_ism.o \
amdgpu_dm_backlight.o \
- amdgpu_dm_audio.o
+ amdgpu_dm_audio.o \
+ amdgpu_dm_dmub.o
ifdef CONFIG_DRM_AMD_DC_FP
AMDGPUDM += dc_fpu.o
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 a07820ba5759..7090e366f85f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -68,6 +68,7 @@
#include "amdgpu_dm_replay.h"
#include "amdgpu_dm_backlight.h"
#include "amdgpu_dm_audio.h"
+#include "amdgpu_dm_dmub.h"
#include "ivsrcid/ivsrcid_vislands30.h"
@@ -108,60 +109,9 @@
#include "modules/inc/mod_power.h"
#include "modules/power/power_helpers.h"
-static_assert(AMDGPU_DMUB_NOTIFICATION_MAX == DMUB_NOTIFICATION_MAX, "AMDGPU_DMUB_NOTIFICATION_MAX mismatch");
-
-#define FIRMWARE_RENOIR_DMUB "amdgpu/renoir_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_RENOIR_DMUB);
-#define FIRMWARE_SIENNA_CICHLID_DMUB "amdgpu/sienna_cichlid_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_SIENNA_CICHLID_DMUB);
-#define FIRMWARE_NAVY_FLOUNDER_DMUB "amdgpu/navy_flounder_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_NAVY_FLOUNDER_DMUB);
-#define FIRMWARE_GREEN_SARDINE_DMUB "amdgpu/green_sardine_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_GREEN_SARDINE_DMUB);
-#define FIRMWARE_VANGOGH_DMUB "amdgpu/vangogh_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_VANGOGH_DMUB);
-#define FIRMWARE_DIMGREY_CAVEFISH_DMUB "amdgpu/dimgrey_cavefish_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_DIMGREY_CAVEFISH_DMUB);
-#define FIRMWARE_BEIGE_GOBY_DMUB "amdgpu/beige_goby_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_BEIGE_GOBY_DMUB);
-#define FIRMWARE_YELLOW_CARP_DMUB "amdgpu/yellow_carp_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_YELLOW_CARP_DMUB);
-#define FIRMWARE_DCN_314_DMUB "amdgpu/dcn_3_1_4_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_DCN_314_DMUB);
-#define FIRMWARE_DCN_315_DMUB "amdgpu/dcn_3_1_5_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_DCN_315_DMUB);
-#define FIRMWARE_DCN316_DMUB "amdgpu/dcn_3_1_6_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_DCN316_DMUB);
-
-#define FIRMWARE_DCN_V3_2_0_DMCUB "amdgpu/dcn_3_2_0_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_DCN_V3_2_0_DMCUB);
-#define FIRMWARE_DCN_V3_2_1_DMCUB "amdgpu/dcn_3_2_1_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_DCN_V3_2_1_DMCUB);
-
-#define FIRMWARE_RAVEN_DMCU "amdgpu/raven_dmcu.bin"
MODULE_FIRMWARE(FIRMWARE_RAVEN_DMCU);
-
-#define FIRMWARE_NAVI12_DMCU "amdgpu/navi12_dmcu.bin"
MODULE_FIRMWARE(FIRMWARE_NAVI12_DMCU);
-#define FIRMWARE_DCN_35_DMUB "amdgpu/dcn_3_5_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_DCN_35_DMUB);
-
-#define FIRMWARE_DCN_351_DMUB "amdgpu/dcn_3_5_1_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_DCN_351_DMUB);
-
-#define FIRMWARE_DCN_36_DMUB "amdgpu/dcn_3_6_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_DCN_36_DMUB);
-
-#define FIRMWARE_DCN_401_DMUB "amdgpu/dcn_4_0_1_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_DCN_401_DMUB);
-
-#define FIRMWARE_DCN_42_DMUB "amdgpu/dcn_4_2_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_DCN_42_DMUB);
-
-#define FIRMWARE_DCN_42B_DMUB "amdgpu/dcn_4_2_1_dmcub.bin"
-MODULE_FIRMWARE(FIRMWARE_DCN_42B_DMUB);
-
/**
* DOC: overview
*
@@ -779,47 +729,6 @@ static void dm_dcn_vertical_interrupt0_high_irq(void *interrupt_params)
}
#endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */
-/**
- * dmub_aux_setconfig_callback - Callback for AUX or SET_CONFIG command.
- * @adev: amdgpu_device pointer
- * @notify: dmub notification structure
- *
- * Dmub AUX or SET_CONFIG command completion processing callback
- * Copies dmub notification to DM which is to be read by AUX command.
- * issuing thread and also signals the event to wake up the thread.
- */
-static void dmub_aux_setconfig_callback(struct amdgpu_device *adev,
- struct dmub_notification *notify)
-{
- if (adev->dm.dmub_notify)
- memcpy(adev->dm.dmub_notify, notify, sizeof(struct dmub_notification));
- if (notify->type == DMUB_NOTIFICATION_AUX_REPLY)
- complete(&adev->dm.dmub_aux_transfer_done);
-}
-
-static void dmub_aux_fused_io_callback(struct amdgpu_device *adev,
- struct dmub_notification *notify)
-{
- if (!adev || !notify) {
- ASSERT(false);
- return;
- }
-
- const struct dmub_cmd_fused_request *req = ¬ify->fused_request;
- const uint8_t ddc_line = req->u.aux.ddc_line;
-
- if (ddc_line >= ARRAY_SIZE(adev->dm.fused_io)) {
- ASSERT(false);
- return;
- }
-
- struct fused_io_sync *sync = &adev->dm.fused_io[ddc_line];
-
- static_assert(sizeof(*req) <= sizeof(sync->reply_data), "Size mismatch");
- memcpy(sync->reply_data, req, sizeof(*req));
- complete(&sync->replied);
-}
-
/**
* dmub_hpd_callback - DMUB HPD interrupt processing callback.
* @adev: amdgpu_device pointer
@@ -909,32 +818,6 @@ static void dmub_hpd_sense_callback(struct amdgpu_device *adev,
drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n");
}
-/**
- * register_dmub_notify_callback - Sets callback for DMUB notify
- * @adev: amdgpu_device pointer
- * @type: Type of dmub notification
- * @callback: Dmub interrupt callback function
- * @dmub_int_thread_offload: offload indicator
- *
- * API to register a dmub callback handler for a dmub notification
- * Also sets indicator whether callback processing to be offloaded.
- * to dmub interrupt handling thread
- * Return: true if successfully registered, false if there is existing registration
- */
-static bool register_dmub_notify_callback(struct amdgpu_device *adev,
- enum dmub_notification_type type,
- dmub_notify_interrupt_callback_t callback,
- bool dmub_int_thread_offload)
-{
- if (callback != NULL && type < ARRAY_SIZE(adev->dm.dmub_thread_offload)) {
- adev->dm.dmub_callback[type] = callback;
- adev->dm.dmub_thread_offload[type] = dmub_int_thread_offload;
- } else
- return false;
-
- return true;
-}
-
static void dm_handle_hpd_work(struct work_struct *work)
{
struct dmub_hpd_work *dmub_hpd_wrk;
@@ -1107,224 +990,6 @@ static void amdgpu_dm_fbc_init(struct drm_connector *connector)
}
-static int dm_dmub_hw_init(struct amdgpu_device *adev)
-{
- const struct dmcub_firmware_header_v1_0 *hdr;
- struct dmub_srv *dmub_srv = adev->dm.dmub_srv;
- struct dmub_srv_fb_info *fb_info = adev->dm.dmub_fb_info;
- const struct firmware *dmub_fw = adev->dm.dmub_fw;
- struct dc *dc = adev->dm.dc;
- struct dmcu *dmcu = adev->dm.dc->res_pool->dmcu;
- struct abm *abm = adev->dm.dc->res_pool->abm;
- struct dc_context *ctx = adev->dm.dc->ctx;
- struct dmub_srv_hw_params hw_params;
- enum dmub_status status;
- const unsigned char *fw_inst_const, *fw_bss_data;
- u32 i, fw_inst_const_size, fw_bss_data_size;
- bool has_hw_support;
-
- if (!dmub_srv)
- /* DMUB isn't supported on the ASIC. */
- return 0;
-
- if (!fb_info) {
- drm_err(adev_to_drm(adev), "No framebuffer info for DMUB service.\n");
- return -EINVAL;
- }
-
- if (!dmub_fw) {
- /* Firmware required for DMUB support. */
- drm_err(adev_to_drm(adev), "No firmware provided for DMUB.\n");
- return -EINVAL;
- }
-
- /* initialize register offsets for ASICs with runtime initialization available */
- if (dmub_srv->hw_funcs.init_reg_offsets)
- dmub_srv->hw_funcs.init_reg_offsets(dmub_srv, ctx);
-
- status = dmub_srv_has_hw_support(dmub_srv, &has_hw_support);
- if (status != DMUB_STATUS_OK) {
- drm_err(adev_to_drm(adev), "Error checking HW support for DMUB: %d\n", status);
- return -EINVAL;
- }
-
- if (!has_hw_support) {
- drm_info(adev_to_drm(adev), "DMUB unsupported on ASIC\n");
- return 0;
- }
-
- /* Reset DMCUB if it was previously running - before we overwrite its memory. */
- status = dmub_srv_hw_reset(dmub_srv);
- if (status != DMUB_STATUS_OK)
- drm_warn(adev_to_drm(adev), "Error resetting DMUB HW: %d\n", status);
-
- hdr = (const struct dmcub_firmware_header_v1_0 *)dmub_fw->data;
-
- fw_inst_const = dmub_fw->data +
- le32_to_cpu(hdr->header.ucode_array_offset_bytes) +
- PSP_HEADER_BYTES_256;
-
- fw_bss_data = dmub_fw->data +
- le32_to_cpu(hdr->header.ucode_array_offset_bytes) +
- le32_to_cpu(hdr->inst_const_bytes);
-
- /* Copy firmware and bios info into FB memory. */
- fw_inst_const_size = adev->dm.fw_inst_size;
-
- fw_bss_data_size = le32_to_cpu(hdr->bss_data_bytes);
-
- /* if adev->firmware.load_type == AMDGPU_FW_LOAD_PSP,
- * amdgpu_ucode_init_single_fw will load dmub firmware
- * fw_inst_const part to cw0; otherwise, the firmware back door load
- * will be done by dm_dmub_hw_init
- */
- if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) {
- memcpy(fb_info->fb[DMUB_WINDOW_0_INST_CONST].cpu_addr, fw_inst_const,
- fw_inst_const_size);
- }
-
- if (fw_bss_data_size)
- memcpy(fb_info->fb[DMUB_WINDOW_2_BSS_DATA].cpu_addr,
- fw_bss_data, fw_bss_data_size);
-
- /* Copy firmware bios info into FB memory. */
- memcpy(fb_info->fb[DMUB_WINDOW_3_VBIOS].cpu_addr, adev->bios,
- adev->bios_size);
-
- /* Reset regions that need to be reset. */
- memset(fb_info->fb[DMUB_WINDOW_4_MAILBOX].cpu_addr, 0,
- fb_info->fb[DMUB_WINDOW_4_MAILBOX].size);
-
- memset(fb_info->fb[DMUB_WINDOW_5_TRACEBUFF].cpu_addr, 0,
- fb_info->fb[DMUB_WINDOW_5_TRACEBUFF].size);
-
- memset(fb_info->fb[DMUB_WINDOW_6_FW_STATE].cpu_addr, 0,
- fb_info->fb[DMUB_WINDOW_6_FW_STATE].size);
-
- memset(fb_info->fb[DMUB_WINDOW_SHARED_STATE].cpu_addr, 0,
- fb_info->fb[DMUB_WINDOW_SHARED_STATE].size);
-
- /* Initialize hardware. */
- memset(&hw_params, 0, sizeof(hw_params));
- hw_params.soc_fb_info.fb_base = adev->gmc.fb_start;
- hw_params.soc_fb_info.fb_offset = adev->vm_manager.vram_base_offset;
-
- /* backdoor load firmware and trigger dmub running */
- if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP)
- hw_params.load_inst_const = true;
-
- if (dmcu)
- hw_params.psp_version = dmcu->psp_version;
-
- for (i = 0; i < fb_info->num_fb; ++i)
- hw_params.fb[i] = &fb_info->fb[i];
-
- /* Enable usb4 dpia in the FW APU */
- if (dc->caps.is_apu &&
- dc->res_pool->usb4_dpia_count != 0 &&
- !dc->debug.dpia_debug.bits.disable_dpia) {
- hw_params.dpia_supported = true;
- hw_params.disable_dpia = dc->debug.dpia_debug.bits.disable_dpia;
- hw_params.dpia_hpd_int_enable_supported = false;
- hw_params.enable_non_transparent_setconfig = dc->config.consolidated_dpia_dp_lt;
- hw_params.disable_dpia_bw_allocation = !dc->config.usb4_bw_alloc_support;
- }
-
- switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
- case IP_VERSION(3, 5, 0):
- case IP_VERSION(3, 5, 1):
- case IP_VERSION(3, 6, 0):
- case IP_VERSION(4, 2, 0):
- case IP_VERSION(4, 2, 1):
- hw_params.ips_sequential_ono = adev->external_rev_id > 0x10;
- hw_params.lower_hbr3_phy_ssc = true;
- break;
- default:
- break;
- }
-
- status = dmub_srv_hw_init(dmub_srv, &hw_params);
- if (status != DMUB_STATUS_OK) {
- drm_err(adev_to_drm(adev), "Error initializing DMUB HW: %d\n", status);
- return -EINVAL;
- }
-
- /* Wait for firmware load to finish. */
- status = dmub_srv_wait_for_auto_load(dmub_srv, 100000);
- if (status != DMUB_STATUS_OK)
- drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status);
-
- /* Init DMCU and ABM if available. */
- if (dmcu && abm) {
- dmcu->funcs->dmcu_init(dmcu);
- abm->dmcu_is_running = dmcu->funcs->is_dmcu_initialized(dmcu);
- }
-
- if (!adev->dm.dc->ctx->dmub_srv)
- adev->dm.dc->ctx->dmub_srv = dc_dmub_srv_create(adev->dm.dc, dmub_srv);
- if (!adev->dm.dc->ctx->dmub_srv) {
- drm_err(adev_to_drm(adev), "Couldn't allocate DC DMUB server!\n");
- return -ENOMEM;
- }
-
- drm_info(adev_to_drm(adev), "DMUB hardware initialized: version=0x%08X\n",
- adev->dm.dmcub_fw_version);
-
- /* Keeping sanity checks off if
- * DCN31 >= 4.0.59.0
- * DCN314 >= 8.0.16.0
- * Otherwise, turn on sanity checks
- */
- switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
- case IP_VERSION(3, 1, 2):
- case IP_VERSION(3, 1, 3):
- if (adev->dm.dmcub_fw_version &&
- adev->dm.dmcub_fw_version >= DMUB_FW_VERSION(4, 0, 0) &&
- adev->dm.dmcub_fw_version < DMUB_FW_VERSION(4, 0, 59))
- adev->dm.dc->debug.sanity_checks = true;
- break;
- case IP_VERSION(3, 1, 4):
- if (adev->dm.dmcub_fw_version &&
- adev->dm.dmcub_fw_version >= DMUB_FW_VERSION(4, 0, 0) &&
- adev->dm.dmcub_fw_version < DMUB_FW_VERSION(8, 0, 16))
- adev->dm.dc->debug.sanity_checks = true;
- break;
- default:
- break;
- }
-
- return 0;
-}
-
-static void dm_dmub_hw_resume(struct amdgpu_device *adev)
-{
- struct dmub_srv *dmub_srv = adev->dm.dmub_srv;
- enum dmub_status status;
- bool init;
- int r;
-
- if (!dmub_srv) {
- /* DMUB isn't supported on the ASIC. */
- return;
- }
-
- status = dmub_srv_is_hw_init(dmub_srv, &init);
- if (status != DMUB_STATUS_OK)
- drm_warn(adev_to_drm(adev), "DMUB hardware init check failed: %d\n", status);
-
- if (status == DMUB_STATUS_OK && init) {
- /* Wait for firmware load to finish. */
- status = dmub_srv_wait_for_auto_load(dmub_srv, 100000);
- if (status != DMUB_STATUS_OK)
- drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status);
- } else {
- /* Perform the full hardware initialization. */
- r = dm_dmub_hw_init(adev);
- if (r)
- drm_err(adev_to_drm(adev), "DMUB interface failed to initialize: status=%d\n", r);
- }
-}
-
static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_addr_space_config *pa_config)
{
u64 pt_base;
@@ -1632,119 +1297,6 @@ dm_free_gpu_mem(
}
-static enum dmub_status
-dm_dmub_send_vbios_gpint_command(struct amdgpu_device *adev,
- enum dmub_gpint_command command_code,
- uint16_t param,
- uint32_t timeout_us)
-{
- union dmub_gpint_data_register reg, test;
- uint32_t i;
-
- /* Assume that VBIOS DMUB is ready to take commands */
-
- reg.bits.status = 1;
- reg.bits.command_code = command_code;
- reg.bits.param = param;
-
- cgs_write_register(adev->dm.cgs_device, 0x34c0 + 0x01f8, reg.all);
-
- for (i = 0; i < timeout_us; ++i) {
- udelay(1);
-
- /* Check if our GPINT got acked */
- reg.bits.status = 0;
- test = (union dmub_gpint_data_register)
- cgs_read_register(adev->dm.cgs_device, 0x34c0 + 0x01f8);
-
- if (test.all == reg.all)
- return DMUB_STATUS_OK;
- }
-
- return DMUB_STATUS_TIMEOUT;
-}
-
-static void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev)
-{
- void *bb;
- long long addr;
- unsigned int bb_size;
- int i = 0;
- uint16_t chunk;
- enum dmub_gpint_command send_addrs[] = {
- DMUB_GPINT__SET_BB_ADDR_WORD0,
- DMUB_GPINT__SET_BB_ADDR_WORD1,
- DMUB_GPINT__SET_BB_ADDR_WORD2,
- DMUB_GPINT__SET_BB_ADDR_WORD3,
- };
- enum dmub_status ret;
-
- switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
- case IP_VERSION(4, 0, 1):
- bb_size = sizeof(struct dml2_soc_bb);
- break;
- case IP_VERSION(4, 2, 0):
- case IP_VERSION(4, 2, 1):
- bb_size = sizeof(struct dml2_soc_bb);
- break;
- default:
- return NULL;
- }
-
- bb = dm_allocate_gpu_mem(adev,
- DC_MEM_ALLOC_TYPE_GART,
- bb_size,
- &addr);
- if (!bb)
- return NULL;
-
- for (i = 0; i < 4; i++) {
- /* Extract 16-bit chunk */
- chunk = ((uint64_t) addr >> (i * 16)) & 0xFFFF;
- /* Send the chunk */
- ret = dm_dmub_send_vbios_gpint_command(adev, send_addrs[i], chunk, 30000);
- if (ret != DMUB_STATUS_OK)
- goto free_bb;
- }
-
- /* Now ask DMUB to copy the bb */
- ret = dm_dmub_send_vbios_gpint_command(adev, DMUB_GPINT__BB_COPY, 1, 200000);
- if (ret != DMUB_STATUS_OK)
- goto free_bb;
-
- return bb;
-
-free_bb:
- dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, (void *) bb);
- return NULL;
-
-}
-
-static enum dmub_ips_disable_type dm_get_default_ips_mode(
- struct amdgpu_device *adev)
-{
- enum dmub_ips_disable_type ret = DMUB_IPS_ENABLE;
-
- switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
- case IP_VERSION(3, 5, 0):
- case IP_VERSION(3, 6, 0):
- case IP_VERSION(3, 5, 1):
- ret = DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF;
- break;
- case IP_VERSION(4, 2, 0):
- case IP_VERSION(4, 2, 1):
- ret = DMUB_IPS_ENABLE;
- break;
- default:
- /* ASICs older than DCN35 do not have IPSs */
- if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 5, 0))
- ret = DMUB_IPS_DISABLE_ALL;
- break;
- }
-
- return ret;
-}
-
static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm)
{
struct mod_power_init_params init_data[MAX_NUM_EDP];
@@ -2140,8 +1692,8 @@ static int amdgpu_dm_init(struct amdgpu_device *adev)
}
amdgpu_dm_outbox_init(adev);
- if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_AUX_REPLY,
- dmub_aux_setconfig_callback, false)) {
+ if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_AUX_REPLY,
+ dm_dmub_aux_setconfig_callback, false)) {
drm_err(adev_to_drm(adev), "fail to register dmub aux callback");
goto error;
}
@@ -2149,8 +1701,8 @@ static int amdgpu_dm_init(struct amdgpu_device *adev)
for (size_t i = 0; i < ARRAY_SIZE(adev->dm.fused_io); i++)
init_completion(&adev->dm.fused_io[i].replied);
- if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_FUSED_IO,
- dmub_aux_fused_io_callback, false)) {
+ if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_FUSED_IO,
+ dm_dmub_aux_fused_io_callback, false)) {
drm_err(adev_to_drm(adev), "fail to register dmub fused io callback");
goto error;
}
@@ -2438,225 +1990,6 @@ static int load_dmcu_fw(struct amdgpu_device *adev)
return 0;
}
-static uint32_t amdgpu_dm_dmub_reg_read(void *ctx, uint32_t address)
-{
- struct amdgpu_device *adev = ctx;
-
- return dm_read_reg(adev->dm.dc->ctx, address);
-}
-
-static void amdgpu_dm_dmub_reg_write(void *ctx, uint32_t address,
- uint32_t value)
-{
- struct amdgpu_device *adev = ctx;
-
- return dm_write_reg(adev->dm.dc->ctx, address, value);
-}
-
-static int dm_dmub_sw_init(struct amdgpu_device *adev)
-{
- struct dmub_srv_create_params create_params;
- struct dmub_srv_fw_meta_info_params fw_meta_info_params;
- struct dmub_srv_region_params region_params;
- struct dmub_srv_region_info region_info;
- struct dmub_srv_memory_params memory_params;
- struct dmub_fw_meta_info fw_info;
- struct dmub_srv_fb_info *fb_info;
- struct dmub_srv *dmub_srv;
- const struct dmcub_firmware_header_v1_0 *hdr;
- enum dmub_asic dmub_asic;
- enum dmub_status status;
- static enum dmub_window_memory_type window_memory_type[DMUB_WINDOW_TOTAL] = {
- DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_0_INST_CONST
- DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_1_STACK
- DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_2_BSS_DATA
- DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_3_VBIOS
- DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_4_MAILBOX
- DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_5_TRACEBUFF
- DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_6_FW_STATE
- DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_7_SCRATCH_MEM
- DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_IB_MEM
- DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_SHARED_STATE
- DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_LSDMA_BUFFER
- DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_CURSOR_OFFLOAD
- };
- int r;
-
- switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
- case IP_VERSION(2, 1, 0):
- dmub_asic = DMUB_ASIC_DCN21;
- break;
- case IP_VERSION(3, 0, 0):
- dmub_asic = DMUB_ASIC_DCN30;
- break;
- case IP_VERSION(3, 0, 1):
- dmub_asic = DMUB_ASIC_DCN301;
- break;
- case IP_VERSION(3, 0, 2):
- dmub_asic = DMUB_ASIC_DCN302;
- break;
- case IP_VERSION(3, 0, 3):
- dmub_asic = DMUB_ASIC_DCN303;
- break;
- case IP_VERSION(3, 1, 2):
- case IP_VERSION(3, 1, 3):
- dmub_asic = (adev->external_rev_id == YELLOW_CARP_B0) ? DMUB_ASIC_DCN31B : DMUB_ASIC_DCN31;
- break;
- case IP_VERSION(3, 1, 4):
- dmub_asic = DMUB_ASIC_DCN314;
- break;
- case IP_VERSION(3, 1, 5):
- dmub_asic = DMUB_ASIC_DCN315;
- break;
- case IP_VERSION(3, 1, 6):
- dmub_asic = DMUB_ASIC_DCN316;
- break;
- case IP_VERSION(3, 2, 0):
- dmub_asic = DMUB_ASIC_DCN32;
- break;
- case IP_VERSION(3, 2, 1):
- dmub_asic = DMUB_ASIC_DCN321;
- break;
- case IP_VERSION(3, 5, 0):
- case IP_VERSION(3, 5, 1):
- dmub_asic = DMUB_ASIC_DCN35;
- break;
- case IP_VERSION(3, 6, 0):
- dmub_asic = DMUB_ASIC_DCN36;
- break;
- case IP_VERSION(4, 0, 1):
- dmub_asic = DMUB_ASIC_DCN401;
- break;
- case IP_VERSION(4, 2, 0):
- dmub_asic = DMUB_ASIC_DCN42;
- break;
- case IP_VERSION(4, 2, 1):
- dmub_asic = DMUB_ASIC_DCN42B;
- break;
- default:
- /* ASIC doesn't support DMUB. */
- return 0;
- }
-
- hdr = (const struct dmcub_firmware_header_v1_0 *)adev->dm.dmub_fw->data;
- adev->dm.dmcub_fw_version = le32_to_cpu(hdr->header.ucode_version);
-
- if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) {
- adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].ucode_id =
- AMDGPU_UCODE_ID_DMCUB;
- adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].fw =
- adev->dm.dmub_fw;
- adev->firmware.fw_size +=
- ALIGN(le32_to_cpu(hdr->inst_const_bytes), PAGE_SIZE);
-
- drm_info(adev_to_drm(adev), "Loading DMUB firmware via PSP: version=0x%08X\n",
- adev->dm.dmcub_fw_version);
- }
-
-
- adev->dm.dmub_srv = kzalloc(sizeof(*adev->dm.dmub_srv), GFP_KERNEL);
- dmub_srv = adev->dm.dmub_srv;
-
- if (!dmub_srv) {
- drm_err(adev_to_drm(adev), "Failed to allocate DMUB service!\n");
- return -ENOMEM;
- }
-
- memset(&create_params, 0, sizeof(create_params));
- create_params.user_ctx = adev;
- create_params.funcs.reg_read = amdgpu_dm_dmub_reg_read;
- create_params.funcs.reg_write = amdgpu_dm_dmub_reg_write;
- create_params.asic = dmub_asic;
-
- /* Create the DMUB service. */
- status = dmub_srv_create(dmub_srv, &create_params);
- if (status != DMUB_STATUS_OK) {
- drm_err(adev_to_drm(adev), "Error creating DMUB service: %d\n", status);
- return -EINVAL;
- }
-
- /* Extract the FW meta info. */
- memset(&fw_meta_info_params, 0, sizeof(fw_meta_info_params));
-
- fw_meta_info_params.inst_const_size = le32_to_cpu(hdr->inst_const_bytes) -
- PSP_HEADER_BYTES_256;
- fw_meta_info_params.bss_data_size = le32_to_cpu(hdr->bss_data_bytes);
- fw_meta_info_params.fw_inst_const = adev->dm.dmub_fw->data +
- le32_to_cpu(hdr->header.ucode_array_offset_bytes) +
- PSP_HEADER_BYTES_256;
- fw_meta_info_params.fw_bss_data = fw_meta_info_params.bss_data_size ? adev->dm.dmub_fw->data +
- le32_to_cpu(hdr->header.ucode_array_offset_bytes) +
- le32_to_cpu(hdr->inst_const_bytes) : NULL;
- fw_meta_info_params.custom_psp_footer_size = 0;
-
- status = dmub_srv_get_fw_meta_info_from_raw_fw(&fw_meta_info_params, &fw_info);
- if (status != DMUB_STATUS_OK) {
- /* Skip returning early, just log the error. */
- drm_err(adev_to_drm(adev), "Error getting DMUB FW meta info: %d\n", status);
- // return -EINVAL;
- }
-
- /* Calculate the size of all the regions for the DMUB service. */
- memset(®ion_params, 0, sizeof(region_params));
-
- region_params.inst_const_size = fw_meta_info_params.inst_const_size;
- region_params.bss_data_size = fw_meta_info_params.bss_data_size;
- region_params.vbios_size = adev->bios_size;
- region_params.fw_bss_data = fw_meta_info_params.fw_bss_data;
- region_params.fw_inst_const = fw_meta_info_params.fw_inst_const;
- region_params.window_memory_type = window_memory_type;
- region_params.fw_info = (status == DMUB_STATUS_OK) ? &fw_info : NULL;
-
- status = dmub_srv_calc_region_info(dmub_srv, ®ion_params,
- ®ion_info);
-
- if (status != DMUB_STATUS_OK) {
- drm_err(adev_to_drm(adev), "Error calculating DMUB region info: %d\n", status);
- return -EINVAL;
- }
-
- /*
- * Allocate a framebuffer based on the total size of all the regions.
- * TODO: Move this into GART.
- */
- r = amdgpu_bo_create_kernel(adev, region_info.fb_size, PAGE_SIZE,
- AMDGPU_GEM_DOMAIN_VRAM |
- AMDGPU_GEM_DOMAIN_GTT,
- &adev->dm.dmub_bo,
- &adev->dm.dmub_bo_gpu_addr,
- &adev->dm.dmub_bo_cpu_addr);
- if (r)
- return r;
-
- /* Rebase the regions on the framebuffer address. */
- memset(&memory_params, 0, sizeof(memory_params));
- memory_params.cpu_fb_addr = adev->dm.dmub_bo_cpu_addr;
- memory_params.gpu_fb_addr = adev->dm.dmub_bo_gpu_addr;
- memory_params.region_info = ®ion_info;
- memory_params.window_memory_type = window_memory_type;
-
- adev->dm.dmub_fb_info =
- kzalloc(sizeof(*adev->dm.dmub_fb_info), GFP_KERNEL);
- fb_info = adev->dm.dmub_fb_info;
-
- if (!fb_info) {
- drm_err(adev_to_drm(adev),
- "Failed to allocate framebuffer info for DMUB service!\n");
- return -ENOMEM;
- }
-
- status = dmub_srv_calc_mem_info(dmub_srv, &memory_params, fb_info);
- if (status != DMUB_STATUS_OK) {
- drm_err(adev_to_drm(adev), "Error calculating DMUB FB info: %d\n", status);
- return -EINVAL;
- }
-
- adev->dm.bb_from_dmub = dm_dmub_get_vbios_bounding_box(adev);
- adev->dm.fw_inst_size = fw_meta_info_params.inst_const_size;
-
- return 0;
-}
-
static int dm_sw_init(struct amdgpu_ip_block *ip_block)
{
struct amdgpu_device *adev = ip_block->adev;
@@ -4381,19 +3714,19 @@ static int register_hpd_handlers(struct amdgpu_device *adev)
int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
if (dc_is_dmub_outbox_supported(adev->dm.dc)) {
- if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD,
+ if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD,
dmub_hpd_callback, true)) {
drm_err(adev_to_drm(adev), "fail to register dmub hpd callback");
return -EINVAL;
}
- if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ,
+ if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ,
dmub_hpd_callback, true)) {
drm_err(adev_to_drm(adev), "fail to register dmub hpd callback");
return -EINVAL;
}
- if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY,
+ if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY,
dmub_hpd_sense_callback, true)) {
drm_err(adev_to_drm(adev), "fail to register dmub hpd sense callback");
return -EINVAL;
@@ -5401,78 +4734,6 @@ DEVICE_ATTR_WO(s3_debug);
#endif
-static int dm_init_microcode(struct amdgpu_device *adev)
-{
- char *fw_name_dmub;
- int r;
-
- switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
- case IP_VERSION(2, 1, 0):
- fw_name_dmub = FIRMWARE_RENOIR_DMUB;
- if (ASICREV_IS_GREEN_SARDINE(adev->external_rev_id))
- fw_name_dmub = FIRMWARE_GREEN_SARDINE_DMUB;
- break;
- case IP_VERSION(3, 0, 0):
- if (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(10, 3, 0))
- fw_name_dmub = FIRMWARE_SIENNA_CICHLID_DMUB;
- else
- fw_name_dmub = FIRMWARE_NAVY_FLOUNDER_DMUB;
- break;
- case IP_VERSION(3, 0, 1):
- fw_name_dmub = FIRMWARE_VANGOGH_DMUB;
- break;
- case IP_VERSION(3, 0, 2):
- fw_name_dmub = FIRMWARE_DIMGREY_CAVEFISH_DMUB;
- break;
- case IP_VERSION(3, 0, 3):
- fw_name_dmub = FIRMWARE_BEIGE_GOBY_DMUB;
- break;
- case IP_VERSION(3, 1, 2):
- case IP_VERSION(3, 1, 3):
- fw_name_dmub = FIRMWARE_YELLOW_CARP_DMUB;
- break;
- case IP_VERSION(3, 1, 4):
- fw_name_dmub = FIRMWARE_DCN_314_DMUB;
- break;
- case IP_VERSION(3, 1, 5):
- fw_name_dmub = FIRMWARE_DCN_315_DMUB;
- break;
- case IP_VERSION(3, 1, 6):
- fw_name_dmub = FIRMWARE_DCN316_DMUB;
- break;
- case IP_VERSION(3, 2, 0):
- fw_name_dmub = FIRMWARE_DCN_V3_2_0_DMCUB;
- break;
- case IP_VERSION(3, 2, 1):
- fw_name_dmub = FIRMWARE_DCN_V3_2_1_DMCUB;
- break;
- case IP_VERSION(3, 5, 0):
- fw_name_dmub = FIRMWARE_DCN_35_DMUB;
- break;
- case IP_VERSION(3, 5, 1):
- fw_name_dmub = FIRMWARE_DCN_351_DMUB;
- break;
- case IP_VERSION(3, 6, 0):
- fw_name_dmub = FIRMWARE_DCN_36_DMUB;
- break;
- case IP_VERSION(4, 0, 1):
- fw_name_dmub = FIRMWARE_DCN_401_DMUB;
- break;
- case IP_VERSION(4, 2, 0):
- fw_name_dmub = FIRMWARE_DCN_42_DMUB;
- break;
- case IP_VERSION(4, 2, 1):
- fw_name_dmub = FIRMWARE_DCN_42B_DMUB;
- break;
- default:
- /* ASIC doesn't support DMUB. */
- return 0;
- }
- r = amdgpu_ucode_request(adev, &adev->dm.dmub_fw, AMDGPU_UCODE_REQUIRED,
- "%s", fw_name_dmub);
- return r;
-}
-
static int dm_early_init(struct amdgpu_ip_block *ip_block)
{
struct amdgpu_device *adev = ip_block->adev;
@@ -13005,179 +12266,6 @@ uint32_t dm_read_reg_func(const struct dc_context *ctx, uint32_t address,
return value;
}
-int amdgpu_dm_process_dmub_aux_transfer_sync(
- struct dc_context *ctx,
- unsigned int link_index,
- struct aux_payload *payload,
- enum aux_return_code_type *operation_result)
-{
- struct amdgpu_device *adev = ctx->driver_context;
- struct dmub_notification *p_notify = adev->dm.dmub_notify;
- int ret = -1;
-
- mutex_lock(&adev->dm.dpia_aux_lock);
- if (!dc_process_dmub_aux_transfer_async(ctx->dc, link_index, payload)) {
- *operation_result = AUX_RET_ERROR_ENGINE_ACQUIRE;
- goto out;
- }
-
- if (!wait_for_completion_timeout(&adev->dm.dmub_aux_transfer_done, 10 * HZ)) {
- drm_err(adev_to_drm(adev), "wait_for_completion_timeout timeout!");
- *operation_result = AUX_RET_ERROR_TIMEOUT;
- goto out;
- }
-
- if (p_notify->result != AUX_RET_SUCCESS) {
- /*
- * Transient states before tunneling is enabled could
- * lead to this error. We can ignore this for now.
- */
- if (p_notify->result == AUX_RET_ERROR_PROTOCOL_ERROR) {
- drm_warn(adev_to_drm(adev), "DPIA AUX failed on 0x%x(%d), error %d\n",
- payload->address, payload->length,
- p_notify->result);
- }
- *operation_result = p_notify->result;
- goto out;
- }
-
- payload->reply[0] = adev->dm.dmub_notify->aux_reply.command & 0xF;
- if (adev->dm.dmub_notify->aux_reply.command & 0xF0)
- /* The reply is stored in the top nibble of the command. */
- payload->reply[0] = (adev->dm.dmub_notify->aux_reply.command >> 4) & 0xF;
-
- /*write req may receive a byte indicating partially written number as well*/
- if (p_notify->aux_reply.length)
- memcpy(payload->data, p_notify->aux_reply.data,
- p_notify->aux_reply.length);
-
- /* success */
- ret = p_notify->aux_reply.length;
- *operation_result = p_notify->result;
-out:
- reinit_completion(&adev->dm.dmub_aux_transfer_done);
- mutex_unlock(&adev->dm.dpia_aux_lock);
- return ret;
-}
-
-static void abort_fused_io(
- struct dc_context *ctx,
- const struct dmub_cmd_fused_request *request
-)
-{
- union dmub_rb_cmd command = { 0 };
- struct dmub_rb_cmd_fused_io *io = &command.fused_io;
-
- io->header.type = DMUB_CMD__FUSED_IO;
- io->header.sub_type = DMUB_CMD__FUSED_IO_ABORT;
- io->header.payload_bytes = sizeof(*io) - sizeof(io->header);
- io->request = *request;
- dm_execute_dmub_cmd(ctx, &command, DM_DMUB_WAIT_TYPE_NO_WAIT);
-}
-
-static bool execute_fused_io(
- struct amdgpu_device *dev,
- struct dc_context *ctx,
- union dmub_rb_cmd *commands,
- uint8_t count,
- uint32_t timeout_us
-)
-{
- const uint8_t ddc_line = commands[0].fused_io.request.u.aux.ddc_line;
-
- if (ddc_line >= ARRAY_SIZE(dev->dm.fused_io))
- return false;
-
- struct fused_io_sync *sync = &dev->dm.fused_io[ddc_line];
- struct dmub_rb_cmd_fused_io *first = &commands[0].fused_io;
- const bool result = dm_execute_dmub_cmd_list(ctx, count, commands, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY)
- && first->header.ret_status
- && first->request.status == FUSED_REQUEST_STATUS_SUCCESS;
-
- if (!result)
- return false;
-
- while (wait_for_completion_timeout(&sync->replied, usecs_to_jiffies(timeout_us))) {
- reinit_completion(&sync->replied);
-
- struct dmub_cmd_fused_request *reply = (struct dmub_cmd_fused_request *) sync->reply_data;
-
- static_assert(sizeof(*reply) <= sizeof(sync->reply_data), "Size mismatch");
-
- if (reply->identifier == first->request.identifier) {
- first->request = *reply;
- return true;
- }
- }
-
- reinit_completion(&sync->replied);
- first->request.status = FUSED_REQUEST_STATUS_TIMEOUT;
- abort_fused_io(ctx, &first->request);
- return false;
-}
-
-bool amdgpu_dm_execute_fused_io(
- struct amdgpu_device *dev,
- struct dc_link *link,
- union dmub_rb_cmd *commands,
- uint8_t count,
- uint32_t timeout_us)
-{
- struct amdgpu_display_manager *dm = &dev->dm;
-
- mutex_lock(&dm->dpia_aux_lock);
-
- const bool result = execute_fused_io(dev, link->ctx, commands, count, timeout_us);
-
- mutex_unlock(&dm->dpia_aux_lock);
- return result;
-}
-
-int amdgpu_dm_process_dmub_set_config_sync(
- struct dc_context *ctx,
- unsigned int link_index,
- struct set_config_cmd_payload *payload,
- enum set_config_status *operation_result)
-{
- struct amdgpu_device *adev = ctx->driver_context;
- bool is_cmd_complete;
- int ret;
-
- mutex_lock(&adev->dm.dpia_aux_lock);
- is_cmd_complete = dc_process_dmub_set_config_async(ctx->dc,
- link_index, payload, adev->dm.dmub_notify);
-
- if (is_cmd_complete || wait_for_completion_timeout(&adev->dm.dmub_aux_transfer_done, 10 * HZ)) {
- ret = 0;
- *operation_result = adev->dm.dmub_notify->sc_status;
- } else {
- drm_err(adev_to_drm(adev), "wait_for_completion_timeout timeout!");
- ret = -1;
- *operation_result = SET_CONFIG_UNKNOWN_ERROR;
- }
-
- if (!is_cmd_complete)
- reinit_completion(&adev->dm.dmub_aux_transfer_done);
- mutex_unlock(&adev->dm.dpia_aux_lock);
- return ret;
-}
-
-bool dm_execute_dmub_cmd(const struct dc_context *ctx, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type)
-{
- struct amdgpu_device *adev = ctx->driver_context;
-
- guard(spinlock_irqsave)(&adev->dm.dmub_lock);
- return dc_dmub_srv_cmd_run(ctx->dmub_srv, cmd, wait_type);
-}
-
-bool dm_execute_dmub_cmd_list(const struct dc_context *ctx, unsigned int count, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type)
-{
- struct amdgpu_device *adev = ctx->driver_context;
-
- guard(spinlock_irqsave)(&adev->dm.dmub_lock);
- return dc_dmub_srv_cmd_run_list(ctx->dmub_srv, count, cmd, wait_type);
-}
-
void dm_acpi_process_phy_transition_interlock(
const struct dc_context *ctx,
struct dm_process_phy_transition_init_params process_phy_transition_init_params)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c
new file mode 100644
index 000000000000..54b3b884968b
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c
@@ -0,0 +1,925 @@
+// 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
+ *
+ */
+
+#include "dm_services_types.h"
+#include "dc.h"
+#include "dc/inc/core_types.h"
+#include "dc/dc_dmub_srv.h"
+#include "dmub/dmub_srv.h"
+#include "dc/inc/hw/dmcu.h"
+#include "dc/inc/hw/abm.h"
+#include "dal_asic_id.h"
+
+#include "amdgpu.h"
+#include "amdgpu_display.h"
+#include "amdgpu_ucode.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_dmub.h"
+#include <linux/component.h>
+#include <linux/firmware.h>
+
+static_assert(AMDGPU_DMUB_NOTIFICATION_MAX == DMUB_NOTIFICATION_MAX, "AMDGPU_DMUB_NOTIFICATION_MAX mismatch");
+
+MODULE_FIRMWARE(FIRMWARE_RENOIR_DMUB);
+MODULE_FIRMWARE(FIRMWARE_SIENNA_CICHLID_DMUB);
+MODULE_FIRMWARE(FIRMWARE_NAVY_FLOUNDER_DMUB);
+MODULE_FIRMWARE(FIRMWARE_GREEN_SARDINE_DMUB);
+MODULE_FIRMWARE(FIRMWARE_VANGOGH_DMUB);
+MODULE_FIRMWARE(FIRMWARE_DIMGREY_CAVEFISH_DMUB);
+MODULE_FIRMWARE(FIRMWARE_BEIGE_GOBY_DMUB);
+MODULE_FIRMWARE(FIRMWARE_YELLOW_CARP_DMUB);
+MODULE_FIRMWARE(FIRMWARE_DCN_314_DMUB);
+MODULE_FIRMWARE(FIRMWARE_DCN_315_DMUB);
+MODULE_FIRMWARE(FIRMWARE_DCN316_DMUB);
+MODULE_FIRMWARE(FIRMWARE_DCN_V3_2_0_DMCUB);
+MODULE_FIRMWARE(FIRMWARE_DCN_V3_2_1_DMCUB);
+MODULE_FIRMWARE(FIRMWARE_DCN_35_DMUB);
+MODULE_FIRMWARE(FIRMWARE_DCN_351_DMUB);
+MODULE_FIRMWARE(FIRMWARE_DCN_36_DMUB);
+MODULE_FIRMWARE(FIRMWARE_DCN_401_DMUB);
+MODULE_FIRMWARE(FIRMWARE_DCN_42_DMUB);
+MODULE_FIRMWARE(FIRMWARE_DCN_42B_DMUB);
+
+/**
+ * dm_dmub_aux_setconfig_callback - Callback for AUX or SET_CONFIG command.
+ * @adev: amdgpu_device pointer
+ * @notify: dmub notification structure
+ *
+ * Dmub AUX or SET_CONFIG command completion processing callback
+ * Copies dmub notification to DM which is to be read by AUX command.
+ * issuing thread and also signals the event to wake up the thread.
+ */
+void dm_dmub_aux_setconfig_callback(struct amdgpu_device *adev,
+ struct dmub_notification *notify)
+{
+ if (adev->dm.dmub_notify)
+ memcpy(adev->dm.dmub_notify, notify, sizeof(struct dmub_notification));
+ if (notify->type == DMUB_NOTIFICATION_AUX_REPLY)
+ complete(&adev->dm.dmub_aux_transfer_done);
+}
+
+void dm_dmub_aux_fused_io_callback(struct amdgpu_device *adev,
+ struct dmub_notification *notify)
+{
+ if (!adev || !notify) {
+ ASSERT(false);
+ return;
+ }
+
+ const struct dmub_cmd_fused_request *req = ¬ify->fused_request;
+ const uint8_t ddc_line = req->u.aux.ddc_line;
+
+ if (ddc_line >= ARRAY_SIZE(adev->dm.fused_io)) {
+ ASSERT(false);
+ return;
+ }
+
+ struct fused_io_sync *sync = &adev->dm.fused_io[ddc_line];
+
+ static_assert(sizeof(*req) <= sizeof(sync->reply_data), "Size mismatch");
+ memcpy(sync->reply_data, req, sizeof(*req));
+ complete(&sync->replied);
+}
+
+/**
+ * dm_register_dmub_notify_callback - Sets callback for DMUB notify
+ * @adev: amdgpu_device pointer
+ * @type: Type of dmub notification
+ * @callback: Dmub interrupt callback function
+ * @dmub_int_thread_offload: offload indicator
+ *
+ * API to register a dmub callback handler for a dmub notification
+ * Also sets indicator whether callback processing to be offloaded.
+ * to dmub interrupt handling thread
+ * Return: true if successfully registered, false if there is existing registration
+ */
+bool dm_register_dmub_notify_callback(struct amdgpu_device *adev,
+ enum dmub_notification_type type,
+ dmub_notify_interrupt_callback_t callback,
+ bool dmub_int_thread_offload)
+{
+ if (callback != NULL && type < ARRAY_SIZE(adev->dm.dmub_thread_offload)) {
+ adev->dm.dmub_callback[type] = callback;
+ adev->dm.dmub_thread_offload[type] = dmub_int_thread_offload;
+ } else
+ return false;
+
+ return true;
+}
+
+int dm_dmub_hw_init(struct amdgpu_device *adev)
+{
+ const struct dmcub_firmware_header_v1_0 *hdr;
+ struct dmub_srv *dmub_srv = adev->dm.dmub_srv;
+ struct dmub_srv_fb_info *fb_info = adev->dm.dmub_fb_info;
+ const struct firmware *dmub_fw = adev->dm.dmub_fw;
+ struct dc *dc = adev->dm.dc;
+ struct dmcu *dmcu = adev->dm.dc->res_pool->dmcu;
+ struct abm *abm = adev->dm.dc->res_pool->abm;
+ struct dc_context *ctx = adev->dm.dc->ctx;
+ struct dmub_srv_hw_params hw_params;
+ enum dmub_status status;
+ const unsigned char *fw_inst_const, *fw_bss_data;
+ u32 i, fw_inst_const_size, fw_bss_data_size;
+ bool has_hw_support;
+
+ if (!dmub_srv)
+ /* DMUB isn't supported on the ASIC. */
+ return 0;
+
+ if (!fb_info) {
+ drm_err(adev_to_drm(adev), "No framebuffer info for DMUB service.\n");
+ return -EINVAL;
+ }
+
+ if (!dmub_fw) {
+ /* Firmware required for DMUB support. */
+ drm_err(adev_to_drm(adev), "No firmware provided for DMUB.\n");
+ return -EINVAL;
+ }
+
+ /* initialize register offsets for ASICs with runtime initialization available */
+ if (dmub_srv->hw_funcs.init_reg_offsets)
+ dmub_srv->hw_funcs.init_reg_offsets(dmub_srv, ctx);
+
+ status = dmub_srv_has_hw_support(dmub_srv, &has_hw_support);
+ if (status != DMUB_STATUS_OK) {
+ drm_err(adev_to_drm(adev), "Error checking HW support for DMUB: %d\n", status);
+ return -EINVAL;
+ }
+
+ if (!has_hw_support) {
+ drm_info(adev_to_drm(adev), "DMUB unsupported on ASIC\n");
+ return 0;
+ }
+
+ /* Reset DMCUB if it was previously running - before we overwrite its memory. */
+ status = dmub_srv_hw_reset(dmub_srv);
+ if (status != DMUB_STATUS_OK)
+ drm_warn(adev_to_drm(adev), "Error resetting DMUB HW: %d\n", status);
+
+ hdr = (const struct dmcub_firmware_header_v1_0 *)dmub_fw->data;
+
+ fw_inst_const = dmub_fw->data +
+ le32_to_cpu(hdr->header.ucode_array_offset_bytes) +
+ PSP_HEADER_BYTES_256;
+
+ fw_bss_data = dmub_fw->data +
+ le32_to_cpu(hdr->header.ucode_array_offset_bytes) +
+ le32_to_cpu(hdr->inst_const_bytes);
+
+ /* Copy firmware and bios info into FB memory. */
+ fw_inst_const_size = adev->dm.fw_inst_size;
+
+ fw_bss_data_size = le32_to_cpu(hdr->bss_data_bytes);
+
+ /* if adev->firmware.load_type == AMDGPU_FW_LOAD_PSP,
+ * amdgpu_ucode_init_single_fw will load dmub firmware
+ * fw_inst_const part to cw0; otherwise, the firmware back door load
+ * will be done by dm_dmub_hw_init
+ */
+ if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) {
+ memcpy(fb_info->fb[DMUB_WINDOW_0_INST_CONST].cpu_addr, fw_inst_const,
+ fw_inst_const_size);
+ }
+
+ if (fw_bss_data_size)
+ memcpy(fb_info->fb[DMUB_WINDOW_2_BSS_DATA].cpu_addr,
+ fw_bss_data, fw_bss_data_size);
+
+ /* Copy firmware bios info into FB memory. */
+ memcpy(fb_info->fb[DMUB_WINDOW_3_VBIOS].cpu_addr, adev->bios,
+ adev->bios_size);
+
+ /* Reset regions that need to be reset. */
+ memset(fb_info->fb[DMUB_WINDOW_4_MAILBOX].cpu_addr, 0,
+ fb_info->fb[DMUB_WINDOW_4_MAILBOX].size);
+
+ memset(fb_info->fb[DMUB_WINDOW_5_TRACEBUFF].cpu_addr, 0,
+ fb_info->fb[DMUB_WINDOW_5_TRACEBUFF].size);
+
+ memset(fb_info->fb[DMUB_WINDOW_6_FW_STATE].cpu_addr, 0,
+ fb_info->fb[DMUB_WINDOW_6_FW_STATE].size);
+
+ memset(fb_info->fb[DMUB_WINDOW_SHARED_STATE].cpu_addr, 0,
+ fb_info->fb[DMUB_WINDOW_SHARED_STATE].size);
+
+ /* Initialize hardware. */
+ memset(&hw_params, 0, sizeof(hw_params));
+ hw_params.soc_fb_info.fb_base = adev->gmc.fb_start;
+ hw_params.soc_fb_info.fb_offset = adev->vm_manager.vram_base_offset;
+
+ /* backdoor load firmware and trigger dmub running */
+ if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP)
+ hw_params.load_inst_const = true;
+
+ if (dmcu)
+ hw_params.psp_version = dmcu->psp_version;
+
+ for (i = 0; i < fb_info->num_fb; ++i)
+ hw_params.fb[i] = &fb_info->fb[i];
+
+ /* Enable usb4 dpia in the FW APU */
+ if (dc->caps.is_apu &&
+ dc->res_pool->usb4_dpia_count != 0 &&
+ !dc->debug.dpia_debug.bits.disable_dpia) {
+ hw_params.dpia_supported = true;
+ hw_params.disable_dpia = dc->debug.dpia_debug.bits.disable_dpia;
+ hw_params.dpia_hpd_int_enable_supported = false;
+ hw_params.enable_non_transparent_setconfig = dc->config.consolidated_dpia_dp_lt;
+ hw_params.disable_dpia_bw_allocation = !dc->config.usb4_bw_alloc_support;
+ }
+
+ switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
+ case IP_VERSION(3, 5, 0):
+ case IP_VERSION(3, 5, 1):
+ case IP_VERSION(3, 6, 0):
+ case IP_VERSION(4, 2, 0):
+ case IP_VERSION(4, 2, 1):
+ hw_params.ips_sequential_ono = adev->external_rev_id > 0x10;
+ hw_params.lower_hbr3_phy_ssc = true;
+ break;
+ default:
+ break;
+ }
+
+ status = dmub_srv_hw_init(dmub_srv, &hw_params);
+ if (status != DMUB_STATUS_OK) {
+ drm_err(adev_to_drm(adev), "Error initializing DMUB HW: %d\n", status);
+ return -EINVAL;
+ }
+
+ /* Wait for firmware load to finish. */
+ status = dmub_srv_wait_for_auto_load(dmub_srv, 100000);
+ if (status != DMUB_STATUS_OK)
+ drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status);
+
+ /* Init DMCU and ABM if available. */
+ if (dmcu && abm) {
+ dmcu->funcs->dmcu_init(dmcu);
+ abm->dmcu_is_running = dmcu->funcs->is_dmcu_initialized(dmcu);
+ }
+
+ if (!adev->dm.dc->ctx->dmub_srv)
+ adev->dm.dc->ctx->dmub_srv = dc_dmub_srv_create(adev->dm.dc, dmub_srv);
+ if (!adev->dm.dc->ctx->dmub_srv) {
+ drm_err(adev_to_drm(adev), "Couldn't allocate DC DMUB server!\n");
+ return -ENOMEM;
+ }
+
+ drm_info(adev_to_drm(adev), "DMUB hardware initialized: version=0x%08X\n",
+ adev->dm.dmcub_fw_version);
+
+ /* Keeping sanity checks off if
+ * DCN31 >= 4.0.59.0
+ * DCN314 >= 8.0.16.0
+ * Otherwise, turn on sanity checks
+ */
+ switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
+ case IP_VERSION(3, 1, 2):
+ case IP_VERSION(3, 1, 3):
+ if (adev->dm.dmcub_fw_version &&
+ adev->dm.dmcub_fw_version >= DMUB_FW_VERSION(4, 0, 0) &&
+ adev->dm.dmcub_fw_version < DMUB_FW_VERSION(4, 0, 59))
+ adev->dm.dc->debug.sanity_checks = true;
+ break;
+ case IP_VERSION(3, 1, 4):
+ if (adev->dm.dmcub_fw_version &&
+ adev->dm.dmcub_fw_version >= DMUB_FW_VERSION(4, 0, 0) &&
+ adev->dm.dmcub_fw_version < DMUB_FW_VERSION(8, 0, 16))
+ adev->dm.dc->debug.sanity_checks = true;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void dm_dmub_hw_resume(struct amdgpu_device *adev)
+{
+ struct dmub_srv *dmub_srv = adev->dm.dmub_srv;
+ enum dmub_status status;
+ bool init;
+ int r;
+
+ if (!dmub_srv) {
+ /* DMUB isn't supported on the ASIC. */
+ return;
+ }
+
+ status = dmub_srv_is_hw_init(dmub_srv, &init);
+ if (status != DMUB_STATUS_OK)
+ drm_warn(adev_to_drm(adev), "DMUB hardware init check failed: %d\n", status);
+
+ if (status == DMUB_STATUS_OK && init) {
+ /* Wait for firmware load to finish. */
+ status = dmub_srv_wait_for_auto_load(dmub_srv, 100000);
+ if (status != DMUB_STATUS_OK)
+ drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status);
+ } else {
+ /* Perform the full hardware initialization. */
+ r = dm_dmub_hw_init(adev);
+ if (r)
+ drm_err(adev_to_drm(adev), "DMUB interface failed to initialize: status=%d\n", r);
+ }
+}
+
+static enum dmub_status
+dm_dmub_send_vbios_gpint_command(struct amdgpu_device *adev,
+ enum dmub_gpint_command command_code,
+ uint16_t param,
+ uint32_t timeout_us)
+{
+ union dmub_gpint_data_register reg, test;
+ uint32_t i;
+
+ /* Assume that VBIOS DMUB is ready to take commands */
+
+ reg.bits.status = 1;
+ reg.bits.command_code = command_code;
+ reg.bits.param = param;
+
+ cgs_write_register(adev->dm.cgs_device, 0x34c0 + 0x01f8, reg.all);
+
+ for (i = 0; i < timeout_us; ++i) {
+ udelay(1);
+
+ /* Check if our GPINT got acked */
+ reg.bits.status = 0;
+ test = (union dmub_gpint_data_register)
+ cgs_read_register(adev->dm.cgs_device, 0x34c0 + 0x01f8);
+
+ if (test.all == reg.all)
+ return DMUB_STATUS_OK;
+ }
+
+ return DMUB_STATUS_TIMEOUT;
+}
+
+static void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev)
+{
+ void *bb;
+ long long addr;
+ unsigned int bb_size;
+ int i = 0;
+ uint16_t chunk;
+ enum dmub_gpint_command send_addrs[] = {
+ DMUB_GPINT__SET_BB_ADDR_WORD0,
+ DMUB_GPINT__SET_BB_ADDR_WORD1,
+ DMUB_GPINT__SET_BB_ADDR_WORD2,
+ DMUB_GPINT__SET_BB_ADDR_WORD3,
+ };
+ enum dmub_status ret;
+
+ switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
+ case IP_VERSION(4, 0, 1):
+ bb_size = sizeof(struct dml2_soc_bb);
+ break;
+ case IP_VERSION(4, 2, 0):
+ case IP_VERSION(4, 2, 1):
+ bb_size = sizeof(struct dml2_soc_bb);
+ break;
+ default:
+ return NULL;
+ }
+
+ bb = dm_allocate_gpu_mem(adev,
+ DC_MEM_ALLOC_TYPE_GART,
+ bb_size,
+ &addr);
+ if (!bb)
+ return NULL;
+
+ for (i = 0; i < 4; i++) {
+ /* Extract 16-bit chunk */
+ chunk = ((uint64_t) addr >> (i * 16)) & 0xFFFF;
+ /* Send the chunk */
+ ret = dm_dmub_send_vbios_gpint_command(adev, send_addrs[i], chunk, 30000);
+ if (ret != DMUB_STATUS_OK)
+ goto free_bb;
+ }
+
+ /* Now ask DMUB to copy the bb */
+ ret = dm_dmub_send_vbios_gpint_command(adev, DMUB_GPINT__BB_COPY, 1, 200000);
+ if (ret != DMUB_STATUS_OK)
+ goto free_bb;
+
+ return bb;
+
+free_bb:
+ dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, (void *) bb);
+ return NULL;
+
+}
+
+enum dmub_ips_disable_type dm_get_default_ips_mode(
+ struct amdgpu_device *adev)
+{
+ enum dmub_ips_disable_type ret = DMUB_IPS_ENABLE;
+
+ switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
+ case IP_VERSION(3, 5, 0):
+ case IP_VERSION(3, 6, 0):
+ case IP_VERSION(3, 5, 1):
+ ret = DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF;
+ break;
+ case IP_VERSION(4, 2, 0):
+ case IP_VERSION(4, 2, 1):
+ ret = DMUB_IPS_ENABLE;
+ break;
+ default:
+ /* ASICs older than DCN35 do not have IPSs */
+ if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 5, 0))
+ ret = DMUB_IPS_DISABLE_ALL;
+ break;
+ }
+
+ return ret;
+}
+
+static uint32_t amdgpu_dm_dmub_reg_read(void *ctx, uint32_t address)
+{
+ struct amdgpu_device *adev = ctx;
+
+ return dm_read_reg(adev->dm.dc->ctx, address);
+}
+
+static void amdgpu_dm_dmub_reg_write(void *ctx, uint32_t address,
+ uint32_t value)
+{
+ struct amdgpu_device *adev = ctx;
+
+ return dm_write_reg(adev->dm.dc->ctx, address, value);
+}
+
+int dm_dmub_sw_init(struct amdgpu_device *adev)
+{
+ struct dmub_srv_create_params create_params;
+ struct dmub_srv_fw_meta_info_params fw_meta_info_params;
+ struct dmub_srv_region_params region_params;
+ struct dmub_srv_region_info region_info;
+ struct dmub_srv_memory_params memory_params;
+ struct dmub_fw_meta_info fw_info;
+ struct dmub_srv_fb_info *fb_info;
+ struct dmub_srv *dmub_srv;
+ const struct dmcub_firmware_header_v1_0 *hdr;
+ enum dmub_asic dmub_asic;
+ enum dmub_status status;
+ static enum dmub_window_memory_type window_memory_type[DMUB_WINDOW_TOTAL] = {
+ DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_0_INST_CONST */
+ DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_1_STACK */
+ DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_2_BSS_DATA */
+ DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_3_VBIOS */
+ DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_4_MAILBOX */
+ DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_5_TRACEBUFF */
+ DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_6_FW_STATE */
+ DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_7_SCRATCH_MEM */
+ DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_IB_MEM */
+ DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_SHARED_STATE */
+ DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_LSDMA_BUFFER */
+ DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_CURSOR_OFFLOAD */
+ };
+ int r;
+
+ switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
+ case IP_VERSION(2, 1, 0):
+ dmub_asic = DMUB_ASIC_DCN21;
+ break;
+ case IP_VERSION(3, 0, 0):
+ dmub_asic = DMUB_ASIC_DCN30;
+ break;
+ case IP_VERSION(3, 0, 1):
+ dmub_asic = DMUB_ASIC_DCN301;
+ break;
+ case IP_VERSION(3, 0, 2):
+ dmub_asic = DMUB_ASIC_DCN302;
+ break;
+ case IP_VERSION(3, 0, 3):
+ dmub_asic = DMUB_ASIC_DCN303;
+ break;
+ case IP_VERSION(3, 1, 2):
+ case IP_VERSION(3, 1, 3):
+ dmub_asic = (adev->external_rev_id == YELLOW_CARP_B0) ? DMUB_ASIC_DCN31B : DMUB_ASIC_DCN31;
+ break;
+ case IP_VERSION(3, 1, 4):
+ dmub_asic = DMUB_ASIC_DCN314;
+ break;
+ case IP_VERSION(3, 1, 5):
+ dmub_asic = DMUB_ASIC_DCN315;
+ break;
+ case IP_VERSION(3, 1, 6):
+ dmub_asic = DMUB_ASIC_DCN316;
+ break;
+ case IP_VERSION(3, 2, 0):
+ dmub_asic = DMUB_ASIC_DCN32;
+ break;
+ case IP_VERSION(3, 2, 1):
+ dmub_asic = DMUB_ASIC_DCN321;
+ break;
+ case IP_VERSION(3, 5, 0):
+ case IP_VERSION(3, 5, 1):
+ dmub_asic = DMUB_ASIC_DCN35;
+ break;
+ case IP_VERSION(3, 6, 0):
+ dmub_asic = DMUB_ASIC_DCN36;
+ break;
+ case IP_VERSION(4, 0, 1):
+ dmub_asic = DMUB_ASIC_DCN401;
+ break;
+ case IP_VERSION(4, 2, 0):
+ dmub_asic = DMUB_ASIC_DCN42;
+ break;
+ case IP_VERSION(4, 2, 1):
+ dmub_asic = DMUB_ASIC_DCN42B;
+ break;
+ default:
+ /* ASIC doesn't support DMUB. */
+ return 0;
+ }
+
+ hdr = (const struct dmcub_firmware_header_v1_0 *)adev->dm.dmub_fw->data;
+ adev->dm.dmcub_fw_version = le32_to_cpu(hdr->header.ucode_version);
+
+ if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) {
+ adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].ucode_id =
+ AMDGPU_UCODE_ID_DMCUB;
+ adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].fw =
+ adev->dm.dmub_fw;
+ adev->firmware.fw_size +=
+ ALIGN(le32_to_cpu(hdr->inst_const_bytes), PAGE_SIZE);
+
+ drm_info(adev_to_drm(adev), "Loading DMUB firmware via PSP: version=0x%08X\n",
+ adev->dm.dmcub_fw_version);
+ }
+
+
+ adev->dm.dmub_srv = kzalloc(sizeof(*adev->dm.dmub_srv), GFP_KERNEL);
+ dmub_srv = adev->dm.dmub_srv;
+
+ if (!dmub_srv) {
+ drm_err(adev_to_drm(adev), "Failed to allocate DMUB service!\n");
+ return -ENOMEM;
+ }
+
+ memset(&create_params, 0, sizeof(create_params));
+ create_params.user_ctx = adev;
+ create_params.funcs.reg_read = amdgpu_dm_dmub_reg_read;
+ create_params.funcs.reg_write = amdgpu_dm_dmub_reg_write;
+ create_params.asic = dmub_asic;
+
+ /* Create the DMUB service. */
+ status = dmub_srv_create(dmub_srv, &create_params);
+ if (status != DMUB_STATUS_OK) {
+ drm_err(adev_to_drm(adev), "Error creating DMUB service: %d\n", status);
+ return -EINVAL;
+ }
+
+ /* Extract the FW meta info. */
+ memset(&fw_meta_info_params, 0, sizeof(fw_meta_info_params));
+
+ fw_meta_info_params.inst_const_size = le32_to_cpu(hdr->inst_const_bytes) -
+ PSP_HEADER_BYTES_256;
+ fw_meta_info_params.bss_data_size = le32_to_cpu(hdr->bss_data_bytes);
+ fw_meta_info_params.fw_inst_const = adev->dm.dmub_fw->data +
+ le32_to_cpu(hdr->header.ucode_array_offset_bytes) +
+ PSP_HEADER_BYTES_256;
+ fw_meta_info_params.fw_bss_data = fw_meta_info_params.bss_data_size ? adev->dm.dmub_fw->data +
+ le32_to_cpu(hdr->header.ucode_array_offset_bytes) +
+ le32_to_cpu(hdr->inst_const_bytes) : NULL;
+ fw_meta_info_params.custom_psp_footer_size = 0;
+
+ status = dmub_srv_get_fw_meta_info_from_raw_fw(&fw_meta_info_params, &fw_info);
+ if (status != DMUB_STATUS_OK) {
+ /* Skip returning early, just log the error. */
+ drm_err(adev_to_drm(adev), "Error getting DMUB FW meta info: %d\n", status);
+ }
+
+ /* Calculate the size of all the regions for the DMUB service. */
+ memset(®ion_params, 0, sizeof(region_params));
+
+ region_params.inst_const_size = fw_meta_info_params.inst_const_size;
+ region_params.bss_data_size = fw_meta_info_params.bss_data_size;
+ region_params.vbios_size = adev->bios_size;
+ region_params.fw_bss_data = fw_meta_info_params.fw_bss_data;
+ region_params.fw_inst_const = fw_meta_info_params.fw_inst_const;
+ region_params.window_memory_type = window_memory_type;
+ region_params.fw_info = (status == DMUB_STATUS_OK) ? &fw_info : NULL;
+
+ status = dmub_srv_calc_region_info(dmub_srv, ®ion_params,
+ ®ion_info);
+
+ if (status != DMUB_STATUS_OK) {
+ drm_err(adev_to_drm(adev), "Error calculating DMUB region info: %d\n", status);
+ return -EINVAL;
+ }
+
+ /*
+ * Allocate a framebuffer based on the total size of all the regions.
+ * TODO: Move this into GART.
+ */
+ r = amdgpu_bo_create_kernel(adev, region_info.fb_size, PAGE_SIZE,
+ AMDGPU_GEM_DOMAIN_VRAM |
+ AMDGPU_GEM_DOMAIN_GTT,
+ &adev->dm.dmub_bo,
+ &adev->dm.dmub_bo_gpu_addr,
+ &adev->dm.dmub_bo_cpu_addr);
+ if (r)
+ return r;
+
+ /* Rebase the regions on the framebuffer address. */
+ memset(&memory_params, 0, sizeof(memory_params));
+ memory_params.cpu_fb_addr = adev->dm.dmub_bo_cpu_addr;
+ memory_params.gpu_fb_addr = adev->dm.dmub_bo_gpu_addr;
+ memory_params.region_info = ®ion_info;
+ memory_params.window_memory_type = window_memory_type;
+
+ adev->dm.dmub_fb_info =
+ kzalloc(sizeof(*adev->dm.dmub_fb_info), GFP_KERNEL);
+ fb_info = adev->dm.dmub_fb_info;
+
+ if (!fb_info) {
+ drm_err(adev_to_drm(adev),
+ "Failed to allocate framebuffer info for DMUB service!\n");
+ return -ENOMEM;
+ }
+
+ status = dmub_srv_calc_mem_info(dmub_srv, &memory_params, fb_info);
+ if (status != DMUB_STATUS_OK) {
+ drm_err(adev_to_drm(adev), "Error calculating DMUB FB info: %d\n", status);
+ return -EINVAL;
+ }
+
+ adev->dm.bb_from_dmub = dm_dmub_get_vbios_bounding_box(adev);
+ adev->dm.fw_inst_size = fw_meta_info_params.inst_const_size;
+
+ return 0;
+}
+
+int dm_init_microcode(struct amdgpu_device *adev)
+{
+ char *fw_name_dmub;
+ int r;
+
+ switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) {
+ case IP_VERSION(2, 1, 0):
+ fw_name_dmub = FIRMWARE_RENOIR_DMUB;
+ if (ASICREV_IS_GREEN_SARDINE(adev->external_rev_id))
+ fw_name_dmub = FIRMWARE_GREEN_SARDINE_DMUB;
+ break;
+ case IP_VERSION(3, 0, 0):
+ if (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(10, 3, 0))
+ fw_name_dmub = FIRMWARE_SIENNA_CICHLID_DMUB;
+ else
+ fw_name_dmub = FIRMWARE_NAVY_FLOUNDER_DMUB;
+ break;
+ case IP_VERSION(3, 0, 1):
+ fw_name_dmub = FIRMWARE_VANGOGH_DMUB;
+ break;
+ case IP_VERSION(3, 0, 2):
+ fw_name_dmub = FIRMWARE_DIMGREY_CAVEFISH_DMUB;
+ break;
+ case IP_VERSION(3, 0, 3):
+ fw_name_dmub = FIRMWARE_BEIGE_GOBY_DMUB;
+ break;
+ case IP_VERSION(3, 1, 2):
+ case IP_VERSION(3, 1, 3):
+ fw_name_dmub = FIRMWARE_YELLOW_CARP_DMUB;
+ break;
+ case IP_VERSION(3, 1, 4):
+ fw_name_dmub = FIRMWARE_DCN_314_DMUB;
+ break;
+ case IP_VERSION(3, 1, 5):
+ fw_name_dmub = FIRMWARE_DCN_315_DMUB;
+ break;
+ case IP_VERSION(3, 1, 6):
+ fw_name_dmub = FIRMWARE_DCN316_DMUB;
+ break;
+ case IP_VERSION(3, 2, 0):
+ fw_name_dmub = FIRMWARE_DCN_V3_2_0_DMCUB;
+ break;
+ case IP_VERSION(3, 2, 1):
+ fw_name_dmub = FIRMWARE_DCN_V3_2_1_DMCUB;
+ break;
+ case IP_VERSION(3, 5, 0):
+ fw_name_dmub = FIRMWARE_DCN_35_DMUB;
+ break;
+ case IP_VERSION(3, 5, 1):
+ fw_name_dmub = FIRMWARE_DCN_351_DMUB;
+ break;
+ case IP_VERSION(3, 6, 0):
+ fw_name_dmub = FIRMWARE_DCN_36_DMUB;
+ break;
+ case IP_VERSION(4, 0, 1):
+ fw_name_dmub = FIRMWARE_DCN_401_DMUB;
+ break;
+ case IP_VERSION(4, 2, 0):
+ fw_name_dmub = FIRMWARE_DCN_42_DMUB;
+ break;
+ case IP_VERSION(4, 2, 1):
+ fw_name_dmub = FIRMWARE_DCN_42B_DMUB;
+ break;
+ default:
+ /* ASIC doesn't support DMUB. */
+ return 0;
+ }
+ r = amdgpu_ucode_request(adev, &adev->dm.dmub_fw, AMDGPU_UCODE_REQUIRED,
+ "%s", fw_name_dmub);
+ return r;
+}
+
+int amdgpu_dm_process_dmub_aux_transfer_sync(
+ struct dc_context *ctx,
+ unsigned int link_index,
+ struct aux_payload *payload,
+ enum aux_return_code_type *operation_result)
+{
+ struct amdgpu_device *adev = ctx->driver_context;
+ struct dmub_notification *p_notify = adev->dm.dmub_notify;
+ int ret = -1;
+
+ mutex_lock(&adev->dm.dpia_aux_lock);
+ if (!dc_process_dmub_aux_transfer_async(ctx->dc, link_index, payload)) {
+ *operation_result = AUX_RET_ERROR_ENGINE_ACQUIRE;
+ goto out;
+ }
+
+ if (!wait_for_completion_timeout(&adev->dm.dmub_aux_transfer_done, 10 * HZ)) {
+ drm_err(adev_to_drm(adev), "wait_for_completion_timeout timeout!");
+ *operation_result = AUX_RET_ERROR_TIMEOUT;
+ goto out;
+ }
+
+ if (p_notify->result != AUX_RET_SUCCESS) {
+ /*
+ * Transient states before tunneling is enabled could
+ * lead to this error. We can ignore this for now.
+ */
+ if (p_notify->result == AUX_RET_ERROR_PROTOCOL_ERROR) {
+ drm_warn(adev_to_drm(adev), "DPIA AUX failed on 0x%x(%d), error %d\n",
+ payload->address, payload->length,
+ p_notify->result);
+ }
+ *operation_result = p_notify->result;
+ goto out;
+ }
+
+ payload->reply[0] = adev->dm.dmub_notify->aux_reply.command & 0xF;
+ if (adev->dm.dmub_notify->aux_reply.command & 0xF0)
+ /* The reply is stored in the top nibble of the command. */
+ payload->reply[0] = (adev->dm.dmub_notify->aux_reply.command >> 4) & 0xF;
+
+ /*write req may receive a byte indicating partially written number as well*/
+ if (p_notify->aux_reply.length)
+ memcpy(payload->data, p_notify->aux_reply.data,
+ p_notify->aux_reply.length);
+
+ /* success */
+ ret = p_notify->aux_reply.length;
+ *operation_result = p_notify->result;
+out:
+ reinit_completion(&adev->dm.dmub_aux_transfer_done);
+ mutex_unlock(&adev->dm.dpia_aux_lock);
+ return ret;
+}
+
+static void abort_fused_io(
+ struct dc_context *ctx,
+ const struct dmub_cmd_fused_request *request
+)
+{
+ union dmub_rb_cmd command = { 0 };
+ struct dmub_rb_cmd_fused_io *io = &command.fused_io;
+
+ io->header.type = DMUB_CMD__FUSED_IO;
+ io->header.sub_type = DMUB_CMD__FUSED_IO_ABORT;
+ io->header.payload_bytes = sizeof(*io) - sizeof(io->header);
+ io->request = *request;
+ dm_execute_dmub_cmd(ctx, &command, DM_DMUB_WAIT_TYPE_NO_WAIT);
+}
+
+static bool execute_fused_io(
+ struct amdgpu_device *dev,
+ struct dc_context *ctx,
+ union dmub_rb_cmd *commands,
+ uint8_t count,
+ uint32_t timeout_us
+)
+{
+ const uint8_t ddc_line = commands[0].fused_io.request.u.aux.ddc_line;
+
+ if (ddc_line >= ARRAY_SIZE(dev->dm.fused_io))
+ return false;
+
+ struct fused_io_sync *sync = &dev->dm.fused_io[ddc_line];
+ struct dmub_rb_cmd_fused_io *first = &commands[0].fused_io;
+ const bool result = dm_execute_dmub_cmd_list(ctx, count, commands, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY)
+ && first->header.ret_status
+ && first->request.status == FUSED_REQUEST_STATUS_SUCCESS;
+
+ if (!result)
+ return false;
+
+ while (wait_for_completion_timeout(&sync->replied, usecs_to_jiffies(timeout_us))) {
+ reinit_completion(&sync->replied);
+
+ struct dmub_cmd_fused_request *reply = (struct dmub_cmd_fused_request *) sync->reply_data;
+
+ static_assert(sizeof(*reply) <= sizeof(sync->reply_data), "Size mismatch");
+
+ if (reply->identifier == first->request.identifier) {
+ first->request = *reply;
+ return true;
+ }
+ }
+
+ reinit_completion(&sync->replied);
+ first->request.status = FUSED_REQUEST_STATUS_TIMEOUT;
+ abort_fused_io(ctx, &first->request);
+ return false;
+}
+
+bool amdgpu_dm_execute_fused_io(
+ struct amdgpu_device *dev,
+ struct dc_link *link,
+ union dmub_rb_cmd *commands,
+ uint8_t count,
+ uint32_t timeout_us)
+{
+ struct amdgpu_display_manager *dm = &dev->dm;
+
+ mutex_lock(&dm->dpia_aux_lock);
+
+ const bool result = execute_fused_io(dev, link->ctx, commands, count, timeout_us);
+
+ mutex_unlock(&dm->dpia_aux_lock);
+ return result;
+}
+
+int amdgpu_dm_process_dmub_set_config_sync(
+ struct dc_context *ctx,
+ unsigned int link_index,
+ struct set_config_cmd_payload *payload,
+ enum set_config_status *operation_result)
+{
+ struct amdgpu_device *adev = ctx->driver_context;
+ bool is_cmd_complete;
+ int ret;
+
+ mutex_lock(&adev->dm.dpia_aux_lock);
+ is_cmd_complete = dc_process_dmub_set_config_async(ctx->dc,
+ link_index, payload, adev->dm.dmub_notify);
+
+ if (is_cmd_complete || wait_for_completion_timeout(&adev->dm.dmub_aux_transfer_done, 10 * HZ)) {
+ ret = 0;
+ *operation_result = adev->dm.dmub_notify->sc_status;
+ } else {
+ drm_err(adev_to_drm(adev), "wait_for_completion_timeout timeout!");
+ ret = -1;
+ *operation_result = SET_CONFIG_UNKNOWN_ERROR;
+ }
+
+ if (!is_cmd_complete)
+ reinit_completion(&adev->dm.dmub_aux_transfer_done);
+ mutex_unlock(&adev->dm.dpia_aux_lock);
+ return ret;
+}
+
+bool dm_execute_dmub_cmd(const struct dc_context *ctx, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type)
+{
+ struct amdgpu_device *adev = ctx->driver_context;
+
+ guard(spinlock_irqsave)(&adev->dm.dmub_lock);
+ return dc_dmub_srv_cmd_run(ctx->dmub_srv, cmd, wait_type);
+}
+
+bool dm_execute_dmub_cmd_list(const struct dc_context *ctx, unsigned int count, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type)
+{
+ struct amdgpu_device *adev = ctx->driver_context;
+
+ guard(spinlock_irqsave)(&adev->dm.dmub_lock);
+ return dc_dmub_srv_cmd_run_list(ctx->dmub_srv, count, cmd, wait_type);
+}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h
new file mode 100644
index 000000000000..a4a03e40ec37
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h
@@ -0,0 +1,68 @@
+/* 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_AMDGPU_DM_DMUB_H_
+#define AMDGPU_DM_AMDGPU_DM_DMUB_H_
+
+#include "amdgpu.h"
+
+void dm_dmub_aux_setconfig_callback(struct amdgpu_device *adev,
+ struct dmub_notification *notify);
+void dm_dmub_aux_fused_io_callback(struct amdgpu_device *adev,
+ struct dmub_notification *notify);
+bool dm_register_dmub_notify_callback(struct amdgpu_device *adev,
+ enum dmub_notification_type type,
+ dmub_notify_interrupt_callback_t callback,
+ bool dmub_int_thread_offload);
+int dm_dmub_hw_init(struct amdgpu_device *adev);
+void dm_dmub_hw_resume(struct amdgpu_device *adev);
+enum dmub_ips_disable_type dm_get_default_ips_mode(struct amdgpu_device *adev);
+int dm_dmub_sw_init(struct amdgpu_device *adev);
+int dm_init_microcode(struct amdgpu_device *adev);
+
+#define FIRMWARE_RENOIR_DMUB "amdgpu/renoir_dmcub.bin"
+#define FIRMWARE_SIENNA_CICHLID_DMUB "amdgpu/sienna_cichlid_dmcub.bin"
+#define FIRMWARE_NAVY_FLOUNDER_DMUB "amdgpu/navy_flounder_dmcub.bin"
+#define FIRMWARE_GREEN_SARDINE_DMUB "amdgpu/green_sardine_dmcub.bin"
+#define FIRMWARE_VANGOGH_DMUB "amdgpu/vangogh_dmcub.bin"
+#define FIRMWARE_DIMGREY_CAVEFISH_DMUB "amdgpu/dimgrey_cavefish_dmcub.bin"
+#define FIRMWARE_BEIGE_GOBY_DMUB "amdgpu/beige_goby_dmcub.bin"
+#define FIRMWARE_YELLOW_CARP_DMUB "amdgpu/yellow_carp_dmcub.bin"
+#define FIRMWARE_DCN_314_DMUB "amdgpu/dcn_3_1_4_dmcub.bin"
+#define FIRMWARE_DCN_315_DMUB "amdgpu/dcn_3_1_5_dmcub.bin"
+#define FIRMWARE_DCN316_DMUB "amdgpu/dcn_3_1_6_dmcub.bin"
+#define FIRMWARE_DCN_V3_2_0_DMCUB "amdgpu/dcn_3_2_0_dmcub.bin"
+#define FIRMWARE_DCN_V3_2_1_DMCUB "amdgpu/dcn_3_2_1_dmcub.bin"
+#define FIRMWARE_DCN_35_DMUB "amdgpu/dcn_3_5_dmcub.bin"
+#define FIRMWARE_DCN_351_DMUB "amdgpu/dcn_3_5_1_dmcub.bin"
+#define FIRMWARE_DCN_36_DMUB "amdgpu/dcn_3_6_dmcub.bin"
+#define FIRMWARE_DCN_401_DMUB "amdgpu/dcn_4_0_1_dmcub.bin"
+#define FIRMWARE_DCN_42_DMUB "amdgpu/dcn_4_2_dmcub.bin"
+#define FIRMWARE_DCN_42B_DMUB "amdgpu/dcn_4_2_1_dmcub.bin"
+#define FIRMWARE_RAVEN_DMCU "amdgpu/raven_dmcu.bin"
+#define FIRMWARE_NAVI12_DMCU "amdgpu/navi12_dmcu.bin"
+
+#endif /* AMDGPU_DM_AMDGPU_DM_DMUB_H_ */
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 10/32] drm/amd/display: Move HPD and IRQ handler code to amdgpu_dm_irq
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (8 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 09/32] drm/amd/display: Extract DMUB code to amdgpu_dm_dmub Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 11/32] drm/amd/display: Extract connector and encoder code to amdgpu_dm_connector Chenyu Chen
` (22 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Move HPD handling (workqueue creation, debounce, handler registration)
and IRQ handler callbacks (vblank, pflip, vupdate, vline0, outbox) from
amdgpu_dm.c into the existing amdgpu_dm_irq.c. This keeps all
IRQ-related code together rather than creating additional files.
No functional change intended.
Assisted-by: Copilot:Claude-Opus-4.6
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1540 +----------------
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 4 +
.../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 1500 +++++++++++++++-
.../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 19 +
4 files changed, 1551 insertions(+), 1512 deletions(-)
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 7090e366f85f..c247f64681dd 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -193,10 +193,6 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_state *state);
static int amdgpu_dm_atomic_check(struct drm_device *dev,
struct drm_atomic_state *state);
-static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector,
- enum dc_detect_reason reason);
-static void handle_hpd_rx_irq(void *param);
-
static bool
is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state,
struct drm_crtc_state *new_crtc_state);
@@ -291,27 +287,6 @@ static int dm_soft_reset(struct amdgpu_ip_block *ip_block)
return 0;
}
-static struct amdgpu_crtc *
-get_crtc_by_otg_inst(struct amdgpu_device *adev,
- int otg_inst)
-{
- struct drm_device *dev = adev_to_drm(adev);
- struct drm_crtc *crtc;
- struct amdgpu_crtc *amdgpu_crtc;
-
- if (WARN_ON(otg_inst == -1))
- return adev->mode_info.crtcs[0];
-
- list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
- amdgpu_crtc = to_amdgpu_crtc(crtc);
-
- if (amdgpu_crtc->otg_inst == otg_inst)
- return amdgpu_crtc;
- }
-
- return NULL;
-}
-
static inline bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state,
struct dm_crtc_state *new_state)
{
@@ -378,563 +353,6 @@ static inline bool update_planes_and_stream_adapter(struct dc *dc,
stream_update);
}
-/**
- * dm_pflip_high_irq() - Handle pageflip interrupt
- * @interrupt_params: ignored
- *
- * Handles the pageflip interrupt by notifying all interested parties
- * that the pageflip has been completed.
- */
-static void dm_pflip_high_irq(void *interrupt_params)
-{
- struct amdgpu_crtc *amdgpu_crtc;
- struct common_irq_params *irq_params = interrupt_params;
- struct amdgpu_device *adev = irq_params->adev;
- struct drm_device *dev = adev_to_drm(adev);
- unsigned long flags;
- struct drm_pending_vblank_event *e;
- u32 vpos, hpos, v_blank_start, v_blank_end;
- bool vrr_active;
-
- amdgpu_crtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_PFLIP);
-
- /* IRQ could occur when in initial stage */
- /* TODO work and BO cleanup */
- if (amdgpu_crtc == NULL) {
- drm_dbg_state(dev, "CRTC is null, returning.\n");
- return;
- }
-
- spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
-
- if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_SUBMITTED) {
- drm_dbg_state(dev,
- "amdgpu_crtc->pflip_status = %d != AMDGPU_FLIP_SUBMITTED(%d) on crtc:%d[%p]\n",
- amdgpu_crtc->pflip_status, AMDGPU_FLIP_SUBMITTED,
- amdgpu_crtc->crtc_id, amdgpu_crtc);
- spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
- return;
- }
-
- /* page flip completed. */
- e = amdgpu_crtc->event;
- amdgpu_crtc->event = NULL;
-
- WARN_ON(!e);
-
- vrr_active = amdgpu_dm_crtc_vrr_active_irq(amdgpu_crtc);
-
- /* Fixed refresh rate, or VRR scanout position outside front-porch? */
- if (!vrr_active ||
- !dc_stream_get_scanoutpos(amdgpu_crtc->dm_irq_params.stream, &v_blank_start,
- &v_blank_end, &hpos, &vpos) ||
- (vpos < v_blank_start)) {
- /* Update to correct count and vblank timestamp if racing with
- * vblank irq. This also updates to the correct vblank timestamp
- * even in VRR mode, as scanout is past the front-porch atm.
- */
- drm_crtc_accurate_vblank_count(&amdgpu_crtc->base);
-
- /* Wake up userspace by sending the pageflip event with proper
- * count and timestamp of vblank of flip completion.
- */
- if (e) {
- drm_crtc_send_vblank_event(&amdgpu_crtc->base, e);
-
- /* Event sent, so done with vblank for this flip */
- drm_crtc_vblank_put(&amdgpu_crtc->base);
- }
- } else if (e) {
- /* VRR active and inside front-porch: vblank count and
- * timestamp for pageflip event will only be up to date after
- * drm_crtc_handle_vblank() has been executed from late vblank
- * irq handler after start of back-porch (vline 0). We queue the
- * pageflip event for send-out by drm_crtc_handle_vblank() with
- * updated timestamp and count, once it runs after us.
- *
- * We need to open-code this instead of using the helper
- * drm_crtc_arm_vblank_event(), as that helper would
- * call drm_crtc_accurate_vblank_count(), which we must
- * not call in VRR mode while we are in front-porch!
- */
-
- /* sequence will be replaced by real count during send-out. */
- e->sequence = drm_crtc_vblank_count(&amdgpu_crtc->base);
- e->pipe = amdgpu_crtc->crtc_id;
-
- list_add_tail(&e->base.link, &adev_to_drm(adev)->vblank_event_list);
- e = NULL;
- }
-
- /* Keep track of vblank of this flip for flip throttling. We use the
- * cooked hw counter, as that one incremented at start of this vblank
- * of pageflip completion, so last_flip_vblank is the forbidden count
- * for queueing new pageflips if vsync + VRR is enabled.
- */
- amdgpu_crtc->dm_irq_params.last_flip_vblank =
- amdgpu_get_vblank_counter_kms(&amdgpu_crtc->base);
-
- amdgpu_crtc->pflip_status = AMDGPU_FLIP_NONE;
- spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
-
- drm_dbg_state(dev,
- "crtc:%d[%p], pflip_stat:AMDGPU_FLIP_NONE, vrr[%d]-fp %d\n",
- amdgpu_crtc->crtc_id, amdgpu_crtc, vrr_active, (int)!e);
-}
-
-static void dm_handle_vmin_vmax_update(struct work_struct *offload_work)
-{
- struct vupdate_offload_work *work = container_of(offload_work, struct vupdate_offload_work, work);
- struct amdgpu_device *adev = work->adev;
- struct dc_stream_state *stream = work->stream;
- struct dc_crtc_timing_adjust *adjust = work->adjust;
-
- mutex_lock(&adev->dm.dc_lock);
- dc_stream_adjust_vmin_vmax(adev->dm.dc, stream, adjust);
- mutex_unlock(&adev->dm.dc_lock);
-
- dc_stream_release(stream);
- kfree(work->adjust);
- kfree(work);
-}
-
-static void schedule_dc_vmin_vmax(struct amdgpu_device *adev,
- struct dc_stream_state *stream,
- struct dc_crtc_timing_adjust *adjust)
-{
- struct vupdate_offload_work *offload_work = kzalloc(sizeof(*offload_work), GFP_NOWAIT);
- if (!offload_work) {
- drm_dbg_driver(adev_to_drm(adev), "Failed to allocate vupdate_offload_work\n");
- return;
- }
-
- struct dc_crtc_timing_adjust *adjust_copy = kzalloc(sizeof(*adjust_copy), GFP_NOWAIT);
- if (!adjust_copy) {
- drm_dbg_driver(adev_to_drm(adev), "Failed to allocate adjust_copy\n");
- kfree(offload_work);
- return;
- }
-
- dc_stream_retain(stream);
- memcpy(adjust_copy, adjust, sizeof(*adjust_copy));
-
- INIT_WORK(&offload_work->work, dm_handle_vmin_vmax_update);
- offload_work->adev = adev;
- offload_work->stream = stream;
- offload_work->adjust = adjust_copy;
-
- queue_work(system_percpu_wq, &offload_work->work);
-}
-
-static void dm_vupdate_high_irq(void *interrupt_params)
-{
- struct common_irq_params *irq_params = interrupt_params;
- struct amdgpu_device *adev = irq_params->adev;
- struct amdgpu_crtc *acrtc;
- struct drm_device *drm_dev;
- struct drm_vblank_crtc *vblank;
- ktime_t frame_duration_ns, previous_timestamp;
- unsigned long flags;
- int vrr_active;
-
- acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE);
-
- if (acrtc) {
- vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc);
- drm_dev = acrtc->base.dev;
- vblank = drm_crtc_vblank_crtc(&acrtc->base);
- previous_timestamp = atomic64_read(&irq_params->previous_timestamp);
- frame_duration_ns = vblank->time - previous_timestamp;
-
- if (frame_duration_ns > 0) {
- trace_amdgpu_refresh_rate_track(acrtc->base.index,
- frame_duration_ns,
- ktime_divns(NSEC_PER_SEC, frame_duration_ns));
- atomic64_set(&irq_params->previous_timestamp, vblank->time);
- }
-
- drm_dbg_vbl(drm_dev,
- "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id,
- vrr_active);
-
- /* Core vblank handling is done here after end of front-porch in
- * vrr mode, as vblank timestamping will give valid results
- * while now done after front-porch. This will also deliver
- * page-flip completion events that have been queued to us
- * if a pageflip happened inside front-porch.
- */
- if (vrr_active && acrtc->dm_irq_params.stream) {
- bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled;
- bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled;
- bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state
- == VRR_STATE_ACTIVE_VARIABLE;
-
- amdgpu_dm_crtc_handle_vblank(acrtc);
-
- /* BTR processing for pre-DCE12 ASICs */
- if (adev->family < AMDGPU_FAMILY_AI) {
- spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
- mod_freesync_handle_v_update(
- adev->dm.freesync_module,
- acrtc->dm_irq_params.stream,
- &acrtc->dm_irq_params.vrr_params);
-
- if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) {
- schedule_dc_vmin_vmax(adev,
- acrtc->dm_irq_params.stream,
- &acrtc->dm_irq_params.vrr_params.adjust);
- }
- spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
- }
- }
- }
-}
-
-/**
- * dm_crtc_high_irq() - Handles CRTC interrupt
- * @interrupt_params: used for determining the CRTC instance
- *
- * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK
- * event handler.
- */
-static void dm_crtc_high_irq(void *interrupt_params)
-{
- struct common_irq_params *irq_params = interrupt_params;
- struct amdgpu_device *adev = irq_params->adev;
- struct drm_writeback_job *job;
- struct amdgpu_crtc *acrtc;
- unsigned long flags;
- int vrr_active;
-
- acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK);
- if (!acrtc)
- return;
-
- if (acrtc->wb_conn) {
- spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags);
-
- if (acrtc->wb_pending) {
- job = list_first_entry_or_null(&acrtc->wb_conn->job_queue,
- struct drm_writeback_job,
- list_entry);
- acrtc->wb_pending = false;
- spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags);
-
- if (job) {
- unsigned int v_total, refresh_hz;
- struct dc_stream_state *stream = acrtc->dm_irq_params.stream;
-
- v_total = stream->adjust.v_total_max ?
- stream->adjust.v_total_max : stream->timing.v_total;
- refresh_hz = div_u64((uint64_t) stream->timing.pix_clk_100hz *
- 100LL, (v_total * stream->timing.h_total));
- mdelay(1000 / refresh_hz);
-
- drm_writeback_signal_completion(acrtc->wb_conn, 0);
- dc_stream_fc_disable_writeback(adev->dm.dc,
- acrtc->dm_irq_params.stream, 0);
- }
- } else
- spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags);
- }
-
- vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc);
-
- drm_dbg_vbl(adev_to_drm(adev),
- "crtc:%d, vupdate-vrr:%d, planes:%d\n", acrtc->crtc_id,
- vrr_active, acrtc->dm_irq_params.active_planes);
-
- /**
- * Core vblank handling at start of front-porch is only possible
- * in non-vrr mode, as only there vblank timestamping will give
- * valid results while done in front-porch. Otherwise defer it
- * to dm_vupdate_high_irq after end of front-porch.
- */
- if (!vrr_active)
- amdgpu_dm_crtc_handle_vblank(acrtc);
-
- /**
- * Following stuff must happen at start of vblank, for crc
- * computation and below-the-range btr support in vrr mode.
- */
- amdgpu_dm_crtc_handle_crc_irq(&acrtc->base);
-
- /* BTR updates need to happen before VUPDATE on Vega and above. */
- if (adev->family < AMDGPU_FAMILY_AI)
- return;
-
- spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
-
- if (acrtc->dm_irq_params.stream &&
- acrtc->dm_irq_params.vrr_params.supported) {
- bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled;
- bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled;
- bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state == VRR_STATE_ACTIVE_VARIABLE;
-
- mod_freesync_handle_v_update(adev->dm.freesync_module,
- acrtc->dm_irq_params.stream,
- &acrtc->dm_irq_params.vrr_params);
-
- /* update vmin_vmax only if freesync is enabled, or only if PSR and REPLAY are disabled */
- if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) {
- schedule_dc_vmin_vmax(adev, acrtc->dm_irq_params.stream,
- &acrtc->dm_irq_params.vrr_params.adjust);
- }
- }
-
- /*
- * If there aren't any active_planes then DCH HUBP may be clock-gated.
- * In that case, pageflip completion interrupts won't fire and pageflip
- * completion events won't get delivered. Prevent this by sending
- * pending pageflip events from here if a flip is still pending.
- *
- * If any planes are enabled, use dm_pflip_high_irq() instead, to
- * avoid race conditions between flip programming and completion,
- * which could cause too early flip completion events.
- */
- if (adev->family >= AMDGPU_FAMILY_RV &&
- acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED &&
- acrtc->dm_irq_params.active_planes == 0) {
- if (acrtc->event) {
- drm_crtc_send_vblank_event(&acrtc->base, acrtc->event);
- acrtc->event = NULL;
- drm_crtc_vblank_put(&acrtc->base);
- }
- acrtc->pflip_status = AMDGPU_FLIP_NONE;
- }
-
- spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
-}
-
-#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
-/**
- * dm_dcn_vertical_interrupt0_high_irq() - Handles OTG Vertical interrupt0 for
- * DCN generation ASICs
- * @interrupt_params: interrupt parameters
- *
- * Used to set crc window/read out crc value at vertical line 0 position
- */
-static void dm_dcn_vertical_interrupt0_high_irq(void *interrupt_params)
-{
- struct common_irq_params *irq_params = interrupt_params;
- struct amdgpu_device *adev = irq_params->adev;
- struct amdgpu_crtc *acrtc;
-
- acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VLINE0);
-
- if (!acrtc)
- return;
-
- amdgpu_dm_crtc_handle_crc_window_irq(&acrtc->base);
-}
-#endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */
-
-/**
- * dmub_hpd_callback - DMUB HPD interrupt processing callback.
- * @adev: amdgpu_device pointer
- * @notify: dmub notification structure
- *
- * Dmub Hpd interrupt processing callback. Gets displayindex through the
- * ink index and calls helper to do the processing.
- */
-static void dmub_hpd_callback(struct amdgpu_device *adev,
- struct dmub_notification *notify)
-{
- struct amdgpu_dm_connector *aconnector;
- struct amdgpu_dm_connector *hpd_aconnector = NULL;
- struct drm_connector *connector;
- struct drm_connector_list_iter iter;
- struct dc_link *link;
- u8 link_index = 0;
- struct drm_device *dev;
-
- if (adev == NULL)
- return;
-
- if (notify == NULL) {
- drm_err(adev_to_drm(adev), "DMUB HPD callback notification was NULL");
- return;
- }
-
- if (notify->link_index > adev->dm.dc->link_count) {
- drm_err(adev_to_drm(adev), "DMUB HPD index (%u)is abnormal", notify->link_index);
- return;
- }
-
- /* Skip DMUB HPD IRQ in suspend/resume. We will probe them later. */
- if (notify->type == DMUB_NOTIFICATION_HPD && adev->in_suspend) {
- drm_info(adev_to_drm(adev), "Skip DMUB HPD IRQ callback in suspend/resume\n");
- return;
- }
-
- link_index = notify->link_index;
- link = adev->dm.dc->links[link_index];
- dev = adev->dm.ddev;
-
- drm_connector_list_iter_begin(dev, &iter);
- drm_for_each_connector_iter(connector, &iter) {
-
- if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
- continue;
-
- aconnector = to_amdgpu_dm_connector(connector);
- if (link && aconnector->dc_link == link) {
- if (notify->type == DMUB_NOTIFICATION_HPD)
- drm_info(adev_to_drm(adev), "DMUB HPD IRQ callback: link_index=%u\n", link_index);
- else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ)
- drm_info(adev_to_drm(adev), "DMUB HPD RX IRQ callback: link_index=%u\n", link_index);
- else
- drm_warn(adev_to_drm(adev), "DMUB Unknown HPD callback type %d, link_index=%u\n",
- notify->type, link_index);
-
- hpd_aconnector = aconnector;
- break;
- }
- }
- drm_connector_list_iter_end(&iter);
-
- if (hpd_aconnector) {
- if (notify->type == DMUB_NOTIFICATION_HPD) {
- if (hpd_aconnector->dc_link->hpd_status == (notify->hpd_status == DP_HPD_PLUG))
- drm_warn(adev_to_drm(adev), "DMUB reported hpd status unchanged. link_index=%u\n", link_index);
- handle_hpd_irq_helper(hpd_aconnector, DETECT_REASON_HPD);
- } else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) {
- handle_hpd_rx_irq(hpd_aconnector);
- }
- }
-}
-
-/**
- * dmub_hpd_sense_callback - DMUB HPD sense processing callback.
- * @adev: amdgpu_device pointer
- * @notify: dmub notification structure
- *
- * HPD sense changes can occur during low power states and need to be
- * notified from firmware to driver.
- */
-static void dmub_hpd_sense_callback(struct amdgpu_device *adev,
- struct dmub_notification *notify)
-{
- drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n");
-}
-
-static void dm_handle_hpd_work(struct work_struct *work)
-{
- struct dmub_hpd_work *dmub_hpd_wrk;
-
- dmub_hpd_wrk = container_of(work, struct dmub_hpd_work, handle_hpd_work);
-
- if (!dmub_hpd_wrk->dmub_notify) {
- drm_err(adev_to_drm(dmub_hpd_wrk->adev), "dmub_hpd_wrk dmub_notify is NULL");
- return;
- }
-
- if (dmub_hpd_wrk->dmub_notify->type < ARRAY_SIZE(dmub_hpd_wrk->adev->dm.dmub_callback)) {
- dmub_hpd_wrk->adev->dm.dmub_callback[dmub_hpd_wrk->dmub_notify->type](dmub_hpd_wrk->adev,
- dmub_hpd_wrk->dmub_notify);
- }
-
- kfree(dmub_hpd_wrk->dmub_notify);
- kfree(dmub_hpd_wrk);
-
-}
-
-static const char *dmub_notification_type_str(enum dmub_notification_type e)
-{
- switch (e) {
- case DMUB_NOTIFICATION_NO_DATA:
- return "NO_DATA";
- case DMUB_NOTIFICATION_AUX_REPLY:
- return "AUX_REPLY";
- case DMUB_NOTIFICATION_HPD:
- return "HPD";
- case DMUB_NOTIFICATION_HPD_IRQ:
- return "HPD_IRQ";
- case DMUB_NOTIFICATION_SET_CONFIG_REPLY:
- return "SET_CONFIG_REPLY";
- case DMUB_NOTIFICATION_DPIA_NOTIFICATION:
- return "DPIA_NOTIFICATION";
- case DMUB_NOTIFICATION_HPD_SENSE_NOTIFY:
- return "HPD_SENSE_NOTIFY";
- case DMUB_NOTIFICATION_FUSED_IO:
- return "FUSED_IO";
- default:
- return "<unknown>";
- }
-}
-
-#define DMUB_TRACE_MAX_READ 64
-/**
- * dm_dmub_outbox1_low_irq() - Handles Outbox interrupt
- * @interrupt_params: used for determining the Outbox instance
- *
- * Handles the Outbox Interrupt
- * event handler.
- */
-static void dm_dmub_outbox1_low_irq(void *interrupt_params)
-{
- struct dmub_notification notify = {0};
- struct common_irq_params *irq_params = interrupt_params;
- struct amdgpu_device *adev = irq_params->adev;
- struct amdgpu_display_manager *dm = &adev->dm;
- struct dmcub_trace_buf_entry entry = { 0 };
- u32 count = 0;
- struct dmub_hpd_work *dmub_hpd_wrk;
-
- do {
- if (dc_dmub_srv_get_dmub_outbox0_msg(dm->dc, &entry)) {
- trace_amdgpu_dmub_trace_high_irq(entry.trace_code, entry.tick_count,
- entry.param0, entry.param1);
-
- drm_dbg_driver(adev_to_drm(adev), "trace_code:%u, tick_count:%u, param0:%u, param1:%u\n",
- entry.trace_code, entry.tick_count, entry.param0, entry.param1);
- } else
- break;
-
- count++;
-
- } while (count <= DMUB_TRACE_MAX_READ);
-
- if (count > DMUB_TRACE_MAX_READ)
- drm_dbg_driver(adev_to_drm(adev), "Warning : count > DMUB_TRACE_MAX_READ");
-
- if (dc_enable_dmub_notifications(adev->dm.dc) &&
- irq_params->irq_src == DC_IRQ_SOURCE_DMCUB_OUTBOX) {
-
- do {
- dc_stat_get_dmub_notification(adev->dm.dc, ¬ify);
- if (notify.type >= ARRAY_SIZE(dm->dmub_thread_offload)) {
- drm_err(adev_to_drm(adev), "DM: notify type %d invalid!", notify.type);
- continue;
- }
- if (!dm->dmub_callback[notify.type]) {
- drm_warn(adev_to_drm(adev), "DMUB notification skipped due to no handler: type=%s\n",
- dmub_notification_type_str(notify.type));
- continue;
- }
- if (dm->dmub_thread_offload[notify.type] == true) {
- dmub_hpd_wrk = kzalloc(sizeof(*dmub_hpd_wrk), GFP_ATOMIC);
- if (!dmub_hpd_wrk) {
- drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk");
- return;
- }
- dmub_hpd_wrk->dmub_notify = kmemdup(¬ify, sizeof(struct dmub_notification),
- GFP_ATOMIC);
- if (!dmub_hpd_wrk->dmub_notify) {
- kfree(dmub_hpd_wrk);
- drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk->dmub_notify");
- return;
- }
- INIT_WORK(&dmub_hpd_wrk->handle_hpd_work, dm_handle_hpd_work);
- dmub_hpd_wrk->adev = adev;
- queue_work(adev->dm.delayed_hpd_wq, &dmub_hpd_wrk->handle_hpd_work);
- } else {
- dm->dmub_callback[notify.type](adev, ¬ify);
- }
- } while (notify.pending_notification);
- }
-}
-
static int dm_set_clockgating_state(struct amdgpu_ip_block *ip_block,
enum amd_clockgating_state state)
{
@@ -1067,168 +485,23 @@ static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_
}
-static void force_connector_state(
- struct amdgpu_dm_connector *aconnector,
- enum drm_connector_force force_state)
-{
- struct drm_connector *connector = &aconnector->base;
-
- mutex_lock(&connector->dev->mode_config.mutex);
- aconnector->base.force = force_state;
- mutex_unlock(&connector->dev->mode_config.mutex);
+struct amdgpu_stutter_quirk {
+ u16 chip_vendor;
+ u16 chip_device;
+ u16 subsys_vendor;
+ u16 subsys_device;
+ u8 revision;
+};
- mutex_lock(&aconnector->hpd_lock);
- drm_kms_helper_connector_hotplug_event(connector);
- mutex_unlock(&aconnector->hpd_lock);
-}
+static const struct amdgpu_stutter_quirk amdgpu_stutter_quirk_list[] = {
+ /* https://bugzilla.kernel.org/show_bug.cgi?id=214417 */
+ { 0x1002, 0x15dd, 0x1002, 0x15dd, 0xc8 },
+ { 0, 0, 0, 0, 0 },
+};
-static void dm_handle_hpd_rx_offload_work(struct work_struct *work)
+static bool dm_should_disable_stutter(struct pci_dev *pdev)
{
- struct hpd_rx_irq_offload_work *offload_work;
- struct amdgpu_dm_connector *aconnector;
- struct dc_link *dc_link;
- struct amdgpu_device *adev;
- enum dc_connection_type new_connection_type = dc_connection_none;
- unsigned long flags;
- union test_response test_response;
-
- memset(&test_response, 0, sizeof(test_response));
-
- offload_work = container_of(work, struct hpd_rx_irq_offload_work, work);
- aconnector = offload_work->offload_wq->aconnector;
- adev = offload_work->adev;
-
- if (!aconnector) {
- drm_err(adev_to_drm(adev), "Can't retrieve aconnector in hpd_rx_irq_offload_work");
- goto skip;
- }
-
- dc_link = aconnector->dc_link;
-
- mutex_lock(&aconnector->hpd_lock);
- if (!dc_link_detect_connection_type(dc_link, &new_connection_type))
- drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
- mutex_unlock(&aconnector->hpd_lock);
-
- if (new_connection_type == dc_connection_none)
- goto skip;
-
- if (amdgpu_in_reset(adev))
- goto skip;
-
- if (offload_work->data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY ||
- offload_work->data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) {
- dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, DOWN_OR_UP_MSG_RDY_EVENT);
- spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags);
- offload_work->offload_wq->is_handling_mst_msg_rdy_event = false;
- spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags);
- goto skip;
- }
-
- mutex_lock(&adev->dm.dc_lock);
- if (offload_work->data.bytes.device_service_irq.bits.AUTOMATED_TEST) {
- dc_link_dp_handle_automated_test(dc_link);
-
- if (aconnector->timing_changed) {
- /* force connector disconnect and reconnect */
- force_connector_state(aconnector, DRM_FORCE_OFF);
- msleep(100);
- force_connector_state(aconnector, DRM_FORCE_UNSPECIFIED);
- }
-
- test_response.bits.ACK = 1;
-
- core_link_write_dpcd(
- dc_link,
- DP_TEST_RESPONSE,
- &test_response.raw,
- sizeof(test_response));
- } else if ((dc_link->connector_signal != SIGNAL_TYPE_EDP) &&
- dc_link_check_link_loss_status(dc_link, &offload_work->data) &&
- dc_link_dp_allow_hpd_rx_irq(dc_link)) {
- /* offload_work->data is from handle_hpd_rx_irq->
- * schedule_hpd_rx_offload_work.this is defer handle
- * for hpd short pulse. upon here, link status may be
- * changed, need get latest link status from dpcd
- * registers. if link status is good, skip run link
- * training again.
- */
- union hpd_irq_data irq_data;
-
- memset(&irq_data, 0, sizeof(irq_data));
-
- /* before dc_link_dp_handle_link_loss, allow new link lost handle
- * request be added to work queue if link lost at end of dc_link_
- * dp_handle_link_loss
- */
- spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags);
- offload_work->offload_wq->is_handling_link_loss = false;
- spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags);
-
- if ((dc_link_dp_read_hpd_rx_irq_data(dc_link, &irq_data) == DC_OK) &&
- dc_link_check_link_loss_status(dc_link, &irq_data))
- dc_link_dp_handle_link_loss(dc_link);
- }
- mutex_unlock(&adev->dm.dc_lock);
-
-skip:
- kfree(offload_work);
-
-}
-
-static struct hpd_rx_irq_offload_work_queue *hpd_rx_irq_create_workqueue(struct amdgpu_device *adev)
-{
- struct dc *dc = adev->dm.dc;
- int max_caps = dc->caps.max_links;
- int i = 0;
- struct hpd_rx_irq_offload_work_queue *hpd_rx_offload_wq = NULL;
-
- hpd_rx_offload_wq = kcalloc(max_caps, sizeof(*hpd_rx_offload_wq), GFP_KERNEL);
-
- if (!hpd_rx_offload_wq)
- return NULL;
-
-
- for (i = 0; i < max_caps; i++) {
- hpd_rx_offload_wq[i].wq =
- create_singlethread_workqueue("amdgpu_dm_hpd_rx_offload_wq");
-
- if (hpd_rx_offload_wq[i].wq == NULL) {
- drm_err(adev_to_drm(adev), "create amdgpu_dm_hpd_rx_offload_wq fail!");
- goto out_err;
- }
-
- spin_lock_init(&hpd_rx_offload_wq[i].offload_lock);
- }
-
- return hpd_rx_offload_wq;
-
-out_err:
- for (i = 0; i < max_caps; i++) {
- if (hpd_rx_offload_wq[i].wq)
- destroy_workqueue(hpd_rx_offload_wq[i].wq);
- }
- kfree(hpd_rx_offload_wq);
- return NULL;
-}
-
-struct amdgpu_stutter_quirk {
- u16 chip_vendor;
- u16 chip_device;
- u16 subsys_vendor;
- u16 subsys_device;
- u8 revision;
-};
-
-static const struct amdgpu_stutter_quirk amdgpu_stutter_quirk_list[] = {
- /* https://bugzilla.kernel.org/show_bug.cgi?id=214417 */
- { 0x1002, 0x15dd, 0x1002, 0x15dd, 0xc8 },
- { 0, 0, 0, 0, 0 },
-};
-
-static bool dm_should_disable_stutter(struct pci_dev *pdev)
-{
- const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list;
+ const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list;
while (p && p->chip_device != 0) {
if (pdev->vendor == p->chip_vendor &&
@@ -1621,7 +894,7 @@ static int amdgpu_dm_init(struct amdgpu_device *adev)
dc_hardware_init(adev->dm.dc);
- adev->dm.hpd_rx_offload_wq = hpd_rx_irq_create_workqueue(adev);
+ adev->dm.hpd_rx_offload_wq = amdgpu_dm_hpd_rx_irq_create_workqueue(adev);
if (!adev->dm.hpd_rx_offload_wq) {
drm_err(adev_to_drm(adev), "failed to create hpd rx offload workqueue.\n");
goto error;
@@ -1708,7 +981,7 @@ static int amdgpu_dm_init(struct amdgpu_device *adev)
}
/* Enable outbox notification only after IRQ handlers are registered and DMUB is alive.
* It is expected that DMUB will resend any pending notifications at this point. Note
- * that hpd and hpd_irq handler registration are deferred to register_hpd_handlers() to
+ * that hpd and hpd_irq handler registration are deferred to amdgpu_dm_register_hpd_handlers() to
* align legacy interface initialization sequence. Connection status will be proactivly
* detected once in the amdgpu_dm_initialize_drm_device.
*/
@@ -2458,7 +1731,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev,
int i = 0;
for (i = 0; i < state->stream_count; i++) {
- acrtc = get_crtc_by_otg_inst(
+ acrtc = amdgpu_dm_get_crtc_by_otg_inst(
adev, state->stream_status[i].primary_otg_inst);
if (acrtc && state->stream_status[i].plane_count != 0) {
@@ -2535,16 +1808,6 @@ static enum dc_status amdgpu_dm_commit_zero_streams(struct dc *dc)
return dc_commit_streams(dc, ¶ms);
}
-static void hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm)
-{
- int i;
-
- if (dm->hpd_rx_offload_wq) {
- for (i = 0; i < dm->dc->caps.max_links; i++)
- flush_workqueue(dm->hpd_rx_offload_wq[i].wq);
- }
-}
-
static int dm_cache_state(struct amdgpu_device *adev)
{
int r;
@@ -2640,7 +1903,7 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block)
amdgpu_dm_irq_suspend(adev);
- hpd_rx_irq_work_suspend(dm);
+ amdgpu_dm_hpd_rx_irq_work_suspend(dm);
return 0;
}
@@ -2666,7 +1929,7 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block)
scoped_guard(mutex, &dm->dc_lock)
amdgpu_dm_ism_force_full_power(dm);
- hpd_rx_irq_work_suspend(dm);
+ amdgpu_dm_hpd_rx_irq_work_suspend(dm);
dc_set_power_state(dm->dc, DC_ACPI_CM_POWER_STATE_D3);
@@ -2697,7 +1960,7 @@ amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_state *state,
return NULL;
}
-static void emulated_link_detect(struct dc_link *link)
+void amdgpu_dm_emulated_link_detect(struct dc_link *link)
{
struct dc_sink_init_data sink_init_data = { 0 };
struct display_sink_capability sink_caps = { 0 };
@@ -2818,8 +2081,8 @@ static void dm_gpureset_commit_state(struct dc_state *dc_state,
}
}
-static void apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev,
- struct dc_sink *sink)
+void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev,
+ struct dc_sink *sink)
{
struct dc_panel_patch *ppatch = NULL;
@@ -3061,14 +2324,14 @@ static int dm_resume(struct amdgpu_ip_block *ip_block)
drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
if (aconnector->base.force && new_connection_type == dc_connection_none) {
- emulated_link_detect(aconnector->dc_link);
+ amdgpu_dm_emulated_link_detect(aconnector->dc_link);
} else {
guard(mutex)(&dm->dc_lock);
dc_exit_ips_for_hw_access(dm->dc);
ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_RESUMEFROMS3S4);
if (ret) {
/* w/a delay for certain panels */
- apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink);
+ amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink);
}
}
@@ -3389,751 +2652,6 @@ void amdgpu_dm_update_connector_after_detect(
mutex_unlock(&dev->mode_config.mutex);
}
-static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2)
-{
- if (!sink1 || !sink2)
- return false;
- if (sink1->sink_signal != sink2->sink_signal)
- return false;
-
- if (sink1->dc_edid.length != sink2->dc_edid.length)
- return false;
-
- if (memcmp(sink1->dc_edid.raw_edid, sink2->dc_edid.raw_edid,
- sink1->dc_edid.length) != 0)
- return false;
- return true;
-}
-
-
-/**
- * DOC: hdmi_hpd_debounce_work
- *
- * HDMI HPD debounce delay in milliseconds. When an HDMI display toggles HPD
- * (such as during power save transitions), this delay determines how long to
- * wait before processing the HPD event. This allows distinguishing between a
- * physical unplug (>hdmi_hpd_debounce_delay)
- * and a spontaneous RX HPD toggle (<hdmi_hpd_debounce_delay).
- *
- * If the toggle is less than this delay, the driver compares sink capabilities
- * and permits a hotplug event if they changed.
- *
- * The default value of 1500ms was chosen based on experimental testing with
- * various monitors that exhibit spontaneous HPD toggling behavior.
- */
-static void hdmi_hpd_debounce_work(struct work_struct *work)
-{
- struct amdgpu_dm_connector *aconnector =
- container_of(to_delayed_work(work), struct amdgpu_dm_connector,
- hdmi_hpd_debounce_work);
- struct drm_connector *connector = &aconnector->base;
- struct drm_device *dev = connector->dev;
- struct amdgpu_device *adev = drm_to_adev(dev);
- struct dc *dc = aconnector->dc_link->ctx->dc;
- bool fake_reconnect = false;
- bool reallow_idle = false;
- bool ret = false;
- guard(mutex)(&aconnector->hpd_lock);
-
- /* Re-detect the display */
- scoped_guard(mutex, &adev->dm.dc_lock) {
- if (dc->caps.ips_support && dc->ctx->dmub_srv->idle_allowed) {
- dc_allow_idle_optimizations(dc, false);
- reallow_idle = true;
- }
- ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD);
- }
-
- if (ret) {
- /* Apply workaround delay for certain panels */
- apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink);
- /* Compare sinks to determine if this was a spontaneous HPD toggle */
- if (are_sinks_equal(aconnector->dc_link->local_sink, aconnector->hdmi_prev_sink)) {
- /*
- * Sinks match - this was a spontaneous HDMI HPD toggle.
- */
- drm_dbg_kms(dev, "HDMI HPD: Sink unchanged after debounce, internal re-enable\n");
- fake_reconnect = true;
- }
-
- /* Update connector state */
- amdgpu_dm_update_connector_after_detect(aconnector);
-
- drm_modeset_lock_all(dev);
- dm_restore_drm_connector_state(dev, connector);
- drm_modeset_unlock_all(dev);
-
- /* Only notify OS if sink actually changed */
- if (!fake_reconnect && aconnector->base.force == DRM_FORCE_UNSPECIFIED)
- drm_kms_helper_hotplug_event(dev);
- }
-
- /* Release the cached sink reference */
- if (aconnector->hdmi_prev_sink) {
- dc_sink_release(aconnector->hdmi_prev_sink);
- aconnector->hdmi_prev_sink = NULL;
- }
-
- scoped_guard(mutex, &adev->dm.dc_lock) {
- if (reallow_idle && dc->caps.ips_support)
- dc_allow_idle_optimizations(dc, true);
- }
-}
-
-static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector,
- enum dc_detect_reason reason)
-{
- struct drm_connector *connector = &aconnector->base;
- struct drm_device *dev = connector->dev;
- enum dc_connection_type new_connection_type = dc_connection_none;
- struct amdgpu_device *adev = drm_to_adev(dev);
- struct dm_connector_state *dm_con_state = to_dm_connector_state(connector->state);
- struct dc *dc = aconnector->dc_link->ctx->dc;
- bool ret = false;
- bool debounce_required = false;
-
- if (adev->dm.disable_hpd_irq)
- return;
-
- /*
- * In case of failure or MST no need to update connector status or notify the OS
- * since (for MST case) MST does this in its own context.
- */
- guard(mutex)(&aconnector->hpd_lock);
-
- if (adev->dm.hdcp_workqueue) {
- hdcp_reset_display(adev->dm.hdcp_workqueue, aconnector->dc_link->link_index);
- dm_con_state->update_hdcp = true;
- }
- if (aconnector->fake_enable)
- aconnector->fake_enable = false;
-
- aconnector->timing_changed = false;
-
- if (!dc_link_detect_connection_type(aconnector->dc_link, &new_connection_type))
- drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
-
- /*
- * Check for HDMI disconnect with debounce enabled.
- */
- debounce_required = (aconnector->hdmi_hpd_debounce_delay_ms > 0 &&
- dc_is_hdmi_signal(aconnector->dc_link->connector_signal) &&
- new_connection_type == dc_connection_none &&
- aconnector->dc_link->local_sink != NULL);
-
- if (aconnector->base.force && new_connection_type == dc_connection_none) {
- emulated_link_detect(aconnector->dc_link);
-
- drm_modeset_lock_all(dev);
- dm_restore_drm_connector_state(dev, connector);
- drm_modeset_unlock_all(dev);
-
- if (aconnector->base.force == DRM_FORCE_UNSPECIFIED ||
- reason == DETECT_REASON_HPDRX)
- drm_kms_helper_connector_hotplug_event(connector);
- } else if (debounce_required) {
- /*
- * HDMI disconnect detected - schedule delayed work instead of
- * processing immediately. This allows us to coalesce spurious
- * HDMI signals from physical unplugs.
- */
- drm_dbg_kms(dev, "HDMI HPD: Disconnect detected, scheduling debounce work (%u ms)\n",
- aconnector->hdmi_hpd_debounce_delay_ms);
-
- /* Cache the current sink for later comparison */
- if (aconnector->hdmi_prev_sink)
- dc_sink_release(aconnector->hdmi_prev_sink);
- aconnector->hdmi_prev_sink = aconnector->dc_link->local_sink;
- if (aconnector->hdmi_prev_sink)
- dc_sink_retain(aconnector->hdmi_prev_sink);
-
- /* Schedule delayed detection. */
- if (mod_delayed_work(system_percpu_wq,
- &aconnector->hdmi_hpd_debounce_work,
- msecs_to_jiffies(aconnector->hdmi_hpd_debounce_delay_ms)))
- drm_dbg_kms(dev, "HDMI HPD: Re-scheduled debounce work\n");
-
- } else {
-
- /* If the aconnector->hdmi_hpd_debounce_work is scheduled, exit early */
- if (delayed_work_pending(&aconnector->hdmi_hpd_debounce_work))
- return;
-
- scoped_guard(mutex, &adev->dm.dc_lock) {
- dc_exit_ips_for_hw_access(dc);
- ret = dc_link_detect(aconnector->dc_link, reason);
- }
- if (ret) {
- /* w/a delay for certain panels */
- apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink);
- amdgpu_dm_update_connector_after_detect(aconnector);
-
- drm_modeset_lock_all(dev);
- dm_restore_drm_connector_state(dev, connector);
- drm_modeset_unlock_all(dev);
-
- if (aconnector->base.force == DRM_FORCE_UNSPECIFIED ||
- reason == DETECT_REASON_HPDRX)
- drm_kms_helper_connector_hotplug_event(connector);
- }
- }
-}
-
-static void handle_hpd_irq(void *param)
-{
- struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param;
-
- handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD);
-
-}
-
-static void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq,
- union hpd_irq_data hpd_irq_data)
-{
- struct hpd_rx_irq_offload_work *offload_work =
- kzalloc(sizeof(*offload_work), GFP_KERNEL);
-
- if (!offload_work) {
- drm_err(adev_to_drm(adev), "Failed to allocate hpd_rx_irq_offload_work.\n");
- return;
- }
-
- INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work);
- offload_work->data = hpd_irq_data;
- offload_work->offload_wq = offload_wq;
- offload_work->adev = adev;
-
- queue_work(offload_wq->wq, &offload_work->work);
- drm_dbg_kms(adev_to_drm(adev), "queue work to handle hpd_rx offload work");
-}
-
-static void handle_hpd_rx_irq(void *param)
-{
- struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param;
- struct drm_connector *connector = &aconnector->base;
- struct drm_device *dev = connector->dev;
- struct dc_link *dc_link = aconnector->dc_link;
- bool is_mst_root_connector = aconnector->mst_mgr.mst_state;
- bool result = false;
- struct amdgpu_device *adev = drm_to_adev(dev);
- union hpd_irq_data hpd_irq_data;
- bool link_loss = false;
- bool has_left_work = false;
- int idx = dc_link->link_index;
- struct hpd_rx_irq_offload_work_queue *offload_wq = &adev->dm.hpd_rx_offload_wq[idx];
-
- memset(&hpd_irq_data, 0, sizeof(hpd_irq_data));
-
- if (adev->dm.disable_hpd_irq)
- return;
-
- /*
- * TODO:Temporary add mutex to protect hpd interrupt not have a gpio
- * conflict, after implement i2c helper, this mutex should be
- * retired.
- */
- mutex_lock(&aconnector->hpd_lock);
-
- result = dc_link_handle_hpd_rx_irq(dc_link, &hpd_irq_data,
- &link_loss, true, &has_left_work);
-
- if (!has_left_work)
- goto out;
-
- if (hpd_irq_data.bytes.device_service_irq.bits.AUTOMATED_TEST) {
- schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data);
- goto out;
- }
-
- if (dc_link_dp_allow_hpd_rx_irq(dc_link)) {
- if (hpd_irq_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY ||
- hpd_irq_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) {
- bool skip = false;
-
- /*
- * DOWN_REP_MSG_RDY is also handled by polling method
- * mgr->cbs->poll_hpd_irq()
- */
- spin_lock(&offload_wq->offload_lock);
- skip = offload_wq->is_handling_mst_msg_rdy_event;
-
- if (!skip)
- offload_wq->is_handling_mst_msg_rdy_event = true;
-
- spin_unlock(&offload_wq->offload_lock);
-
- if (!skip)
- schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data);
-
- goto out;
- }
-
- if (link_loss) {
- bool skip = false;
-
- spin_lock(&offload_wq->offload_lock);
- skip = offload_wq->is_handling_link_loss;
-
- if (!skip)
- offload_wq->is_handling_link_loss = true;
-
- spin_unlock(&offload_wq->offload_lock);
-
- if (!skip)
- schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data);
-
- goto out;
- }
- }
-
-out:
- if (result && !is_mst_root_connector) {
- /* Downstream Port status changed. */
- handle_hpd_irq_helper(aconnector, DETECT_REASON_HPDRX);
- }
- if (hpd_irq_data.bytes.device_service_irq.bits.CP_IRQ) {
- if (adev->dm.hdcp_workqueue)
- hdcp_handle_cpirq(adev->dm.hdcp_workqueue, aconnector->base.index);
- }
-
- if (dc_link->type != dc_connection_mst_branch)
- drm_dp_cec_irq(&aconnector->dm_dp_aux.aux);
-
- mutex_unlock(&aconnector->hpd_lock);
-}
-
-static int register_hpd_handlers(struct amdgpu_device *adev)
-{
- struct drm_device *dev = adev_to_drm(adev);
- struct drm_connector *connector;
- struct amdgpu_dm_connector *aconnector;
- const struct dc_link *dc_link;
- struct dc_interrupt_params int_params = {0};
-
- int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT;
- int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
-
- if (dc_is_dmub_outbox_supported(adev->dm.dc)) {
- if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD,
- dmub_hpd_callback, true)) {
- drm_err(adev_to_drm(adev), "fail to register dmub hpd callback");
- return -EINVAL;
- }
-
- if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ,
- dmub_hpd_callback, true)) {
- drm_err(adev_to_drm(adev), "fail to register dmub hpd callback");
- return -EINVAL;
- }
-
- if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY,
- dmub_hpd_sense_callback, true)) {
- drm_err(adev_to_drm(adev), "fail to register dmub hpd sense callback");
- return -EINVAL;
- }
- }
-
- list_for_each_entry(connector,
- &dev->mode_config.connector_list, head) {
-
- if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
- continue;
-
- aconnector = to_amdgpu_dm_connector(connector);
- dc_link = aconnector->dc_link;
-
- if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) {
- int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
- int_params.irq_source = dc_link->irq_source_hpd;
-
- if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
- int_params.irq_source < DC_IRQ_SOURCE_HPD1 ||
- int_params.irq_source > DC_IRQ_SOURCE_HPD6) {
- drm_err(adev_to_drm(adev), "Failed to register hpd irq!\n");
- return -EINVAL;
- }
-
- if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
- handle_hpd_irq, (void *) aconnector))
- return -ENOMEM;
- }
-
- if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) {
-
- /* Also register for DP short pulse (hpd_rx). */
- int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
- int_params.irq_source = dc_link->irq_source_hpd_rx;
-
- if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
- int_params.irq_source < DC_IRQ_SOURCE_HPD1RX ||
- int_params.irq_source > DC_IRQ_SOURCE_HPD6RX) {
- drm_err(adev_to_drm(adev), "Failed to register hpd rx irq!\n");
- return -EINVAL;
- }
-
- if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
- handle_hpd_rx_irq, (void *) aconnector))
- return -ENOMEM;
- }
- }
- return 0;
-}
-
-/* Register IRQ sources and initialize IRQ callbacks */
-static int dce110_register_irq_handlers(struct amdgpu_device *adev)
-{
- struct dc *dc = adev->dm.dc;
- struct common_irq_params *c_irq_params;
- struct dc_interrupt_params int_params = {0};
- int r;
- int i;
- unsigned int src_id;
- unsigned int client_id = AMDGPU_IRQ_CLIENTID_LEGACY;
- /* Use different interrupts for VBLANK on DCE 6 vs. newer. */
- const unsigned int vblank_d1 =
- adev->dm.dc->ctx->dce_version >= DCE_VERSION_8_0
- ? VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0 : 1;
-
- if (adev->family >= AMDGPU_FAMILY_AI)
- client_id = SOC15_IH_CLIENTID_DCE;
-
- int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT;
- int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
-
- /*
- * Actions of amdgpu_irq_add_id():
- * 1. Register a set() function with base driver.
- * Base driver will call set() function to enable/disable an
- * interrupt in DC hardware.
- * 2. Register amdgpu_dm_irq_handler().
- * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts
- * coming from DC hardware.
- * amdgpu_dm_irq_handler() will re-direct the interrupt to DC
- * for acknowledging and handling.
- */
-
- /* Use VBLANK interrupt */
- for (i = 0; i < adev->mode_info.num_crtc; i++) {
- src_id = vblank_d1 + i;
- r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->crtc_irq);
- if (r) {
- drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n");
- return r;
- }
-
- int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
- int_params.irq_source =
- dc_interrupt_to_irq_source(dc, src_id, 0);
-
- if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
- int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 ||
- int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) {
- drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n");
- return -EINVAL;
- }
-
- c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1];
-
- c_irq_params->adev = adev;
- c_irq_params->irq_src = int_params.irq_source;
-
- if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
- dm_crtc_high_irq, c_irq_params))
- return -ENOMEM;
- }
-
- if (dc_supports_vrr(adev->dm.dc->ctx->dce_version)) {
- /* Use VUPDATE interrupt */
- for (i = 0; i < adev->mode_info.num_crtc; i++) {
- src_id = VISLANDS30_IV_SRCID_D1_V_UPDATE_INT + i * 2;
- r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->vupdate_irq);
- if (r) {
- drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n");
- return r;
- }
-
- int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
- int_params.irq_source =
- dc_interrupt_to_irq_source(dc, src_id, 0);
-
- if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
- int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 ||
- int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) {
- drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n");
- return -EINVAL;
- }
-
- c_irq_params = &adev->dm.vupdate_params[
- int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1];
- c_irq_params->adev = adev;
- c_irq_params->irq_src = int_params.irq_source;
-
- if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
- dm_vupdate_high_irq, c_irq_params))
- return -ENOMEM;
- }
- }
-
- /* Use GRPH_PFLIP interrupt */
- for (i = VISLANDS30_IV_SRCID_D1_GRPH_PFLIP;
- i <= VISLANDS30_IV_SRCID_D6_GRPH_PFLIP; i += 2) {
- r = amdgpu_irq_add_id(adev, client_id, i, &adev->pageflip_irq);
- if (r) {
- drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n");
- return r;
- }
-
- int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
- int_params.irq_source =
- dc_interrupt_to_irq_source(dc, i, 0);
-
- if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
- int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST ||
- int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) {
- drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n");
- return -EINVAL;
- }
-
- c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST];
-
- c_irq_params->adev = adev;
- c_irq_params->irq_src = int_params.irq_source;
-
- if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
- dm_pflip_high_irq, c_irq_params))
- return -ENOMEM;
- }
-
- /* HPD */
- r = amdgpu_irq_add_id(adev, client_id,
- VISLANDS30_IV_SRCID_HOTPLUG_DETECT_A, &adev->hpd_irq);
- if (r) {
- drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n");
- return r;
- }
-
- r = register_hpd_handlers(adev);
-
- return r;
-}
-
-/* Register IRQ sources and initialize IRQ callbacks */
-static int dcn10_register_irq_handlers(struct amdgpu_device *adev)
-{
- struct dc *dc = adev->dm.dc;
- struct common_irq_params *c_irq_params;
- struct dc_interrupt_params int_params = {0};
- int r;
- int i;
-#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
- static const unsigned int vrtl_int_srcid[] = {
- DCN_1_0__SRCID__OTG1_VERTICAL_INTERRUPT0_CONTROL,
- DCN_1_0__SRCID__OTG2_VERTICAL_INTERRUPT0_CONTROL,
- DCN_1_0__SRCID__OTG3_VERTICAL_INTERRUPT0_CONTROL,
- DCN_1_0__SRCID__OTG4_VERTICAL_INTERRUPT0_CONTROL,
- DCN_1_0__SRCID__OTG5_VERTICAL_INTERRUPT0_CONTROL,
- DCN_1_0__SRCID__OTG6_VERTICAL_INTERRUPT0_CONTROL
- };
-#endif
-
- int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT;
- int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
-
- /*
- * Actions of amdgpu_irq_add_id():
- * 1. Register a set() function with base driver.
- * Base driver will call set() function to enable/disable an
- * interrupt in DC hardware.
- * 2. Register amdgpu_dm_irq_handler().
- * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts
- * coming from DC hardware.
- * amdgpu_dm_irq_handler() will re-direct the interrupt to DC
- * for acknowledging and handling.
- */
-
- /* Use VSTARTUP interrupt */
- for (i = DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP;
- i <= DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP + adev->mode_info.num_crtc - 1;
- i++) {
- r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->crtc_irq);
-
- if (r) {
- drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n");
- return r;
- }
-
- int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
- int_params.irq_source =
- dc_interrupt_to_irq_source(dc, i, 0);
-
- if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
- int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 ||
- int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) {
- drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n");
- return -EINVAL;
- }
-
- c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1];
-
- c_irq_params->adev = adev;
- c_irq_params->irq_src = int_params.irq_source;
-
- if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
- dm_crtc_high_irq, c_irq_params))
- return -ENOMEM;
- }
-
- /* Use otg vertical line interrupt */
-#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
- for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) {
- r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE,
- vrtl_int_srcid[i], &adev->vline0_irq);
-
- if (r) {
- drm_err(adev_to_drm(adev), "Failed to add vline0 irq id!\n");
- return r;
- }
-
- int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
- int_params.irq_source =
- dc_interrupt_to_irq_source(dc, vrtl_int_srcid[i], 0);
-
- if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
- int_params.irq_source < DC_IRQ_SOURCE_DC1_VLINE0 ||
- int_params.irq_source > DC_IRQ_SOURCE_DC6_VLINE0) {
- drm_err(adev_to_drm(adev), "Failed to register vline0 irq!\n");
- return -EINVAL;
- }
-
- c_irq_params = &adev->dm.vline0_params[int_params.irq_source
- - DC_IRQ_SOURCE_DC1_VLINE0];
-
- c_irq_params->adev = adev;
- c_irq_params->irq_src = int_params.irq_source;
-
- if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
- dm_dcn_vertical_interrupt0_high_irq,
- c_irq_params))
- return -ENOMEM;
- }
-#endif
-
- /* Use VUPDATE_NO_LOCK interrupt on DCN, which seems to correspond to
- * the regular VUPDATE interrupt on DCE. We want DC_IRQ_SOURCE_VUPDATEx
- * to trigger at end of each vblank, regardless of state of the lock,
- * matching DCE behaviour.
- */
- for (i = DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT;
- i <= DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT + adev->mode_info.num_crtc - 1;
- i++) {
- r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->vupdate_irq);
-
- if (r) {
- drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n");
- return r;
- }
-
- int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
- int_params.irq_source =
- dc_interrupt_to_irq_source(dc, i, 0);
-
- if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
- int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 ||
- int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) {
- drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n");
- return -EINVAL;
- }
-
- c_irq_params = &adev->dm.vupdate_params[int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1];
-
- c_irq_params->adev = adev;
- c_irq_params->irq_src = int_params.irq_source;
-
- if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
- dm_vupdate_high_irq, c_irq_params))
- return -ENOMEM;
- }
-
- /* Use GRPH_PFLIP interrupt */
- for (i = DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT;
- i <= DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT + dc->caps.max_otg_num - 1;
- i++) {
- r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->pageflip_irq);
- if (r) {
- drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n");
- return r;
- }
-
- int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
- int_params.irq_source =
- dc_interrupt_to_irq_source(dc, i, 0);
-
- if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
- int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST ||
- int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) {
- drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n");
- return -EINVAL;
- }
-
- c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST];
-
- c_irq_params->adev = adev;
- c_irq_params->irq_src = int_params.irq_source;
-
- if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
- dm_pflip_high_irq, c_irq_params))
- return -ENOMEM;
- }
-
- /* HPD */
- r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DC_HPD1_INT,
- &adev->hpd_irq);
- if (r) {
- drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n");
- return r;
- }
-
- r = register_hpd_handlers(adev);
-
- return r;
-}
-/* Register Outbox IRQ sources and initialize IRQ callbacks */
-static int register_outbox_irq_handlers(struct amdgpu_device *adev)
-{
- struct dc *dc = adev->dm.dc;
- struct common_irq_params *c_irq_params;
- struct dc_interrupt_params int_params = {0};
- int r, i;
-
- int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT;
- int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
-
- r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT,
- &adev->dmub_outbox_irq);
- if (r) {
- drm_err(adev_to_drm(adev), "Failed to add outbox irq id!\n");
- return r;
- }
-
- if (dc->ctx->dmub_srv) {
- i = DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT;
- int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
- int_params.irq_source =
- dc_interrupt_to_irq_source(dc, i, 0);
-
- c_irq_params = &adev->dm.dmub_outbox_params[0];
-
- c_irq_params->adev = adev;
- c_irq_params->irq_src = int_params.irq_source;
-
- if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
- dm_dmub_outbox1_low_irq, c_irq_params))
- return -ENOMEM;
- }
-
- return 0;
-}
-
/*
* Acquires the lock for the atomic state object and returns
* the new atomic state.
@@ -4436,7 +2954,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev)
case IP_VERSION(4, 0, 1):
case IP_VERSION(4, 2, 0):
case IP_VERSION(4, 2, 1):
- if (register_outbox_irq_handlers(dm->adev)) {
+ if (amdgpu_dm_register_outbox_irq_handlers(dm->adev)) {
drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n");
goto fail;
}
@@ -4549,7 +3067,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev)
drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
if (aconnector->base.force && new_connection_type == dc_connection_none) {
- emulated_link_detect(link);
+ amdgpu_dm_emulated_link_detect(link);
amdgpu_dm_update_connector_after_detect(aconnector);
} else {
bool ret = false;
@@ -4613,7 +3131,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev)
case CHIP_VEGA10:
case CHIP_VEGA12:
case CHIP_VEGA20:
- if (dce110_register_irq_handlers(dm->adev)) {
+ if (amdgpu_dm_dce110_register_irq_handlers(dm->adev)) {
drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n");
goto fail;
}
@@ -4643,7 +3161,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev)
case IP_VERSION(4, 0, 1):
case IP_VERSION(4, 2, 0):
case IP_VERSION(4, 2, 1):
- if (dcn10_register_irq_handlers(dm->adev)) {
+ if (amdgpu_dm_dcn10_register_irq_handlers(dm->adev)) {
drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n");
goto fail;
}
@@ -7755,7 +6273,7 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
if (amdgpu_hdmi_hpd_debounce_delay_ms) {
aconnector->hdmi_hpd_debounce_delay_ms = min(amdgpu_hdmi_hpd_debounce_delay_ms,
AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS);
- INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, hdmi_hpd_debounce_work);
+ INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, amdgpu_dm_hdmi_hpd_debounce_work);
aconnector->hdmi_prev_sink = NULL;
} else {
aconnector->hdmi_hpd_debounce_delay_ms = 0;
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 01f614e6da75..c995faff4a07 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1167,4 +1167,8 @@ int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector);
void retrieve_dmi_info(struct amdgpu_display_manager *dm);
+void amdgpu_dm_emulated_link_detect(struct dc_link *link);
+void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev,
+ struct dc_sink *sink);
+
#endif /* __AMDGPU_DM_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c
index 5948e2a6219e..2433180d014f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c
@@ -26,10 +26,24 @@
#include "dm_services_types.h"
#include "dc.h"
+#include "dc/dc_dmub_srv.h"
+#include "dc/dc_stat.h"
#include "amdgpu.h"
+#include "amdgpu_display.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_irq.h"
+#include "amdgpu_dm_crtc.h"
+#include "amdgpu_dm_hdcp.h"
+#include "amdgpu_dm_mst_types.h"
+#include "amdgpu_dm_dmub.h"
+#include "amdgpu_dm_trace.h"
+#include "link/protocols/link_dpcd.h"
+#include "link_service_types.h"
+#include "ivsrcid/ivsrcid_vislands30.h"
+#include "ivsrcid/dcn/irqsrcs_dcn_1_0.h"
+#include "modules/inc/mod_freesync.h"
+#include <drm/drm_vblank.h>
/**
* DOC: overview
@@ -55,7 +69,8 @@
* are all set to the DM generic handler amdgpu_dm_irq_handler(), which looks up
* DM's IRQ tables. However, in order for base driver to recognize this hook, DM
* still needs to register the IRQ with the base driver. See
- * dce110_register_irq_handlers() and dcn10_register_irq_handlers().
+ * amdgpu_dm_dce110_register_irq_handlers() and
+ * amdgpu_dm_dcn10_register_irq_handlers().
*
* To expose DC's hardware interrupt toggle to the base driver, DM implements
* &amdgpu_irq_src_funcs.set hooks. Base driver calls it through
@@ -1020,3 +1035,1486 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev)
if (dev->mode_config.poll_enabled)
drm_kms_helper_poll_fini(dev);
}
+
+/* ========== HPD handling ========== */
+static void force_connector_state(
+ struct amdgpu_dm_connector *aconnector,
+ enum drm_connector_force force_state)
+{
+ struct drm_connector *connector = &aconnector->base;
+
+ mutex_lock(&connector->dev->mode_config.mutex);
+ aconnector->base.force = force_state;
+ mutex_unlock(&connector->dev->mode_config.mutex);
+
+ mutex_lock(&aconnector->hpd_lock);
+ drm_kms_helper_connector_hotplug_event(connector);
+ mutex_unlock(&aconnector->hpd_lock);
+}
+
+static void dm_handle_hpd_rx_offload_work(struct work_struct *work)
+{
+ struct hpd_rx_irq_offload_work *offload_work;
+ struct amdgpu_dm_connector *aconnector;
+ struct dc_link *dc_link;
+ struct amdgpu_device *adev;
+ enum dc_connection_type new_connection_type = dc_connection_none;
+ unsigned long flags;
+ union test_response test_response;
+
+ memset(&test_response, 0, sizeof(test_response));
+
+ offload_work = container_of(work, struct hpd_rx_irq_offload_work, work);
+ aconnector = offload_work->offload_wq->aconnector;
+ adev = offload_work->adev;
+
+ if (!aconnector) {
+ drm_err(adev_to_drm(adev), "Can't retrieve aconnector in hpd_rx_irq_offload_work");
+ goto skip;
+ }
+
+ dc_link = aconnector->dc_link;
+
+ mutex_lock(&aconnector->hpd_lock);
+ if (!dc_link_detect_connection_type(dc_link, &new_connection_type))
+ drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
+ mutex_unlock(&aconnector->hpd_lock);
+
+ if (new_connection_type == dc_connection_none)
+ goto skip;
+
+ if (amdgpu_in_reset(adev))
+ goto skip;
+
+ if (offload_work->data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY ||
+ offload_work->data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) {
+ dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, DOWN_OR_UP_MSG_RDY_EVENT);
+ spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags);
+ offload_work->offload_wq->is_handling_mst_msg_rdy_event = false;
+ spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags);
+ goto skip;
+ }
+
+ mutex_lock(&adev->dm.dc_lock);
+ if (offload_work->data.bytes.device_service_irq.bits.AUTOMATED_TEST) {
+ dc_link_dp_handle_automated_test(dc_link);
+
+ if (aconnector->timing_changed) {
+ /* force connector disconnect and reconnect */
+ force_connector_state(aconnector, DRM_FORCE_OFF);
+ msleep(100);
+ force_connector_state(aconnector, DRM_FORCE_UNSPECIFIED);
+ }
+
+ test_response.bits.ACK = 1;
+
+ core_link_write_dpcd(
+ dc_link,
+ DP_TEST_RESPONSE,
+ &test_response.raw,
+ sizeof(test_response));
+ } else if ((dc_link->connector_signal != SIGNAL_TYPE_EDP) &&
+ dc_link_check_link_loss_status(dc_link, &offload_work->data) &&
+ dc_link_dp_allow_hpd_rx_irq(dc_link)) {
+ /* offload_work->data is from handle_hpd_rx_irq->
+ * schedule_hpd_rx_offload_work.this is defer handle
+ * for hpd short pulse. upon here, link status may be
+ * changed, need get latest link status from dpcd
+ * registers. if link status is good, skip run link
+ * training again.
+ */
+ union hpd_irq_data irq_data;
+
+ memset(&irq_data, 0, sizeof(irq_data));
+
+ /* before dc_link_dp_handle_link_loss, allow new link lost handle
+ * request be added to work queue if link lost at end of dc_link_
+ * dp_handle_link_loss
+ */
+ spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags);
+ offload_work->offload_wq->is_handling_link_loss = false;
+ spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags);
+
+ if ((dc_link_dp_read_hpd_rx_irq_data(dc_link, &irq_data) == DC_OK) &&
+ dc_link_check_link_loss_status(dc_link, &irq_data))
+ dc_link_dp_handle_link_loss(dc_link);
+ }
+ mutex_unlock(&adev->dm.dc_lock);
+
+skip:
+ kfree(offload_work);
+
+}
+
+struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(struct amdgpu_device *adev)
+{
+ struct dc *dc = adev->dm.dc;
+ int max_caps = dc->caps.max_links;
+ int i = 0;
+ struct hpd_rx_irq_offload_work_queue *hpd_rx_offload_wq = NULL;
+
+ hpd_rx_offload_wq = kcalloc(max_caps, sizeof(*hpd_rx_offload_wq), GFP_KERNEL);
+
+ if (!hpd_rx_offload_wq)
+ return NULL;
+
+
+ for (i = 0; i < max_caps; i++) {
+ hpd_rx_offload_wq[i].wq =
+ create_singlethread_workqueue("amdgpu_dm_hpd_rx_offload_wq");
+
+ if (hpd_rx_offload_wq[i].wq == NULL) {
+ drm_err(adev_to_drm(adev), "create amdgpu_dm_hpd_rx_offload_wq fail!");
+ goto out_err;
+ }
+
+ spin_lock_init(&hpd_rx_offload_wq[i].offload_lock);
+ }
+
+ return hpd_rx_offload_wq;
+
+out_err:
+ for (i = 0; i < max_caps; i++) {
+ if (hpd_rx_offload_wq[i].wq)
+ destroy_workqueue(hpd_rx_offload_wq[i].wq);
+ }
+ kfree(hpd_rx_offload_wq);
+ return NULL;
+}
+
+void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm)
+{
+ int i;
+
+ if (dm->hpd_rx_offload_wq) {
+ for (i = 0; i < dm->dc->caps.max_links; i++)
+ flush_workqueue(dm->hpd_rx_offload_wq[i].wq);
+ }
+}
+
+static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2)
+{
+ if (!sink1 || !sink2)
+ return false;
+ if (sink1->sink_signal != sink2->sink_signal)
+ return false;
+
+ if (sink1->dc_edid.length != sink2->dc_edid.length)
+ return false;
+
+ if (memcmp(sink1->dc_edid.raw_edid, sink2->dc_edid.raw_edid,
+ sink1->dc_edid.length) != 0)
+ return false;
+ return true;
+}
+
+
+/**
+ * DOC: amdgpu_dm_hdmi_hpd_debounce_work
+ *
+ * HDMI HPD debounce delay in milliseconds. When an HDMI display toggles HPD
+ * (such as during power save transitions), this delay determines how long to
+ * wait before processing the HPD event. This allows distinguishing between a
+ * physical unplug (>hdmi_hpd_debounce_delay)
+ * and a spontaneous RX HPD toggle (<hdmi_hpd_debounce_delay).
+ *
+ * If the toggle is less than this delay, the driver compares sink capabilities
+ * and permits a hotplug event if they changed.
+ *
+ * The default value of 1500ms was chosen based on experimental testing with
+ * various monitors that exhibit spontaneous HPD toggling behavior.
+ */
+void amdgpu_dm_hdmi_hpd_debounce_work(struct work_struct *work)
+{
+ struct amdgpu_dm_connector *aconnector =
+ container_of(to_delayed_work(work), struct amdgpu_dm_connector,
+ hdmi_hpd_debounce_work);
+ struct drm_connector *connector = &aconnector->base;
+ struct drm_device *dev = connector->dev;
+ struct amdgpu_device *adev = drm_to_adev(dev);
+ struct dc *dc = aconnector->dc_link->ctx->dc;
+ bool fake_reconnect = false;
+ bool reallow_idle = false;
+ bool ret = false;
+
+ guard(mutex)(&aconnector->hpd_lock);
+
+ /* Re-detect the display */
+ scoped_guard(mutex, &adev->dm.dc_lock) {
+ if (dc->caps.ips_support && dc->ctx->dmub_srv->idle_allowed) {
+ dc_allow_idle_optimizations(dc, false);
+ reallow_idle = true;
+ }
+ ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD);
+ }
+
+ if (ret) {
+ /* Apply workaround delay for certain panels */
+ amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink);
+ /* Compare sinks to determine if this was a spontaneous HPD toggle */
+ if (are_sinks_equal(aconnector->dc_link->local_sink, aconnector->hdmi_prev_sink)) {
+ /*
+ * Sinks match - this was a spontaneous HDMI HPD toggle.
+ */
+ drm_dbg_kms(dev, "HDMI HPD: Sink unchanged after debounce, internal re-enable\n");
+ fake_reconnect = true;
+ }
+
+ /* Update connector state */
+ amdgpu_dm_update_connector_after_detect(aconnector);
+
+ drm_modeset_lock_all(dev);
+ dm_restore_drm_connector_state(dev, connector);
+ drm_modeset_unlock_all(dev);
+
+ /* Only notify OS if sink actually changed */
+ if (!fake_reconnect && aconnector->base.force == DRM_FORCE_UNSPECIFIED)
+ drm_kms_helper_hotplug_event(dev);
+ }
+
+ /* Release the cached sink reference */
+ if (aconnector->hdmi_prev_sink) {
+ dc_sink_release(aconnector->hdmi_prev_sink);
+ aconnector->hdmi_prev_sink = NULL;
+ }
+
+ scoped_guard(mutex, &adev->dm.dc_lock) {
+ if (reallow_idle && dc->caps.ips_support)
+ dc_allow_idle_optimizations(dc, true);
+ }
+}
+
+static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector,
+ enum dc_detect_reason reason)
+{
+ struct drm_connector *connector = &aconnector->base;
+ struct drm_device *dev = connector->dev;
+ enum dc_connection_type new_connection_type = dc_connection_none;
+ struct amdgpu_device *adev = drm_to_adev(dev);
+ struct dm_connector_state *dm_con_state = to_dm_connector_state(connector->state);
+ struct dc *dc = aconnector->dc_link->ctx->dc;
+ bool ret = false;
+ bool debounce_required = false;
+
+ if (adev->dm.disable_hpd_irq)
+ return;
+
+ /*
+ * In case of failure or MST no need to update connector status or notify the OS
+ * since (for MST case) MST does this in its own context.
+ */
+ guard(mutex)(&aconnector->hpd_lock);
+
+ if (adev->dm.hdcp_workqueue) {
+ hdcp_reset_display(adev->dm.hdcp_workqueue, aconnector->dc_link->link_index);
+ dm_con_state->update_hdcp = true;
+ }
+ if (aconnector->fake_enable)
+ aconnector->fake_enable = false;
+
+ aconnector->timing_changed = false;
+
+ if (!dc_link_detect_connection_type(aconnector->dc_link, &new_connection_type))
+ drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
+
+ /*
+ * Check for HDMI disconnect with debounce enabled.
+ */
+ debounce_required = (aconnector->hdmi_hpd_debounce_delay_ms > 0 &&
+ dc_is_hdmi_signal(aconnector->dc_link->connector_signal) &&
+ new_connection_type == dc_connection_none &&
+ aconnector->dc_link->local_sink != NULL);
+
+ if (aconnector->base.force && new_connection_type == dc_connection_none) {
+ amdgpu_dm_emulated_link_detect(aconnector->dc_link);
+
+ drm_modeset_lock_all(dev);
+ dm_restore_drm_connector_state(dev, connector);
+ drm_modeset_unlock_all(dev);
+
+ if (aconnector->base.force == DRM_FORCE_UNSPECIFIED ||
+ reason == DETECT_REASON_HPDRX)
+ drm_kms_helper_connector_hotplug_event(connector);
+ } else if (debounce_required) {
+ /*
+ * HDMI disconnect detected - schedule delayed work instead of
+ * processing immediately. This allows us to coalesce spurious
+ * HDMI signals from physical unplugs.
+ */
+ drm_dbg_kms(dev, "HDMI HPD: Disconnect detected, scheduling debounce work (%u ms)\n",
+ aconnector->hdmi_hpd_debounce_delay_ms);
+
+ /* Cache the current sink for later comparison */
+ if (aconnector->hdmi_prev_sink)
+ dc_sink_release(aconnector->hdmi_prev_sink);
+ aconnector->hdmi_prev_sink = aconnector->dc_link->local_sink;
+ if (aconnector->hdmi_prev_sink)
+ dc_sink_retain(aconnector->hdmi_prev_sink);
+
+ /* Schedule delayed detection. */
+ if (mod_delayed_work(system_percpu_wq,
+ &aconnector->hdmi_hpd_debounce_work,
+ msecs_to_jiffies(aconnector->hdmi_hpd_debounce_delay_ms)))
+ drm_dbg_kms(dev, "HDMI HPD: Re-scheduled debounce work\n");
+
+ } else {
+
+ /* If the aconnector->hdmi_hpd_debounce_work is scheduled, exit early */
+ if (delayed_work_pending(&aconnector->hdmi_hpd_debounce_work))
+ return;
+
+ scoped_guard(mutex, &adev->dm.dc_lock) {
+ dc_exit_ips_for_hw_access(dc);
+ ret = dc_link_detect(aconnector->dc_link, reason);
+ }
+ if (ret) {
+ /* w/a delay for certain panels */
+ amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink);
+ amdgpu_dm_update_connector_after_detect(aconnector);
+
+ drm_modeset_lock_all(dev);
+ dm_restore_drm_connector_state(dev, connector);
+ drm_modeset_unlock_all(dev);
+
+ if (aconnector->base.force == DRM_FORCE_UNSPECIFIED ||
+ reason == DETECT_REASON_HPDRX)
+ drm_kms_helper_connector_hotplug_event(connector);
+ }
+ }
+}
+
+static void handle_hpd_irq(void *param)
+{
+ struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param;
+
+ handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD);
+
+}
+
+static void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq,
+ union hpd_irq_data hpd_irq_data)
+{
+ struct hpd_rx_irq_offload_work *offload_work =
+ kzalloc(sizeof(*offload_work), GFP_KERNEL);
+
+ if (!offload_work) {
+ drm_err(adev_to_drm(adev), "Failed to allocate hpd_rx_irq_offload_work.\n");
+ return;
+ }
+
+ INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work);
+ offload_work->data = hpd_irq_data;
+ offload_work->offload_wq = offload_wq;
+ offload_work->adev = adev;
+
+ queue_work(offload_wq->wq, &offload_work->work);
+ drm_dbg_kms(adev_to_drm(adev), "queue work to handle hpd_rx offload work");
+}
+
+static void handle_hpd_rx_irq(void *param)
+{
+ struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param;
+ struct drm_connector *connector = &aconnector->base;
+ struct drm_device *dev = connector->dev;
+ struct dc_link *dc_link = aconnector->dc_link;
+ bool is_mst_root_connector = aconnector->mst_mgr.mst_state;
+ bool result = false;
+ struct amdgpu_device *adev = drm_to_adev(dev);
+ union hpd_irq_data hpd_irq_data;
+ bool link_loss = false;
+ bool has_left_work = false;
+ int idx = dc_link->link_index;
+ struct hpd_rx_irq_offload_work_queue *offload_wq = &adev->dm.hpd_rx_offload_wq[idx];
+
+ memset(&hpd_irq_data, 0, sizeof(hpd_irq_data));
+
+ if (adev->dm.disable_hpd_irq)
+ return;
+
+ /*
+ * TODO:Temporary add mutex to protect hpd interrupt not have a gpio
+ * conflict, after implement i2c helper, this mutex should be
+ * retired.
+ */
+ mutex_lock(&aconnector->hpd_lock);
+
+ result = dc_link_handle_hpd_rx_irq(dc_link, &hpd_irq_data,
+ &link_loss, true, &has_left_work);
+
+ if (!has_left_work)
+ goto out;
+
+ if (hpd_irq_data.bytes.device_service_irq.bits.AUTOMATED_TEST) {
+ schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data);
+ goto out;
+ }
+
+ if (dc_link_dp_allow_hpd_rx_irq(dc_link)) {
+ if (hpd_irq_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY ||
+ hpd_irq_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) {
+ bool skip = false;
+
+ /*
+ * DOWN_REP_MSG_RDY is also handled by polling method
+ * mgr->cbs->poll_hpd_irq()
+ */
+ spin_lock(&offload_wq->offload_lock);
+ skip = offload_wq->is_handling_mst_msg_rdy_event;
+
+ if (!skip)
+ offload_wq->is_handling_mst_msg_rdy_event = true;
+
+ spin_unlock(&offload_wq->offload_lock);
+
+ if (!skip)
+ schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data);
+
+ goto out;
+ }
+
+ if (link_loss) {
+ bool skip = false;
+
+ spin_lock(&offload_wq->offload_lock);
+ skip = offload_wq->is_handling_link_loss;
+
+ if (!skip)
+ offload_wq->is_handling_link_loss = true;
+
+ spin_unlock(&offload_wq->offload_lock);
+
+ if (!skip)
+ schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data);
+
+ goto out;
+ }
+ }
+
+out:
+ if (result && !is_mst_root_connector) {
+ /* Downstream Port status changed. */
+ handle_hpd_irq_helper(aconnector, DETECT_REASON_HPDRX);
+ }
+ if (hpd_irq_data.bytes.device_service_irq.bits.CP_IRQ) {
+ if (adev->dm.hdcp_workqueue)
+ hdcp_handle_cpirq(adev->dm.hdcp_workqueue, aconnector->base.index);
+ }
+
+ if (dc_link->type != dc_connection_mst_branch)
+ drm_dp_cec_irq(&aconnector->dm_dp_aux.aux);
+
+ mutex_unlock(&aconnector->hpd_lock);
+}
+
+/**
+ * dmub_hpd_callback - DMUB HPD interrupt processing callback.
+ * @adev: amdgpu_device pointer
+ * @notify: dmub notification structure
+ *
+ * Dmub Hpd interrupt processing callback. Gets displayindex through the
+ * ink index and calls helper to do the processing.
+ */
+static void dmub_hpd_callback(struct amdgpu_device *adev,
+ struct dmub_notification *notify)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct amdgpu_dm_connector *hpd_aconnector = NULL;
+ struct drm_connector *connector;
+ struct drm_connector_list_iter iter;
+ struct dc_link *link;
+ u8 link_index = 0;
+ struct drm_device *dev;
+
+ if (adev == NULL)
+ return;
+
+ if (notify == NULL) {
+ drm_err(adev_to_drm(adev), "DMUB HPD callback notification was NULL");
+ return;
+ }
+
+ if (notify->link_index > adev->dm.dc->link_count) {
+ drm_err(adev_to_drm(adev), "DMUB HPD index (%u)is abnormal", notify->link_index);
+ return;
+ }
+
+ /* Skip DMUB HPD IRQ in suspend/resume. We will probe them later. */
+ if (notify->type == DMUB_NOTIFICATION_HPD && adev->in_suspend) {
+ drm_info(adev_to_drm(adev), "Skip DMUB HPD IRQ callback in suspend/resume\n");
+ return;
+ }
+
+ link_index = notify->link_index;
+ link = adev->dm.dc->links[link_index];
+ dev = adev->dm.ddev;
+
+ drm_connector_list_iter_begin(dev, &iter);
+ drm_for_each_connector_iter(connector, &iter) {
+
+ if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ continue;
+
+ aconnector = to_amdgpu_dm_connector(connector);
+ if (link && aconnector->dc_link == link) {
+ if (notify->type == DMUB_NOTIFICATION_HPD)
+ drm_info(adev_to_drm(adev), "DMUB HPD IRQ callback: link_index=%u\n", link_index);
+ else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ)
+ drm_info(adev_to_drm(adev), "DMUB HPD RX IRQ callback: link_index=%u\n", link_index);
+ else
+ drm_warn(adev_to_drm(adev), "DMUB Unknown HPD callback type %d, link_index=%u\n",
+ notify->type, link_index);
+
+ hpd_aconnector = aconnector;
+ break;
+ }
+ }
+ drm_connector_list_iter_end(&iter);
+
+ if (hpd_aconnector) {
+ if (notify->type == DMUB_NOTIFICATION_HPD) {
+ if (hpd_aconnector->dc_link->hpd_status == (notify->hpd_status == DP_HPD_PLUG))
+ drm_warn(adev_to_drm(adev), "DMUB reported hpd status unchanged. link_index=%u\n", link_index);
+ handle_hpd_irq_helper(hpd_aconnector, DETECT_REASON_HPD);
+ } else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) {
+ handle_hpd_rx_irq(hpd_aconnector);
+ }
+ }
+}
+
+/**
+ * dmub_hpd_sense_callback - DMUB HPD sense processing callback.
+ * @adev: amdgpu_device pointer
+ * @notify: dmub notification structure
+ *
+ * HPD sense changes can occur during low power states and need to be
+ * notified from firmware to driver.
+ */
+static void dmub_hpd_sense_callback(struct amdgpu_device *adev,
+ struct dmub_notification *notify)
+{
+ drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n");
+}
+
+int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev)
+{
+ struct drm_device *dev = adev_to_drm(adev);
+ struct drm_connector *connector;
+ struct amdgpu_dm_connector *aconnector;
+ const struct dc_link *dc_link;
+ struct dc_interrupt_params int_params = {0};
+
+ int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT;
+ int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
+
+ if (dc_is_dmub_outbox_supported(adev->dm.dc)) {
+ if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD,
+ dmub_hpd_callback, true)) {
+ drm_err(adev_to_drm(adev), "fail to register dmub hpd callback");
+ return -EINVAL;
+ }
+
+ if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ,
+ dmub_hpd_callback, true)) {
+ drm_err(adev_to_drm(adev), "fail to register dmub hpd callback");
+ return -EINVAL;
+ }
+
+ if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY,
+ dmub_hpd_sense_callback, true)) {
+ drm_err(adev_to_drm(adev), "fail to register dmub hpd sense callback");
+ return -EINVAL;
+ }
+ }
+
+ list_for_each_entry(connector,
+ &dev->mode_config.connector_list, head) {
+
+ if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ continue;
+
+ aconnector = to_amdgpu_dm_connector(connector);
+ dc_link = aconnector->dc_link;
+
+ if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) {
+ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
+ int_params.irq_source = dc_link->irq_source_hpd;
+
+ if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
+ int_params.irq_source < DC_IRQ_SOURCE_HPD1 ||
+ int_params.irq_source > DC_IRQ_SOURCE_HPD6) {
+ drm_err(adev_to_drm(adev), "Failed to register hpd irq!\n");
+ return -EINVAL;
+ }
+
+ if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ handle_hpd_irq, (void *) aconnector))
+ return -ENOMEM;
+ }
+
+ if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) {
+
+ /* Also register for DP short pulse (hpd_rx). */
+ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
+ int_params.irq_source = dc_link->irq_source_hpd_rx;
+
+ if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
+ int_params.irq_source < DC_IRQ_SOURCE_HPD1RX ||
+ int_params.irq_source > DC_IRQ_SOURCE_HPD6RX) {
+ drm_err(adev_to_drm(adev), "Failed to register hpd rx irq!\n");
+ return -EINVAL;
+ }
+
+ if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ handle_hpd_rx_irq, (void *) aconnector))
+ return -ENOMEM;
+ }
+ }
+ return 0;
+}
+
+/* ========== IRQ handlers ========== */
+struct amdgpu_crtc *
+amdgpu_dm_get_crtc_by_otg_inst(struct amdgpu_device *adev,
+ int otg_inst)
+{
+ struct drm_device *dev = adev_to_drm(adev);
+ struct drm_crtc *crtc;
+ struct amdgpu_crtc *amdgpu_crtc;
+
+ if (WARN_ON(otg_inst == -1))
+ return adev->mode_info.crtcs[0];
+
+ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+ amdgpu_crtc = to_amdgpu_crtc(crtc);
+
+ if (amdgpu_crtc->otg_inst == otg_inst)
+ return amdgpu_crtc;
+ }
+
+ return NULL;
+}
+
+/**
+ * dm_pflip_high_irq() - Handle pageflip interrupt
+ * @interrupt_params: ignored
+ *
+ * Handles the pageflip interrupt by notifying all interested parties
+ * that the pageflip has been completed.
+ */
+static void dm_pflip_high_irq(void *interrupt_params)
+{
+ struct amdgpu_crtc *amdgpu_crtc;
+ struct common_irq_params *irq_params = interrupt_params;
+ struct amdgpu_device *adev = irq_params->adev;
+ struct drm_device *dev = adev_to_drm(adev);
+ unsigned long flags;
+ struct drm_pending_vblank_event *e;
+ u32 vpos, hpos, v_blank_start, v_blank_end;
+ bool vrr_active;
+
+ amdgpu_crtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_PFLIP);
+
+ /* IRQ could occur when in initial stage */
+ /* TODO work and BO cleanup */
+ if (amdgpu_crtc == NULL) {
+ drm_dbg_state(dev, "CRTC is null, returning.\n");
+ return;
+ }
+
+ spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
+
+ if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_SUBMITTED) {
+ drm_dbg_state(dev,
+ "amdgpu_crtc->pflip_status = %d != AMDGPU_FLIP_SUBMITTED(%d) on crtc:%d[%p]\n",
+ amdgpu_crtc->pflip_status, AMDGPU_FLIP_SUBMITTED,
+ amdgpu_crtc->crtc_id, amdgpu_crtc);
+ spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
+ return;
+ }
+
+ /* page flip completed. */
+ e = amdgpu_crtc->event;
+ amdgpu_crtc->event = NULL;
+
+ WARN_ON(!e);
+
+ vrr_active = amdgpu_dm_crtc_vrr_active_irq(amdgpu_crtc);
+
+ /* Fixed refresh rate, or VRR scanout position outside front-porch? */
+ if (!vrr_active ||
+ !dc_stream_get_scanoutpos(amdgpu_crtc->dm_irq_params.stream, &v_blank_start,
+ &v_blank_end, &hpos, &vpos) ||
+ (vpos < v_blank_start)) {
+ /* Update to correct count and vblank timestamp if racing with
+ * vblank irq. This also updates to the correct vblank timestamp
+ * even in VRR mode, as scanout is past the front-porch atm.
+ */
+ drm_crtc_accurate_vblank_count(&amdgpu_crtc->base);
+
+ /* Wake up userspace by sending the pageflip event with proper
+ * count and timestamp of vblank of flip completion.
+ */
+ if (e) {
+ drm_crtc_send_vblank_event(&amdgpu_crtc->base, e);
+
+ /* Event sent, so done with vblank for this flip */
+ drm_crtc_vblank_put(&amdgpu_crtc->base);
+ }
+ } else if (e) {
+ /* VRR active and inside front-porch: vblank count and
+ * timestamp for pageflip event will only be up to date after
+ * drm_crtc_handle_vblank() has been executed from late vblank
+ * irq handler after start of back-porch (vline 0). We queue the
+ * pageflip event for send-out by drm_crtc_handle_vblank() with
+ * updated timestamp and count, once it runs after us.
+ *
+ * We need to open-code this instead of using the helper
+ * drm_crtc_arm_vblank_event(), as that helper would
+ * call drm_crtc_accurate_vblank_count(), which we must
+ * not call in VRR mode while we are in front-porch!
+ */
+
+ /* sequence will be replaced by real count during send-out. */
+ e->sequence = drm_crtc_vblank_count(&amdgpu_crtc->base);
+ e->pipe = amdgpu_crtc->crtc_id;
+
+ list_add_tail(&e->base.link, &adev_to_drm(adev)->vblank_event_list);
+ e = NULL;
+ }
+
+ /* Keep track of vblank of this flip for flip throttling. We use the
+ * cooked hw counter, as that one incremented at start of this vblank
+ * of pageflip completion, so last_flip_vblank is the forbidden count
+ * for queueing new pageflips if vsync + VRR is enabled.
+ */
+ amdgpu_crtc->dm_irq_params.last_flip_vblank =
+ amdgpu_get_vblank_counter_kms(&amdgpu_crtc->base);
+
+ amdgpu_crtc->pflip_status = AMDGPU_FLIP_NONE;
+ spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
+
+ drm_dbg_state(dev,
+ "crtc:%d[%p], pflip_stat:AMDGPU_FLIP_NONE, vrr[%d]-fp %d\n",
+ amdgpu_crtc->crtc_id, amdgpu_crtc, vrr_active, (int)!e);
+}
+
+static void dm_handle_vmin_vmax_update(struct work_struct *offload_work)
+{
+ struct vupdate_offload_work *work = container_of(offload_work, struct vupdate_offload_work, work);
+ struct amdgpu_device *adev = work->adev;
+ struct dc_stream_state *stream = work->stream;
+ struct dc_crtc_timing_adjust *adjust = work->adjust;
+
+ mutex_lock(&adev->dm.dc_lock);
+ dc_stream_adjust_vmin_vmax(adev->dm.dc, stream, adjust);
+ mutex_unlock(&adev->dm.dc_lock);
+
+ dc_stream_release(stream);
+ kfree(work->adjust);
+ kfree(work);
+}
+
+static void schedule_dc_vmin_vmax(struct amdgpu_device *adev,
+ struct dc_stream_state *stream,
+ struct dc_crtc_timing_adjust *adjust)
+{
+ struct vupdate_offload_work *offload_work = kzalloc(sizeof(*offload_work), GFP_NOWAIT);
+ struct dc_crtc_timing_adjust *adjust_copy = kzalloc(sizeof(*adjust_copy), GFP_NOWAIT);
+
+ if (!offload_work) {
+ drm_dbg_driver(adev_to_drm(adev), "Failed to allocate vupdate_offload_work\n");
+ return;
+ }
+
+ if (!adjust_copy) {
+ drm_dbg_driver(adev_to_drm(adev), "Failed to allocate adjust_copy\n");
+ kfree(offload_work);
+ return;
+ }
+
+ dc_stream_retain(stream);
+ memcpy(adjust_copy, adjust, sizeof(*adjust_copy));
+
+ INIT_WORK(&offload_work->work, dm_handle_vmin_vmax_update);
+ offload_work->adev = adev;
+ offload_work->stream = stream;
+ offload_work->adjust = adjust_copy;
+
+ queue_work(system_percpu_wq, &offload_work->work);
+}
+
+static void dm_vupdate_high_irq(void *interrupt_params)
+{
+ struct common_irq_params *irq_params = interrupt_params;
+ struct amdgpu_device *adev = irq_params->adev;
+ struct amdgpu_crtc *acrtc;
+ struct drm_device *drm_dev;
+ struct drm_vblank_crtc *vblank;
+ ktime_t frame_duration_ns, previous_timestamp;
+ unsigned long flags;
+ int vrr_active;
+
+ acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE);
+
+ if (acrtc) {
+ vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc);
+ drm_dev = acrtc->base.dev;
+ vblank = drm_crtc_vblank_crtc(&acrtc->base);
+ previous_timestamp = atomic64_read(&irq_params->previous_timestamp);
+ frame_duration_ns = vblank->time - previous_timestamp;
+
+ if (frame_duration_ns > 0) {
+ trace_amdgpu_refresh_rate_track(acrtc->base.index,
+ frame_duration_ns,
+ ktime_divns(NSEC_PER_SEC, frame_duration_ns));
+ atomic64_set(&irq_params->previous_timestamp, vblank->time);
+ }
+
+ drm_dbg_vbl(drm_dev,
+ "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id,
+ vrr_active);
+
+ /* Core vblank handling is done here after end of front-porch in
+ * vrr mode, as vblank timestamping will give valid results
+ * while now done after front-porch. This will also deliver
+ * page-flip completion events that have been queued to us
+ * if a pageflip happened inside front-porch.
+ */
+ if (vrr_active && acrtc->dm_irq_params.stream) {
+ bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled;
+ bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled;
+ bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state
+ == VRR_STATE_ACTIVE_VARIABLE;
+
+ amdgpu_dm_crtc_handle_vblank(acrtc);
+
+ /* BTR processing for pre-DCE12 ASICs */
+ if (adev->family < AMDGPU_FAMILY_AI) {
+ spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
+ mod_freesync_handle_v_update(
+ adev->dm.freesync_module,
+ acrtc->dm_irq_params.stream,
+ &acrtc->dm_irq_params.vrr_params);
+
+ if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) {
+ schedule_dc_vmin_vmax(adev,
+ acrtc->dm_irq_params.stream,
+ &acrtc->dm_irq_params.vrr_params.adjust);
+ }
+ spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
+ }
+ }
+ }
+}
+
+/**
+ * dm_crtc_high_irq() - Handles CRTC interrupt
+ * @interrupt_params: used for determining the CRTC instance
+ *
+ * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK
+ * event handler.
+ */
+static void dm_crtc_high_irq(void *interrupt_params)
+{
+ struct common_irq_params *irq_params = interrupt_params;
+ struct amdgpu_device *adev = irq_params->adev;
+ struct drm_writeback_job *job;
+ struct amdgpu_crtc *acrtc;
+ unsigned long flags;
+ int vrr_active;
+
+ acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK);
+ if (!acrtc)
+ return;
+
+ if (acrtc->wb_conn) {
+ spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags);
+
+ if (acrtc->wb_pending) {
+ job = list_first_entry_or_null(&acrtc->wb_conn->job_queue,
+ struct drm_writeback_job,
+ list_entry);
+ acrtc->wb_pending = false;
+ spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags);
+
+ if (job) {
+ unsigned int v_total, refresh_hz;
+ struct dc_stream_state *stream = acrtc->dm_irq_params.stream;
+
+ v_total = stream->adjust.v_total_max ?
+ stream->adjust.v_total_max : stream->timing.v_total;
+ refresh_hz = div_u64((uint64_t) stream->timing.pix_clk_100hz *
+ 100LL, (v_total * stream->timing.h_total));
+ mdelay(1000 / refresh_hz);
+
+ drm_writeback_signal_completion(acrtc->wb_conn, 0);
+ dc_stream_fc_disable_writeback(adev->dm.dc,
+ acrtc->dm_irq_params.stream, 0);
+ }
+ } else
+ spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags);
+ }
+
+ vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc);
+
+ drm_dbg_vbl(adev_to_drm(adev),
+ "crtc:%d, vupdate-vrr:%d, planes:%d\n", acrtc->crtc_id,
+ vrr_active, acrtc->dm_irq_params.active_planes);
+
+ /**
+ * Core vblank handling at start of front-porch is only possible
+ * in non-vrr mode, as only there vblank timestamping will give
+ * valid results while done in front-porch. Otherwise defer it
+ * to dm_vupdate_high_irq after end of front-porch.
+ */
+ if (!vrr_active)
+ amdgpu_dm_crtc_handle_vblank(acrtc);
+
+ /**
+ * Following stuff must happen at start of vblank, for crc
+ * computation and below-the-range btr support in vrr mode.
+ */
+ amdgpu_dm_crtc_handle_crc_irq(&acrtc->base);
+
+ /* BTR updates need to happen before VUPDATE on Vega and above. */
+ if (adev->family < AMDGPU_FAMILY_AI)
+ return;
+
+ spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags);
+
+ if (acrtc->dm_irq_params.stream &&
+ acrtc->dm_irq_params.vrr_params.supported) {
+ bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled;
+ bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled;
+ bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state == VRR_STATE_ACTIVE_VARIABLE;
+
+ mod_freesync_handle_v_update(adev->dm.freesync_module,
+ acrtc->dm_irq_params.stream,
+ &acrtc->dm_irq_params.vrr_params);
+
+ /* update vmin_vmax only if freesync is enabled, or only if PSR and REPLAY are disabled */
+ if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) {
+ schedule_dc_vmin_vmax(adev, acrtc->dm_irq_params.stream,
+ &acrtc->dm_irq_params.vrr_params.adjust);
+ }
+ }
+
+ /*
+ * If there aren't any active_planes then DCH HUBP may be clock-gated.
+ * In that case, pageflip completion interrupts won't fire and pageflip
+ * completion events won't get delivered. Prevent this by sending
+ * pending pageflip events from here if a flip is still pending.
+ *
+ * If any planes are enabled, use dm_pflip_high_irq() instead, to
+ * avoid race conditions between flip programming and completion,
+ * which could cause too early flip completion events.
+ */
+ if (adev->family >= AMDGPU_FAMILY_RV &&
+ acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED &&
+ acrtc->dm_irq_params.active_planes == 0) {
+ if (acrtc->event) {
+ drm_crtc_send_vblank_event(&acrtc->base, acrtc->event);
+ acrtc->event = NULL;
+ drm_crtc_vblank_put(&acrtc->base);
+ }
+ acrtc->pflip_status = AMDGPU_FLIP_NONE;
+ }
+
+ spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags);
+}
+
+#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
+/**
+ * dm_dcn_vertical_interrupt0_high_irq() - Handles OTG Vertical interrupt0 for
+ * DCN generation ASICs
+ * @interrupt_params: interrupt parameters
+ *
+ * Used to set crc window/read out crc value at vertical line 0 position
+ */
+static void dm_dcn_vertical_interrupt0_high_irq(void *interrupt_params)
+{
+ struct common_irq_params *irq_params = interrupt_params;
+ struct amdgpu_device *adev = irq_params->adev;
+ struct amdgpu_crtc *acrtc;
+
+ acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VLINE0);
+
+ if (!acrtc)
+ return;
+
+ amdgpu_dm_crtc_handle_crc_window_irq(&acrtc->base);
+}
+#endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */
+
+static void dm_handle_hpd_work(struct work_struct *work)
+{
+ struct dmub_hpd_work *dmub_hpd_wrk;
+
+ dmub_hpd_wrk = container_of(work, struct dmub_hpd_work, handle_hpd_work);
+
+ if (!dmub_hpd_wrk->dmub_notify) {
+ drm_err(adev_to_drm(dmub_hpd_wrk->adev), "dmub_hpd_wrk dmub_notify is NULL");
+ return;
+ }
+
+ if (dmub_hpd_wrk->dmub_notify->type < ARRAY_SIZE(dmub_hpd_wrk->adev->dm.dmub_callback)) {
+ dmub_hpd_wrk->adev->dm.dmub_callback[dmub_hpd_wrk->dmub_notify->type](dmub_hpd_wrk->adev,
+ dmub_hpd_wrk->dmub_notify);
+ }
+
+ kfree(dmub_hpd_wrk->dmub_notify);
+ kfree(dmub_hpd_wrk);
+
+}
+
+static const char *dmub_notification_type_str(enum dmub_notification_type e)
+{
+ switch (e) {
+ case DMUB_NOTIFICATION_NO_DATA:
+ return "NO_DATA";
+ case DMUB_NOTIFICATION_AUX_REPLY:
+ return "AUX_REPLY";
+ case DMUB_NOTIFICATION_HPD:
+ return "HPD";
+ case DMUB_NOTIFICATION_HPD_IRQ:
+ return "HPD_IRQ";
+ case DMUB_NOTIFICATION_SET_CONFIG_REPLY:
+ return "SET_CONFIG_REPLY";
+ case DMUB_NOTIFICATION_DPIA_NOTIFICATION:
+ return "DPIA_NOTIFICATION";
+ case DMUB_NOTIFICATION_HPD_SENSE_NOTIFY:
+ return "HPD_SENSE_NOTIFY";
+ case DMUB_NOTIFICATION_FUSED_IO:
+ return "FUSED_IO";
+ default:
+ return "<unknown>";
+ }
+}
+
+#define DMUB_TRACE_MAX_READ 64
+/**
+ * dm_dmub_outbox1_low_irq() - Handles Outbox interrupt
+ * @interrupt_params: used for determining the Outbox instance
+ *
+ * Handles the Outbox Interrupt
+ * event handler.
+ */
+static void dm_dmub_outbox1_low_irq(void *interrupt_params)
+{
+ struct dmub_notification notify = {0};
+ struct common_irq_params *irq_params = interrupt_params;
+ struct amdgpu_device *adev = irq_params->adev;
+ struct amdgpu_display_manager *dm = &adev->dm;
+ struct dmcub_trace_buf_entry entry = { 0 };
+ u32 count = 0;
+ struct dmub_hpd_work *dmub_hpd_wrk;
+
+ do {
+ if (dc_dmub_srv_get_dmub_outbox0_msg(dm->dc, &entry)) {
+ trace_amdgpu_dmub_trace_high_irq(entry.trace_code, entry.tick_count,
+ entry.param0, entry.param1);
+
+ drm_dbg_driver(adev_to_drm(adev), "trace_code:%u, tick_count:%u, param0:%u, param1:%u\n",
+ entry.trace_code, entry.tick_count, entry.param0, entry.param1);
+ } else
+ break;
+
+ count++;
+
+ } while (count <= DMUB_TRACE_MAX_READ);
+
+ if (count > DMUB_TRACE_MAX_READ)
+ drm_dbg_driver(adev_to_drm(adev), "Warning : count > DMUB_TRACE_MAX_READ");
+
+ if (dc_enable_dmub_notifications(adev->dm.dc) &&
+ irq_params->irq_src == DC_IRQ_SOURCE_DMCUB_OUTBOX) {
+
+ do {
+ dc_stat_get_dmub_notification(adev->dm.dc, ¬ify);
+ if (notify.type >= ARRAY_SIZE(dm->dmub_thread_offload)) {
+ drm_err(adev_to_drm(adev), "DM: notify type %d invalid!", notify.type);
+ continue;
+ }
+ if (!dm->dmub_callback[notify.type]) {
+ drm_warn(adev_to_drm(adev), "DMUB notification skipped due to no handler: type=%s\n",
+ dmub_notification_type_str(notify.type));
+ continue;
+ }
+ if (dm->dmub_thread_offload[notify.type] == true) {
+ dmub_hpd_wrk = kzalloc(sizeof(*dmub_hpd_wrk), GFP_ATOMIC);
+ if (!dmub_hpd_wrk) {
+ drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk");
+ return;
+ }
+ dmub_hpd_wrk->dmub_notify = kmemdup(¬ify, sizeof(struct dmub_notification),
+ GFP_ATOMIC);
+ if (!dmub_hpd_wrk->dmub_notify) {
+ kfree(dmub_hpd_wrk);
+ drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk->dmub_notify");
+ return;
+ }
+ INIT_WORK(&dmub_hpd_wrk->handle_hpd_work, dm_handle_hpd_work);
+ dmub_hpd_wrk->adev = adev;
+ queue_work(adev->dm.delayed_hpd_wq, &dmub_hpd_wrk->handle_hpd_work);
+ } else {
+ dm->dmub_callback[notify.type](adev, ¬ify);
+ }
+ } while (notify.pending_notification);
+ }
+}
+
+/* Register IRQ sources and initialize IRQ callbacks */
+int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev)
+{
+ struct dc *dc = adev->dm.dc;
+ struct common_irq_params *c_irq_params;
+ struct dc_interrupt_params int_params = {0};
+ int r;
+ int i;
+ unsigned int src_id;
+ unsigned int client_id = AMDGPU_IRQ_CLIENTID_LEGACY;
+ /* Use different interrupts for VBLANK on DCE 6 vs. newer. */
+ const unsigned int vblank_d1 =
+ adev->dm.dc->ctx->dce_version >= DCE_VERSION_8_0
+ ? VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0 : 1;
+
+ if (adev->family >= AMDGPU_FAMILY_AI)
+ client_id = SOC15_IH_CLIENTID_DCE;
+
+ int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT;
+ int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
+
+ /*
+ * Actions of amdgpu_irq_add_id():
+ * 1. Register a set() function with base driver.
+ * Base driver will call set() function to enable/disable an
+ * interrupt in DC hardware.
+ * 2. Register amdgpu_dm_irq_handler().
+ * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts
+ * coming from DC hardware.
+ * amdgpu_dm_irq_handler() will re-direct the interrupt to DC
+ * for acknowledging and handling.
+ */
+
+ /* Use VBLANK interrupt */
+ for (i = 0; i < adev->mode_info.num_crtc; i++) {
+ src_id = vblank_d1 + i;
+ r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->crtc_irq);
+ if (r) {
+ drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n");
+ return r;
+ }
+
+ int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+ int_params.irq_source =
+ dc_interrupt_to_irq_source(dc, src_id, 0);
+
+ if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
+ int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 ||
+ int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) {
+ drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n");
+ return -EINVAL;
+ }
+
+ c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1];
+
+ c_irq_params->adev = adev;
+ c_irq_params->irq_src = int_params.irq_source;
+
+ if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_crtc_high_irq, c_irq_params))
+ return -ENOMEM;
+ }
+
+ if (dc_supports_vrr(adev->dm.dc->ctx->dce_version)) {
+ /* Use VUPDATE interrupt */
+ for (i = 0; i < adev->mode_info.num_crtc; i++) {
+ src_id = VISLANDS30_IV_SRCID_D1_V_UPDATE_INT + i * 2;
+ r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->vupdate_irq);
+ if (r) {
+ drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n");
+ return r;
+ }
+
+ int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+ int_params.irq_source =
+ dc_interrupt_to_irq_source(dc, src_id, 0);
+
+ if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
+ int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 ||
+ int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) {
+ drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n");
+ return -EINVAL;
+ }
+
+ c_irq_params = &adev->dm.vupdate_params[
+ int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1];
+ c_irq_params->adev = adev;
+ c_irq_params->irq_src = int_params.irq_source;
+
+ if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_vupdate_high_irq, c_irq_params))
+ return -ENOMEM;
+ }
+ }
+
+ /* Use GRPH_PFLIP interrupt */
+ for (i = VISLANDS30_IV_SRCID_D1_GRPH_PFLIP;
+ i <= VISLANDS30_IV_SRCID_D6_GRPH_PFLIP; i += 2) {
+ r = amdgpu_irq_add_id(adev, client_id, i, &adev->pageflip_irq);
+ if (r) {
+ drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n");
+ return r;
+ }
+
+ int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+ int_params.irq_source =
+ dc_interrupt_to_irq_source(dc, i, 0);
+
+ if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
+ int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST ||
+ int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) {
+ drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n");
+ return -EINVAL;
+ }
+
+ c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST];
+
+ c_irq_params->adev = adev;
+ c_irq_params->irq_src = int_params.irq_source;
+
+ if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_pflip_high_irq, c_irq_params))
+ return -ENOMEM;
+ }
+
+ /* HPD */
+ r = amdgpu_irq_add_id(adev, client_id,
+ VISLANDS30_IV_SRCID_HOTPLUG_DETECT_A, &adev->hpd_irq);
+ if (r) {
+ drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n");
+ return r;
+ }
+
+ r = amdgpu_dm_register_hpd_handlers(adev);
+
+ return r;
+}
+
+/* Register IRQ sources and initialize IRQ callbacks */
+int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev)
+{
+ struct dc *dc = adev->dm.dc;
+ struct common_irq_params *c_irq_params;
+ struct dc_interrupt_params int_params = {0};
+ int r;
+ int i;
+#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
+ static const unsigned int vrtl_int_srcid[] = {
+ DCN_1_0__SRCID__OTG1_VERTICAL_INTERRUPT0_CONTROL,
+ DCN_1_0__SRCID__OTG2_VERTICAL_INTERRUPT0_CONTROL,
+ DCN_1_0__SRCID__OTG3_VERTICAL_INTERRUPT0_CONTROL,
+ DCN_1_0__SRCID__OTG4_VERTICAL_INTERRUPT0_CONTROL,
+ DCN_1_0__SRCID__OTG5_VERTICAL_INTERRUPT0_CONTROL,
+ DCN_1_0__SRCID__OTG6_VERTICAL_INTERRUPT0_CONTROL
+ };
+#endif
+
+ int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT;
+ int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
+
+ /*
+ * Actions of amdgpu_irq_add_id():
+ * 1. Register a set() function with base driver.
+ * Base driver will call set() function to enable/disable an
+ * interrupt in DC hardware.
+ * 2. Register amdgpu_dm_irq_handler().
+ * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts
+ * coming from DC hardware.
+ * amdgpu_dm_irq_handler() will re-direct the interrupt to DC
+ * for acknowledging and handling.
+ */
+
+ /* Use VSTARTUP interrupt */
+ for (i = DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP;
+ i <= DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP + adev->mode_info.num_crtc - 1;
+ i++) {
+ r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->crtc_irq);
+
+ if (r) {
+ drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n");
+ return r;
+ }
+
+ int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+ int_params.irq_source =
+ dc_interrupt_to_irq_source(dc, i, 0);
+
+ if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
+ int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 ||
+ int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) {
+ drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n");
+ return -EINVAL;
+ }
+
+ c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1];
+
+ c_irq_params->adev = adev;
+ c_irq_params->irq_src = int_params.irq_source;
+
+ if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_crtc_high_irq, c_irq_params))
+ return -ENOMEM;
+ }
+
+ /* Use otg vertical line interrupt */
+#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
+ for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) {
+ r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE,
+ vrtl_int_srcid[i], &adev->vline0_irq);
+
+ if (r) {
+ drm_err(adev_to_drm(adev), "Failed to add vline0 irq id!\n");
+ return r;
+ }
+
+ int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+ int_params.irq_source =
+ dc_interrupt_to_irq_source(dc, vrtl_int_srcid[i], 0);
+
+ if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
+ int_params.irq_source < DC_IRQ_SOURCE_DC1_VLINE0 ||
+ int_params.irq_source > DC_IRQ_SOURCE_DC6_VLINE0) {
+ drm_err(adev_to_drm(adev), "Failed to register vline0 irq!\n");
+ return -EINVAL;
+ }
+
+ c_irq_params = &adev->dm.vline0_params[int_params.irq_source
+ - DC_IRQ_SOURCE_DC1_VLINE0];
+
+ c_irq_params->adev = adev;
+ c_irq_params->irq_src = int_params.irq_source;
+
+ if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_dcn_vertical_interrupt0_high_irq,
+ c_irq_params))
+ return -ENOMEM;
+ }
+#endif
+
+ /* Use VUPDATE_NO_LOCK interrupt on DCN, which seems to correspond to
+ * the regular VUPDATE interrupt on DCE. We want DC_IRQ_SOURCE_VUPDATEx
+ * to trigger at end of each vblank, regardless of state of the lock,
+ * matching DCE behaviour.
+ */
+ for (i = DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT;
+ i <= DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT + adev->mode_info.num_crtc - 1;
+ i++) {
+ r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->vupdate_irq);
+
+ if (r) {
+ drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n");
+ return r;
+ }
+
+ int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+ int_params.irq_source =
+ dc_interrupt_to_irq_source(dc, i, 0);
+
+ if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
+ int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 ||
+ int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) {
+ drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n");
+ return -EINVAL;
+ }
+
+ c_irq_params = &adev->dm.vupdate_params[int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1];
+
+ c_irq_params->adev = adev;
+ c_irq_params->irq_src = int_params.irq_source;
+
+ if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_vupdate_high_irq, c_irq_params))
+ return -ENOMEM;
+ }
+
+ /* Use GRPH_PFLIP interrupt */
+ for (i = DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT;
+ i <= DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT + dc->caps.max_otg_num - 1;
+ i++) {
+ r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->pageflip_irq);
+ if (r) {
+ drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n");
+ return r;
+ }
+
+ int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+ int_params.irq_source =
+ dc_interrupt_to_irq_source(dc, i, 0);
+
+ if (int_params.irq_source == DC_IRQ_SOURCE_INVALID ||
+ int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST ||
+ int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) {
+ drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n");
+ return -EINVAL;
+ }
+
+ c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST];
+
+ c_irq_params->adev = adev;
+ c_irq_params->irq_src = int_params.irq_source;
+
+ if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_pflip_high_irq, c_irq_params))
+ return -ENOMEM;
+ }
+
+ /* HPD */
+ r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DC_HPD1_INT,
+ &adev->hpd_irq);
+ if (r) {
+ drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n");
+ return r;
+ }
+
+ r = amdgpu_dm_register_hpd_handlers(adev);
+
+ return r;
+}
+
+/* Register Outbox IRQ sources and initialize IRQ callbacks */
+int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev)
+{
+ struct dc *dc = adev->dm.dc;
+ struct common_irq_params *c_irq_params;
+ struct dc_interrupt_params int_params = {0};
+ int r, i;
+
+ int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT;
+ int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT;
+
+ r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT,
+ &adev->dmub_outbox_irq);
+ if (r) {
+ drm_err(adev_to_drm(adev), "Failed to add outbox irq id!\n");
+ return r;
+ }
+
+ if (dc->ctx->dmub_srv) {
+ i = DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT;
+ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
+ int_params.irq_source =
+ dc_interrupt_to_irq_source(dc, i, 0);
+
+ c_irq_params = &adev->dm.dmub_outbox_params[0];
+
+ c_irq_params->adev = adev;
+ c_irq_params->irq_src = int_params.irq_source;
+
+ if (!amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_dmub_outbox1_low_irq, c_irq_params))
+ return -ENOMEM;
+ }
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h
index 4f6b58f4f90d..ba6968f5626f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h
@@ -27,6 +27,12 @@
#include "irq_types.h" /* DAL irq definitions */
+struct amdgpu_device;
+struct amdgpu_crtc;
+struct amdgpu_display_manager;
+struct hpd_rx_irq_offload_work_queue;
+struct work_struct;
+
/*
* Display Manager IRQ-related interfaces (for use by DAL).
*/
@@ -101,4 +107,17 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev);
void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev);
void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev);
+/* HPD handling */
+struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(struct amdgpu_device *adev);
+void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm);
+int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev);
+void amdgpu_dm_hdmi_hpd_debounce_work(struct work_struct *work);
+
+/* IRQ handlers */
+struct amdgpu_crtc *amdgpu_dm_get_crtc_by_otg_inst(struct amdgpu_device *adev,
+ int otg_inst);
+int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev);
+int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev);
+int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev);
+
#endif /* __AMDGPU_DM_IRQ_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 11/32] drm/amd/display: Extract connector and encoder code to amdgpu_dm_connector
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (9 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 10/32] drm/amd/display: Move HPD and IRQ handler code to amdgpu_dm_irq Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 12/32] drm/amd/display: Fix conflicting types for dc_plane_cm functions Chenyu Chen
` (21 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Move connector lifecycle functions (init, detect, mode validation,
property handling, EDID parsing, hotplug processing) and encoder
functions (init, destroy, atomic_check, helper_funcs) from amdgpu_dm.c
to amdgpu_dm_connector.c.
No functional change intended.
Assisted-by: Copilot:Claude-Opus-4.6
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +-
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 3738 +----------------
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 50 +-
.../display/amdgpu_dm/amdgpu_dm_connector.c | 3557 ++++++++++++++++
.../display/amdgpu_dm/amdgpu_dm_connector.h | 147 +
.../amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 2 +-
.../display/amdgpu_dm/amdgpu_dm_mst_types.c | 2 +-
7 files changed, 3828 insertions(+), 3671 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
index a6408da05583..d83878e35b61 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile
@@ -44,7 +44,8 @@ AMDGPUDM = \
amdgpu_dm_ism.o \
amdgpu_dm_backlight.o \
amdgpu_dm_audio.o \
- amdgpu_dm_dmub.o
+ amdgpu_dm_dmub.o \
+ amdgpu_dm_connector.o
ifdef CONFIG_DRM_AMD_DC_FP
AMDGPUDM += dc_fpu.o
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 c247f64681dd..ebca180847c9 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -69,6 +69,7 @@
#include "amdgpu_dm_backlight.h"
#include "amdgpu_dm_audio.h"
#include "amdgpu_dm_dmub.h"
+#include "amdgpu_dm_connector.h"
#include "ivsrcid/ivsrcid_vislands30.h"
@@ -125,46 +126,7 @@ MODULE_FIRMWARE(FIRMWARE_NAVI12_DMCU);
/* basic init/fini API */
static int amdgpu_dm_init(struct amdgpu_device *adev);
static void amdgpu_dm_fini(struct amdgpu_device *adev);
-static bool is_freesync_video_mode(const struct drm_display_mode *mode, struct amdgpu_dm_connector *aconnector);
static void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state);
-static struct amdgpu_i2c_adapter *
-create_i2c(struct ddc_service *ddc_service, bool oem);
-
-static enum drm_mode_subconnector get_subconnector_type(struct dc_link *link)
-{
- switch (link->dpcd_caps.dongle_type) {
- case DISPLAY_DONGLE_NONE:
- return DRM_MODE_SUBCONNECTOR_Native;
- case DISPLAY_DONGLE_DP_VGA_CONVERTER:
- return DRM_MODE_SUBCONNECTOR_VGA;
- case DISPLAY_DONGLE_DP_DVI_CONVERTER:
- case DISPLAY_DONGLE_DP_DVI_DONGLE:
- return DRM_MODE_SUBCONNECTOR_DVID;
- case DISPLAY_DONGLE_DP_HDMI_CONVERTER:
- case DISPLAY_DONGLE_DP_HDMI_DONGLE:
- return DRM_MODE_SUBCONNECTOR_HDMIA;
- case DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE:
- default:
- return DRM_MODE_SUBCONNECTOR_Unknown;
- }
-}
-
-static void update_subconnector_property(struct amdgpu_dm_connector *aconnector)
-{
- struct dc_link *link = aconnector->dc_link;
- struct drm_connector *connector = &aconnector->base;
- enum drm_mode_subconnector subconnector = DRM_MODE_SUBCONNECTOR_Unknown;
-
- if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort)
- return;
-
- if (aconnector->dc_sink)
- subconnector = get_subconnector_type(link);
-
- drm_object_property_set_value(&connector->base,
- connector->dev->mode_config.dp_subconnector_property,
- subconnector);
-}
/*
* initializes drm_device display related structures, based on the information
@@ -177,18 +139,9 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev);
/* removes and deallocates the drm structures, created by the above function */
static void amdgpu_dm_destroy_drm_device(struct amdgpu_display_manager *dm);
-static int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm,
- struct amdgpu_dm_connector *amdgpu_dm_connector,
- u32 link_index,
- struct amdgpu_encoder *amdgpu_encoder);
-static int amdgpu_dm_encoder_init(struct drm_device *dev,
- struct amdgpu_encoder *aencoder,
- uint32_t link_index);
-
-static int amdgpu_dm_connector_get_modes(struct drm_connector *connector);
-
static int amdgpu_dm_atomic_setup_commit(struct drm_atomic_state *state);
static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_state *state);
+static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context);
static int amdgpu_dm_atomic_check(struct drm_device *dev,
struct drm_atomic_state *state);
@@ -196,6 +149,13 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev,
static bool
is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state,
struct drm_crtc_state *new_crtc_state);
+
+static inline void amdgpu_dm_exit_ips_for_hw_access(struct dc *dc)
+{
+ if (dc->ctx->dmub_srv && !dc->ctx->dmub_srv->idle_exit_counter)
+ dc_exit_ips_for_hw_access(dc);
+}
+
/*
* dm_vblank_get_counter
*
@@ -369,45 +329,6 @@ static int dm_set_powergating_state(struct amdgpu_ip_block *ip_block,
static int dm_early_init(struct amdgpu_ip_block *ip_block);
/* Allocate memory for FBC compressed data */
-static void amdgpu_dm_fbc_init(struct drm_connector *connector)
-{
- struct amdgpu_device *adev = drm_to_adev(connector->dev);
- struct dm_compressor_info *compressor = &adev->dm.compressor;
- struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(connector);
- struct drm_display_mode *mode;
- unsigned long max_size = 0;
-
- if (adev->dm.dc->fbc_compressor == NULL)
- return;
-
- if (aconn->dc_link->connector_signal != SIGNAL_TYPE_EDP)
- return;
-
- if (compressor->bo_ptr)
- return;
-
-
- list_for_each_entry(mode, &connector->modes, head) {
- if (max_size < (unsigned long) mode->htotal * mode->vtotal)
- max_size = (unsigned long) mode->htotal * mode->vtotal;
- }
-
- if (max_size) {
- int r = amdgpu_bo_create_kernel(adev, max_size * 4, PAGE_SIZE,
- AMDGPU_GEM_DOMAIN_GTT, &compressor->bo_ptr,
- &compressor->gpu_addr, &compressor->cpu_addr);
-
- if (r)
- drm_err(adev_to_drm(adev), "DM: Failed to initialize FBC\n");
- else {
- adev->dm.dc->ctx->fbc_gpu_addr = compressor->gpu_addr;
- drm_info(adev_to_drm(adev), "DM: FBC alloc %lu\n", max_size*4);
- }
-
- }
-
-}
-
static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_addr_space_config *pa_config)
{
u64 pt_base;
@@ -634,40 +555,6 @@ static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm)
return 0;
}
-static void hdmi_frl_status_polling_work(struct work_struct *work)
-{
- struct amdgpu_display_manager *dm =
- container_of(to_delayed_work(work), struct amdgpu_display_manager,
- hdmi_frl_status_polling_work);
- struct dc *dc = dm->dc;
- struct dc_link *dc_link;
- bool link_update = false;
-
- for (int i = 0; i < MAX_LINKS; i++) {
- dc_link = dc->links[i];
-
- if (!dc_link || !dc_link->local_sink)
- continue;
-
- if (!dc_is_hdmi_signal(dc_link->connector_signal))
- continue;
-
- if (dc_link->connector_signal != SIGNAL_TYPE_HDMI_FRL)
- continue;
-
- link_update = dc_link_frl_poll_status_flag(dc_link);
- if (link_update) {
- mutex_lock(&dm->dc_lock);
- dc_link_detect(dc_link, DETECT_REASON_RETRAIN);
- mutex_unlock(&dm->dc_lock);
- }
- }
-
- queue_delayed_work(dm->hdmi_frl_status_polling_wq,
- &dm->hdmi_frl_status_polling_work,
- msecs_to_jiffies(dm->hdmi_frl_status_polling_delay_ms));
-}
-
static int amdgpu_dm_init(struct amdgpu_device *adev)
{
struct dc_init_data init_data;
@@ -947,8 +834,6 @@ static int amdgpu_dm_init(struct amdgpu_device *adev)
create_singlethread_workqueue("hdmi_frl_status_polling_workqueue");
if (!adev->dm.hdmi_frl_status_polling_wq)
drm_err(adev_to_drm(adev), "failed to initialize hdmi_frl_status_polling_workqueue\n");
- adev->dm.hdmi_frl_status_polling_delay_ms = 200;
- INIT_DELAYED_WORK(&adev->dm.hdmi_frl_status_polling_work, hdmi_frl_status_polling_work);
}
if (dc_is_dmub_outbox_supported(adev->dm.dc)) {
init_completion(&adev->dm.dmub_aux_transfer_done);
@@ -981,9 +866,10 @@ static int amdgpu_dm_init(struct amdgpu_device *adev)
}
/* Enable outbox notification only after IRQ handlers are registered and DMUB is alive.
* It is expected that DMUB will resend any pending notifications at this point. Note
- * that hpd and hpd_irq handler registration are deferred to amdgpu_dm_register_hpd_handlers() to
- * align legacy interface initialization sequence. Connection status will be proactivly
- * detected once in the amdgpu_dm_initialize_drm_device.
+ * that hpd and hpd_irq handler registration are deferred to
+ * amdgpu_dm_register_hpd_handlers() to align legacy interface initialization
+ * sequence. Connection status will be proactivly detected once in the
+ * amdgpu_dm_initialize_drm_device.
*/
dc_enable_dmub_outbox(adev->dm.dc);
@@ -1316,41 +1202,6 @@ static int dm_sw_fini(struct amdgpu_ip_block *ip_block)
return 0;
}
-static int detect_mst_link_for_all_connectors(struct drm_device *dev)
-{
- struct amdgpu_dm_connector *aconnector;
- struct drm_connector *connector;
- struct drm_connector_list_iter iter;
- int ret = 0;
-
- drm_connector_list_iter_begin(dev, &iter);
- drm_for_each_connector_iter(connector, &iter) {
-
- if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
- continue;
-
- aconnector = to_amdgpu_dm_connector(connector);
- if (aconnector->dc_link->type == dc_connection_mst_branch &&
- aconnector->mst_mgr.aux) {
- drm_dbg_kms(dev, "DM_MST: starting TM on aconnector: %p [id: %d]\n",
- aconnector,
- aconnector->base.base.id);
-
- ret = drm_dp_mst_topology_mgr_set_mst(&aconnector->mst_mgr, true);
- if (ret < 0) {
- drm_err(dev, "DM_MST: Failed to start MST\n");
- aconnector->dc_link->type =
- dc_connection_single;
- ret = dm_helpers_dp_mst_stop_top_mgr(aconnector->dc_link->ctx,
- aconnector->dc_link);
- break;
- }
- }
- }
- drm_connector_list_iter_end(&iter);
-
- return ret;
-}
static void amdgpu_dm_boot_time_crc_init(struct amdgpu_device *adev)
{
@@ -1448,7 +1299,7 @@ static int dm_late_init(struct amdgpu_ip_block *ip_block)
}
}
- return detect_mst_link_for_all_connectors(adev_to_drm(adev));
+ return amdgpu_dm_detect_mst_link_for_all_connectors(adev_to_drm(adev));
}
static void resume_mst_branch_status(struct drm_dp_mst_topology_mgr *mgr)
@@ -1502,48 +1353,6 @@ static void resume_mst_branch_status(struct drm_dp_mst_topology_mgr *mgr)
mutex_unlock(&mgr->lock);
}
-void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector)
-{
- struct cec_notifier *n = aconnector->notifier;
-
- if (!n)
- return;
-
- cec_notifier_phys_addr_invalidate(n);
-}
-
-void hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector)
-{
- struct drm_connector *connector = &aconnector->base;
- struct cec_notifier *n = aconnector->notifier;
-
- if (!n)
- return;
-
- cec_notifier_set_phys_addr(n,
- connector->display_info.source_physical_address);
-}
-
-static void s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend)
-{
- struct amdgpu_dm_connector *aconnector;
- struct drm_connector *connector;
- struct drm_connector_list_iter conn_iter;
-
- drm_connector_list_iter_begin(ddev, &conn_iter);
- drm_for_each_connector_iter(connector, &conn_iter) {
- if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
- continue;
-
- aconnector = to_amdgpu_dm_connector(connector);
- if (suspend)
- hdmi_cec_unset_edid(aconnector);
- else
- hdmi_cec_set_edid(aconnector);
- }
- drm_connector_list_iter_end(&conn_iter);
-}
-
static void s3_handle_mst(struct drm_device *dev, bool suspend)
{
struct amdgpu_dm_connector *aconnector;
@@ -1646,7 +1455,7 @@ static int dm_oem_i2c_hw_init(struct amdgpu_device *adev)
oem_ddc_service = dc_get_oem_i2c_device(adev->dm.dc);
if (oem_ddc_service) {
- oem_i2c = create_i2c(oem_ddc_service, true);
+ oem_i2c = amdgpu_dm_create_i2c(oem_ddc_service, true);
if (!oem_i2c) {
drm_info(adev_to_drm(adev), "Failed to create oem i2c adapter data\n");
return -ENOMEM;
@@ -1915,7 +1724,7 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block)
return r;
}
- s3_handle_hdmi_cec(adev_to_drm(adev), true);
+ amdgpu_dm_s3_handle_hdmi_cec(adev_to_drm(adev), true);
s3_handle_mst(adev_to_drm(adev), true);
@@ -1941,24 +1750,6 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block)
return 0;
}
-struct drm_connector *
-amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_state *state,
- struct drm_crtc *crtc)
-{
- u32 i;
- struct drm_connector_state *new_con_state;
- struct drm_connector *connector;
- struct drm_crtc *crtc_from_state;
-
- for_each_new_connector_in_state(state, connector, new_con_state, i) {
- crtc_from_state = new_con_state->crtc;
-
- if (crtc_from_state == crtc)
- return connector;
- }
-
- return NULL;
-}
void amdgpu_dm_emulated_link_detect(struct dc_link *link)
{
@@ -2289,7 +2080,7 @@ static int dm_resume(struct amdgpu_ip_block *ip_block)
*/
amdgpu_dm_irq_resume_early(adev);
- s3_handle_hdmi_cec(ddev, false);
+ amdgpu_dm_s3_handle_hdmi_cec(ddev, false);
/* On resume we need to rewrite the MSTM control bits to enable MST*/
s3_handle_mst(ddev, false);
@@ -2442,216 +2233,6 @@ static struct drm_mode_config_helper_funcs amdgpu_dm_mode_config_helperfuncs = {
.atomic_commit_setup = amdgpu_dm_atomic_setup_commit,
};
-#define DDC_MANUFACTURERNAME_SAMSUNG 0x2D4C
-
-static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector)
-{
- struct drm_connector *connector = &aconnector->base;
- struct drm_display_info *display_info = &connector->display_info;
- struct dc_link *link = aconnector->dc_link;
- struct amdgpu_device *adev;
-
- adev = drm_to_adev(connector->dev);
-
- link->panel_type = PANEL_TYPE_NONE;
-
- switch (display_info->amd_vsdb.panel_type) {
- case AMD_VSDB_PANEL_TYPE_OLED:
- link->panel_type = PANEL_TYPE_OLED;
- break;
- case AMD_VSDB_PANEL_TYPE_MINILED:
- link->panel_type = PANEL_TYPE_MINILED;
- break;
- }
-
- /* If VSDB didn't determine panel type, check DPCD ext caps */
- if (link->panel_type == PANEL_TYPE_NONE) {
- if (link->dpcd_sink_ext_caps.bits.miniled == 1)
- link->panel_type = PANEL_TYPE_MINILED;
- if (link->dpcd_sink_ext_caps.bits.oled == 1)
- link->panel_type = PANEL_TYPE_OLED;
- }
-
- /*
- * TODO: get panel type from DID2 that has device technology field
- * to specify if it's OLED or not. But we need to wait for DID2
- * support in DC and EDID parser to be able to use it here.
- */
-
- if (link->panel_type == PANEL_TYPE_NONE) {
- struct drm_amd_vsdb_info *vsdb = &display_info->amd_vsdb;
- u32 lum1_max = vsdb->luminance_range1.max_luminance;
- u32 lum2_max = vsdb->luminance_range2.max_luminance;
-
- if (vsdb->version && link->local_sink &&
- link->local_sink->edid_caps.manufacturer_id ==
- DDC_MANUFACTURERNAME_SAMSUNG &&
- lum1_max >= ((lum2_max * 3) / 2))
- link->panel_type = PANEL_TYPE_MINILED;
- }
-
- if (link->panel_type == PANEL_TYPE_OLED)
- drm_object_property_set_value(&connector->base,
- adev_to_drm(adev)->mode_config.panel_type_property,
- DRM_MODE_PANEL_TYPE_OLED);
- else
- drm_object_property_set_value(&connector->base,
- adev_to_drm(adev)->mode_config.panel_type_property,
- DRM_MODE_PANEL_TYPE_UNKNOWN);
-
- drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type);
-}
-
-DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T))
-
-void amdgpu_dm_update_connector_after_detect(
- struct amdgpu_dm_connector *aconnector)
-{
- struct drm_connector *connector = &aconnector->base;
- struct dc_sink *sink __free(sink_release) = NULL;
- struct drm_device *dev = connector->dev;
-
- /* MST handled by drm_mst framework */
- if (aconnector->mst_mgr.mst_state == true)
- return;
-
- sink = aconnector->dc_link->local_sink;
- if (sink)
- dc_sink_retain(sink);
-
- /*
- * Edid mgmt connector gets first update only in mode_valid hook and then
- * the connector sink is set to either fake or physical sink depends on link status.
- * Skip if already done during boot.
- */
- if (aconnector->base.force != DRM_FORCE_UNSPECIFIED
- && aconnector->dc_em_sink) {
-
- /*
- * For S3 resume with headless use eml_sink to fake stream
- * because on resume connector->sink is set to NULL
- */
- guard(mutex)(&dev->mode_config.mutex);
-
- if (sink) {
- if (aconnector->dc_sink) {
- amdgpu_dm_update_freesync_caps(connector, NULL, true);
- /*
- * retain and release below are used to
- * bump up refcount for sink because the link doesn't point
- * to it anymore after disconnect, so on next crtc to connector
- * reshuffle by UMD we will get into unwanted dc_sink release
- */
- dc_sink_release(aconnector->dc_sink);
- }
- aconnector->dc_sink = sink;
- dc_sink_retain(aconnector->dc_sink);
- amdgpu_dm_update_freesync_caps(connector,
- aconnector->drm_edid, true);
- } else {
- amdgpu_dm_update_freesync_caps(connector, NULL, true);
- if (!aconnector->dc_sink) {
- aconnector->dc_sink = aconnector->dc_em_sink;
- dc_sink_retain(aconnector->dc_sink);
- }
- }
-
- return;
- }
-
- /*
- * TODO: temporary guard to look for proper fix
- * if this sink is MST sink, we should not do anything
- */
- if (sink && sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT_MST)
- return;
-
- if (aconnector->dc_sink == sink) {
- /*
- * We got a DP short pulse (Link Loss, DP CTS, etc...).
- * Do nothing!!
- */
- drm_dbg_kms(dev, "DCHPD: connector_id=%d: dc_sink didn't change.\n",
- aconnector->connector_id);
- return;
- }
-
- drm_dbg_kms(dev, "DCHPD: connector_id=%d: Old sink=%p New sink=%p\n",
- aconnector->connector_id, aconnector->dc_sink, sink);
-
- /* When polling, DRM has already locked the mutex for us. */
- if (!drm_kms_helper_is_poll_worker())
- mutex_lock(&dev->mode_config.mutex);
-
- /*
- * 1. Update status of the drm connector
- * 2. Send an event and let userspace tell us what to do
- */
- if (sink) {
- /*
- * TODO: check if we still need the S3 mode update workaround.
- * If yes, put it here.
- */
- if (aconnector->dc_sink) {
- amdgpu_dm_update_freesync_caps(connector, NULL, true);
- dc_sink_release(aconnector->dc_sink);
- }
-
- aconnector->dc_sink = sink;
- dc_sink_retain(aconnector->dc_sink);
- drm_edid_free(aconnector->drm_edid);
- aconnector->drm_edid = NULL;
- if (sink->dc_edid.length == 0) {
- hdmi_cec_unset_edid(aconnector);
- if (aconnector->dc_link->aux_mode) {
- drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux);
- }
- } else {
- const struct edid *edid = (const struct edid *)sink->dc_edid.raw_edid;
-
- aconnector->drm_edid = drm_edid_alloc(edid, sink->dc_edid.length);
- drm_edid_connector_update(connector, aconnector->drm_edid);
-
- hdmi_cec_set_edid(aconnector);
- if (aconnector->dc_link->aux_mode)
- drm_dp_cec_attach(&aconnector->dm_dp_aux.aux,
- connector->display_info.source_physical_address);
- }
-
- if (!aconnector->timing_requested) {
- aconnector->timing_requested =
- kzalloc(sizeof(struct dc_crtc_timing), GFP_KERNEL);
- if (!aconnector->timing_requested)
- drm_err(dev,
- "failed to create aconnector->requested_timing\n");
- }
-
- amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true);
- amdgpu_dm_update_connector_ext_caps(aconnector);
- dm_set_panel_type(aconnector);
- } else {
- hdmi_cec_unset_edid(aconnector);
- drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux);
- amdgpu_dm_update_freesync_caps(connector, NULL, true);
- aconnector->num_modes = 0;
- dc_sink_release(aconnector->dc_sink);
- aconnector->dc_sink = NULL;
- drm_edid_free(aconnector->drm_edid);
- aconnector->drm_edid = NULL;
- kfree(aconnector->timing_requested);
- aconnector->timing_requested = NULL;
- /* Set CP to DESIRED if it was ENABLED, so we can re-enable it again on hotplug */
- if (connector->state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED)
- connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED;
- }
-
- update_subconnector_property(aconnector);
-
- /* When polling, the mutex will be unlocked for us by DRM. */
- if (!drm_kms_helper_is_poll_worker())
- mutex_unlock(&dev->mode_config.mutex);
-}
-
/*
* Acquires the lock for the atomic state object and returns
* the new atomic state.
@@ -2839,10 +2420,6 @@ static int initialize_plane(struct amdgpu_display_manager *dm,
}
-static void amdgpu_set_panel_orientation(struct drm_connector *connector);
-
-
-
/*
* In this architecture, the association
* connector -> encoder -> crtc
@@ -3407,16 +2984,6 @@ static bool modereset_required(struct drm_crtc_state *crtc_state)
return !crtc_state->active && drm_atomic_crtc_needs_modeset(crtc_state);
}
-static void amdgpu_dm_encoder_destroy(struct drm_encoder *encoder)
-{
- drm_encoder_cleanup(encoder);
- kfree(encoder);
-}
-
-static const struct drm_encoder_funcs amdgpu_dm_encoder_funcs = {
- .destroy = amdgpu_dm_encoder_destroy,
-};
-
static int
fill_plane_color_attributes(const struct drm_plane_state *plane_state,
const enum surface_pixel_format format,
@@ -3800,7 +3367,7 @@ static void fill_dc_dirty_rects(struct drm_plane *plane,
&flip_addrs->dirty_rect_count, true);
}
-static void update_stream_scaling_settings(struct drm_device *dev,
+void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev,
const struct drm_display_mode *mode,
const struct dm_connector_state *dm_state,
struct dc_stream_state *stream)
@@ -3856,1952 +3423,37 @@ static void update_stream_scaling_settings(struct drm_device *dev,
}
-static enum dc_color_depth
-convert_color_depth_from_display_info(const struct drm_connector *connector,
- bool is_y420, int requested_bpc)
+static int dm_update_mst_vcpi_slots_for_dsc(struct drm_atomic_state *state,
+ struct dc_state *dc_state,
+ struct dsc_mst_fairness_vars *vars)
{
- u8 bpc;
-
- if (is_y420) {
- bpc = 8;
-
- /* Cap display bpc based on HDMI 2.0 HF-VSDB */
- if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48)
- bpc = 16;
- else if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36)
- bpc = 12;
- else if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30)
- bpc = 10;
- } else {
- bpc = (uint8_t)connector->display_info.bpc;
- /* Assume 8 bpc by default if no bpc is specified. */
- bpc = bpc ? bpc : 8;
- }
+ struct dc_stream_state *stream = NULL;
+ struct drm_connector *connector;
+ struct drm_connector_state *new_con_state;
+ struct amdgpu_dm_connector *aconnector;
+ struct dm_connector_state *dm_conn_state;
+ int i, j, ret;
+ int vcpi, pbn_div, pbn = 0, slot_num = 0;
- if (requested_bpc > 0) {
- /*
- * Cap display bpc based on the user requested value.
- *
- * The value for state->max_bpc may not correctly updated
- * depending on when the connector gets added to the state
- * or if this was called outside of atomic check, so it
- * can't be used directly.
- */
- bpc = min_t(u8, bpc, requested_bpc);
+ for_each_new_connector_in_state(state, connector, new_con_state, i) {
- /* Round down to the nearest even number. */
- bpc = bpc - (bpc & 1);
- }
+ if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ continue;
- switch (bpc) {
- case 0:
- /*
- * Temporary Work around, DRM doesn't parse color depth for
- * EDID revision before 1.4
- * TODO: Fix edid parsing
- */
- return COLOR_DEPTH_888;
- case 6:
- return COLOR_DEPTH_666;
- case 8:
- return COLOR_DEPTH_888;
- case 10:
- return COLOR_DEPTH_101010;
- case 12:
- return COLOR_DEPTH_121212;
- case 14:
- return COLOR_DEPTH_141414;
- case 16:
- return COLOR_DEPTH_161616;
- default:
- return COLOR_DEPTH_UNDEFINED;
- }
-}
+ aconnector = to_amdgpu_dm_connector(connector);
-static enum dc_aspect_ratio
-get_aspect_ratio(const struct drm_display_mode *mode_in)
-{
- /* 1-1 mapping, since both enums follow the HDMI spec. */
- return (enum dc_aspect_ratio) mode_in->picture_aspect_ratio;
-}
+ if (!aconnector->mst_output_port)
+ continue;
-static enum dc_color_space
-get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing,
- const struct drm_connector_state *connector_state)
-{
- enum dc_color_space color_space = COLOR_SPACE_SRGB;
+ if (!new_con_state || !new_con_state->crtc)
+ continue;
- switch (connector_state->colorspace) {
- case DRM_MODE_COLORIMETRY_BT601_YCC:
- if (dc_crtc_timing->flags.Y_ONLY)
- color_space = COLOR_SPACE_YCBCR601_LIMITED;
- else
- color_space = COLOR_SPACE_YCBCR601;
- break;
- case DRM_MODE_COLORIMETRY_BT709_YCC:
- if (dc_crtc_timing->flags.Y_ONLY)
- color_space = COLOR_SPACE_YCBCR709_LIMITED;
- else
- color_space = COLOR_SPACE_YCBCR709;
- break;
- case DRM_MODE_COLORIMETRY_OPRGB:
- color_space = COLOR_SPACE_ADOBERGB;
- break;
- case DRM_MODE_COLORIMETRY_BT2020_RGB:
- case DRM_MODE_COLORIMETRY_BT2020_YCC:
- if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB)
- color_space = COLOR_SPACE_2020_RGB_FULLRANGE;
- else
- color_space = COLOR_SPACE_2020_YCBCR_LIMITED;
- break;
- case DRM_MODE_COLORIMETRY_DEFAULT: // ITU601
- default:
- if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) {
- color_space = COLOR_SPACE_SRGB;
- if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED)
- color_space = COLOR_SPACE_SRGB_LIMITED;
- /*
- * 27030khz is the separation point between HDTV and SDTV
- * according to HDMI spec, we use YCbCr709 and YCbCr601
- * respectively
- */
- } else if (dc_crtc_timing->pix_clk_100hz > 270300) {
- if (dc_crtc_timing->flags.Y_ONLY)
- color_space =
- COLOR_SPACE_YCBCR709_LIMITED;
- else
- color_space = COLOR_SPACE_YCBCR709;
- } else {
- if (dc_crtc_timing->flags.Y_ONLY)
- color_space =
- COLOR_SPACE_YCBCR601_LIMITED;
- else
- color_space = COLOR_SPACE_YCBCR601;
- }
- break;
- }
+ dm_conn_state = to_dm_connector_state(new_con_state);
- return color_space;
-}
-
-static enum display_content_type
-get_output_content_type(const struct drm_connector_state *connector_state)
-{
- switch (connector_state->content_type) {
- default:
- case DRM_MODE_CONTENT_TYPE_NO_DATA:
- return DISPLAY_CONTENT_TYPE_NO_DATA;
- case DRM_MODE_CONTENT_TYPE_GRAPHICS:
- return DISPLAY_CONTENT_TYPE_GRAPHICS;
- case DRM_MODE_CONTENT_TYPE_PHOTO:
- return DISPLAY_CONTENT_TYPE_PHOTO;
- case DRM_MODE_CONTENT_TYPE_CINEMA:
- return DISPLAY_CONTENT_TYPE_CINEMA;
- case DRM_MODE_CONTENT_TYPE_GAME:
- return DISPLAY_CONTENT_TYPE_GAME;
- }
-}
-
-static bool adjust_colour_depth_from_display_info(
- struct dc_crtc_timing *timing_out,
- const struct drm_display_info *info)
-{
- enum dc_color_depth depth = timing_out->display_color_depth;
- int normalized_clk;
-
- do {
- normalized_clk = timing_out->pix_clk_100hz / 10;
- /* YCbCr 4:2:0 requires additional adjustment of 1/2 */
- if (timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420)
- normalized_clk /= 2;
- /* Adjusting pix clock following on HDMI spec based on colour depth */
- switch (depth) {
- case COLOR_DEPTH_888:
- break;
- case COLOR_DEPTH_101010:
- normalized_clk = (normalized_clk * 30) / 24;
- break;
- case COLOR_DEPTH_121212:
- normalized_clk = (normalized_clk * 36) / 24;
- break;
- case COLOR_DEPTH_161616:
- normalized_clk = (normalized_clk * 48) / 24;
- break;
- default:
- /* The above depths are the only ones valid for HDMI. */
- return false;
- }
- if (normalized_clk <= info->max_tmds_clock) {
- timing_out->display_color_depth = depth;
- return true;
- }
- } while (--depth > COLOR_DEPTH_666);
- return false;
-}
-
-static void fill_stream_properties_from_drm_display_mode(
- struct dc_stream_state *stream,
- const struct drm_display_mode *mode_in,
- const struct drm_connector *connector,
- const struct drm_connector_state *connector_state,
- const struct dc_stream_state *old_stream,
- int requested_bpc)
-{
- struct dc_crtc_timing *timing_out = &stream->timing;
- const struct drm_display_info *info = &connector->display_info;
- struct amdgpu_dm_connector *aconnector = NULL;
- struct hdmi_vendor_infoframe hv_frame;
- struct hdmi_avi_infoframe avi_frame;
- ssize_t err;
-
- if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
- aconnector = to_amdgpu_dm_connector(connector);
-
- memset(&hv_frame, 0, sizeof(hv_frame));
- memset(&avi_frame, 0, sizeof(avi_frame));
-
- timing_out->h_border_left = 0;
- timing_out->h_border_right = 0;
- timing_out->v_border_top = 0;
- timing_out->v_border_bottom = 0;
- /* TODO: un-hardcode */
- if (drm_mode_is_420_only(info, mode_in)
- && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A ||
- stream->signal == SIGNAL_TYPE_HDMI_FRL)
- && aconnector
- && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420)
- timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420;
- else if (drm_mode_is_420_also(info, mode_in)
- && aconnector
- && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420
- || aconnector->force_yuv420_output))
- timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420;
- else if ((connector->display_info.color_formats & DRM_COLOR_FORMAT_YCBCR422)
- && aconnector
- && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR422
- || aconnector->force_yuv422_output))
- timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR422;
- else if ((connector->display_info.color_formats & DRM_COLOR_FORMAT_YCBCR444)
- && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A ||
- stream->signal == SIGNAL_TYPE_HDMI_FRL)
- && aconnector
- && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR444)
- timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR444;
- else
- timing_out->pixel_encoding = PIXEL_ENCODING_RGB;
-
- timing_out->timing_3d_format = TIMING_3D_FORMAT_NONE;
- timing_out->display_color_depth = convert_color_depth_from_display_info(
- connector,
- (timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420),
- requested_bpc);
- timing_out->scan_type = SCANNING_TYPE_NODATA;
- timing_out->hdmi_vic = 0;
-
- if (old_stream) {
- timing_out->vic = old_stream->timing.vic;
- timing_out->flags.HSYNC_POSITIVE_POLARITY = old_stream->timing.flags.HSYNC_POSITIVE_POLARITY;
- timing_out->flags.VSYNC_POSITIVE_POLARITY = old_stream->timing.flags.VSYNC_POSITIVE_POLARITY;
- } else {
- timing_out->vic = drm_match_cea_mode(mode_in);
- if (mode_in->flags & DRM_MODE_FLAG_PHSYNC)
- timing_out->flags.HSYNC_POSITIVE_POLARITY = 1;
- if (mode_in->flags & DRM_MODE_FLAG_PVSYNC)
- timing_out->flags.VSYNC_POSITIVE_POLARITY = 1;
- }
-
- if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A ||
- stream->signal == SIGNAL_TYPE_HDMI_FRL) {
- err = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame,
- (struct drm_connector *)connector,
- mode_in);
- if (err < 0)
- drm_warn_once(connector->dev, "Failed to setup avi infoframe on connector %s: %zd\n",
- connector->name, err);
- timing_out->vic = avi_frame.video_code;
- err = drm_hdmi_vendor_infoframe_from_display_mode(&hv_frame,
- (struct drm_connector *)connector,
- mode_in);
- if (err < 0)
- drm_warn_once(connector->dev, "Failed to setup vendor infoframe on connector %s: %zd\n",
- connector->name, err);
- timing_out->hdmi_vic = hv_frame.vic;
- }
-
- if (aconnector && is_freesync_video_mode(mode_in, aconnector)) {
- timing_out->h_addressable = mode_in->hdisplay;
- timing_out->h_total = mode_in->htotal;
- timing_out->h_sync_width = mode_in->hsync_end - mode_in->hsync_start;
- timing_out->h_front_porch = mode_in->hsync_start - mode_in->hdisplay;
- timing_out->v_total = mode_in->vtotal;
- timing_out->v_addressable = mode_in->vdisplay;
- timing_out->v_front_porch = mode_in->vsync_start - mode_in->vdisplay;
- timing_out->v_sync_width = mode_in->vsync_end - mode_in->vsync_start;
- timing_out->pix_clk_100hz = mode_in->clock * 10;
- } else {
- timing_out->h_addressable = mode_in->crtc_hdisplay;
- timing_out->h_total = mode_in->crtc_htotal;
- timing_out->h_sync_width = mode_in->crtc_hsync_end - mode_in->crtc_hsync_start;
- timing_out->h_front_porch = mode_in->crtc_hsync_start - mode_in->crtc_hdisplay;
- timing_out->v_total = mode_in->crtc_vtotal;
- timing_out->v_addressable = mode_in->crtc_vdisplay;
- timing_out->v_front_porch = mode_in->crtc_vsync_start - mode_in->crtc_vdisplay;
- timing_out->v_sync_width = mode_in->crtc_vsync_end - mode_in->crtc_vsync_start;
- timing_out->pix_clk_100hz = mode_in->crtc_clock * 10;
- }
-
- timing_out->aspect_ratio = get_aspect_ratio(mode_in);
-
- stream->out_transfer_func.type = TF_TYPE_PREDEFINED;
- stream->out_transfer_func.tf = TRANSFER_FUNCTION_SRGB;
- if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A) {
- if (!adjust_colour_depth_from_display_info(timing_out, info) &&
- drm_mode_is_420_also(info, mode_in) &&
- timing_out->pixel_encoding != PIXEL_ENCODING_YCBCR420) {
- timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420;
- adjust_colour_depth_from_display_info(timing_out, info);
- }
- }
-
- stream->output_color_space = get_output_color_space(timing_out, connector_state);
- stream->content_type = get_output_content_type(connector_state);
-}
-
-static void
-copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode,
- struct drm_display_mode *dst_mode)
-{
- dst_mode->crtc_hdisplay = src_mode->crtc_hdisplay;
- dst_mode->crtc_vdisplay = src_mode->crtc_vdisplay;
- dst_mode->crtc_clock = src_mode->crtc_clock;
- dst_mode->crtc_hblank_start = src_mode->crtc_hblank_start;
- dst_mode->crtc_hblank_end = src_mode->crtc_hblank_end;
- dst_mode->crtc_hsync_start = src_mode->crtc_hsync_start;
- dst_mode->crtc_hsync_end = src_mode->crtc_hsync_end;
- dst_mode->crtc_htotal = src_mode->crtc_htotal;
- dst_mode->crtc_hskew = src_mode->crtc_hskew;
- dst_mode->crtc_vblank_start = src_mode->crtc_vblank_start;
- dst_mode->crtc_vblank_end = src_mode->crtc_vblank_end;
- dst_mode->crtc_vsync_start = src_mode->crtc_vsync_start;
- dst_mode->crtc_vsync_end = src_mode->crtc_vsync_end;
- dst_mode->crtc_vtotal = src_mode->crtc_vtotal;
-}
-
-static void
-decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode,
- const struct drm_display_mode *native_mode,
- bool scale_enabled)
-{
- if (scale_enabled || (
- native_mode->clock == drm_mode->clock &&
- native_mode->htotal == drm_mode->htotal &&
- native_mode->vtotal == drm_mode->vtotal)) {
- if (native_mode->crtc_clock)
- copy_crtc_timing_for_drm_display_mode(native_mode, drm_mode);
- } else {
- /* no scaling nor amdgpu inserted, no need to patch */
- }
-}
-
-static struct dc_sink *
-create_fake_sink(struct drm_device *dev, struct dc_link *link)
-{
- struct dc_sink_init_data sink_init_data = { 0 };
- struct dc_sink *sink = NULL;
-
- sink_init_data.link = link;
- sink_init_data.sink_signal = link->connector_signal;
-
- sink = dc_sink_create(&sink_init_data);
- if (!sink) {
- drm_err(dev, "Failed to create sink!\n");
- return NULL;
- }
- sink->sink_signal = SIGNAL_TYPE_VIRTUAL;
-
- return sink;
-}
-
-static void set_multisync_trigger_params(
- struct dc_stream_state *stream)
-{
- struct dc_stream_state *master = NULL;
-
- if (stream->triggered_crtc_reset.enabled) {
- master = stream->triggered_crtc_reset.event_source;
- stream->triggered_crtc_reset.event =
- master->timing.flags.VSYNC_POSITIVE_POLARITY ?
- CRTC_EVENT_VSYNC_RISING : CRTC_EVENT_VSYNC_FALLING;
- stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_PIXEL;
- }
-}
-
-static void set_master_stream(struct dc_stream_state *stream_set[],
- int stream_count)
-{
- int j, highest_rfr = 0, master_stream = 0;
-
- for (j = 0; j < stream_count; j++) {
- if (stream_set[j] && stream_set[j]->triggered_crtc_reset.enabled) {
- int refresh_rate = 0;
-
- refresh_rate = (stream_set[j]->timing.pix_clk_100hz*100)/
- (stream_set[j]->timing.h_total*stream_set[j]->timing.v_total);
- if (refresh_rate > highest_rfr) {
- highest_rfr = refresh_rate;
- master_stream = j;
- }
- }
- }
- for (j = 0; j < stream_count; j++) {
- if (stream_set[j])
- stream_set[j]->triggered_crtc_reset.event_source = stream_set[master_stream];
- }
-}
-
-static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context)
-{
- int i = 0;
- struct dc_stream_state *stream;
-
- if (context->stream_count < 2)
- return;
- for (i = 0; i < context->stream_count ; i++) {
- if (!context->streams[i])
- continue;
- /*
- * TODO: add a function to read AMD VSDB bits and set
- * crtc_sync_master.multi_sync_enabled flag
- * For now it's set to false
- */
- }
-
- set_master_stream(context->streams, context->stream_count);
-
- for (i = 0; i < context->stream_count ; i++) {
- stream = context->streams[i];
-
- if (!stream)
- continue;
-
- set_multisync_trigger_params(stream);
- }
-}
-
-/**
- * DOC: FreeSync Video
- *
- * When a userspace application wants to play a video, the content follows a
- * standard format definition that usually specifies the FPS for that format.
- * The below list illustrates some video format and the expected FPS,
- * respectively:
- *
- * - TV/NTSC (23.976 FPS)
- * - Cinema (24 FPS)
- * - TV/PAL (25 FPS)
- * - TV/NTSC (29.97 FPS)
- * - TV/NTSC (30 FPS)
- * - Cinema HFR (48 FPS)
- * - TV/PAL (50 FPS)
- * - Commonly used (60 FPS)
- * - Multiples of 24 (48,72,96 FPS)
- *
- * The list of standards video format is not huge and can be added to the
- * connector modeset list beforehand. With that, userspace can leverage
- * FreeSync to extends the front porch in order to attain the target refresh
- * rate. Such a switch will happen seamlessly, without screen blanking or
- * reprogramming of the output in any other way. If the userspace requests a
- * modesetting change compatible with FreeSync modes that only differ in the
- * refresh rate, DC will skip the full update and avoid blink during the
- * transition. For example, the video player can change the modesetting from
- * 60Hz to 30Hz for playing TV/NTSC content when it goes full screen without
- * causing any display blink. This same concept can be applied to a mode
- * setting change.
- */
-static struct drm_display_mode *
-get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
- bool use_probed_modes)
-{
- struct drm_display_mode *m, *m_pref = NULL;
- u16 current_refresh, highest_refresh;
- struct list_head *list_head = use_probed_modes ?
- &aconnector->base.probed_modes :
- &aconnector->base.modes;
-
- if (aconnector->base.connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
- return NULL;
-
- if (aconnector->freesync_vid_base.clock != 0)
- return &aconnector->freesync_vid_base;
-
- /* Find the preferred mode */
- list_for_each_entry(m, list_head, head) {
- if (m->type & DRM_MODE_TYPE_PREFERRED) {
- m_pref = m;
- break;
- }
- }
-
- if (!m_pref) {
- /* Probably an EDID with no preferred mode. Fallback to first entry */
- m_pref = list_first_entry_or_null(
- &aconnector->base.modes, struct drm_display_mode, head);
- if (!m_pref) {
- drm_dbg_driver(aconnector->base.dev, "No preferred mode found in EDID\n");
- return NULL;
- }
- }
-
- highest_refresh = drm_mode_vrefresh(m_pref);
-
- /*
- * Find the mode with highest refresh rate with same resolution.
- * For some monitors, preferred mode is not the mode with highest
- * supported refresh rate.
- */
- list_for_each_entry(m, list_head, head) {
- current_refresh = drm_mode_vrefresh(m);
-
- if (m->hdisplay == m_pref->hdisplay &&
- m->vdisplay == m_pref->vdisplay &&
- highest_refresh < current_refresh) {
- highest_refresh = current_refresh;
- m_pref = m;
- }
- }
-
- drm_mode_copy(&aconnector->freesync_vid_base, m_pref);
- return m_pref;
-}
-
-static bool is_freesync_video_mode(const struct drm_display_mode *mode,
- struct amdgpu_dm_connector *aconnector)
-{
- struct drm_display_mode *high_mode;
- int timing_diff;
-
- high_mode = get_highest_refresh_rate_mode(aconnector, false);
- if (!high_mode || !mode)
- return false;
-
- timing_diff = high_mode->vtotal - mode->vtotal;
-
- if (high_mode->clock == 0 || high_mode->clock != mode->clock ||
- high_mode->hdisplay != mode->hdisplay ||
- high_mode->vdisplay != mode->vdisplay ||
- high_mode->hsync_start != mode->hsync_start ||
- high_mode->hsync_end != mode->hsync_end ||
- high_mode->htotal != mode->htotal ||
- high_mode->hskew != mode->hskew ||
- high_mode->vscan != mode->vscan ||
- high_mode->vsync_start - mode->vsync_start != timing_diff ||
- high_mode->vsync_end - mode->vsync_end != timing_diff)
- return false;
- else
- return true;
-}
-
-#if defined(CONFIG_DRM_AMD_DC_FP)
-static void update_dsc_caps(struct amdgpu_dm_connector *aconnector,
- struct dc_sink *sink, struct dc_stream_state *stream,
- struct dsc_dec_dpcd_caps *dsc_caps)
-{
- stream->timing.flags.DSC = 0;
- dsc_caps->is_dsc_supported = false;
-
- if (aconnector->dc_link && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT ||
- sink->sink_signal == SIGNAL_TYPE_EDP)) {
- if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE)
- dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc,
- aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw,
- aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw,
- dsc_caps);
- else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) {
- if (aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.fields.dsc_support.DSC_PASSTHROUGH_SUPPORT &&
- !aconnector->dsc_settings.dsc_force_disable_passthrough &&
- aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps > 0 &&
- sink->edid_caps.frl_dsc_support &&
- sink->edid_caps.max_frl_rate > 0 &&
- sink->edid_caps.frl_dsc_max_frl_rate > 0)
- dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps);
- else
- dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc,
- aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw,
- aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw,
- dsc_caps);
- }
- } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) {
- if (sink->edid_caps.frl_dsc_support &&
- sink->edid_caps.max_frl_rate > 0 &&
- sink->edid_caps.frl_dsc_max_frl_rate > 0)
- dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps);
- }
-}
-
-static void apply_dsc_policy_for_edp(struct amdgpu_dm_connector *aconnector,
- struct dc_sink *sink, struct dc_stream_state *stream,
- struct dsc_dec_dpcd_caps *dsc_caps,
- uint32_t max_dsc_target_bpp_limit_override)
-{
- const struct dc_link_settings *verified_link_cap = NULL;
- u32 link_bw_in_kbps;
- u32 edp_min_bpp_x16, edp_max_bpp_x16;
- struct dc *dc = sink->ctx->dc;
- struct dc_dsc_bw_range bw_range = {0};
- struct dc_dsc_config dsc_cfg = {0};
- struct dc_dsc_config_options dsc_options = {0};
-
- dc_dsc_get_default_config_option(dc, &dsc_options);
- dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16;
-
- verified_link_cap = dc_link_get_link_cap(stream->link);
- link_bw_in_kbps = dc_link_bandwidth_kbps(stream->link, verified_link_cap);
- edp_min_bpp_x16 = 8 * 16;
- edp_max_bpp_x16 = 8 * 16;
-
- if (edp_max_bpp_x16 > dsc_caps->edp_max_bits_per_pixel)
- edp_max_bpp_x16 = dsc_caps->edp_max_bits_per_pixel;
-
- if (edp_max_bpp_x16 < edp_min_bpp_x16)
- edp_min_bpp_x16 = edp_max_bpp_x16;
-
- if (dc_dsc_compute_bandwidth_range(dc->res_pool->dscs[0],
- dc->debug.dsc_min_slice_height_override,
- edp_min_bpp_x16, edp_max_bpp_x16,
- dsc_caps,
- &stream->timing,
- dc_link_get_highest_encoding_format(aconnector->dc_link),
- &bw_range)) {
-
- if (bw_range.max_kbps < link_bw_in_kbps) {
- if (dc_dsc_compute_config(dc->res_pool->dscs[0],
- dsc_caps,
- &dsc_options,
- 0,
- &stream->timing,
- dc_link_get_highest_encoding_format(aconnector->dc_link),
- &dsc_cfg)) {
- stream->timing.dsc_cfg = dsc_cfg;
- stream->timing.flags.DSC = 1;
- stream->timing.dsc_cfg.bits_per_pixel = edp_max_bpp_x16;
- }
- return;
- }
- }
-
- if (dc_dsc_compute_config(dc->res_pool->dscs[0],
- dsc_caps,
- &dsc_options,
- link_bw_in_kbps,
- &stream->timing,
- dc_link_get_highest_encoding_format(aconnector->dc_link),
- &dsc_cfg)) {
- stream->timing.dsc_cfg = dsc_cfg;
- stream->timing.flags.DSC = 1;
- }
-}
-
-static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector,
- struct dc_sink *sink, struct dc_stream_state *stream,
- struct dsc_dec_dpcd_caps *dsc_caps)
-{
- struct drm_connector *drm_connector = &aconnector->base;
- u32 link_bandwidth_kbps;
- struct dc *dc = sink->ctx->dc;
- const struct dc_hdmi_frl_link_settings *frl_verified_link_cap = NULL;
- u32 converter_bw_in_kbps;
- u32 sink_bw_in_kbps;
- u32 dsc_sink_bw_in_kbps;
- u32 max_supported_bw_in_kbps, timing_bw_in_kbps;
- u32 dsc_max_supported_bw_in_kbps;
- u32 max_dsc_target_bpp_limit_override =
- drm_connector->display_info.max_dsc_bpp;
- struct dc_dsc_config_options dsc_options = {0};
-
- dc_dsc_get_default_config_option(dc, &dsc_options);
- dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16;
-
- link_bandwidth_kbps = dc_link_bandwidth_kbps(aconnector->dc_link,
- dc_link_get_link_cap(aconnector->dc_link));
-
- /* Set DSC policy according to dsc_clock_en */
- dc_dsc_policy_set_enable_dsc_when_not_needed(
- aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE);
-
- if (sink->sink_signal == SIGNAL_TYPE_EDP &&
- !aconnector->dc_link->panel_config.dsc.disable_dsc_edp &&
- dc->caps.edp_dsc_support && aconnector->dsc_settings.dsc_force_enable != DSC_CLK_FORCE_DISABLE) {
-
- apply_dsc_policy_for_edp(aconnector, sink, stream, dsc_caps, max_dsc_target_bpp_limit_override);
-
- } else if (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT) {
- if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE) {
- if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0],
- dsc_caps,
- &dsc_options,
- link_bandwidth_kbps,
- &stream->timing,
- dc_link_get_highest_encoding_format(aconnector->dc_link),
- &stream->timing.dsc_cfg)) {
- stream->timing.flags.DSC = 1;
- drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from SST RX\n",
- __func__, drm_connector->name);
- }
- } else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) {
- timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing,
- dc_link_get_highest_encoding_format(aconnector->dc_link));
- converter_bw_in_kbps = aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps;
- sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.max_frl_rate);
- dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate);
-
- if (dsc_caps->is_frl) {
- max_supported_bw_in_kbps = min(link_bandwidth_kbps, converter_bw_in_kbps);
- max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, sink_bw_in_kbps);
- dsc_max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, dsc_sink_bw_in_kbps);
- } else {
- max_supported_bw_in_kbps = link_bandwidth_kbps;
- dsc_max_supported_bw_in_kbps = link_bandwidth_kbps;
- }
-
- if (timing_bw_in_kbps > max_supported_bw_in_kbps &&
- max_supported_bw_in_kbps > 0 &&
- dsc_max_supported_bw_in_kbps > 0)
- if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0],
- dsc_caps,
- &dsc_options,
- dsc_max_supported_bw_in_kbps,
- &stream->timing,
- dc_link_get_highest_encoding_format(aconnector->dc_link),
- &stream->timing.dsc_cfg)) {
- stream->timing.flags.DSC = 1;
- drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from %s\n",
- __func__, drm_connector->name,
- (dsc_caps->is_frl == 1) ? "HDMI FRL RX" : "DP-HDMI PCON");
- }
- }
- }
- else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) {
- struct dc_dsc_policy dsc_policy = {0};
-
- frl_verified_link_cap = dc_link_get_frl_link_cap(stream->link);
- if (frl_verified_link_cap->frl_link_rate != HDMI_FRL_LINK_RATE_DISABLE &&
- aconnector->dc_link->frl_flags.force_frl_dsc) {
- dc_dsc_policy_set_enable_dsc_when_not_needed(true);
- dc_dsc_get_policy_for_timing(&stream->timing, 0, &dsc_policy, dc_link_get_highest_encoding_format(stream->link));
- }
-
- timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing, DC_LINK_ENCODING_HDMI_FRL);
- link_bandwidth_kbps = dc_link_frl_bandwidth_kbps(stream->link, frl_verified_link_cap->frl_link_rate);
- dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate);
-
- if ((timing_bw_in_kbps > link_bandwidth_kbps && dsc_sink_bw_in_kbps > 0) ||
- (dsc_policy.enable_dsc_when_not_needed || dsc_options.force_dsc_when_not_needed)) {
- if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0],
- dsc_caps,
- &dsc_options,
- dsc_sink_bw_in_kbps,
- &stream->timing,
- dc_link_get_highest_encoding_format(aconnector->dc_link),
- &stream->timing.dsc_cfg)) {
- stream->timing.flags.DSC = 1;
- drm_dbg_driver(drm_connector->dev, "%s: HDMI_FRL_DSC [%s] DSC is selected from HDMI FRL RX\n",
- __func__, drm_connector->name);
- }
- }
- }
-
- /* Overwrite the stream flag if DSC is enabled through debugfs */
- if (aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE)
- stream->timing.flags.DSC = 1;
-
- if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_h)
- stream->timing.dsc_cfg.num_slices_h = aconnector->dsc_settings.dsc_num_slices_h;
-
- if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_v)
- stream->timing.dsc_cfg.num_slices_v = aconnector->dsc_settings.dsc_num_slices_v;
-
- if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_bits_per_pixel)
- stream->timing.dsc_cfg.bits_per_pixel = aconnector->dsc_settings.dsc_bits_per_pixel;
-}
-#endif
-
-static struct dc_stream_state *
-create_stream_for_sink(struct drm_connector *connector,
- const struct drm_display_mode *drm_mode,
- const struct dm_connector_state *dm_state,
- const struct dc_stream_state *old_stream,
- int requested_bpc)
-{
- struct drm_device *dev = connector->dev;
- struct amdgpu_dm_connector *aconnector = NULL;
- struct drm_display_mode *preferred_mode = NULL;
- const struct drm_connector_state *con_state = &dm_state->base;
- struct dc_stream_state *stream = NULL;
- struct drm_display_mode mode;
- struct drm_display_mode saved_mode;
- struct drm_display_mode *freesync_mode = NULL;
- bool native_mode_found = false;
- bool recalculate_timing = false;
- bool scale = dm_state->scaling != RMX_OFF;
- int mode_refresh;
- 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 = {0};
-#endif
- struct dc_link *link = NULL;
- struct dc_sink *sink = NULL;
-
- drm_mode_init(&mode, drm_mode);
- memset(&saved_mode, 0, sizeof(saved_mode));
-
- if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) {
- aconnector = NULL;
- aconnector = to_amdgpu_dm_connector(connector);
- link = aconnector->dc_link;
- } else {
- struct drm_writeback_connector *wbcon = NULL;
- struct amdgpu_dm_wb_connector *dm_wbcon = NULL;
-
- wbcon = drm_connector_to_writeback(connector);
- dm_wbcon = to_amdgpu_dm_wb_connector(wbcon);
- link = dm_wbcon->link;
- }
-
- if (!aconnector || !aconnector->dc_sink) {
- sink = create_fake_sink(dev, link);
- if (!sink)
- return stream;
-
- } else {
- sink = aconnector->dc_sink;
- dc_sink_retain(sink);
- }
-
- stream = dc_create_stream_for_sink(sink);
-
- if (stream == NULL) {
- drm_err(dev, "Failed to create stream for sink!\n");
- goto finish;
- }
-
- /* We leave this NULL for writeback connectors */
- stream->dm_stream_context = aconnector;
-
- stream->timing.flags.LTE_340MCSC_SCRAMBLE =
- connector->display_info.hdmi.scdc.scrambling.low_rates;
-
- list_for_each_entry(preferred_mode, &connector->modes, head) {
- /* Search for preferred mode */
- if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) {
- native_mode_found = true;
- break;
- }
- }
- if (!native_mode_found)
- preferred_mode = list_first_entry_or_null(
- &connector->modes,
- struct drm_display_mode,
- head);
-
- mode_refresh = drm_mode_vrefresh(&mode);
-
- if (preferred_mode == NULL) {
- /*
- * This may not be an error, the use case is when we have no
- * usermode calls to reset and set mode upon hotplug. In this
- * case, we call set mode ourselves to restore the previous mode
- * and the modelist may not be filled in time.
- */
- drm_dbg_driver(dev, "No preferred mode found\n");
- } else if (aconnector) {
- recalculate_timing = amdgpu_freesync_vid_mode &&
- is_freesync_video_mode(&mode, aconnector);
- if (recalculate_timing) {
- freesync_mode = get_highest_refresh_rate_mode(aconnector, false);
- drm_mode_copy(&saved_mode, &mode);
- saved_mode.picture_aspect_ratio = mode.picture_aspect_ratio;
- drm_mode_copy(&mode, freesync_mode);
- mode.picture_aspect_ratio = saved_mode.picture_aspect_ratio;
- } else {
- decide_crtc_timing_for_drm_display_mode(
- &mode, preferred_mode, scale);
-
- preferred_refresh = drm_mode_vrefresh(preferred_mode);
- }
- }
-
- if (recalculate_timing)
- drm_mode_set_crtcinfo(&saved_mode, 0);
-
- /*
- * If scaling is enabled and refresh rate didn't change
- * we copy the vic and polarities of the old timings
- */
- if (!scale || mode_refresh != preferred_refresh)
- fill_stream_properties_from_drm_display_mode(
- stream, &mode, connector, con_state, NULL,
- requested_bpc);
- else
- fill_stream_properties_from_drm_display_mode(
- stream, &mode, connector, con_state, old_stream,
- requested_bpc);
-
- /* The rest isn't needed for writeback connectors */
- if (!aconnector)
- goto finish;
-
- if (aconnector->timing_changed) {
- drm_dbg(aconnector->base.dev,
- "overriding timing for automated test, bpc %d, changing to %d\n",
- stream->timing.display_color_depth,
- aconnector->timing_requested->display_color_depth);
- stream->timing = *aconnector->timing_requested;
- }
-
-#if defined(CONFIG_DRM_AMD_DC_FP)
- /* SST DSC determination policy */
- update_dsc_caps(aconnector, sink, stream, &dsc_caps);
- if (aconnector->dsc_settings.dsc_force_enable != DSC_CLK_FORCE_DISABLE && dsc_caps.is_dsc_supported)
- apply_dsc_policy_for_stream(aconnector, sink, stream, &dsc_caps);
-#endif
-
- update_stream_scaling_settings(dev, &mode, dm_state, stream);
-
- amdgpu_dm_fill_audio_info(
- &stream->audio_info,
- connector,
- sink);
-
- update_stream_signal(stream, sink);
-
- if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A ||
- stream->signal == SIGNAL_TYPE_HDMI_FRL)
- mod_build_hf_vsif_infopacket(stream, &stream->vsp_infopacket, false, false);
-
- if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT ||
- stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST ||
- stream->signal == SIGNAL_TYPE_EDP) {
- const struct dc_edid_caps *edid_caps;
- unsigned int disable_colorimetry = 0;
-
- if (aconnector->dc_sink) {
- edid_caps = &aconnector->dc_sink->edid_caps;
- disable_colorimetry = edid_caps->panel_patch.disable_colorimetry;
- }
-
- //
- // should decide stream support vsc sdp colorimetry capability
- // before building vsc info packet
- //
- stream->use_vsc_sdp_for_colorimetry = stream->link->dpcd_caps.dpcd_rev.raw >= 0x14 &&
- stream->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED &&
- !disable_colorimetry;
-
- if (stream->out_transfer_func.tf == TRANSFER_FUNCTION_GAMMA22)
- tf = TRANSFER_FUNC_GAMMA_22;
- mod_build_vsc_infopacket(stream, &stream->vsc_infopacket, stream->output_color_space, tf);
- aconnector->sr_skip_count = AMDGPU_DM_PSR_ENTRY_DELAY;
-
- }
-finish:
- dc_sink_release(sink);
-
- return stream;
-}
-
-/**
- * amdgpu_dm_connector_poll - Poll a connector to see if it's connected to a display
- * @aconnector: DM connector to poll (owns @base drm_connector and @dc_link)
- * @force: if true, force polling even when DAC load detection was used
- *
- * Used for connectors that don't support HPD (hotplug detection) to
- * periodically check whether the connector is connected to a display.
- *
- * When connection was determined via DAC load detection, we avoid
- * re-running it on normal polls to prevent visible glitches, unless
- * @force is set.
- *
- * Return: The probed connector status (connected/disconnected/unknown).
- */
-static enum drm_connector_status
-amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force)
-{
- struct drm_connector *connector = &aconnector->base;
- struct drm_device *dev = connector->dev;
- struct amdgpu_device *adev = drm_to_adev(dev);
- struct dc_link *link = aconnector->dc_link;
- enum dc_connection_type conn_type = dc_connection_none;
- enum drm_connector_status status = connector_status_disconnected;
-
- /* When we determined the connection using DAC load detection,
- * do NOT poll the connector do detect disconnect because
- * that would run DAC load detection again which can cause
- * visible visual glitches.
- *
- * Only allow to poll such a connector again when forcing.
- */
- if (!force && link->local_sink && link->type == dc_connection_analog_load)
- return connector->status;
-
- mutex_lock(&aconnector->hpd_lock);
-
- if (dc_link_detect_connection_type(aconnector->dc_link, &conn_type) &&
- conn_type != dc_connection_none) {
- mutex_lock(&adev->dm.dc_lock);
-
- /* Only call full link detection when a sink isn't created yet,
- * ie. just when the display is plugged in, otherwise we risk flickering.
- */
- if (link->local_sink ||
- dc_link_detect(link, DETECT_REASON_HPD))
- status = connector_status_connected;
-
- mutex_unlock(&adev->dm.dc_lock);
- }
-
- if (connector->status != status) {
- if (status == connector_status_disconnected) {
- if (link->local_sink)
- dc_sink_release(link->local_sink);
-
- link->local_sink = NULL;
- link->dpcd_sink_count = 0;
- link->type = dc_connection_none;
- }
-
- amdgpu_dm_update_connector_after_detect(aconnector);
- }
-
- mutex_unlock(&aconnector->hpd_lock);
- return status;
-}
-
-/**
- * amdgpu_dm_connector_detect() - Detect whether a DRM connector is connected to a display
- *
- * A connector is considered connected when it has a sink that is not NULL.
- * For connectors that support HPD (hotplug detection), the connection is
- * handled in the HPD interrupt.
- * For connectors that may not support HPD, such as analog connectors,
- * DRM will call this function repeatedly to poll them.
- *
- * Notes:
- * 1. This interface is NOT called in context of HPD irq.
- * 2. This interface *is called* in context of user-mode ioctl. Which
- * makes it a bad place for *any* MST-related activity.
- *
- * @connector: The DRM connector we are checking. We convert it to
- * amdgpu_dm_connector so we can read the DC link and state.
- * @force: If true, do a full detect again. This is used even when
- * a lighter check would normally be used to avoid flicker.
- *
- * Return: The connector status (connected, disconnected, or unknown).
- *
- */
-static enum drm_connector_status
-amdgpu_dm_connector_detect(struct drm_connector *connector, bool force)
-{
- struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
-
- update_subconnector_property(aconnector);
-
- if (aconnector->base.force == DRM_FORCE_ON ||
- aconnector->base.force == DRM_FORCE_ON_DIGITAL)
- return connector_status_connected;
- else if (aconnector->base.force == DRM_FORCE_OFF)
- return connector_status_disconnected;
-
- /* Poll analog connectors and only when either
- * disconnected or connected to an analog display.
- */
- if (drm_kms_helper_is_poll_worker() &&
- dc_connector_supports_analog(aconnector->dc_link->link_id.id) &&
- (!aconnector->dc_sink || aconnector->dc_sink->edid_caps.analog))
- return amdgpu_dm_connector_poll(aconnector, force);
-
- return (aconnector->dc_sink ? connector_status_connected :
- connector_status_disconnected);
-}
-
-int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector,
- struct drm_connector_state *connector_state,
- struct drm_property *property,
- uint64_t val)
-{
- struct drm_device *dev = connector->dev;
- struct amdgpu_device *adev = drm_to_adev(dev);
- struct dm_connector_state *dm_old_state =
- to_dm_connector_state(connector->state);
- struct dm_connector_state *dm_new_state =
- to_dm_connector_state(connector_state);
-
- int ret = -EINVAL;
-
- if (property == dev->mode_config.scaling_mode_property) {
- enum amdgpu_rmx_type rmx_type;
-
- switch (val) {
- case DRM_MODE_SCALE_CENTER:
- rmx_type = RMX_CENTER;
- break;
- case DRM_MODE_SCALE_ASPECT:
- rmx_type = RMX_ASPECT;
- break;
- case DRM_MODE_SCALE_FULLSCREEN:
- rmx_type = RMX_FULL;
- break;
- case DRM_MODE_SCALE_NONE:
- default:
- rmx_type = RMX_OFF;
- break;
- }
-
- if (dm_old_state->scaling == rmx_type)
- return 0;
-
- dm_new_state->scaling = rmx_type;
- ret = 0;
- } else if (property == adev->mode_info.underscan_hborder_property) {
- dm_new_state->underscan_hborder = val;
- ret = 0;
- } else if (property == adev->mode_info.underscan_vborder_property) {
- dm_new_state->underscan_vborder = val;
- ret = 0;
- } else if (property == adev->mode_info.underscan_property) {
- dm_new_state->underscan_enable = val;
- ret = 0;
- } else if (property == adev->mode_info.abm_level_property) {
- switch (val) {
- case ABM_SYSFS_CONTROL:
- dm_new_state->abm_sysfs_forbidden = false;
- break;
- case ABM_LEVEL_OFF:
- dm_new_state->abm_sysfs_forbidden = true;
- dm_new_state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE;
- break;
- default:
- dm_new_state->abm_sysfs_forbidden = true;
- dm_new_state->abm_level = val;
- }
- ret = 0;
- }
-
- return ret;
-}
-
-int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector,
- const struct drm_connector_state *state,
- struct drm_property *property,
- uint64_t *val)
-{
- struct drm_device *dev = connector->dev;
- struct amdgpu_device *adev = drm_to_adev(dev);
- struct dm_connector_state *dm_state =
- to_dm_connector_state(state);
- int ret = -EINVAL;
-
- if (property == dev->mode_config.scaling_mode_property) {
- switch (dm_state->scaling) {
- case RMX_CENTER:
- *val = DRM_MODE_SCALE_CENTER;
- break;
- case RMX_ASPECT:
- *val = DRM_MODE_SCALE_ASPECT;
- break;
- case RMX_FULL:
- *val = DRM_MODE_SCALE_FULLSCREEN;
- break;
- case RMX_OFF:
- default:
- *val = DRM_MODE_SCALE_NONE;
- break;
- }
- ret = 0;
- } else if (property == adev->mode_info.underscan_hborder_property) {
- *val = dm_state->underscan_hborder;
- ret = 0;
- } else if (property == adev->mode_info.underscan_vborder_property) {
- *val = dm_state->underscan_vborder;
- ret = 0;
- } else if (property == adev->mode_info.underscan_property) {
- *val = dm_state->underscan_enable;
- ret = 0;
- } else if (property == adev->mode_info.abm_level_property) {
- if (!dm_state->abm_sysfs_forbidden)
- *val = ABM_SYSFS_CONTROL;
- else
- *val = (dm_state->abm_level != ABM_LEVEL_IMMEDIATE_DISABLE) ?
- dm_state->abm_level : 0;
- ret = 0;
- }
-
- return ret;
-}
-
-static void amdgpu_dm_connector_unregister(struct drm_connector *connector)
-{
- struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector);
-
- if (amdgpu_dm_should_create_sysfs(amdgpu_dm_connector))
- sysfs_remove_group(&connector->kdev->kobj, &amdgpu_group);
-
- cec_notifier_conn_unregister(amdgpu_dm_connector->notifier);
- drm_dp_aux_unregister(&amdgpu_dm_connector->dm_dp_aux.aux);
-}
-
-static void amdgpu_dm_connector_destroy(struct drm_connector *connector)
-{
- struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
- struct amdgpu_device *adev = drm_to_adev(connector->dev);
- struct amdgpu_display_manager *dm = &adev->dm;
-
- /*
- * Call only if mst_mgr was initialized before since it's not done
- * for all connector types.
- */
- if (aconnector->mst_mgr.dev)
- drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr);
-
- /* Cancel and flush any pending HDMI HPD debounce work */
- if (aconnector->hdmi_hpd_debounce_delay_ms) {
- cancel_delayed_work_sync(&aconnector->hdmi_hpd_debounce_work);
- if (aconnector->hdmi_prev_sink) {
- dc_sink_release(aconnector->hdmi_prev_sink);
- aconnector->hdmi_prev_sink = NULL;
- }
- }
-
- if (aconnector->bl_idx != -1) {
- backlight_device_unregister(dm->backlight_dev[aconnector->bl_idx]);
- dm->backlight_dev[aconnector->bl_idx] = NULL;
- }
-
- if (aconnector->dc_em_sink)
- dc_sink_release(aconnector->dc_em_sink);
- aconnector->dc_em_sink = NULL;
- if (aconnector->dc_sink)
- dc_sink_release(aconnector->dc_sink);
- aconnector->dc_sink = NULL;
-
- drm_dp_cec_unregister_connector(&aconnector->dm_dp_aux.aux);
- drm_connector_unregister(connector);
- drm_connector_cleanup(connector);
- kfree(aconnector->dm_dp_aux.aux.name);
-
- kfree(connector);
-}
-
-void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector)
-{
- struct dm_connector_state *state =
- to_dm_connector_state(connector->state);
-
- if (connector->state)
- __drm_atomic_helper_connector_destroy_state(connector->state);
-
- kfree(state);
-
- state = kzalloc(sizeof(*state), GFP_KERNEL);
-
- if (state) {
- state->scaling = RMX_OFF;
- state->underscan_enable = false;
- state->underscan_hborder = 0;
- state->underscan_vborder = 0;
- state->base.max_requested_bpc = 8;
- state->vcpi_slots = 0;
- state->pbn = 0;
-
- if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
- if (amdgpu_dm_abm_level <= 0)
- state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE;
- else
- state->abm_level = amdgpu_dm_abm_level;
- }
-
- __drm_atomic_helper_connector_reset(connector, &state->base);
- }
-}
-
-struct drm_connector_state *
-amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector)
-{
- struct dm_connector_state *state =
- to_dm_connector_state(connector->state);
-
- struct dm_connector_state *new_state =
- kmemdup(state, sizeof(*state), GFP_KERNEL);
-
- if (!new_state)
- return NULL;
-
- __drm_atomic_helper_connector_duplicate_state(connector, &new_state->base);
-
- new_state->freesync_capable = state->freesync_capable;
- new_state->abm_level = state->abm_level;
- new_state->scaling = state->scaling;
- new_state->underscan_enable = state->underscan_enable;
- new_state->underscan_hborder = state->underscan_hborder;
- new_state->underscan_vborder = state->underscan_vborder;
- new_state->vcpi_slots = state->vcpi_slots;
- new_state->pbn = state->pbn;
- return &new_state->base;
-}
-
-static int
-amdgpu_dm_connector_late_register(struct drm_connector *connector)
-{
- struct amdgpu_dm_connector *amdgpu_dm_connector =
- to_amdgpu_dm_connector(connector);
- int r;
-
- if (amdgpu_dm_should_create_sysfs(amdgpu_dm_connector)) {
- r = sysfs_create_group(&connector->kdev->kobj,
- &amdgpu_group);
- if (r)
- return r;
- }
-
- amdgpu_dm_register_backlight_device(amdgpu_dm_connector);
-
- if ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) ||
- (connector->connector_type == DRM_MODE_CONNECTOR_eDP)) {
- amdgpu_dm_connector->dm_dp_aux.aux.dev = connector->kdev;
- r = drm_dp_aux_register(&amdgpu_dm_connector->dm_dp_aux.aux);
- if (r)
- return r;
- }
-
-#if defined(CONFIG_DEBUG_FS)
- connector_debugfs_init(amdgpu_dm_connector);
-#endif
-
- return 0;
-}
-
-static void amdgpu_dm_connector_funcs_force(struct drm_connector *connector)
-{
- struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
- struct dc_link *dc_link = aconnector->dc_link;
- struct dc_sink *dc_em_sink = aconnector->dc_em_sink;
- const struct drm_edid *drm_edid;
- struct i2c_adapter *ddc;
- struct drm_device *dev = connector->dev;
-
- if (dc_link && dc_link->aux_mode)
- ddc = &aconnector->dm_dp_aux.aux.ddc;
- else
- ddc = &aconnector->i2c->base;
-
- drm_edid = drm_edid_read_ddc(connector, ddc);
- drm_edid_connector_update(connector, drm_edid);
- if (!drm_edid) {
- drm_err(dev, "No EDID found on connector: %s.\n", connector->name);
- return;
- }
-
- aconnector->drm_edid = drm_edid;
- /* Update emulated (virtual) sink's EDID */
- if (dc_em_sink && dc_link) {
- // FIXME: Get rid of drm_edid_raw()
- const struct edid *edid = drm_edid_raw(drm_edid);
-
- memset(&dc_em_sink->edid_caps, 0, sizeof(struct dc_edid_caps));
- memmove(dc_em_sink->dc_edid.raw_edid, edid,
- (edid->extensions + 1) * EDID_LENGTH);
- dm_helpers_parse_edid_caps(
- dc_link,
- &dc_em_sink->dc_edid,
- &dc_em_sink->edid_caps);
- }
-}
-
-static const struct drm_connector_funcs amdgpu_dm_connector_funcs = {
- .reset = amdgpu_dm_connector_funcs_reset,
- .detect = amdgpu_dm_connector_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
- .destroy = amdgpu_dm_connector_destroy,
- .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state,
- .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
- .atomic_set_property = amdgpu_dm_connector_atomic_set_property,
- .atomic_get_property = amdgpu_dm_connector_atomic_get_property,
- .late_register = amdgpu_dm_connector_late_register,
- .early_unregister = amdgpu_dm_connector_unregister,
- .force = amdgpu_dm_connector_funcs_force
-};
-
-static int get_modes(struct drm_connector *connector)
-{
- return amdgpu_dm_connector_get_modes(connector);
-}
-
-static void create_eml_sink(struct amdgpu_dm_connector *aconnector)
-{
- struct drm_connector *connector = &aconnector->base;
- struct dc_link *dc_link = aconnector->dc_link;
- struct dc_sink_init_data init_params = {
- .link = aconnector->dc_link,
- .sink_signal = SIGNAL_TYPE_VIRTUAL
- };
- const struct drm_edid *drm_edid;
- const struct edid *edid;
- struct i2c_adapter *ddc;
-
- if (dc_link && dc_link->aux_mode)
- ddc = &aconnector->dm_dp_aux.aux.ddc;
- else
- ddc = &aconnector->i2c->base;
-
- drm_edid = drm_edid_read_ddc(connector, ddc);
- drm_edid_connector_update(connector, drm_edid);
- if (!drm_edid) {
- drm_err(connector->dev, "No EDID found on connector: %s.\n", connector->name);
- return;
- }
-
- if (connector->display_info.is_hdmi)
- init_params.sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
-
- aconnector->drm_edid = drm_edid;
-
- edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw()
- aconnector->dc_em_sink = dc_link_add_remote_sink(
- aconnector->dc_link,
- (uint8_t *)edid,
- (edid->extensions + 1) * EDID_LENGTH,
- &init_params);
-
- if (aconnector->base.force == DRM_FORCE_ON) {
- aconnector->dc_sink = aconnector->dc_link->local_sink ?
- aconnector->dc_link->local_sink :
- aconnector->dc_em_sink;
- if (aconnector->dc_sink)
- dc_sink_retain(aconnector->dc_sink);
- }
-}
-
-static void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector)
-{
- struct dc_link *link = (struct dc_link *)aconnector->dc_link;
-
- /*
- * In case of headless boot with force on for DP managed connector
- * Those settings have to be != 0 to get initial modeset
- */
- if (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT) {
- link->verified_link_cap.lane_count = LANE_COUNT_FOUR;
- link->verified_link_cap.link_rate = LINK_RATE_HIGH2;
- }
-
- create_eml_sink(aconnector);
-}
-
-static enum dc_status dm_validate_stream_and_context(struct dc *dc,
- struct dc_stream_state *stream)
-{
- enum dc_status dc_result = DC_ERROR_UNEXPECTED;
- struct dc_plane_state *dc_plane_state = NULL;
- struct dc_state *dc_state = NULL;
-
- if (!stream)
- goto cleanup;
-
- dc_plane_state = dc_create_plane_state(dc);
- if (!dc_plane_state)
- goto cleanup;
-
- dc_state = dc_state_create(dc, NULL);
- if (!dc_state)
- goto cleanup;
-
- /* populate stream to plane */
- dc_plane_state->src_rect.height = stream->src.height;
- dc_plane_state->src_rect.width = stream->src.width;
- dc_plane_state->dst_rect.height = stream->src.height;
- dc_plane_state->dst_rect.width = stream->src.width;
- dc_plane_state->clip_rect.height = stream->src.height;
- dc_plane_state->clip_rect.width = stream->src.width;
- dc_plane_state->plane_size.surface_pitch = ((stream->src.width + 255) / 256) * 256;
- dc_plane_state->plane_size.surface_size.height = stream->src.height;
- dc_plane_state->plane_size.surface_size.width = stream->src.width;
- dc_plane_state->plane_size.chroma_size.height = stream->src.height;
- dc_plane_state->plane_size.chroma_size.width = stream->src.width;
- dc_plane_state->format = SURFACE_PIXEL_FORMAT_GRPH_ARGB8888;
- dc_plane_state->tiling_info.gfx9.swizzle = DC_SW_UNKNOWN;
- dc_plane_state->rotation = ROTATION_ANGLE_0;
- dc_plane_state->is_tiling_rotated = false;
- dc_plane_state->tiling_info.gfx8.array_mode = DC_ARRAY_LINEAR_GENERAL;
-
- dc_result = dc_validate_stream(dc, stream);
- if (dc_result == DC_OK)
- dc_result = dc_validate_plane(dc, dc_plane_state);
-
- if (dc_result == DC_OK)
- dc_result = dc_state_add_stream(dc, dc_state, stream);
-
- if (dc_result == DC_OK && !dc_state_add_plane(
- dc,
- stream,
- dc_plane_state,
- dc_state))
- dc_result = DC_FAIL_ATTACH_SURFACES;
-
- if (dc_result == DC_OK)
- dc_result = dc_validate_global_state(dc, dc_state, DC_VALIDATE_MODE_ONLY);
-
-cleanup:
- if (dc_state)
- dc_state_release(dc_state);
-
- if (dc_plane_state)
- dc_plane_state_release(dc_plane_state);
-
- return dc_result;
-}
-
-struct dc_stream_state *
-create_validate_stream_for_sink(struct drm_connector *connector,
- const struct drm_display_mode *drm_mode,
- const struct dm_connector_state *dm_state,
- const struct dc_stream_state *old_stream)
-{
- struct amdgpu_dm_connector *aconnector = NULL;
- struct amdgpu_device *adev = drm_to_adev(connector->dev);
- struct dc_stream_state *stream;
- const struct drm_connector_state *drm_state = dm_state ? &dm_state->base : NULL;
- int requested_bpc = drm_state ? drm_state->max_requested_bpc : 8;
- enum dc_status dc_result = DC_OK;
- uint8_t bpc_limit = 6;
-
- if (!dm_state)
- return NULL;
-
- if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
- aconnector = to_amdgpu_dm_connector(connector);
-
- if (aconnector &&
- (aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_TYPE_A ||
- aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_FRL ||
- aconnector->dc_link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER))
- bpc_limit = 8;
-
- do {
- drm_dbg_kms(connector->dev, "Trying with %d bpc\n", requested_bpc);
- stream = create_stream_for_sink(connector, drm_mode,
- dm_state, old_stream,
- requested_bpc);
- if (stream == NULL) {
- drm_err(adev_to_drm(adev), "Failed to create stream for sink!\n");
- break;
- }
-
- dc_result = dc_validate_stream(adev->dm.dc, stream);
-
- if (!aconnector) /* writeback connector */
- return stream;
-
- if (dc_result == DC_OK && stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST)
- dc_result = dm_dp_mst_is_port_support_mode(aconnector, stream);
-
- if (dc_result == DC_OK)
- dc_result = dm_validate_stream_and_context(adev->dm.dc, stream);
-
- if (dc_result != DC_OK) {
- drm_dbg_kms(connector->dev, "Pruned mode %d x %d (clk %d) %s %s -- %s\n",
- drm_mode->hdisplay,
- drm_mode->vdisplay,
- drm_mode->clock,
- dc_pixel_encoding_to_str(stream->timing.pixel_encoding),
- dc_color_depth_to_str(stream->timing.display_color_depth),
- dc_status_to_str(dc_result));
-
- dc_stream_release(stream);
- stream = NULL;
- requested_bpc -= 2; /* lower bpc to retry validation */
- }
-
- } while (stream == NULL && requested_bpc >= bpc_limit);
-
- switch (dc_result) {
- /*
- * If we failed to validate DP bandwidth stream with the requested RGB color depth,
- * we try to fallback and configure in order:
- * YUV422 (8bpc, 6bpc)
- * YUV420 (8bpc, 6bpc)
- */
- case DC_FAIL_ENC_VALIDATE:
- case DC_EXCEED_DONGLE_CAP:
- case DC_NO_DP_LINK_BANDWIDTH:
- /* recursively entered twice and already tried both YUV422 and YUV420 */
- if (aconnector->force_yuv422_output && aconnector->force_yuv420_output)
- break;
- /* first failure; try YUV422 */
- if (!aconnector->force_yuv422_output) {
- drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV422\n",
- __func__, __LINE__, dc_result);
- aconnector->force_yuv422_output = true;
- /* recursively entered and YUV422 failed, try YUV420 */
- } else if (!aconnector->force_yuv420_output) {
- drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV420\n",
- __func__, __LINE__, dc_result);
- aconnector->force_yuv420_output = true;
- }
- stream = create_validate_stream_for_sink(connector, drm_mode,
- dm_state, old_stream);
- aconnector->force_yuv422_output = false;
- aconnector->force_yuv420_output = false;
- break;
- case DC_OK:
- break;
- default:
- drm_dbg_kms(connector->dev, "%s:%d Unhandled validation failure %d\n",
- __func__, __LINE__, dc_result);
- break;
- }
-
- return stream;
-}
-
-enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector,
- const struct drm_display_mode *mode)
-{
- int result = MODE_ERROR;
- struct dc_sink *dc_sink;
- struct drm_display_mode *test_mode;
- /* TODO: Unhardcode stream count */
- struct dc_stream_state *stream;
- /* we always have an amdgpu_dm_connector here since we got
- * here via the amdgpu_dm_connector_helper_funcs
- */
- struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
-
- if ((mode->flags & DRM_MODE_FLAG_INTERLACE) ||
- (mode->flags & DRM_MODE_FLAG_DBLSCAN))
- return result;
-
- /*
- * Only run this the first time mode_valid is called to initilialize
- * EDID mgmt
- */
- if (aconnector->base.force != DRM_FORCE_UNSPECIFIED &&
- !aconnector->dc_em_sink)
- handle_edid_mgmt(aconnector);
-
- dc_sink = to_amdgpu_dm_connector(connector)->dc_sink;
-
- if (dc_sink == NULL && aconnector->base.force != DRM_FORCE_ON_DIGITAL &&
- aconnector->base.force != DRM_FORCE_ON) {
- drm_err(connector->dev, "dc_sink is NULL!\n");
- goto fail;
- }
-
- test_mode = drm_mode_duplicate(connector->dev, mode);
- if (!test_mode)
- goto fail;
-
- drm_mode_set_crtcinfo(test_mode, 0);
-
- stream = create_validate_stream_for_sink(connector, test_mode,
- to_dm_connector_state(connector->state),
- NULL);
- drm_mode_destroy(connector->dev, test_mode);
- if (stream) {
- dc_stream_release(stream);
- result = MODE_OK;
- }
-
-fail:
- /* TODO: error handling*/
- return result;
-}
-
-static int fill_hdr_info_packet(const struct drm_connector_state *state,
- struct dc_info_packet *out)
-{
- struct hdmi_drm_infoframe frame;
- unsigned char buf[30]; /* 26 + 4 */
- ssize_t len;
- int ret, i;
-
- memset(out, 0, sizeof(*out));
-
- if (!state->hdr_output_metadata)
- return 0;
-
- ret = drm_hdmi_infoframe_set_hdr_metadata(&frame, state);
- if (ret)
- return ret;
-
- len = hdmi_drm_infoframe_pack_only(&frame, buf, sizeof(buf));
- if (len < 0)
- return (int)len;
-
- /* Static metadata is a fixed 26 bytes + 4 byte header. */
- if (len != 30)
- return -EINVAL;
-
- /* Prepare the infopacket for DC. */
- switch (state->connector->connector_type) {
- case DRM_MODE_CONNECTOR_HDMIA:
- out->hb0 = 0x87; /* type */
- out->hb1 = 0x01; /* version */
- out->hb2 = 0x1A; /* length */
- out->sb[0] = buf[3]; /* checksum */
- i = 1;
- break;
-
- case DRM_MODE_CONNECTOR_DisplayPort:
- case DRM_MODE_CONNECTOR_eDP:
- out->hb0 = 0x00; /* sdp id, zero */
- out->hb1 = 0x87; /* type */
- out->hb2 = 0x1D; /* payload len - 1 */
- out->hb3 = (0x13 << 2); /* sdp version */
- out->sb[0] = 0x01; /* version */
- out->sb[1] = 0x1A; /* length */
- i = 2;
- break;
-
- default:
- return -EINVAL;
- }
-
- memcpy(&out->sb[i], &buf[4], 26);
- out->valid = true;
-
- print_hex_dump(KERN_DEBUG, "HDR SB:", DUMP_PREFIX_NONE, 16, 1, out->sb,
- sizeof(out->sb), false);
-
- return 0;
-}
-
-static int
-amdgpu_dm_connector_atomic_check(struct drm_connector *conn,
- struct drm_atomic_state *state)
-{
- struct drm_connector_state *new_con_state =
- drm_atomic_get_new_connector_state(state, conn);
- struct drm_connector_state *old_con_state =
- drm_atomic_get_old_connector_state(state, conn);
- struct drm_crtc *crtc = new_con_state->crtc;
- struct drm_crtc_state *new_crtc_state;
- struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(conn);
- int ret;
-
- if (WARN_ON(unlikely(!old_con_state || !new_con_state)))
- return -EINVAL;
-
- trace_amdgpu_dm_connector_atomic_check(new_con_state);
-
- if (conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort) {
- ret = drm_dp_mst_root_conn_atomic_check(new_con_state, &aconn->mst_mgr);
- if (ret < 0)
- return ret;
- }
-
- if (!crtc)
- return 0;
-
- if (new_con_state->privacy_screen_sw_state != old_con_state->privacy_screen_sw_state) {
- new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
- if (IS_ERR(new_crtc_state))
- return PTR_ERR(new_crtc_state);
-
- new_crtc_state->mode_changed = true;
- }
-
- if (new_con_state->colorspace != old_con_state->colorspace) {
- new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
- if (IS_ERR(new_crtc_state))
- return PTR_ERR(new_crtc_state);
-
- new_crtc_state->mode_changed = true;
- }
-
- if (new_con_state->content_type != old_con_state->content_type) {
- new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
- if (IS_ERR(new_crtc_state))
- return PTR_ERR(new_crtc_state);
-
- new_crtc_state->mode_changed = true;
- }
-
- if (!drm_connector_atomic_hdr_metadata_equal(old_con_state, new_con_state)) {
- struct dc_info_packet hdr_infopacket;
-
- ret = fill_hdr_info_packet(new_con_state, &hdr_infopacket);
- if (ret)
- return ret;
-
- new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
- if (IS_ERR(new_crtc_state))
- return PTR_ERR(new_crtc_state);
-
- /*
- * DC considers the stream backends changed if the
- * static metadata changes. Forcing the modeset also
- * gives a simple way for userspace to switch from
- * 8bpc to 10bpc when setting the metadata to enter
- * or exit HDR.
- *
- * Changing the static metadata after it's been
- * set is permissible, however. So only force a
- * modeset if we're entering or exiting HDR.
- */
- new_crtc_state->mode_changed = new_crtc_state->mode_changed ||
- !old_con_state->hdr_output_metadata ||
- !new_con_state->hdr_output_metadata;
- }
-
- return 0;
-}
-
-static const struct drm_connector_helper_funcs
-amdgpu_dm_connector_helper_funcs = {
- /*
- * If hotplugging a second bigger display in FB Con mode, bigger resolution
- * modes will be filtered by drm_mode_validate_size(), and those modes
- * are missing after user start lightdm. So we need to renew modes list.
- * in get_modes call back, not just return the modes count
- */
- .get_modes = get_modes,
- .mode_valid = amdgpu_dm_connector_mode_valid,
- .atomic_check = amdgpu_dm_connector_atomic_check,
-};
-
-static void dm_encoder_helper_disable(struct drm_encoder *encoder)
-{
-
-}
-
-int convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth)
-{
- switch (display_color_depth) {
- case COLOR_DEPTH_666:
- return 6;
- case COLOR_DEPTH_888:
- return 8;
- case COLOR_DEPTH_101010:
- return 10;
- case COLOR_DEPTH_121212:
- return 12;
- case COLOR_DEPTH_141414:
- return 14;
- case COLOR_DEPTH_161616:
- return 16;
- default:
- break;
- }
- return 0;
-}
-
-static int dm_encoder_helper_atomic_check(struct drm_encoder *encoder,
- struct drm_crtc_state *crtc_state,
- struct drm_connector_state *conn_state)
-{
- struct drm_atomic_state *state = crtc_state->state;
- struct drm_connector *connector = conn_state->connector;
- struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
- struct dm_connector_state *dm_new_connector_state = to_dm_connector_state(conn_state);
- const struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
- struct drm_dp_mst_topology_mgr *mst_mgr;
- struct drm_dp_mst_port *mst_port;
- struct drm_dp_mst_topology_state *mst_state;
- enum dc_color_depth color_depth;
- int clock, bpp = 0;
- bool is_y420 = false;
-
- if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) ||
- (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) {
- struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
- struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
- enum drm_mode_status result;
-
- result = drm_crtc_helper_mode_valid_fixed(encoder->crtc, adjusted_mode, native_mode);
- if (result != MODE_OK && dm_new_connector_state->scaling == RMX_OFF) {
- drm_dbg_driver(encoder->dev,
- "mode %dx%d@%dHz is not native, enabling scaling\n",
- adjusted_mode->hdisplay, adjusted_mode->vdisplay,
- drm_mode_vrefresh(adjusted_mode));
- dm_new_connector_state->scaling = RMX_ASPECT;
- }
- return 0;
- }
-
- if (!aconnector->mst_output_port)
- return 0;
-
- mst_port = aconnector->mst_output_port;
- mst_mgr = &aconnector->mst_root->mst_mgr;
-
- if (!crtc_state->connectors_changed && !crtc_state->mode_changed)
- return 0;
-
- mst_state = drm_atomic_get_mst_topology_state(state, mst_mgr);
- if (IS_ERR(mst_state))
- return PTR_ERR(mst_state);
-
- mst_state->pbn_div.full = dm_mst_get_pbn_divider(aconnector->mst_root->dc_link);
-
- if (!state->duplicated) {
- int max_bpc = conn_state->max_requested_bpc;
-
- is_y420 = drm_mode_is_420_also(&connector->display_info, adjusted_mode) &&
- aconnector->force_yuv420_output;
- color_depth = convert_color_depth_from_display_info(connector,
- is_y420,
- max_bpc);
- bpp = convert_dc_color_depth_into_bpc(color_depth) * 3;
- clock = adjusted_mode->clock;
- dm_new_connector_state->pbn = drm_dp_calc_pbn_mode(clock, bpp << 4);
- }
-
- dm_new_connector_state->vcpi_slots =
- drm_dp_atomic_find_time_slots(state, mst_mgr, mst_port,
- dm_new_connector_state->pbn);
- if (dm_new_connector_state->vcpi_slots < 0) {
- drm_dbg_atomic(connector->dev, "failed finding vcpi slots: %d\n", (int)dm_new_connector_state->vcpi_slots);
- return dm_new_connector_state->vcpi_slots;
- }
- return 0;
-}
-
-const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs = {
- .disable = dm_encoder_helper_disable,
- .atomic_check = dm_encoder_helper_atomic_check
-};
-
-static int dm_update_mst_vcpi_slots_for_dsc(struct drm_atomic_state *state,
- struct dc_state *dc_state,
- struct dsc_mst_fairness_vars *vars)
-{
- struct dc_stream_state *stream = NULL;
- struct drm_connector *connector;
- struct drm_connector_state *new_con_state;
- struct amdgpu_dm_connector *aconnector;
- struct dm_connector_state *dm_conn_state;
- int i, j, ret;
- int vcpi, pbn_div, pbn = 0, slot_num = 0;
-
- for_each_new_connector_in_state(state, connector, new_con_state, i) {
-
- if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
- continue;
-
- aconnector = to_amdgpu_dm_connector(connector);
-
- if (!aconnector->mst_output_port)
- continue;
-
- if (!new_con_state || !new_con_state->crtc)
- continue;
-
- dm_conn_state = to_dm_connector_state(new_con_state);
-
- for (j = 0; j < dc_state->stream_count; j++) {
- stream = dc_state->streams[j];
- if (!stream)
- continue;
+ for (j = 0; j < dc_state->stream_count; j++) {
+ stream = dc_state->streams[j];
+ if (!stream)
+ continue;
if ((struct amdgpu_dm_connector *)stream->dm_stream_context == aconnector)
break;
@@ -5843,756 +3495,9 @@ static int dm_update_mst_vcpi_slots_for_dsc(struct drm_atomic_state *state,
return vcpi;
dm_conn_state->pbn = pbn;
- dm_conn_state->vcpi_slots = vcpi;
- }
- return 0;
-}
-
-static int to_drm_connector_type(enum signal_type st, uint32_t connector_id)
-{
- switch (st) {
- case SIGNAL_TYPE_HDMI_TYPE_A:
- return DRM_MODE_CONNECTOR_HDMIA;
- case SIGNAL_TYPE_EDP:
- return DRM_MODE_CONNECTOR_eDP;
- case SIGNAL_TYPE_LVDS:
- return DRM_MODE_CONNECTOR_LVDS;
- case SIGNAL_TYPE_RGB:
- return DRM_MODE_CONNECTOR_VGA;
- case SIGNAL_TYPE_DISPLAY_PORT:
- case SIGNAL_TYPE_DISPLAY_PORT_MST:
- /* External DP bridges have a different connector type. */
- if (connector_id == CONNECTOR_ID_VGA)
- return DRM_MODE_CONNECTOR_VGA;
- else if (connector_id == CONNECTOR_ID_LVDS)
- return DRM_MODE_CONNECTOR_LVDS;
-
- return DRM_MODE_CONNECTOR_DisplayPort;
- case SIGNAL_TYPE_DVI_DUAL_LINK:
- case SIGNAL_TYPE_DVI_SINGLE_LINK:
- if (connector_id == CONNECTOR_ID_SINGLE_LINK_DVII ||
- connector_id == CONNECTOR_ID_DUAL_LINK_DVII)
- return DRM_MODE_CONNECTOR_DVII;
-
- return DRM_MODE_CONNECTOR_DVID;
- case SIGNAL_TYPE_VIRTUAL:
- return DRM_MODE_CONNECTOR_VIRTUAL;
-
- default:
- return DRM_MODE_CONNECTOR_Unknown;
- }
-}
-
-static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector)
-{
- struct drm_encoder *encoder;
-
- /* There is only one encoder per connector */
- drm_connector_for_each_possible_encoder(connector, encoder)
- return encoder;
-
- return NULL;
-}
-
-static void amdgpu_dm_get_native_mode(struct drm_connector *connector)
-{
- struct drm_encoder *encoder;
- struct amdgpu_encoder *amdgpu_encoder;
-
- encoder = amdgpu_dm_connector_to_encoder(connector);
-
- if (encoder == NULL)
- return;
-
- amdgpu_encoder = to_amdgpu_encoder(encoder);
-
- amdgpu_encoder->native_mode.clock = 0;
-
- if (!list_empty(&connector->probed_modes)) {
- struct drm_display_mode *preferred_mode = NULL;
-
- list_for_each_entry(preferred_mode,
- &connector->probed_modes,
- head) {
- if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED)
- amdgpu_encoder->native_mode = *preferred_mode;
-
- break;
- }
-
- }
-}
-
-static struct drm_display_mode *
-amdgpu_dm_create_common_mode(struct drm_encoder *encoder,
- const char *name,
- int hdisplay, int vdisplay)
-{
- struct drm_device *dev = encoder->dev;
- struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
- struct drm_display_mode *mode = NULL;
- struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
-
- mode = drm_mode_duplicate(dev, native_mode);
-
- if (mode == NULL)
- return NULL;
-
- mode->hdisplay = hdisplay;
- mode->vdisplay = vdisplay;
- mode->type &= ~DRM_MODE_TYPE_PREFERRED;
- strscpy(mode->name, name, DRM_DISPLAY_MODE_LEN);
-
- return mode;
-
-}
-
-static const struct amdgpu_dm_mode_size {
- char name[DRM_DISPLAY_MODE_LEN];
- int w;
- int h;
-} common_modes[] = {
- { "640x480", 640, 480},
- { "800x600", 800, 600},
- { "1024x768", 1024, 768},
- { "1280x720", 1280, 720},
- { "1280x800", 1280, 800},
- {"1280x1024", 1280, 1024},
- { "1440x900", 1440, 900},
- {"1680x1050", 1680, 1050},
- {"1600x1200", 1600, 1200},
- {"1920x1080", 1920, 1080},
- {"1920x1200", 1920, 1200}
-};
-
-static void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder,
- struct drm_connector *connector)
-{
- struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
- struct drm_display_mode *mode = NULL;
- struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
- struct amdgpu_dm_connector *amdgpu_dm_connector =
- to_amdgpu_dm_connector(connector);
- int i;
- int n;
-
- if ((connector->connector_type != DRM_MODE_CONNECTOR_eDP) &&
- (connector->connector_type != DRM_MODE_CONNECTOR_LVDS))
- return;
-
- n = ARRAY_SIZE(common_modes);
-
- for (i = 0; i < n; i++) {
- struct drm_display_mode *curmode = NULL;
- bool mode_existed = false;
-
- if (common_modes[i].w > native_mode->hdisplay ||
- common_modes[i].h > native_mode->vdisplay ||
- (common_modes[i].w == native_mode->hdisplay &&
- common_modes[i].h == native_mode->vdisplay))
- continue;
-
- list_for_each_entry(curmode, &connector->probed_modes, head) {
- if (common_modes[i].w == curmode->hdisplay &&
- common_modes[i].h == curmode->vdisplay) {
- mode_existed = true;
- break;
- }
- }
-
- if (mode_existed)
- continue;
-
- mode = amdgpu_dm_create_common_mode(encoder,
- common_modes[i].name, common_modes[i].w,
- common_modes[i].h);
- if (!mode)
- continue;
-
- drm_mode_probed_add(connector, mode);
- amdgpu_dm_connector->num_modes++;
- }
-}
-
-static void amdgpu_set_panel_orientation(struct drm_connector *connector)
-{
- struct drm_encoder *encoder;
- struct amdgpu_encoder *amdgpu_encoder;
- const struct drm_display_mode *native_mode;
-
- if (connector->connector_type != DRM_MODE_CONNECTOR_eDP &&
- connector->connector_type != DRM_MODE_CONNECTOR_LVDS)
- return;
-
- mutex_lock(&connector->dev->mode_config.mutex);
- amdgpu_dm_connector_get_modes(connector);
- mutex_unlock(&connector->dev->mode_config.mutex);
-
- encoder = amdgpu_dm_connector_to_encoder(connector);
- if (!encoder)
- return;
-
- amdgpu_encoder = to_amdgpu_encoder(encoder);
-
- native_mode = &amdgpu_encoder->native_mode;
- if (native_mode->hdisplay == 0 || native_mode->vdisplay == 0)
- return;
-
- drm_connector_set_panel_orientation_with_quirk(connector,
- DRM_MODE_PANEL_ORIENTATION_UNKNOWN,
- native_mode->hdisplay,
- native_mode->vdisplay);
-}
-
-static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector,
- const struct drm_edid *drm_edid)
-{
- struct amdgpu_dm_connector *amdgpu_dm_connector =
- to_amdgpu_dm_connector(connector);
-
- if (drm_edid) {
- /* empty probed_modes */
- INIT_LIST_HEAD(&connector->probed_modes);
- amdgpu_dm_connector->num_modes =
- drm_edid_connector_add_modes(connector);
-
- /* sorting the probed modes before calling function
- * amdgpu_dm_get_native_mode() since EDID can have
- * more than one preferred mode. The modes that are
- * later in the probed mode list could be of higher
- * and preferred resolution. For example, 3840x2160
- * resolution in base EDID preferred timing and 4096x2160
- * preferred resolution in DID extension block later.
- */
- drm_mode_sort(&connector->probed_modes);
- amdgpu_dm_get_native_mode(connector);
-
- /* Freesync capabilities are reset by calling
- * drm_edid_connector_add_modes() and need to be
- * restored here.
- */
- amdgpu_dm_update_freesync_caps(connector, drm_edid, false);
- } else {
- amdgpu_dm_connector->num_modes = 0;
- }
-}
-
-static bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector,
- struct drm_display_mode *mode)
-{
- struct drm_display_mode *m;
-
- list_for_each_entry(m, &aconnector->base.probed_modes, head) {
- if (drm_mode_equal(m, mode))
- return true;
- }
-
- return false;
-}
-
-static uint add_fs_modes(struct amdgpu_dm_connector *aconnector)
-{
- const struct drm_display_mode *m;
- struct drm_display_mode *new_mode;
- uint i;
- u32 new_modes_count = 0;
-
- /* Standard FPS values
- *
- * 23.976 - TV/NTSC
- * 24 - Cinema
- * 25 - TV/PAL
- * 29.97 - TV/NTSC
- * 30 - TV/NTSC
- * 48 - Cinema HFR
- * 50 - TV/PAL
- * 60 - Commonly used
- * 48,72,96,120 - Multiples of 24
- */
- static const u32 common_rates[] = {
- 23976, 24000, 25000, 29970, 30000,
- 48000, 50000, 60000, 72000, 96000, 120000
- };
-
- /*
- * Find mode with highest refresh rate with the same resolution
- * as the preferred mode. Some monitors report a preferred mode
- * with lower resolution than the highest refresh rate supported.
- */
-
- m = get_highest_refresh_rate_mode(aconnector, true);
- if (!m)
- return 0;
-
- for (i = 0; i < ARRAY_SIZE(common_rates); i++) {
- u64 target_vtotal, target_vtotal_diff;
- u64 num, den;
-
- if (drm_mode_vrefresh(m) * 1000 < common_rates[i])
- continue;
-
- if (common_rates[i] < aconnector->min_vfreq * 1000 ||
- common_rates[i] > aconnector->max_vfreq * 1000)
- continue;
-
- num = (unsigned long long)m->clock * 1000 * 1000;
- den = common_rates[i] * (unsigned long long)m->htotal;
- target_vtotal = div_u64(num, den);
- target_vtotal_diff = target_vtotal - m->vtotal;
-
- /* Check for illegal modes */
- if (m->vsync_start + target_vtotal_diff < m->vdisplay ||
- m->vsync_end + target_vtotal_diff < m->vsync_start ||
- m->vtotal + target_vtotal_diff < m->vsync_end)
- continue;
-
- new_mode = drm_mode_duplicate(aconnector->base.dev, m);
- if (!new_mode)
- goto out;
-
- new_mode->vtotal += (u16)target_vtotal_diff;
- new_mode->vsync_start += (u16)target_vtotal_diff;
- new_mode->vsync_end += (u16)target_vtotal_diff;
- new_mode->type &= ~DRM_MODE_TYPE_PREFERRED;
- new_mode->type |= DRM_MODE_TYPE_DRIVER;
-
- if (!is_duplicate_mode(aconnector, new_mode)) {
- drm_mode_probed_add(&aconnector->base, new_mode);
- new_modes_count += 1;
- } else
- drm_mode_destroy(aconnector->base.dev, new_mode);
- }
- out:
- return new_modes_count;
-}
-
-static void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector,
- const struct drm_edid *drm_edid)
-{
- struct amdgpu_dm_connector *amdgpu_dm_connector =
- to_amdgpu_dm_connector(connector);
-
- if (!(amdgpu_freesync_vid_mode && drm_edid))
- return;
-
- if (!amdgpu_dm_connector->dc_sink || !amdgpu_dm_connector->dc_link)
- return;
-
- if (!dc_supports_vrr(amdgpu_dm_connector->dc_sink->ctx->dce_version))
- return;
-
- if (dc_connector_supports_analog(amdgpu_dm_connector->dc_link->link_id.id) &&
- amdgpu_dm_connector->dc_sink->edid_caps.analog)
- return;
-
- if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10)
- amdgpu_dm_connector->num_modes +=
- add_fs_modes(amdgpu_dm_connector);
-}
-
-static int amdgpu_dm_connector_get_modes(struct drm_connector *connector)
-{
- struct amdgpu_dm_connector *amdgpu_dm_connector =
- to_amdgpu_dm_connector(connector);
- struct dc_link *dc_link = amdgpu_dm_connector->dc_link;
- struct drm_encoder *encoder;
- const struct drm_edid *drm_edid = amdgpu_dm_connector->drm_edid;
- struct dc_link_settings *verified_link_cap = &dc_link->verified_link_cap;
- const struct dc *dc = dc_link->dc;
-
- encoder = amdgpu_dm_connector_to_encoder(connector);
-
- if (!drm_edid) {
- amdgpu_dm_connector->num_modes =
- drm_add_modes_noedid(connector, 640, 480);
- if (dc->link_srv->dp_get_encoding_format(verified_link_cap) == DP_128b_132b_ENCODING)
- amdgpu_dm_connector->num_modes +=
- drm_add_modes_noedid(connector, 1920, 1080);
-
- if (amdgpu_dm_connector->dc_sink &&
- amdgpu_dm_connector->dc_sink->edid_caps.analog &&
- dc_connector_supports_analog(dc_link->link_id.id)) {
- /* Analog monitor connected by DAC load detection.
- * Add common modes. It will be up to the user to select one that works.
- */
- for (int i = 0; i < ARRAY_SIZE(common_modes); i++)
- amdgpu_dm_connector->num_modes += drm_add_modes_noedid(
- connector, common_modes[i].w, common_modes[i].h);
- }
- } else {
- amdgpu_dm_connector_ddc_get_modes(connector, drm_edid);
- if (encoder)
- amdgpu_dm_connector_add_common_modes(encoder, connector);
- amdgpu_dm_connector_add_freesync_modes(connector, drm_edid);
- }
- amdgpu_dm_fbc_init(connector);
-
- return amdgpu_dm_connector->num_modes;
-}
-
-static const u32 supported_colorspaces =
- BIT(DRM_MODE_COLORIMETRY_BT709_YCC) |
- BIT(DRM_MODE_COLORIMETRY_OPRGB) |
- BIT(DRM_MODE_COLORIMETRY_BT2020_RGB) |
- BIT(DRM_MODE_COLORIMETRY_BT2020_YCC);
-
-void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
- struct amdgpu_dm_connector *aconnector,
- int connector_type,
- struct dc_link *link,
- int link_index)
-{
- struct amdgpu_device *adev = drm_to_adev(dm->ddev);
-
- /*
- * Some of the properties below require access to state, like bpc.
- * Allocate some default initial connector state with our reset helper.
- */
- if (aconnector->base.funcs->reset)
- aconnector->base.funcs->reset(&aconnector->base);
-
- aconnector->connector_id = link_index;
- aconnector->bl_idx = -1;
- aconnector->dc_link = link;
- aconnector->base.interlace_allowed = false;
- aconnector->base.doublescan_allowed = false;
- aconnector->base.stereo_allowed = false;
- aconnector->base.dpms = DRM_MODE_DPMS_OFF;
- aconnector->hpd.hpd = AMDGPU_HPD_NONE; /* not used */
- aconnector->audio_inst = -1;
- aconnector->pack_sdp_v1_3 = false;
- aconnector->as_type = ADAPTIVE_SYNC_TYPE_NONE;
- memset(&aconnector->vsdb_info, 0, sizeof(aconnector->vsdb_info));
- mutex_init(&aconnector->hpd_lock);
- mutex_init(&aconnector->handle_mst_msg_ready);
-
- /*
- * If HDMI HPD debounce delay is set, use the minimum between selected
- * value and AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS
- */
- if (amdgpu_hdmi_hpd_debounce_delay_ms) {
- aconnector->hdmi_hpd_debounce_delay_ms = min(amdgpu_hdmi_hpd_debounce_delay_ms,
- AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS);
- INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, amdgpu_dm_hdmi_hpd_debounce_work);
- aconnector->hdmi_prev_sink = NULL;
- } else {
- aconnector->hdmi_hpd_debounce_delay_ms = 0;
- }
-
- /*
- * configure support HPD hot plug connector_>polled default value is 0
- * which means HPD hot plug not supported
- */
- switch (connector_type) {
- case DRM_MODE_CONNECTOR_HDMIA:
- aconnector->base.polled = DRM_CONNECTOR_POLL_HPD;
- aconnector->base.ycbcr_420_allowed =
- link->link_enc->features.hdmi_ycbcr420_supported ? true : false;
- break;
- case DRM_MODE_CONNECTOR_DisplayPort:
- aconnector->base.polled = DRM_CONNECTOR_POLL_HPD;
- link->link_enc = link_enc_cfg_get_link_enc(link);
- ASSERT(link->link_enc);
- if (link->link_enc)
- aconnector->base.ycbcr_420_allowed =
- link->link_enc->features.dp_ycbcr420_supported ? true : false;
- break;
- case DRM_MODE_CONNECTOR_DVID:
- aconnector->base.polled = DRM_CONNECTOR_POLL_HPD;
- break;
- case DRM_MODE_CONNECTOR_DVII:
- case DRM_MODE_CONNECTOR_VGA:
- aconnector->base.polled =
- DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
- break;
- default:
- break;
- }
-
- drm_object_attach_property(&aconnector->base.base,
- dm->ddev->mode_config.scaling_mode_property,
- DRM_MODE_SCALE_NONE);
-
- if (connector_type == DRM_MODE_CONNECTOR_HDMIA
- || (connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root))
- drm_connector_attach_broadcast_rgb_property(&aconnector->base);
-
- drm_object_attach_property(&aconnector->base.base,
- adev->mode_info.underscan_property,
- UNDERSCAN_OFF);
- drm_object_attach_property(&aconnector->base.base,
- adev->mode_info.underscan_hborder_property,
- 0);
- drm_object_attach_property(&aconnector->base.base,
- adev->mode_info.underscan_vborder_property,
- 0);
-
- if (!aconnector->mst_root)
- drm_connector_attach_max_bpc_property(&aconnector->base, 8, 16);
-
- aconnector->base.state->max_bpc = 16;
- aconnector->base.state->max_requested_bpc = aconnector->base.state->max_bpc;
-
- if (connector_type == DRM_MODE_CONNECTOR_HDMIA) {
- /* Content Type is currently only implemented for HDMI. */
- drm_connector_attach_content_type_property(&aconnector->base);
- }
-
- if (connector_type == DRM_MODE_CONNECTOR_HDMIA) {
- if (!drm_mode_create_hdmi_colorspace_property(&aconnector->base, supported_colorspaces))
- drm_connector_attach_colorspace_property(&aconnector->base);
- } else if ((connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root) ||
- connector_type == DRM_MODE_CONNECTOR_eDP) {
- if (!drm_mode_create_dp_colorspace_property(&aconnector->base, supported_colorspaces))
- drm_connector_attach_colorspace_property(&aconnector->base);
- }
-
- if (connector_type == DRM_MODE_CONNECTOR_HDMIA ||
- connector_type == DRM_MODE_CONNECTOR_DisplayPort ||
- connector_type == DRM_MODE_CONNECTOR_eDP) {
- drm_connector_attach_hdr_output_metadata_property(&aconnector->base);
-
- if (!aconnector->mst_root)
- drm_connector_attach_vrr_capable_property(&aconnector->base);
-
- if (adev->dm.hdcp_workqueue)
- drm_connector_attach_content_protection_property(&aconnector->base, true);
- }
-
- if (connector_type == DRM_MODE_CONNECTOR_eDP) {
- struct drm_privacy_screen *privacy_screen;
-
- drm_connector_attach_panel_type_property(&aconnector->base);
-
- privacy_screen = drm_privacy_screen_get(adev_to_drm(adev)->dev, NULL);
- if (!IS_ERR(privacy_screen)) {
- drm_connector_attach_privacy_screen_provider(&aconnector->base,
- privacy_screen);
- } else if (PTR_ERR(privacy_screen) != -ENODEV) {
- drm_warn(adev_to_drm(adev), "Error getting privacy-screen\n");
- }
- }
-}
-
-static int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap,
- struct i2c_msg *msgs, int num)
-{
- struct amdgpu_i2c_adapter *i2c = i2c_get_adapdata(i2c_adap);
- struct ddc_service *ddc_service = i2c->ddc_service;
- struct i2c_command cmd;
- int i;
- int result = -EIO;
-
- if (!ddc_service->ddc_pin)
- return result;
-
- cmd.payloads = kcalloc(num, sizeof(struct i2c_payload), GFP_KERNEL);
-
- if (!cmd.payloads)
- return result;
-
- cmd.number_of_payloads = num;
- cmd.engine = I2C_COMMAND_ENGINE_DEFAULT;
- cmd.speed = 100;
-
- for (i = 0; i < num; i++) {
- cmd.payloads[i].write = !(msgs[i].flags & I2C_M_RD);
- cmd.payloads[i].address = msgs[i].addr;
- cmd.payloads[i].length = msgs[i].len;
- cmd.payloads[i].data = msgs[i].buf;
- }
-
- if (i2c->oem) {
- if (dc_submit_i2c_oem(
- ddc_service->ctx->dc,
- &cmd))
- result = num;
- } else {
- if (dc_submit_i2c(
- ddc_service->ctx->dc,
- ddc_service->link->link_index,
- &cmd))
- result = num;
- }
-
- kfree(cmd.payloads);
- return result;
-}
-
-static u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap)
-{
- return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
-}
-
-static const struct i2c_algorithm amdgpu_dm_i2c_algo = {
- .master_xfer = amdgpu_dm_i2c_xfer,
- .functionality = amdgpu_dm_i2c_func,
-};
-
-static struct amdgpu_i2c_adapter *
-create_i2c(struct ddc_service *ddc_service, bool oem)
-{
- struct amdgpu_device *adev = ddc_service->ctx->driver_context;
- struct amdgpu_i2c_adapter *i2c;
-
- i2c = kzalloc(sizeof(struct amdgpu_i2c_adapter), GFP_KERNEL);
- if (!i2c)
- return NULL;
- i2c->base.owner = THIS_MODULE;
- i2c->base.dev.parent = &adev->pdev->dev;
- i2c->base.algo = &amdgpu_dm_i2c_algo;
- if (oem)
- snprintf(i2c->base.name, sizeof(i2c->base.name), "AMDGPU DM i2c OEM bus");
- else
- snprintf(i2c->base.name, sizeof(i2c->base.name), "AMDGPU DM i2c hw bus %d",
- ddc_service->link->link_index);
- i2c_set_adapdata(&i2c->base, i2c);
- i2c->ddc_service = ddc_service;
- i2c->oem = oem;
-
- return i2c;
-}
-
-int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector)
-{
- struct cec_connector_info conn_info;
- struct drm_device *ddev = aconnector->base.dev;
- struct device *hdmi_dev = ddev->dev;
-
- if (amdgpu_dc_debug_mask & DC_DISABLE_HDMI_CEC) {
- drm_info(ddev, "HDMI-CEC feature masked\n");
- return -EINVAL;
- }
-
- cec_fill_conn_info_from_drm(&conn_info, &aconnector->base);
- aconnector->notifier =
- cec_notifier_conn_register(hdmi_dev, NULL, &conn_info);
- if (!aconnector->notifier) {
- drm_err(ddev, "Failed to create cec notifier\n");
- return -ENOMEM;
- }
-
- return 0;
-}
-
-/*
- * Note: this function assumes that dc_link_detect() was called for the
- * dc_link which will be represented by this aconnector.
- */
-static int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm,
- struct amdgpu_dm_connector *aconnector,
- u32 link_index,
- struct amdgpu_encoder *aencoder)
-{
- int res = 0;
- int connector_type;
- struct dc *dc = dm->dc;
- struct dc_link *link = dc_get_link_at_index(dc, link_index);
- struct amdgpu_i2c_adapter *i2c;
-
- /* Not needed for writeback connector */
- link->priv = aconnector;
-
-
- i2c = create_i2c(link->ddc, false);
- if (!i2c) {
- drm_err(adev_to_drm(dm->adev), "Failed to create i2c adapter data\n");
- return -ENOMEM;
- }
-
- aconnector->i2c = i2c;
- res = devm_i2c_add_adapter(dm->adev->dev, &i2c->base);
-
- if (res) {
- drm_err(adev_to_drm(dm->adev), "Failed to register hw i2c %d\n", link->link_index);
- goto out_free;
- }
-
- connector_type = to_drm_connector_type(link->connector_signal, link->link_id.id);
-
- res = drm_connector_init_with_ddc(
- dm->ddev,
- &aconnector->base,
- &amdgpu_dm_connector_funcs,
- connector_type,
- &i2c->base);
-
- if (res) {
- drm_err(adev_to_drm(dm->adev), "connector_init failed\n");
- aconnector->connector_id = -1;
- goto out_free;
- }
-
- drm_connector_helper_add(
- &aconnector->base,
- &amdgpu_dm_connector_helper_funcs);
-
- amdgpu_dm_connector_init_helper(
- dm,
- aconnector,
- connector_type,
- link,
- link_index);
-
- drm_connector_attach_encoder(
- &aconnector->base, &aencoder->base);
-
- if (connector_type == DRM_MODE_CONNECTOR_HDMIA ||
- connector_type == DRM_MODE_CONNECTOR_HDMIB)
- amdgpu_dm_initialize_hdmi_connector(aconnector);
-
- if (dc_is_dp_signal(link->connector_signal))
- amdgpu_dm_initialize_dp_connector(dm, aconnector, link->link_index);
-
-out_free:
- if (res) {
- kfree(i2c);
- aconnector->i2c = NULL;
- }
- return res;
-}
-
-int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev)
-{
- switch (adev->mode_info.num_crtc) {
- case 1:
- return 0x1;
- case 2:
- return 0x3;
- case 3:
- return 0x7;
- case 4:
- return 0xf;
- case 5:
- return 0x1f;
- case 6:
- default:
- return 0x3f;
- }
-}
-
-static int amdgpu_dm_encoder_init(struct drm_device *dev,
- struct amdgpu_encoder *aencoder,
- uint32_t link_index)
-{
- struct amdgpu_device *adev = drm_to_adev(dev);
-
- int res = drm_encoder_init(dev,
- &aencoder->base,
- &amdgpu_dm_encoder_funcs,
- DRM_MODE_ENCODER_TMDS,
- NULL);
-
- aencoder->base.possible_crtcs = amdgpu_dm_get_encoder_crtc_mask(adev);
-
- if (!res)
- aencoder->encoder_id = link_index;
- else
- aencoder->encoder_id = -1;
-
- drm_encoder_helper_add(&aencoder->base, &amdgpu_dm_encoder_helper_funcs);
-
- return res;
+ dm_conn_state->vcpi_slots = vcpi;
+ }
+ return 0;
}
static void manage_dm_interrupts(struct amdgpu_device *adev,
@@ -8173,6 +5078,72 @@ static int amdgpu_dm_atomic_setup_commit(struct drm_atomic_state *state)
return 0;
}
+static void set_multisync_trigger_params(
+ struct dc_stream_state *stream)
+{
+ struct dc_stream_state *master = NULL;
+
+ if (stream->triggered_crtc_reset.enabled) {
+ master = stream->triggered_crtc_reset.event_source;
+ stream->triggered_crtc_reset.event =
+ master->timing.flags.VSYNC_POSITIVE_POLARITY ?
+ CRTC_EVENT_VSYNC_RISING : CRTC_EVENT_VSYNC_FALLING;
+ stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_PIXEL;
+ }
+}
+
+static void set_master_stream(struct dc_stream_state *stream_set[],
+ int stream_count)
+{
+ int j, highest_rfr = 0, master_stream = 0;
+
+ for (j = 0; j < stream_count; j++) {
+ if (stream_set[j] && stream_set[j]->triggered_crtc_reset.enabled) {
+ int refresh_rate = 0;
+
+ refresh_rate = (stream_set[j]->timing.pix_clk_100hz*100)/
+ (stream_set[j]->timing.h_total*stream_set[j]->timing.v_total);
+ if (refresh_rate > highest_rfr) {
+ highest_rfr = refresh_rate;
+ master_stream = j;
+ }
+ }
+ }
+ for (j = 0; j < stream_count; j++) {
+ if (stream_set[j])
+ stream_set[j]->triggered_crtc_reset.event_source = stream_set[master_stream];
+ }
+}
+
+static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context)
+{
+ int i = 0;
+ struct dc_stream_state *stream;
+
+ if (context->stream_count < 2)
+ return;
+ for (i = 0; i < context->stream_count ; i++) {
+ if (!context->streams[i])
+ continue;
+ /*
+ * TODO: add a function to read AMD VSDB bits and set
+ * crtc_sync_master.multi_sync_enabled flag
+ * For now it's set to false
+ */
+ }
+
+ set_master_stream(context->streams, context->stream_count);
+
+ for (i = 0; i < context->stream_count ; i++) {
+ stream = context->streams[i];
+
+ if (!stream)
+ continue;
+
+ set_multisync_trigger_params(stream);
+ }
+}
+
/**
* amdgpu_dm_atomic_commit_tail() - AMDgpu DM's commit tail implementation.
* @state: The atomic state to commit
@@ -8241,7 +5212,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_state *state)
if ((new_con_state->hdmi.broadcast_rgb != old_con_state->hdmi.broadcast_rgb) &&
(dm_old_crtc_state->stream->output_color_space !=
- get_output_color_space(&dm_new_crtc_state->stream->timing, new_con_state)))
+ amdgpu_dm_get_output_color_space(&dm_new_crtc_state->stream->timing, new_con_state)))
output_color_space_changed = true;
abm_changed = dm_new_crtc_state->abm_level !=
@@ -8255,7 +5226,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_state *state)
stream_update.stream = dm_new_crtc_state->stream;
if (scaling_changed) {
- update_stream_scaling_settings(dev, &dm_new_con_state->base.crtc->mode,
+ amdgpu_dm_update_stream_scaling_settings(dev, &dm_new_con_state->base.crtc->mode,
dm_new_con_state, dm_new_crtc_state->stream);
stream_update.src = dm_new_crtc_state->stream->src;
@@ -8264,7 +5235,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_state *state)
if (output_color_space_changed) {
dm_new_crtc_state->stream->output_color_space
- = get_output_color_space(&dm_new_crtc_state->stream->timing, new_con_state);
+ = amdgpu_dm_get_output_color_space(&dm_new_crtc_state->stream->timing, new_con_state);
stream_update.output_color_space = &dm_new_crtc_state->stream->output_color_space;
}
@@ -8276,7 +5247,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_state *state)
}
if (hdr_changed) {
- fill_hdr_info_packet(new_con_state, &hdr_packet);
+ amdgpu_dm_fill_hdr_info_packet(new_con_state, &hdr_packet);
stream_update.hdr_static_metadata = &hdr_packet;
}
@@ -8484,104 +5455,6 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_state *state)
trace_amdgpu_dm_atomic_commit_tail_finish(state);
}
-static int dm_force_atomic_commit(struct drm_connector *connector)
-{
- int ret = 0;
- struct drm_device *ddev = connector->dev;
- struct drm_atomic_state *state = drm_atomic_state_alloc(ddev);
- struct amdgpu_crtc *disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc);
- struct drm_plane *plane = disconnected_acrtc->base.primary;
- struct drm_connector_state *conn_state;
- struct drm_crtc_state *crtc_state;
- struct drm_plane_state *plane_state;
-
- if (!state)
- return -ENOMEM;
-
- state->acquire_ctx = ddev->mode_config.acquire_ctx;
-
- /* Construct an atomic state to restore previous display setting */
-
- /*
- * Attach connectors to drm_atomic_state
- */
- conn_state = drm_atomic_get_connector_state(state, connector);
-
- /* Check for error in getting connector state */
- if (IS_ERR(conn_state)) {
- ret = PTR_ERR(conn_state);
- goto out;
- }
-
- /* Attach crtc to drm_atomic_state*/
- crtc_state = drm_atomic_get_crtc_state(state, &disconnected_acrtc->base);
-
- /* Check for error in getting crtc state */
- if (IS_ERR(crtc_state)) {
- ret = PTR_ERR(crtc_state);
- goto out;
- }
-
- /* force a restore */
- crtc_state->mode_changed = true;
-
- /* Attach plane to drm_atomic_state */
- plane_state = drm_atomic_get_plane_state(state, plane);
-
- /* Check for error in getting plane state */
- if (IS_ERR(plane_state)) {
- ret = PTR_ERR(plane_state);
- goto out;
- }
-
- /* Call commit internally with the state we just constructed */
- ret = drm_atomic_commit(state);
-
-out:
- drm_atomic_state_put(state);
- if (ret)
- drm_err(ddev, "Restoring old state failed with %i\n", ret);
-
- return ret;
-}
-
-/*
- * This function handles all cases when set mode does not come upon hotplug.
- * This includes when a display is unplugged then plugged back into the
- * same port and when running without usermode desktop manager supprot
- */
-void dm_restore_drm_connector_state(struct drm_device *dev,
- struct drm_connector *connector)
-{
- struct amdgpu_dm_connector *aconnector;
- struct amdgpu_crtc *disconnected_acrtc;
- struct dm_crtc_state *acrtc_state;
-
- if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
- return;
-
- aconnector = to_amdgpu_dm_connector(connector);
-
- if (!aconnector->dc_sink || !connector->state || !connector->encoder)
- return;
-
- disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc);
- if (!disconnected_acrtc)
- return;
-
- acrtc_state = to_dm_crtc_state(disconnected_acrtc->base.state);
- if (!acrtc_state->stream)
- return;
-
- /*
- * If the previous sink is not released and different from the current,
- * we deduce we are in a state where we can not rely on usermode call
- * to turn on the display, so we do it here
- */
- if (acrtc_state->stream->sink != aconnector->dc_sink)
- dm_force_atomic_commit(&aconnector->base);
-}
-
/*
* Grabs all modesetting locks to serialize against any blocking commits,
* Waits for completion of all non blocking commits.
@@ -8783,7 +5656,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm,
if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
goto skip_modeset;
- new_stream = create_validate_stream_for_sink(connector,
+ new_stream = amdgpu_dm_create_validate_stream_for_sink(connector,
&new_crtc_state->mode,
dm_new_conn_state,
dm_old_crtc_state->stream);
@@ -8811,7 +5684,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm,
dm_new_crtc_state->abm_level = dm_new_conn_state->abm_level;
- ret = fill_hdr_info_packet(drm_new_conn_state,
+ ret = amdgpu_dm_fill_hdr_info_packet(drm_new_conn_state,
&new_stream->hdr_static_metadata);
if (ret)
goto fail;
@@ -8880,11 +5753,11 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm,
goto skip_modeset;
} else if (amdgpu_freesync_vid_mode && aconnector &&
- is_freesync_video_mode(&new_crtc_state->mode,
+ amdgpu_dm_is_freesync_video_mode(&new_crtc_state->mode,
aconnector)) {
struct drm_display_mode *high_mode;
- high_mode = get_highest_refresh_rate_mode(aconnector, false);
+ high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false);
if (!drm_mode_equal(&new_crtc_state->mode, high_mode))
set_freesync_fixed_config(dm_new_crtc_state);
}
@@ -8976,7 +5849,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm,
/* Scaling or underscan settings */
if (is_scaling_state_different(dm_old_conn_state, dm_new_conn_state) ||
drm_atomic_crtc_needs_modeset(new_crtc_state))
- update_stream_scaling_settings(adev_to_drm(adev),
+ amdgpu_dm_update_stream_scaling_settings(adev_to_drm(adev),
&new_crtc_state->mode, dm_new_conn_state, dm_new_crtc_state->stream);
/* ABM settings */
@@ -10355,373 +7228,6 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev,
return ret;
}
-static bool dm_edid_parser_send_cea(struct amdgpu_display_manager *dm,
- unsigned int offset,
- unsigned int total_length,
- u8 *data,
- unsigned int length,
- struct amdgpu_hdmi_vsdb_info *vsdb)
-{
- bool res;
- union dmub_rb_cmd cmd;
- struct dmub_cmd_send_edid_cea *input;
- struct dmub_cmd_edid_cea_output *output;
-
- if (length > DMUB_EDID_CEA_DATA_CHUNK_BYTES)
- return false;
-
- memset(&cmd, 0, sizeof(cmd));
-
- input = &cmd.edid_cea.data.input;
-
- cmd.edid_cea.header.type = DMUB_CMD__EDID_CEA;
- cmd.edid_cea.header.sub_type = 0;
- cmd.edid_cea.header.payload_bytes =
- sizeof(cmd.edid_cea) - sizeof(cmd.edid_cea.header);
- input->offset = offset;
- input->length = length;
- input->cea_total_length = total_length;
- memcpy(input->payload, data, length);
-
- res = dc_wake_and_execute_dmub_cmd(dm->dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY);
- if (!res) {
- drm_err(adev_to_drm(dm->adev), "EDID CEA parser failed\n");
- return false;
- }
-
- output = &cmd.edid_cea.data.output;
-
- if (output->type == DMUB_CMD__EDID_CEA_ACK) {
- if (!output->ack.success) {
- drm_err(adev_to_drm(dm->adev), "EDID CEA ack failed at offset %d\n",
- output->ack.offset);
- }
- } else if (output->type == DMUB_CMD__EDID_CEA_AMD_VSDB) {
- if (!output->amd_vsdb.vsdb_found)
- return false;
-
- vsdb->freesync_supported = output->amd_vsdb.freesync_supported;
- vsdb->amd_vsdb_version = output->amd_vsdb.amd_vsdb_version;
- vsdb->min_refresh_rate_hz = output->amd_vsdb.min_frame_rate;
- vsdb->max_refresh_rate_hz = output->amd_vsdb.max_frame_rate;
- vsdb->freesync_mccs_vcp_code = output->amd_vsdb.freesync_mccs_vcp_code;
- } else {
- drm_warn(adev_to_drm(dm->adev), "Unknown EDID CEA parser results\n");
- return false;
- }
-
- return true;
-}
-
-static bool parse_edid_cea_dmcu(struct amdgpu_display_manager *dm,
- u8 *edid_ext, int len,
- struct amdgpu_hdmi_vsdb_info *vsdb_info)
-{
- int i;
-
- /* send extension block to DMCU for parsing */
- for (i = 0; i < len; i += 8) {
- bool res;
- int offset;
-
- /* send 8 bytes a time */
- if (!dc_edid_parser_send_cea(dm->dc, i, len, &edid_ext[i], 8))
- return false;
-
- if (i+8 == len) {
- /* EDID block sent completed, expect result */
- int version, min_rate, max_rate;
-
- res = dc_edid_parser_recv_amd_vsdb(dm->dc, &version, &min_rate, &max_rate);
- if (res) {
- /* amd vsdb found */
- vsdb_info->freesync_supported = 1;
- vsdb_info->amd_vsdb_version = version;
- vsdb_info->min_refresh_rate_hz = min_rate;
- vsdb_info->max_refresh_rate_hz = max_rate;
- /* Not enabled on DMCU*/
- vsdb_info->freesync_mccs_vcp_code = 0;
- return true;
- }
- /* not amd vsdb */
- return false;
- }
-
- /* check for ack*/
- res = dc_edid_parser_recv_cea_ack(dm->dc, &offset);
- if (!res)
- return false;
- }
-
- return false;
-}
-
-static bool parse_edid_cea_dmub(struct amdgpu_display_manager *dm,
- u8 *edid_ext, int len,
- struct amdgpu_hdmi_vsdb_info *vsdb_info)
-{
- int i;
-
- /* send extension block to DMCU for parsing */
- for (i = 0; i < len; i += 8) {
- /* send 8 bytes a time */
- if (!dm_edid_parser_send_cea(dm, i, len, &edid_ext[i], 8, vsdb_info))
- return false;
- }
-
- return vsdb_info->freesync_supported;
-}
-
-static bool parse_edid_cea(struct amdgpu_dm_connector *aconnector,
- u8 *edid_ext, int len,
- struct amdgpu_hdmi_vsdb_info *vsdb_info)
-{
- struct amdgpu_device *adev = drm_to_adev(aconnector->base.dev);
- bool ret;
-
- mutex_lock(&adev->dm.dc_lock);
- if (adev->dm.dmub_srv)
- ret = parse_edid_cea_dmub(&adev->dm, edid_ext, len, vsdb_info);
- else
- ret = parse_edid_cea_dmcu(&adev->dm, edid_ext, len, vsdb_info);
- mutex_unlock(&adev->dm.dc_lock);
- return ret;
-}
-
-static void parse_edid_displayid_vrr(struct drm_connector *connector,
- const struct edid *edid)
-{
- u8 *edid_ext = NULL;
- int i;
- int j = 0;
- u16 min_vfreq;
- u16 max_vfreq;
-
- if (!edid || !edid->extensions)
- return;
-
- /* Find DisplayID extension */
- for (i = 0; i < edid->extensions; i++) {
- edid_ext = (void *)(edid + (i + 1));
- if (edid_ext[0] == DISPLAYID_EXT)
- break;
- }
-
- if (i == edid->extensions)
- return;
-
- while (j < EDID_LENGTH) {
- /* Get dynamic video timing range from DisplayID if available */
- if (EDID_LENGTH - j > 13 && edid_ext[j] == 0x25 &&
- (edid_ext[j+1] & 0xFE) == 0 && (edid_ext[j+2] == 9)) {
- min_vfreq = edid_ext[j+9];
- if (edid_ext[j+1] & 7)
- max_vfreq = edid_ext[j+10] + ((edid_ext[j+11] & 3) << 8);
- else
- max_vfreq = edid_ext[j+10];
-
- if (max_vfreq && min_vfreq) {
- connector->display_info.monitor_range.max_vfreq = max_vfreq;
- connector->display_info.monitor_range.min_vfreq = min_vfreq;
-
- return;
- }
- }
- j++;
- }
-}
-
-static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector,
- struct amdgpu_hdmi_vsdb_info *vsdb_info)
-{
- struct drm_connector *connector = &aconnector->base;
-
- vsdb_info->replay_mode = connector->display_info.amd_vsdb.replay_mode;
- vsdb_info->amd_vsdb_version = connector->display_info.amd_vsdb.version;
-
- return connector->display_info.amd_vsdb.version != 0;
-}
-
-static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector,
- const struct edid *edid,
- struct amdgpu_hdmi_vsdb_info *vsdb_info)
-{
- u8 *edid_ext = NULL;
- int i;
- bool valid_vsdb_found = false;
-
- /*----- drm_find_cea_extension() -----*/
- /* No EDID or EDID extensions */
- if (edid == NULL || edid->extensions == 0)
- return -ENODEV;
-
- /* Find CEA extension */
- for (i = 0; i < edid->extensions; i++) {
- edid_ext = (uint8_t *)edid + EDID_LENGTH * (i + 1);
- if (edid_ext[0] == CEA_EXT)
- break;
- }
-
- if (i == edid->extensions)
- return -ENODEV;
-
- /*----- cea_db_offsets() -----*/
- if (edid_ext[0] != CEA_EXT)
- return -ENODEV;
-
- valid_vsdb_found = parse_edid_cea(aconnector, edid_ext, EDID_LENGTH, vsdb_info);
-
- return valid_vsdb_found ? i : -ENODEV;
-}
-
-/**
- * amdgpu_dm_update_freesync_caps - Update Freesync capabilities
- *
- * @connector: Connector to query.
- * @drm_edid: DRM EDID from monitor
- * @do_mccs: Controls whether MCCS (Monitor Control Command Set) over
- * DDC (Display Data Channel) transactions are performed. When true,
- * the driver queries the monitor to get or update additional FreeSync
- * capability information. When false, these transactions are skipped.
- *
- * Amdgpu supports Freesync in DP and HDMI displays, and it is required to keep
- * track of some of the display information in the internal data struct used by
- * amdgpu_dm. This function checks which type of connector we need to set the
- * FreeSync parameters.
- */
-void amdgpu_dm_update_freesync_caps(struct drm_connector *connector,
- const struct drm_edid *drm_edid, bool do_mccs)
-{
- int i = 0;
- struct amdgpu_dm_connector *amdgpu_dm_connector =
- to_amdgpu_dm_connector(connector);
- struct dm_connector_state *dm_con_state = NULL;
- struct dc_sink *sink;
- struct amdgpu_device *adev = drm_to_adev(connector->dev);
- struct amdgpu_hdmi_vsdb_info vsdb_info = {0};
- const struct edid *edid;
- bool freesync_capable = false;
- enum adaptive_sync_type as_type = ADAPTIVE_SYNC_TYPE_NONE;
-
- if (!connector->state) {
- drm_err(adev_to_drm(adev), "%s - Connector has no state", __func__);
- goto update;
- }
-
- sink = amdgpu_dm_connector->dc_sink ?
- amdgpu_dm_connector->dc_sink :
- amdgpu_dm_connector->dc_em_sink;
-
- drm_edid_connector_update(connector, drm_edid);
-
- if (!drm_edid || !sink) {
- dm_con_state = to_dm_connector_state(connector->state);
-
- amdgpu_dm_connector->min_vfreq = 0;
- amdgpu_dm_connector->max_vfreq = 0;
- freesync_capable = false;
-
- goto update;
- }
-
- dm_con_state = to_dm_connector_state(connector->state);
-
- if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version))
- goto update;
-
- edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw()
-
- /* Some eDP panels only have the refresh rate range info in DisplayID */
- if ((connector->display_info.monitor_range.min_vfreq == 0 ||
- connector->display_info.monitor_range.max_vfreq == 0))
- parse_edid_displayid_vrr(connector, edid);
-
- if (edid && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT ||
- sink->sink_signal == SIGNAL_TYPE_EDP)) {
- if (amdgpu_dm_connector->dc_link &&
- amdgpu_dm_connector->dc_link->dpcd_caps.allow_invalid_MSA_timing_param) {
- amdgpu_dm_connector->min_vfreq = connector->display_info.monitor_range.min_vfreq;
- amdgpu_dm_connector->max_vfreq = connector->display_info.monitor_range.max_vfreq;
- if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10)
- freesync_capable = true;
- }
-
- get_amd_vsdb(amdgpu_dm_connector, &vsdb_info);
-
- if (vsdb_info.replay_mode) {
- amdgpu_dm_connector->vsdb_info.replay_mode = vsdb_info.replay_mode;
- amdgpu_dm_connector->vsdb_info.amd_vsdb_version = vsdb_info.amd_vsdb_version;
- amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP;
- }
-
- } else if (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) {
- i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info);
- if (i >= 0) {
- amdgpu_dm_connector->vsdb_info = vsdb_info;
- sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code;
-
- if (vsdb_info.freesync_supported) {
- amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz;
- amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz;
- if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10)
- freesync_capable = true;
-
- connector->display_info.monitor_range.min_vfreq = vsdb_info.min_refresh_rate_hz;
- connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz;
- }
- }
- }
-
- if (amdgpu_dm_connector->dc_link)
- as_type = dm_get_adaptive_sync_support_type(amdgpu_dm_connector->dc_link);
-
- if (as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) {
- i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info);
- if (i >= 0) {
- amdgpu_dm_connector->vsdb_info = vsdb_info;
- sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code;
-
- if (vsdb_info.freesync_supported && vsdb_info.amd_vsdb_version > 0) {
- amdgpu_dm_connector->pack_sdp_v1_3 = true;
- amdgpu_dm_connector->as_type = as_type;
-
- amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz;
- amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz;
- if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10)
- freesync_capable = true;
-
- connector->display_info.monitor_range.min_vfreq = vsdb_info.min_refresh_rate_hz;
- connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz;
- }
- }
- }
-
- /* Handle MCCS */
- if (do_mccs) {
- dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink);
-
- if (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported)
- freesync_capable = false;
-
- if (sink->mccs_caps.freesync_supported && freesync_capable)
- dm_helpers_mccs_vcp_set(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink);
- }
-
-update:
- if (dm_con_state)
- dm_con_state->freesync_capable = freesync_capable;
-
- if (connector->state && amdgpu_dm_connector->dc_link && !freesync_capable &&
- amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported) {
- amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported = false;
- amdgpu_dm_connector->dc_link->replay_settings.replay_feature_enabled = false;
- }
-
- if (connector->vrr_capable_property)
- drm_connector_set_vrr_capable_property(connector,
- freesync_capable);
-}
-
void amdgpu_dm_trigger_timing_sync(struct drm_device *dev)
{
struct amdgpu_device *adev = drm_to_adev(dev);
@@ -10741,12 +7247,6 @@ void amdgpu_dm_trigger_timing_sync(struct drm_device *dev)
mutex_unlock(&adev->dm.dc_lock);
}
-static inline void amdgpu_dm_exit_ips_for_hw_access(struct dc *dc)
-{
- if (dc->ctx->dmub_srv && !dc->ctx->dmub_srv->idle_exit_counter)
- dc_exit_ips_for_hw_access(dc);
-}
-
void dm_write_reg_func(const struct dc_context *ctx, uint32_t address,
u32 value, const char *func_name)
{
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 c995faff4a07..2940dd5b7348 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1061,35 +1061,7 @@ struct dm_connector_state {
#define to_dm_connector_state(x)\
container_of((x), struct dm_connector_state, base)
-void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector);
-struct drm_connector_state *
-amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector);
-int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector,
- struct drm_connector_state *state,
- struct drm_property *property,
- uint64_t val);
-
-int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector,
- const struct drm_connector_state *state,
- struct drm_property *property,
- uint64_t *val);
-
-int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev);
-
-void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
- struct amdgpu_dm_connector *aconnector,
- int connector_type,
- struct dc_link *link,
- int link_index);
-
-enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector,
- const struct drm_display_mode *mode);
-
-void dm_restore_drm_connector_state(struct drm_device *dev,
- struct drm_connector *connector);
-
-void amdgpu_dm_update_freesync_caps(struct drm_connector *connector,
- const struct drm_edid *drm_edid, bool do_mccs);
+#include "amdgpu_dm_connector.h"
void amdgpu_dm_trigger_timing_sync(struct drm_device *dev);
@@ -1113,14 +1085,9 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc,
struct drm_plane_state *plane_state,
struct dc_plane_state *dc_plane_state);
-void amdgpu_dm_update_connector_after_detect(
- struct amdgpu_dm_connector *aconnector);
-
void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *info,
struct dc_edid_caps *edid_caps);
-extern const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs;
-
int amdgpu_dm_process_dmub_aux_transfer_sync(struct dc_context *ctx, unsigned int link_index,
struct aux_payload *payload, enum aux_return_code_type *operation_result);
@@ -1135,20 +1102,9 @@ bool amdgpu_dm_execute_fused_io(
int amdgpu_dm_process_dmub_set_config_sync(struct dc_context *ctx, unsigned int link_index,
struct set_config_cmd_payload *payload, enum set_config_status *operation_result);
-struct dc_stream_state *
- create_validate_stream_for_sink(struct drm_connector *connector,
- const struct drm_display_mode *drm_mode,
- const struct dm_connector_state *dm_state,
- const struct dc_stream_state *old_stream);
-
int dm_atomic_get_state(struct drm_atomic_state *state,
struct dm_atomic_state **dm_state);
-struct drm_connector *
-amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_state *state,
- struct drm_crtc *crtc);
-
-int convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth);
struct idle_workqueue *idle_create_workqueue(struct amdgpu_device *adev);
void *dm_allocate_gpu_mem(struct amdgpu_device *adev,
@@ -1161,10 +1117,6 @@ void dm_free_gpu_mem(struct amdgpu_device *adev,
bool amdgpu_dm_is_headless(struct amdgpu_device *adev);
-void hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector);
-void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector);
-int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector);
-
void retrieve_dmi_info(struct amdgpu_display_manager *dm);
void amdgpu_dm_emulated_link_detect(struct dc_link *link);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
new file mode 100644
index 000000000000..6ef257622f1a
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -0,0 +1,3557 @@
+// 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
+ *
+ */
+
+#include "dm_services_types.h"
+#include "dc.h"
+#include "dc/dc_dmub_srv.h"
+#include "dc/dc_edid_parser.h"
+#include "dc/dc_stat.h"
+#include "dc/dc_state.h"
+#include "dc/dc_stream.h"
+#include "dc/inc/core_types.h"
+#include "link_enc_cfg.h"
+#include "link/protocols/link_dpcd.h"
+#include "link_service_types.h"
+#include "link/protocols/link_dp_capability.h"
+#include "link/protocols/link_ddc.h"
+
+#include "amdgpu.h"
+#include "amdgpu_display.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_connector.h"
+#include "amdgpu_dm_plane.h"
+#include "amdgpu_dm_crtc.h"
+#include "amdgpu_dm_wb.h"
+#include "amdgpu_dm_mst_types.h"
+#if defined(CONFIG_DEBUG_FS)
+#include "amdgpu_dm_debugfs.h"
+#endif
+#include "amdgpu_dm_backlight.h"
+#include "amdgpu_dm_audio.h"
+#include "amdgpu_dm_irq.h"
+#include "amdgpu_dm_psr.h"
+#include "dm_helpers.h"
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_uapi.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_eld.h>
+#include <drm/drm_fixed.h>
+#include <drm/drm_mode.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_utils.h>
+#include <drm/display/drm_dp_mst_helper.h>
+#include <drm/display/drm_hdmi_helper.h>
+#include <drm/drm_privacy_screen_consumer.h>
+#include <drm/display/drm_hdcp_helper.h>
+
+#include <linux/backlight.h>
+
+#include <media/cec-notifier.h>
+
+#include "modules/inc/mod_freesync.h"
+#include "modules/inc/mod_power.h"
+
+#include "amdgpu_dm_trace.h"
+
+/* Encoder functions */
+
+static void amdgpu_dm_encoder_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+ kfree(encoder);
+}
+
+static const struct drm_encoder_funcs amdgpu_dm_encoder_funcs = {
+ .destroy = amdgpu_dm_encoder_destroy,
+};
+
+static void dm_encoder_helper_disable(struct drm_encoder *encoder)
+{
+}
+
+static int dm_encoder_helper_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct drm_atomic_state *state = crtc_state->state;
+ struct drm_connector *connector = conn_state->connector;
+ struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+ struct dm_connector_state *dm_new_connector_state = to_dm_connector_state(conn_state);
+ const struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+ struct drm_dp_mst_topology_mgr *mst_mgr;
+ struct drm_dp_mst_port *mst_port;
+ struct drm_dp_mst_topology_state *mst_state;
+ enum dc_color_depth color_depth;
+ int clock, bpp = 0;
+ bool is_y420 = false;
+
+ if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) ||
+ (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) {
+ struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
+ struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
+ enum drm_mode_status result;
+
+ result = drm_crtc_helper_mode_valid_fixed(encoder->crtc, adjusted_mode, native_mode);
+ if (result != MODE_OK && dm_new_connector_state->scaling == RMX_OFF) {
+ drm_dbg_driver(encoder->dev,
+ "mode %dx%d@%dHz is not native, enabling scaling\n",
+ adjusted_mode->hdisplay, adjusted_mode->vdisplay,
+ drm_mode_vrefresh(adjusted_mode));
+ dm_new_connector_state->scaling = RMX_ASPECT;
+ }
+ return 0;
+ }
+
+ if (!aconnector->mst_output_port)
+ return 0;
+
+ mst_port = aconnector->mst_output_port;
+ mst_mgr = &aconnector->mst_root->mst_mgr;
+
+ if (!crtc_state->connectors_changed && !crtc_state->mode_changed)
+ return 0;
+
+ mst_state = drm_atomic_get_mst_topology_state(state, mst_mgr);
+ if (IS_ERR(mst_state))
+ return PTR_ERR(mst_state);
+
+ mst_state->pbn_div.full = dm_mst_get_pbn_divider(aconnector->mst_root->dc_link);
+
+ if (!state->duplicated) {
+ int max_bpc = conn_state->max_requested_bpc;
+
+ is_y420 = drm_mode_is_420_also(&connector->display_info, adjusted_mode) &&
+ aconnector->force_yuv420_output;
+ color_depth = amdgpu_dm_convert_color_depth_from_display_info(connector,
+ is_y420,
+ max_bpc);
+ bpp = amdgpu_dm_convert_dc_color_depth_into_bpc(color_depth) * 3;
+ clock = adjusted_mode->clock;
+ dm_new_connector_state->pbn = drm_dp_calc_pbn_mode(clock, bpp << 4);
+ }
+
+ dm_new_connector_state->vcpi_slots =
+ drm_dp_atomic_find_time_slots(state, mst_mgr, mst_port,
+ dm_new_connector_state->pbn);
+ if (dm_new_connector_state->vcpi_slots < 0) {
+ drm_dbg_atomic(connector->dev, "failed finding vcpi slots: %d\n", (int)dm_new_connector_state->vcpi_slots);
+ return dm_new_connector_state->vcpi_slots;
+ }
+ return 0;
+}
+
+const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs = {
+ .disable = dm_encoder_helper_disable,
+ .atomic_check = dm_encoder_helper_atomic_check
+};
+
+int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev)
+{
+ switch (adev->mode_info.num_crtc) {
+ case 1:
+ return 0x1;
+ case 2:
+ return 0x3;
+ case 3:
+ return 0x7;
+ case 4:
+ return 0xf;
+ case 5:
+ return 0x1f;
+ case 6:
+ default:
+ return 0x3f;
+ }
+}
+
+int amdgpu_dm_encoder_init(struct drm_device *dev,
+ struct amdgpu_encoder *aencoder,
+ uint32_t link_index)
+{
+ struct amdgpu_device *adev = drm_to_adev(dev);
+
+ int res = drm_encoder_init(dev,
+ &aencoder->base,
+ &amdgpu_dm_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS,
+ NULL);
+
+ aencoder->base.possible_crtcs = amdgpu_dm_get_encoder_crtc_mask(adev);
+
+ if (!res)
+ aencoder->encoder_id = link_index;
+ else
+ aencoder->encoder_id = -1;
+
+ drm_encoder_helper_add(&aencoder->base, &amdgpu_dm_encoder_helper_funcs);
+
+ return res;
+}
+
+static enum drm_mode_subconnector get_subconnector_type(struct dc_link *link)
+{
+ switch (link->dpcd_caps.dongle_type) {
+ case DISPLAY_DONGLE_NONE:
+ return DRM_MODE_SUBCONNECTOR_Native;
+ case DISPLAY_DONGLE_DP_VGA_CONVERTER:
+ return DRM_MODE_SUBCONNECTOR_VGA;
+ case DISPLAY_DONGLE_DP_DVI_CONVERTER:
+ case DISPLAY_DONGLE_DP_DVI_DONGLE:
+ return DRM_MODE_SUBCONNECTOR_DVID;
+ case DISPLAY_DONGLE_DP_HDMI_CONVERTER:
+ case DISPLAY_DONGLE_DP_HDMI_DONGLE:
+ return DRM_MODE_SUBCONNECTOR_HDMIA;
+ case DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE:
+ default:
+ return DRM_MODE_SUBCONNECTOR_Unknown;
+ }
+}
+
+static void update_subconnector_property(struct amdgpu_dm_connector *aconnector)
+{
+ struct dc_link *link = aconnector->dc_link;
+ struct drm_connector *connector = &aconnector->base;
+ enum drm_mode_subconnector subconnector = DRM_MODE_SUBCONNECTOR_Unknown;
+
+ if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort)
+ return;
+
+ if (aconnector->dc_sink)
+ subconnector = get_subconnector_type(link);
+
+ drm_object_property_set_value(&connector->base,
+ connector->dev->mode_config.dp_subconnector_property,
+ subconnector);
+}
+
+static int amdgpu_dm_connector_get_modes(struct drm_connector *connector);
+
+static void amdgpu_dm_fbc_init(struct drm_connector *connector)
+{
+ struct amdgpu_device *adev = drm_to_adev(connector->dev);
+ struct dm_compressor_info *compressor = &adev->dm.compressor;
+ struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(connector);
+ struct drm_display_mode *mode;
+ unsigned long max_size = 0;
+
+ if (adev->dm.dc->fbc_compressor == NULL)
+ return;
+
+ if (aconn->dc_link->connector_signal != SIGNAL_TYPE_EDP)
+ return;
+
+ if (compressor->bo_ptr)
+ return;
+
+
+ list_for_each_entry(mode, &connector->modes, head) {
+ if (max_size < (unsigned long) mode->htotal * mode->vtotal)
+ max_size = (unsigned long) mode->htotal * mode->vtotal;
+ }
+
+ if (max_size) {
+ int r = amdgpu_bo_create_kernel(adev, max_size * 4, PAGE_SIZE,
+ AMDGPU_GEM_DOMAIN_GTT, &compressor->bo_ptr,
+ &compressor->gpu_addr, &compressor->cpu_addr);
+
+ if (r)
+ drm_err(adev_to_drm(adev), "DM: Failed to initialize FBC\n");
+ else {
+ adev->dm.dc->ctx->fbc_gpu_addr = compressor->gpu_addr;
+ drm_info(adev_to_drm(adev), "DM: FBC alloc %lu\n", max_size*4);
+ }
+
+ }
+
+}
+
+
+int amdgpu_dm_detect_mst_link_for_all_connectors(struct drm_device *dev)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector *connector;
+ struct drm_connector_list_iter iter;
+ int ret = 0;
+
+ drm_connector_list_iter_begin(dev, &iter);
+ drm_for_each_connector_iter(connector, &iter) {
+
+ if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ continue;
+
+ aconnector = to_amdgpu_dm_connector(connector);
+ if (aconnector->dc_link->type == dc_connection_mst_branch &&
+ aconnector->mst_mgr.aux) {
+ drm_dbg_kms(dev, "DM_MST: starting TM on aconnector: %p [id: %d]\n",
+ aconnector,
+ aconnector->base.base.id);
+
+ ret = drm_dp_mst_topology_mgr_set_mst(&aconnector->mst_mgr, true);
+ if (ret < 0) {
+ drm_err(dev, "DM_MST: Failed to start MST\n");
+ aconnector->dc_link->type =
+ dc_connection_single;
+ ret = dm_helpers_dp_mst_stop_top_mgr(aconnector->dc_link->ctx,
+ aconnector->dc_link);
+ break;
+ }
+ }
+ }
+ drm_connector_list_iter_end(&iter);
+
+ return ret;
+}
+
+static void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector)
+{
+ struct cec_notifier *n = aconnector->notifier;
+
+ if (!n)
+ return;
+
+ cec_notifier_phys_addr_invalidate(n);
+}
+
+void amdgpu_dm_hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector)
+{
+ struct drm_connector *connector = &aconnector->base;
+ struct cec_notifier *n = aconnector->notifier;
+
+ if (!n)
+ return;
+
+ cec_notifier_set_phys_addr(n,
+ connector->display_info.source_physical_address);
+}
+
+void amdgpu_dm_s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector *connector;
+ struct drm_connector_list_iter conn_iter;
+
+ drm_connector_list_iter_begin(ddev, &conn_iter);
+ drm_for_each_connector_iter(connector, &conn_iter) {
+ if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ continue;
+
+ aconnector = to_amdgpu_dm_connector(connector);
+ if (suspend)
+ hdmi_cec_unset_edid(aconnector);
+ else
+ amdgpu_dm_hdmi_cec_set_edid(aconnector);
+ }
+ drm_connector_list_iter_end(&conn_iter);
+}
+
+
+struct drm_connector *
+amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_state *state,
+ struct drm_crtc *crtc)
+{
+ u32 i;
+ struct drm_connector_state *new_con_state;
+ struct drm_connector *connector;
+ struct drm_crtc *crtc_from_state;
+
+ for_each_new_connector_in_state(state, connector, new_con_state, i) {
+ crtc_from_state = new_con_state->crtc;
+
+ if (crtc_from_state == crtc)
+ return connector;
+ }
+
+ return NULL;
+}
+
+static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector)
+{
+ struct drm_connector *connector = &aconnector->base;
+ struct drm_display_info *display_info = &connector->display_info;
+ struct dc_link *link = aconnector->dc_link;
+ struct amdgpu_device *adev;
+
+ adev = drm_to_adev(connector->dev);
+
+ link->panel_type = PANEL_TYPE_NONE;
+
+ switch (display_info->amd_vsdb.panel_type) {
+ case AMD_VSDB_PANEL_TYPE_OLED:
+ link->panel_type = PANEL_TYPE_OLED;
+ break;
+ case AMD_VSDB_PANEL_TYPE_MINILED:
+ link->panel_type = PANEL_TYPE_MINILED;
+ break;
+ }
+
+ /* If VSDB didn't determine panel type, check DPCD ext caps */
+ if (link->panel_type == PANEL_TYPE_NONE) {
+ if (link->dpcd_sink_ext_caps.bits.miniled == 1)
+ link->panel_type = PANEL_TYPE_MINILED;
+ if (link->dpcd_sink_ext_caps.bits.oled == 1)
+ link->panel_type = PANEL_TYPE_OLED;
+ }
+
+ /*
+ * TODO: get panel type from DID2 that has device technology field
+ * to specify if it's OLED or not. But we need to wait for DID2
+ * support in DC and EDID parser to be able to use it here.
+ */
+
+ if (link->panel_type == PANEL_TYPE_NONE) {
+ struct drm_amd_vsdb_info *vsdb = &display_info->amd_vsdb;
+ u32 lum1_max = vsdb->luminance_range1.max_luminance;
+ u32 lum2_max = vsdb->luminance_range2.max_luminance;
+
+ if (vsdb->version && link->local_sink &&
+ link->local_sink->edid_caps.manufacturer_id ==
+ DDC_MANUFACTURERNAME_SAMSUNG &&
+ lum1_max >= ((lum2_max * 3) / 2))
+ link->panel_type = PANEL_TYPE_MINILED;
+ }
+
+ if (link->panel_type == PANEL_TYPE_OLED)
+ drm_object_property_set_value(&connector->base,
+ adev_to_drm(adev)->mode_config.panel_type_property,
+ DRM_MODE_PANEL_TYPE_OLED);
+ else
+ drm_object_property_set_value(&connector->base,
+ adev_to_drm(adev)->mode_config.panel_type_property,
+ DRM_MODE_PANEL_TYPE_UNKNOWN);
+
+ drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type);
+}
+
+DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T))
+
+void amdgpu_dm_update_connector_after_detect(
+ struct amdgpu_dm_connector *aconnector)
+{
+ struct drm_connector *connector = &aconnector->base;
+ struct dc_sink *sink __free(sink_release) = NULL;
+ struct drm_device *dev = connector->dev;
+
+ /* MST handled by drm_mst framework */
+ if (aconnector->mst_mgr.mst_state == true)
+ return;
+
+ sink = aconnector->dc_link->local_sink;
+ if (sink)
+ dc_sink_retain(sink);
+
+ /*
+ * Edid mgmt connector gets first update only in mode_valid hook and then
+ * the connector sink is set to either fake or physical sink depends on link status.
+ * Skip if already done during boot.
+ */
+ if (aconnector->base.force != DRM_FORCE_UNSPECIFIED
+ && aconnector->dc_em_sink) {
+
+ /*
+ * For S3 resume with headless use eml_sink to fake stream
+ * because on resume connector->sink is set to NULL
+ */
+ guard(mutex)(&dev->mode_config.mutex);
+
+ if (sink) {
+ if (aconnector->dc_sink) {
+ amdgpu_dm_update_freesync_caps(connector, NULL, true);
+ /*
+ * retain and release below are used to
+ * bump up refcount for sink because the link doesn't point
+ * to it anymore after disconnect, so on next crtc to connector
+ * reshuffle by UMD we will get into unwanted dc_sink release
+ */
+ dc_sink_release(aconnector->dc_sink);
+ }
+ aconnector->dc_sink = sink;
+ dc_sink_retain(aconnector->dc_sink);
+ amdgpu_dm_update_freesync_caps(connector,
+ aconnector->drm_edid, true);
+ } else {
+ amdgpu_dm_update_freesync_caps(connector, NULL, true);
+ if (!aconnector->dc_sink) {
+ aconnector->dc_sink = aconnector->dc_em_sink;
+ dc_sink_retain(aconnector->dc_sink);
+ }
+ }
+
+ return;
+ }
+
+ /*
+ * TODO: temporary guard to look for proper fix
+ * if this sink is MST sink, we should not do anything
+ */
+ if (sink && sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT_MST)
+ return;
+
+ if (aconnector->dc_sink == sink) {
+ /*
+ * We got a DP short pulse (Link Loss, DP CTS, etc...).
+ * Do nothing!!
+ */
+ drm_dbg_kms(dev, "DCHPD: connector_id=%d: dc_sink didn't change.\n",
+ aconnector->connector_id);
+ return;
+ }
+
+ drm_dbg_kms(dev, "DCHPD: connector_id=%d: Old sink=%p New sink=%p\n",
+ aconnector->connector_id, aconnector->dc_sink, sink);
+
+ /* When polling, DRM has already locked the mutex for us. */
+ if (!drm_kms_helper_is_poll_worker())
+ mutex_lock(&dev->mode_config.mutex);
+
+ /*
+ * 1. Update status of the drm connector
+ * 2. Send an event and let userspace tell us what to do
+ */
+ if (sink) {
+ /*
+ * TODO: check if we still need the S3 mode update workaround.
+ * If yes, put it here.
+ */
+ if (aconnector->dc_sink) {
+ amdgpu_dm_update_freesync_caps(connector, NULL, true);
+ dc_sink_release(aconnector->dc_sink);
+ }
+
+ aconnector->dc_sink = sink;
+ dc_sink_retain(aconnector->dc_sink);
+ drm_edid_free(aconnector->drm_edid);
+ aconnector->drm_edid = NULL;
+ if (sink->dc_edid.length == 0) {
+ hdmi_cec_unset_edid(aconnector);
+ if (aconnector->dc_link->aux_mode)
+ drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux);
+ } else {
+ const struct edid *edid = (const struct edid *)sink->dc_edid.raw_edid;
+
+ aconnector->drm_edid = drm_edid_alloc(edid, sink->dc_edid.length);
+ drm_edid_connector_update(connector, aconnector->drm_edid);
+
+ amdgpu_dm_hdmi_cec_set_edid(aconnector);
+ if (aconnector->dc_link->aux_mode)
+ drm_dp_cec_attach(&aconnector->dm_dp_aux.aux,
+ connector->display_info.source_physical_address);
+ }
+
+ if (!aconnector->timing_requested) {
+ aconnector->timing_requested =
+ kzalloc(sizeof(struct dc_crtc_timing), GFP_KERNEL);
+ if (!aconnector->timing_requested)
+ drm_err(dev,
+ "failed to create aconnector->requested_timing\n");
+ }
+
+ amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true);
+ amdgpu_dm_update_connector_ext_caps(aconnector);
+ dm_set_panel_type(aconnector);
+ } else {
+ hdmi_cec_unset_edid(aconnector);
+ drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux);
+ amdgpu_dm_update_freesync_caps(connector, NULL, true);
+ aconnector->num_modes = 0;
+ dc_sink_release(aconnector->dc_sink);
+ aconnector->dc_sink = NULL;
+ drm_edid_free(aconnector->drm_edid);
+ aconnector->drm_edid = NULL;
+ kfree(aconnector->timing_requested);
+ aconnector->timing_requested = NULL;
+ /* Set CP to DESIRED if it was ENABLED, so we can re-enable it again on hotplug */
+ if (connector->state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED)
+ connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+ }
+
+ update_subconnector_property(aconnector);
+
+ /* When polling, the mutex will be unlocked for us by DRM. */
+ if (!drm_kms_helper_is_poll_worker())
+ mutex_unlock(&dev->mode_config.mutex);
+}
+
+enum dc_color_depth
+amdgpu_dm_convert_color_depth_from_display_info(const struct drm_connector *connector,
+ bool is_y420, int requested_bpc)
+{
+ u8 bpc;
+
+ if (is_y420) {
+ bpc = 8;
+
+ /* Cap display bpc based on HDMI 2.0 HF-VSDB */
+ if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48)
+ bpc = 16;
+ else if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36)
+ bpc = 12;
+ else if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30)
+ bpc = 10;
+ } else {
+ bpc = (uint8_t)connector->display_info.bpc;
+ /* Assume 8 bpc by default if no bpc is specified. */
+ bpc = bpc ? bpc : 8;
+ }
+
+ if (requested_bpc > 0) {
+ /*
+ * Cap display bpc based on the user requested value.
+ *
+ * The value for state->max_bpc may not correctly updated
+ * depending on when the connector gets added to the state
+ * or if this was called outside of atomic check, so it
+ * can't be used directly.
+ */
+ bpc = min_t(u8, bpc, requested_bpc);
+
+ /* Round down to the nearest even number. */
+ bpc = bpc - (bpc & 1);
+ }
+
+ switch (bpc) {
+ case 0:
+ /*
+ * Temporary Work around, DRM doesn't parse color depth for
+ * EDID revision before 1.4
+ * TODO: Fix edid parsing
+ */
+ return COLOR_DEPTH_888;
+ case 6:
+ return COLOR_DEPTH_666;
+ case 8:
+ return COLOR_DEPTH_888;
+ case 10:
+ return COLOR_DEPTH_101010;
+ case 12:
+ return COLOR_DEPTH_121212;
+ case 14:
+ return COLOR_DEPTH_141414;
+ case 16:
+ return COLOR_DEPTH_161616;
+ default:
+ return COLOR_DEPTH_UNDEFINED;
+ }
+}
+
+static enum dc_aspect_ratio
+get_aspect_ratio(const struct drm_display_mode *mode_in)
+{
+ /* 1-1 mapping, since both enums follow the HDMI spec. */
+ return (enum dc_aspect_ratio) mode_in->picture_aspect_ratio;
+}
+
+enum dc_color_space
+amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing,
+ const struct drm_connector_state *connector_state)
+{
+ enum dc_color_space color_space = COLOR_SPACE_SRGB;
+
+ switch (connector_state->colorspace) {
+ case DRM_MODE_COLORIMETRY_BT601_YCC:
+ if (dc_crtc_timing->flags.Y_ONLY)
+ color_space = COLOR_SPACE_YCBCR601_LIMITED;
+ else
+ color_space = COLOR_SPACE_YCBCR601;
+ break;
+ case DRM_MODE_COLORIMETRY_BT709_YCC:
+ if (dc_crtc_timing->flags.Y_ONLY)
+ color_space = COLOR_SPACE_YCBCR709_LIMITED;
+ else
+ color_space = COLOR_SPACE_YCBCR709;
+ break;
+ case DRM_MODE_COLORIMETRY_OPRGB:
+ color_space = COLOR_SPACE_ADOBERGB;
+ break;
+ case DRM_MODE_COLORIMETRY_BT2020_RGB:
+ case DRM_MODE_COLORIMETRY_BT2020_YCC:
+ if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB)
+ color_space = COLOR_SPACE_2020_RGB_FULLRANGE;
+ else
+ color_space = COLOR_SPACE_2020_YCBCR_LIMITED;
+ break;
+ case DRM_MODE_COLORIMETRY_DEFAULT: /* ITU601 */
+ default:
+ if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) {
+ color_space = COLOR_SPACE_SRGB;
+ if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED)
+ color_space = COLOR_SPACE_SRGB_LIMITED;
+ /*
+ * 27030khz is the separation point between HDTV and SDTV
+ * according to HDMI spec, we use YCbCr709 and YCbCr601
+ * respectively
+ */
+ } else if (dc_crtc_timing->pix_clk_100hz > 270300) {
+ if (dc_crtc_timing->flags.Y_ONLY)
+ color_space =
+ COLOR_SPACE_YCBCR709_LIMITED;
+ else
+ color_space = COLOR_SPACE_YCBCR709;
+ } else {
+ if (dc_crtc_timing->flags.Y_ONLY)
+ color_space =
+ COLOR_SPACE_YCBCR601_LIMITED;
+ else
+ color_space = COLOR_SPACE_YCBCR601;
+ }
+ break;
+ }
+
+ return color_space;
+}
+
+static enum display_content_type
+get_output_content_type(const struct drm_connector_state *connector_state)
+{
+ switch (connector_state->content_type) {
+ default:
+ case DRM_MODE_CONTENT_TYPE_NO_DATA:
+ return DISPLAY_CONTENT_TYPE_NO_DATA;
+ case DRM_MODE_CONTENT_TYPE_GRAPHICS:
+ return DISPLAY_CONTENT_TYPE_GRAPHICS;
+ case DRM_MODE_CONTENT_TYPE_PHOTO:
+ return DISPLAY_CONTENT_TYPE_PHOTO;
+ case DRM_MODE_CONTENT_TYPE_CINEMA:
+ return DISPLAY_CONTENT_TYPE_CINEMA;
+ case DRM_MODE_CONTENT_TYPE_GAME:
+ return DISPLAY_CONTENT_TYPE_GAME;
+ }
+}
+
+static bool adjust_colour_depth_from_display_info(
+ struct dc_crtc_timing *timing_out,
+ const struct drm_display_info *info)
+{
+ enum dc_color_depth depth = timing_out->display_color_depth;
+ int normalized_clk;
+
+ do {
+ normalized_clk = timing_out->pix_clk_100hz / 10;
+ /* YCbCr 4:2:0 requires additional adjustment of 1/2 */
+ if (timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420)
+ normalized_clk /= 2;
+ /* Adjusting pix clock following on HDMI spec based on colour depth */
+ switch (depth) {
+ case COLOR_DEPTH_888:
+ break;
+ case COLOR_DEPTH_101010:
+ normalized_clk = (normalized_clk * 30) / 24;
+ break;
+ case COLOR_DEPTH_121212:
+ normalized_clk = (normalized_clk * 36) / 24;
+ break;
+ case COLOR_DEPTH_161616:
+ normalized_clk = (normalized_clk * 48) / 24;
+ break;
+ default:
+ /* The above depths are the only ones valid for HDMI. */
+ return false;
+ }
+ if (normalized_clk <= info->max_tmds_clock) {
+ timing_out->display_color_depth = depth;
+ return true;
+ }
+ } while (--depth > COLOR_DEPTH_666);
+ return false;
+}
+
+static void fill_stream_properties_from_drm_display_mode(
+ struct dc_stream_state *stream,
+ const struct drm_display_mode *mode_in,
+ const struct drm_connector *connector,
+ const struct drm_connector_state *connector_state,
+ const struct dc_stream_state *old_stream,
+ int requested_bpc)
+{
+ struct dc_crtc_timing *timing_out = &stream->timing;
+ const struct drm_display_info *info = &connector->display_info;
+ struct amdgpu_dm_connector *aconnector = NULL;
+ struct hdmi_vendor_infoframe hv_frame;
+ struct hdmi_avi_infoframe avi_frame;
+ ssize_t err;
+
+ if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
+ aconnector = to_amdgpu_dm_connector(connector);
+
+ memset(&hv_frame, 0, sizeof(hv_frame));
+ memset(&avi_frame, 0, sizeof(avi_frame));
+
+ timing_out->h_border_left = 0;
+ timing_out->h_border_right = 0;
+ timing_out->v_border_top = 0;
+ timing_out->v_border_bottom = 0;
+ /* TODO: un-hardcode */
+ if (drm_mode_is_420_only(info, mode_in)
+ && stream->signal == SIGNAL_TYPE_HDMI_TYPE_A)
+ timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420;
+ else if (drm_mode_is_420_also(info, mode_in)
+ && aconnector
+ && aconnector->force_yuv420_output)
+ timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420;
+ else if ((connector->display_info.color_formats & DRM_COLOR_FORMAT_YCBCR422)
+ && aconnector
+ && aconnector->force_yuv422_output)
+ timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR422;
+ else if ((connector->display_info.color_formats & DRM_COLOR_FORMAT_YCBCR444)
+ && stream->signal == SIGNAL_TYPE_HDMI_TYPE_A)
+ timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR444;
+ else
+ timing_out->pixel_encoding = PIXEL_ENCODING_RGB;
+
+ timing_out->timing_3d_format = TIMING_3D_FORMAT_NONE;
+ timing_out->display_color_depth = amdgpu_dm_convert_color_depth_from_display_info(
+ connector,
+ (timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420),
+ requested_bpc);
+ timing_out->scan_type = SCANNING_TYPE_NODATA;
+ timing_out->hdmi_vic = 0;
+
+ if (old_stream) {
+ timing_out->vic = old_stream->timing.vic;
+ timing_out->flags.HSYNC_POSITIVE_POLARITY = old_stream->timing.flags.HSYNC_POSITIVE_POLARITY;
+ timing_out->flags.VSYNC_POSITIVE_POLARITY = old_stream->timing.flags.VSYNC_POSITIVE_POLARITY;
+ } else {
+ timing_out->vic = drm_match_cea_mode(mode_in);
+ if (mode_in->flags & DRM_MODE_FLAG_PHSYNC)
+ timing_out->flags.HSYNC_POSITIVE_POLARITY = 1;
+ if (mode_in->flags & DRM_MODE_FLAG_PVSYNC)
+ timing_out->flags.VSYNC_POSITIVE_POLARITY = 1;
+ }
+
+ if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A ||
+ stream->signal == SIGNAL_TYPE_HDMI_FRL) {
+ err = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame,
+ (struct drm_connector *)connector,
+ mode_in);
+ if (err < 0)
+ drm_warn_once(connector->dev, "Failed to setup avi infoframe on connector %s: %zd\n",
+ connector->name, err);
+ timing_out->vic = avi_frame.video_code;
+ err = drm_hdmi_vendor_infoframe_from_display_mode(&hv_frame,
+ (struct drm_connector *)connector,
+ mode_in);
+ if (err < 0)
+ drm_warn_once(connector->dev, "Failed to setup vendor infoframe on connector %s: %zd\n",
+ connector->name, err);
+ timing_out->hdmi_vic = hv_frame.vic;
+ }
+
+ if (aconnector && amdgpu_dm_is_freesync_video_mode(mode_in, aconnector)) {
+ timing_out->h_addressable = mode_in->hdisplay;
+ timing_out->h_total = mode_in->htotal;
+ timing_out->h_sync_width = mode_in->hsync_end - mode_in->hsync_start;
+ timing_out->h_front_porch = mode_in->hsync_start - mode_in->hdisplay;
+ timing_out->v_total = mode_in->vtotal;
+ timing_out->v_addressable = mode_in->vdisplay;
+ timing_out->v_front_porch = mode_in->vsync_start - mode_in->vdisplay;
+ timing_out->v_sync_width = mode_in->vsync_end - mode_in->vsync_start;
+ timing_out->pix_clk_100hz = mode_in->clock * 10;
+ } else {
+ timing_out->h_addressable = mode_in->crtc_hdisplay;
+ timing_out->h_total = mode_in->crtc_htotal;
+ timing_out->h_sync_width = mode_in->crtc_hsync_end - mode_in->crtc_hsync_start;
+ timing_out->h_front_porch = mode_in->crtc_hsync_start - mode_in->crtc_hdisplay;
+ timing_out->v_total = mode_in->crtc_vtotal;
+ timing_out->v_addressable = mode_in->crtc_vdisplay;
+ timing_out->v_front_porch = mode_in->crtc_vsync_start - mode_in->crtc_vdisplay;
+ timing_out->v_sync_width = mode_in->crtc_vsync_end - mode_in->crtc_vsync_start;
+ timing_out->pix_clk_100hz = mode_in->crtc_clock * 10;
+ }
+
+ timing_out->aspect_ratio = get_aspect_ratio(mode_in);
+
+ stream->out_transfer_func.type = TF_TYPE_PREDEFINED;
+ stream->out_transfer_func.tf = TRANSFER_FUNCTION_SRGB;
+ if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A) {
+ if (!adjust_colour_depth_from_display_info(timing_out, info) &&
+ drm_mode_is_420_also(info, mode_in) &&
+ timing_out->pixel_encoding != PIXEL_ENCODING_YCBCR420) {
+ timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420;
+ adjust_colour_depth_from_display_info(timing_out, info);
+ }
+ }
+
+ stream->output_color_space = amdgpu_dm_get_output_color_space(timing_out, connector_state);
+ stream->content_type = get_output_content_type(connector_state);
+}
+
+static void
+copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode,
+ struct drm_display_mode *dst_mode)
+{
+ dst_mode->crtc_hdisplay = src_mode->crtc_hdisplay;
+ dst_mode->crtc_vdisplay = src_mode->crtc_vdisplay;
+ dst_mode->crtc_clock = src_mode->crtc_clock;
+ dst_mode->crtc_hblank_start = src_mode->crtc_hblank_start;
+ dst_mode->crtc_hblank_end = src_mode->crtc_hblank_end;
+ dst_mode->crtc_hsync_start = src_mode->crtc_hsync_start;
+ dst_mode->crtc_hsync_end = src_mode->crtc_hsync_end;
+ dst_mode->crtc_htotal = src_mode->crtc_htotal;
+ dst_mode->crtc_hskew = src_mode->crtc_hskew;
+ dst_mode->crtc_vblank_start = src_mode->crtc_vblank_start;
+ dst_mode->crtc_vblank_end = src_mode->crtc_vblank_end;
+ dst_mode->crtc_vsync_start = src_mode->crtc_vsync_start;
+ dst_mode->crtc_vsync_end = src_mode->crtc_vsync_end;
+ dst_mode->crtc_vtotal = src_mode->crtc_vtotal;
+}
+
+static void
+decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode,
+ const struct drm_display_mode *native_mode,
+ bool scale_enabled)
+{
+ if (scale_enabled || (
+ native_mode->clock == drm_mode->clock &&
+ native_mode->htotal == drm_mode->htotal &&
+ native_mode->vtotal == drm_mode->vtotal)) {
+ if (native_mode->crtc_clock)
+ copy_crtc_timing_for_drm_display_mode(native_mode, drm_mode);
+ } else {
+ /* no scaling nor amdgpu inserted, no need to patch */
+ }
+}
+
+static struct dc_sink *
+create_fake_sink(struct drm_device *dev, struct dc_link *link)
+{
+ struct dc_sink_init_data sink_init_data = { 0 };
+ struct dc_sink *sink = NULL;
+
+ sink_init_data.link = link;
+ sink_init_data.sink_signal = link->connector_signal;
+
+ sink = dc_sink_create(&sink_init_data);
+ if (!sink) {
+ drm_err(dev, "Failed to create sink!\n");
+ return NULL;
+ }
+ sink->sink_signal = SIGNAL_TYPE_VIRTUAL;
+
+ return sink;
+}
+
+/**
+ * DOC: FreeSync Video
+ *
+ * When a userspace application wants to play a video, the content follows a
+ * standard format definition that usually specifies the FPS for that format.
+ * The below list illustrates some video format and the expected FPS,
+ * respectively:
+ *
+ * - TV/NTSC (23.976 FPS)
+ * - Cinema (24 FPS)
+ * - TV/PAL (25 FPS)
+ * - TV/NTSC (29.97 FPS)
+ * - TV/NTSC (30 FPS)
+ * - Cinema HFR (48 FPS)
+ * - TV/PAL (50 FPS)
+ * - Commonly used (60 FPS)
+ * - Multiples of 24 (48,72,96 FPS)
+ *
+ * The list of standards video format is not huge and can be added to the
+ * connector modeset list beforehand. With that, userspace can leverage
+ * FreeSync to extends the front porch in order to attain the target refresh
+ * rate. Such a switch will happen seamlessly, without screen blanking or
+ * reprogramming of the output in any other way. If the userspace requests a
+ * modesetting change compatible with FreeSync modes that only differ in the
+ * refresh rate, DC will skip the full update and avoid blink during the
+ * transition. For example, the video player can change the modesetting from
+ * 60Hz to 30Hz for playing TV/NTSC content when it goes full screen without
+ * causing any display blink. This same concept can be applied to a mode
+ * setting change.
+ */
+struct drm_display_mode *
+amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
+ bool use_probed_modes)
+{
+ struct drm_display_mode *m, *m_pref = NULL;
+ u16 current_refresh, highest_refresh;
+ struct list_head *list_head = use_probed_modes ?
+ &aconnector->base.probed_modes :
+ &aconnector->base.modes;
+
+ if (aconnector->base.connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ return NULL;
+
+ if (aconnector->freesync_vid_base.clock != 0)
+ return &aconnector->freesync_vid_base;
+
+ /* Find the preferred mode */
+ list_for_each_entry(m, list_head, head) {
+ if (m->type & DRM_MODE_TYPE_PREFERRED) {
+ m_pref = m;
+ break;
+ }
+ }
+
+ if (!m_pref) {
+ /* Probably an EDID with no preferred mode. Fallback to first entry */
+ m_pref = list_first_entry_or_null(
+ &aconnector->base.modes, struct drm_display_mode, head);
+ if (!m_pref) {
+ drm_dbg_driver(aconnector->base.dev, "No preferred mode found in EDID\n");
+ return NULL;
+ }
+ }
+
+ highest_refresh = drm_mode_vrefresh(m_pref);
+
+ /*
+ * Find the mode with highest refresh rate with same resolution.
+ * For some monitors, preferred mode is not the mode with highest
+ * supported refresh rate.
+ */
+ list_for_each_entry(m, list_head, head) {
+ current_refresh = drm_mode_vrefresh(m);
+
+ if (m->hdisplay == m_pref->hdisplay &&
+ m->vdisplay == m_pref->vdisplay &&
+ highest_refresh < current_refresh) {
+ highest_refresh = current_refresh;
+ m_pref = m;
+ }
+ }
+
+ drm_mode_copy(&aconnector->freesync_vid_base, m_pref);
+ return m_pref;
+}
+
+
+bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode,
+ struct amdgpu_dm_connector *aconnector)
+{
+ struct drm_display_mode *high_mode;
+ int timing_diff;
+
+ high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false);
+ if (!high_mode || !mode)
+ return false;
+
+ timing_diff = high_mode->vtotal - mode->vtotal;
+
+ if (high_mode->clock == 0 || high_mode->clock != mode->clock ||
+ high_mode->hdisplay != mode->hdisplay ||
+ high_mode->vdisplay != mode->vdisplay ||
+ high_mode->hsync_start != mode->hsync_start ||
+ high_mode->hsync_end != mode->hsync_end ||
+ high_mode->htotal != mode->htotal ||
+ high_mode->hskew != mode->hskew ||
+ high_mode->vscan != mode->vscan ||
+ high_mode->vsync_start - mode->vsync_start != timing_diff ||
+ high_mode->vsync_end - mode->vsync_end != timing_diff)
+ return false;
+ else
+ return true;
+}
+
+#if defined(CONFIG_DRM_AMD_DC_FP)
+static void update_dsc_caps(struct amdgpu_dm_connector *aconnector,
+ struct dc_sink *sink, struct dc_stream_state *stream,
+ struct dsc_dec_dpcd_caps *dsc_caps)
+{
+ stream->timing.flags.DSC = 0;
+ dsc_caps->is_dsc_supported = false;
+
+ if (aconnector->dc_link && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT ||
+ sink->sink_signal == SIGNAL_TYPE_EDP)) {
+ if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE)
+ dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc,
+ aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw,
+ aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw,
+ dsc_caps);
+ else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) {
+ if (aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.fields.dsc_support.DSC_PASSTHROUGH_SUPPORT &&
+ !aconnector->dsc_settings.dsc_force_disable_passthrough &&
+ aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps > 0 &&
+ sink->edid_caps.frl_dsc_support &&
+ sink->edid_caps.max_frl_rate > 0 &&
+ sink->edid_caps.frl_dsc_max_frl_rate > 0)
+ dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps);
+ else
+ dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc,
+ aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw,
+ aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw,
+ dsc_caps);
+ }
+ } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) {
+ if (sink->edid_caps.frl_dsc_support &&
+ sink->edid_caps.max_frl_rate > 0 &&
+ sink->edid_caps.frl_dsc_max_frl_rate > 0)
+ dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps);
+ }
+}
+
+
+
+
+static void apply_dsc_policy_for_edp(struct amdgpu_dm_connector *aconnector,
+ struct dc_sink *sink, struct dc_stream_state *stream,
+ struct dsc_dec_dpcd_caps *dsc_caps,
+ uint32_t max_dsc_target_bpp_limit_override)
+{
+ const struct dc_link_settings *verified_link_cap = NULL;
+ u32 link_bw_in_kbps;
+ u32 edp_min_bpp_x16, edp_max_bpp_x16;
+ struct dc *dc = sink->ctx->dc;
+ struct dc_dsc_bw_range bw_range = {0};
+ struct dc_dsc_config dsc_cfg = {0};
+ struct dc_dsc_config_options dsc_options = {0};
+
+ dc_dsc_get_default_config_option(dc, &dsc_options);
+ dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16;
+
+ verified_link_cap = dc_link_get_link_cap(stream->link);
+ link_bw_in_kbps = dc_link_bandwidth_kbps(stream->link, verified_link_cap);
+ edp_min_bpp_x16 = 8 * 16;
+ edp_max_bpp_x16 = 8 * 16;
+
+ if (edp_max_bpp_x16 > dsc_caps->edp_max_bits_per_pixel)
+ edp_max_bpp_x16 = dsc_caps->edp_max_bits_per_pixel;
+
+ if (edp_max_bpp_x16 < edp_min_bpp_x16)
+ edp_min_bpp_x16 = edp_max_bpp_x16;
+
+ if (dc_dsc_compute_bandwidth_range(dc->res_pool->dscs[0],
+ dc->debug.dsc_min_slice_height_override,
+ edp_min_bpp_x16, edp_max_bpp_x16,
+ dsc_caps,
+ &stream->timing,
+ dc_link_get_highest_encoding_format(aconnector->dc_link),
+ &bw_range)) {
+
+ if (bw_range.max_kbps < link_bw_in_kbps) {
+ if (dc_dsc_compute_config(dc->res_pool->dscs[0],
+ dsc_caps,
+ &dsc_options,
+ 0,
+ &stream->timing,
+ dc_link_get_highest_encoding_format(aconnector->dc_link),
+ &dsc_cfg)) {
+ stream->timing.dsc_cfg = dsc_cfg;
+ stream->timing.flags.DSC = 1;
+ stream->timing.dsc_cfg.bits_per_pixel = edp_max_bpp_x16;
+ }
+ return;
+ }
+ }
+
+ if (dc_dsc_compute_config(dc->res_pool->dscs[0],
+ dsc_caps,
+ &dsc_options,
+ link_bw_in_kbps,
+ &stream->timing,
+ dc_link_get_highest_encoding_format(aconnector->dc_link),
+ &dsc_cfg)) {
+ stream->timing.dsc_cfg = dsc_cfg;
+ stream->timing.flags.DSC = 1;
+ }
+}
+
+static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector,
+ struct dc_sink *sink, struct dc_stream_state *stream,
+ struct dsc_dec_dpcd_caps *dsc_caps)
+{
+ struct drm_connector *drm_connector = &aconnector->base;
+ u32 link_bandwidth_kbps;
+ struct dc *dc = sink->ctx->dc;
+ const struct dc_hdmi_frl_link_settings *frl_verified_link_cap = NULL;
+ u32 converter_bw_in_kbps;
+ u32 sink_bw_in_kbps;
+ u32 dsc_sink_bw_in_kbps;
+ u32 max_supported_bw_in_kbps, timing_bw_in_kbps;
+ u32 dsc_max_supported_bw_in_kbps;
+ u32 max_dsc_target_bpp_limit_override =
+ drm_connector->display_info.max_dsc_bpp;
+ struct dc_dsc_config_options dsc_options = {0};
+
+ dc_dsc_get_default_config_option(dc, &dsc_options);
+ dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16;
+
+ link_bandwidth_kbps = dc_link_bandwidth_kbps(aconnector->dc_link,
+ dc_link_get_link_cap(aconnector->dc_link));
+
+ /* Set DSC policy according to dsc_clock_en */
+ dc_dsc_policy_set_enable_dsc_when_not_needed(
+ aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE);
+
+ if (sink->sink_signal == SIGNAL_TYPE_EDP &&
+ !aconnector->dc_link->panel_config.dsc.disable_dsc_edp &&
+ dc->caps.edp_dsc_support && aconnector->dsc_settings.dsc_force_enable != DSC_CLK_FORCE_DISABLE) {
+
+ apply_dsc_policy_for_edp(aconnector, sink, stream, dsc_caps, max_dsc_target_bpp_limit_override);
+
+ } else if (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT) {
+ if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE) {
+ if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0],
+ dsc_caps,
+ &dsc_options,
+ link_bandwidth_kbps,
+ &stream->timing,
+ dc_link_get_highest_encoding_format(aconnector->dc_link),
+ &stream->timing.dsc_cfg)) {
+ stream->timing.flags.DSC = 1;
+ drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from SST RX\n",
+ __func__, drm_connector->name);
+ }
+ } else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) {
+ timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing,
+ dc_link_get_highest_encoding_format(aconnector->dc_link));
+ converter_bw_in_kbps = aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps;
+ sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.max_frl_rate);
+ dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate);
+
+ if (dsc_caps->is_frl) {
+ max_supported_bw_in_kbps = min(link_bandwidth_kbps, converter_bw_in_kbps);
+ max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, sink_bw_in_kbps);
+ dsc_max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, dsc_sink_bw_in_kbps);
+ } else {
+ max_supported_bw_in_kbps = link_bandwidth_kbps;
+ dsc_max_supported_bw_in_kbps = link_bandwidth_kbps;
+ }
+
+ if (timing_bw_in_kbps > max_supported_bw_in_kbps &&
+ max_supported_bw_in_kbps > 0 &&
+ dsc_max_supported_bw_in_kbps > 0)
+ if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0],
+ dsc_caps,
+ &dsc_options,
+ dsc_max_supported_bw_in_kbps,
+ &stream->timing,
+ dc_link_get_highest_encoding_format(aconnector->dc_link),
+ &stream->timing.dsc_cfg)) {
+ stream->timing.flags.DSC = 1;
+ drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from %s\n",
+ __func__, drm_connector->name,
+ (dsc_caps->is_frl == 1) ? "HDMI FRL RX" : "DP-HDMI PCON");
+ }
+ }
+ } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) {
+ frl_verified_link_cap = dc_link_get_frl_link_cap(stream->link);
+ timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing, DC_LINK_ENCODING_HDMI_FRL);
+ link_bandwidth_kbps = dc_link_frl_bandwidth_kbps(stream->link, frl_verified_link_cap->frl_link_rate);
+ dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate);
+
+ if (timing_bw_in_kbps > link_bandwidth_kbps &&
+ dsc_sink_bw_in_kbps > 0)
+ if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0],
+ dsc_caps,
+ &dsc_options,
+ dsc_sink_bw_in_kbps,
+ &stream->timing,
+ dc_link_get_highest_encoding_format(aconnector->dc_link),
+ &stream->timing.dsc_cfg)) {
+ stream->timing.flags.DSC = 1;
+ drm_dbg_driver(drm_connector->dev, "%s: HDMI_FRL_DSC [%s] DSC is selected from HDMI FRL RX\n",
+ __func__, drm_connector->name);
+ }
+ }
+
+ /* Overwrite the stream flag if DSC is enabled through debugfs */
+ if (aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE)
+ stream->timing.flags.DSC = 1;
+
+ if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_h)
+ stream->timing.dsc_cfg.num_slices_h = aconnector->dsc_settings.dsc_num_slices_h;
+
+ if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_v)
+ stream->timing.dsc_cfg.num_slices_v = aconnector->dsc_settings.dsc_num_slices_v;
+
+ if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_bits_per_pixel)
+ stream->timing.dsc_cfg.bits_per_pixel = aconnector->dsc_settings.dsc_bits_per_pixel;
+}
+#endif
+
+static struct dc_stream_state *
+create_stream_for_sink(struct drm_connector *connector,
+ const struct drm_display_mode *drm_mode,
+ const struct dm_connector_state *dm_state,
+ const struct dc_stream_state *old_stream,
+ int requested_bpc)
+{
+ struct drm_device *dev = connector->dev;
+ struct amdgpu_dm_connector *aconnector = NULL;
+ struct drm_display_mode *preferred_mode = NULL;
+ const struct drm_connector_state *con_state = &dm_state->base;
+ struct dc_stream_state *stream = NULL;
+ struct drm_display_mode mode;
+ struct drm_display_mode saved_mode;
+ struct drm_display_mode *freesync_mode = NULL;
+ bool native_mode_found = false;
+ bool recalculate_timing = false;
+ bool scale = dm_state->scaling != RMX_OFF;
+ int mode_refresh;
+ 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 = {0};
+#endif
+ struct dc_link *link = NULL;
+ struct dc_sink *sink = NULL;
+
+ drm_mode_init(&mode, drm_mode);
+ memset(&saved_mode, 0, sizeof(saved_mode));
+
+ if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) {
+ aconnector = NULL;
+ aconnector = to_amdgpu_dm_connector(connector);
+ link = aconnector->dc_link;
+ } else {
+ struct drm_writeback_connector *wbcon = NULL;
+ struct amdgpu_dm_wb_connector *dm_wbcon = NULL;
+
+ wbcon = drm_connector_to_writeback(connector);
+ dm_wbcon = to_amdgpu_dm_wb_connector(wbcon);
+ link = dm_wbcon->link;
+ }
+
+ if (!aconnector || !aconnector->dc_sink) {
+ sink = create_fake_sink(dev, link);
+ if (!sink)
+ return stream;
+
+ } else {
+ sink = aconnector->dc_sink;
+ dc_sink_retain(sink);
+ }
+
+ stream = dc_create_stream_for_sink(sink);
+
+ if (stream == NULL) {
+ drm_err(dev, "Failed to create stream for sink!\n");
+ goto finish;
+ }
+
+ /* We leave this NULL for writeback connectors */
+ stream->dm_stream_context = aconnector;
+
+ stream->timing.flags.LTE_340MCSC_SCRAMBLE =
+ connector->display_info.hdmi.scdc.scrambling.low_rates;
+
+ list_for_each_entry(preferred_mode, &connector->modes, head) {
+ /* Search for preferred mode */
+ if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) {
+ native_mode_found = true;
+ break;
+ }
+ }
+ if (!native_mode_found)
+ preferred_mode = list_first_entry_or_null(
+ &connector->modes,
+ struct drm_display_mode,
+ head);
+
+ mode_refresh = drm_mode_vrefresh(&mode);
+
+ if (preferred_mode == NULL) {
+ /*
+ * This may not be an error, the use case is when we have no
+ * usermode calls to reset and set mode upon hotplug. In this
+ * case, we call set mode ourselves to restore the previous mode
+ * and the modelist may not be filled in time.
+ */
+ drm_dbg_driver(dev, "No preferred mode found\n");
+ } else if (aconnector) {
+ recalculate_timing = amdgpu_freesync_vid_mode &&
+ amdgpu_dm_is_freesync_video_mode(&mode, aconnector);
+ if (recalculate_timing) {
+ freesync_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false);
+ drm_mode_copy(&saved_mode, &mode);
+ saved_mode.picture_aspect_ratio = mode.picture_aspect_ratio;
+ drm_mode_copy(&mode, freesync_mode);
+ mode.picture_aspect_ratio = saved_mode.picture_aspect_ratio;
+ } else {
+ decide_crtc_timing_for_drm_display_mode(
+ &mode, preferred_mode, scale);
+
+ preferred_refresh = drm_mode_vrefresh(preferred_mode);
+ }
+ }
+
+ if (recalculate_timing)
+ drm_mode_set_crtcinfo(&saved_mode, 0);
+
+ /*
+ * If scaling is enabled and refresh rate didn't change
+ * we copy the vic and polarities of the old timings
+ */
+ if (!scale || mode_refresh != preferred_refresh)
+ fill_stream_properties_from_drm_display_mode(
+ stream, &mode, connector, con_state, NULL,
+ requested_bpc);
+ else
+ fill_stream_properties_from_drm_display_mode(
+ stream, &mode, connector, con_state, old_stream,
+ requested_bpc);
+
+ /* The rest isn't needed for writeback connectors */
+ if (!aconnector)
+ goto finish;
+
+ if (aconnector->timing_changed) {
+ drm_dbg(aconnector->base.dev,
+ "overriding timing for automated test, bpc %d, changing to %d\n",
+ stream->timing.display_color_depth,
+ aconnector->timing_requested->display_color_depth);
+ stream->timing = *aconnector->timing_requested;
+ }
+
+#if defined(CONFIG_DRM_AMD_DC_FP)
+ /* SST DSC determination policy */
+ update_dsc_caps(aconnector, sink, stream, &dsc_caps);
+ if (aconnector->dsc_settings.dsc_force_enable != DSC_CLK_FORCE_DISABLE && dsc_caps.is_dsc_supported)
+ apply_dsc_policy_for_stream(aconnector, sink, stream, &dsc_caps);
+#endif
+
+ amdgpu_dm_update_stream_scaling_settings(dev, &mode, dm_state, stream);
+
+ amdgpu_dm_fill_audio_info(
+ &stream->audio_info,
+ connector,
+ sink);
+
+ update_stream_signal(stream, sink);
+
+ if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A ||
+ stream->signal == SIGNAL_TYPE_HDMI_FRL)
+ mod_build_hf_vsif_infopacket(stream, &stream->vsp_infopacket, false, false);
+
+ if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT ||
+ stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST ||
+ stream->signal == SIGNAL_TYPE_EDP) {
+ const struct dc_edid_caps *edid_caps;
+ unsigned int disable_colorimetry = 0;
+
+ if (aconnector->dc_sink) {
+ edid_caps = &aconnector->dc_sink->edid_caps;
+ disable_colorimetry = edid_caps->panel_patch.disable_colorimetry;
+ }
+
+ /*
+ * should decide stream support vsc sdp colorimetry capability
+ * before building vsc info packet
+ */
+ stream->use_vsc_sdp_for_colorimetry = stream->link->dpcd_caps.dpcd_rev.raw >= 0x14 &&
+ stream->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED &&
+ !disable_colorimetry;
+
+ if (stream->out_transfer_func.tf == TRANSFER_FUNCTION_GAMMA22)
+ tf = TRANSFER_FUNC_GAMMA_22;
+ mod_build_vsc_infopacket(stream, &stream->vsc_infopacket, stream->output_color_space, tf);
+ aconnector->sr_skip_count = AMDGPU_DM_PSR_ENTRY_DELAY;
+
+ }
+finish:
+ dc_sink_release(sink);
+
+ return stream;
+}
+
+/**
+ * amdgpu_dm_connector_poll - Poll a connector to see if it's connected to a display
+ * @aconnector: DM connector to poll (owns @base drm_connector and @dc_link)
+ * @force: if true, force polling even when DAC load detection was used
+ *
+ * Used for connectors that don't support HPD (hotplug detection) to
+ * periodically check whether the connector is connected to a display.
+ *
+ * When connection was determined via DAC load detection, we avoid
+ * re-running it on normal polls to prevent visible glitches, unless
+ * @force is set.
+ *
+ * Return: The probed connector status (connected/disconnected/unknown).
+ */
+static enum drm_connector_status
+amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force)
+{
+ struct drm_connector *connector = &aconnector->base;
+ struct drm_device *dev = connector->dev;
+ struct amdgpu_device *adev = drm_to_adev(dev);
+ struct dc_link *link = aconnector->dc_link;
+ enum dc_connection_type conn_type = dc_connection_none;
+ enum drm_connector_status status = connector_status_disconnected;
+
+ /* When we determined the connection using DAC load detection,
+ * do NOT poll the connector do detect disconnect because
+ * that would run DAC load detection again which can cause
+ * visible visual glitches.
+ *
+ * Only allow to poll such a connector again when forcing.
+ */
+ if (!force && link->local_sink && link->type == dc_connection_analog_load)
+ return connector->status;
+
+ mutex_lock(&aconnector->hpd_lock);
+
+ if (dc_link_detect_connection_type(aconnector->dc_link, &conn_type) &&
+ conn_type != dc_connection_none) {
+ mutex_lock(&adev->dm.dc_lock);
+
+ /* Only call full link detection when a sink isn't created yet,
+ * ie. just when the display is plugged in, otherwise we risk flickering.
+ */
+ if (link->local_sink ||
+ dc_link_detect(link, DETECT_REASON_HPD))
+ status = connector_status_connected;
+
+ mutex_unlock(&adev->dm.dc_lock);
+ }
+
+ if (connector->status != status) {
+ if (status == connector_status_disconnected) {
+ if (link->local_sink)
+ dc_sink_release(link->local_sink);
+
+ link->local_sink = NULL;
+ link->dpcd_sink_count = 0;
+ link->type = dc_connection_none;
+ }
+
+ amdgpu_dm_update_connector_after_detect(aconnector);
+ }
+
+ mutex_unlock(&aconnector->hpd_lock);
+ return status;
+}
+
+/**
+ * amdgpu_dm_connector_detect() - Detect whether a DRM connector is connected to a display
+ *
+ * A connector is considered connected when it has a sink that is not NULL.
+ * For connectors that support HPD (hotplug detection), the connection is
+ * handled in the HPD interrupt.
+ * For connectors that may not support HPD, such as analog connectors,
+ * DRM will call this function repeatedly to poll them.
+ *
+ * Notes:
+ * 1. This interface is NOT called in context of HPD irq.
+ * 2. This interface *is called* in context of user-mode ioctl. Which
+ * makes it a bad place for *any* MST-related activity.
+ *
+ * @connector: The DRM connector we are checking. We convert it to
+ * amdgpu_dm_connector so we can read the DC link and state.
+ * @force: If true, do a full detect again. This is used even when
+ * a lighter check would normally be used to avoid flicker.
+ *
+ * Return: The connector status (connected, disconnected, or unknown).
+ *
+ */
+static enum drm_connector_status
+amdgpu_dm_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+
+ update_subconnector_property(aconnector);
+
+ if (aconnector->base.force == DRM_FORCE_ON ||
+ aconnector->base.force == DRM_FORCE_ON_DIGITAL)
+ return connector_status_connected;
+ else if (aconnector->base.force == DRM_FORCE_OFF)
+ return connector_status_disconnected;
+
+ /* Poll analog connectors and only when either
+ * disconnected or connected to an analog display.
+ */
+ if (drm_kms_helper_is_poll_worker() &&
+ dc_connector_supports_analog(aconnector->dc_link->link_id.id) &&
+ (!aconnector->dc_sink || aconnector->dc_sink->edid_caps.analog))
+ return amdgpu_dm_connector_poll(aconnector, force);
+
+ return (aconnector->dc_sink ? connector_status_connected :
+ connector_status_disconnected);
+}
+
+int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector,
+ struct drm_connector_state *connector_state,
+ struct drm_property *property,
+ uint64_t val)
+{
+ struct drm_device *dev = connector->dev;
+ struct amdgpu_device *adev = drm_to_adev(dev);
+ struct dm_connector_state *dm_old_state =
+ to_dm_connector_state(connector->state);
+ struct dm_connector_state *dm_new_state =
+ to_dm_connector_state(connector_state);
+
+ int ret = -EINVAL;
+
+ if (property == dev->mode_config.scaling_mode_property) {
+ enum amdgpu_rmx_type rmx_type;
+
+ switch (val) {
+ case DRM_MODE_SCALE_CENTER:
+ rmx_type = RMX_CENTER;
+ break;
+ case DRM_MODE_SCALE_ASPECT:
+ rmx_type = RMX_ASPECT;
+ break;
+ case DRM_MODE_SCALE_FULLSCREEN:
+ rmx_type = RMX_FULL;
+ break;
+ case DRM_MODE_SCALE_NONE:
+ default:
+ rmx_type = RMX_OFF;
+ break;
+ }
+
+ if (dm_old_state->scaling == rmx_type)
+ return 0;
+
+ dm_new_state->scaling = rmx_type;
+ ret = 0;
+ } else if (property == adev->mode_info.underscan_hborder_property) {
+ dm_new_state->underscan_hborder = val;
+ ret = 0;
+ } else if (property == adev->mode_info.underscan_vborder_property) {
+ dm_new_state->underscan_vborder = val;
+ ret = 0;
+ } else if (property == adev->mode_info.underscan_property) {
+ dm_new_state->underscan_enable = val;
+ ret = 0;
+ } else if (property == adev->mode_info.abm_level_property) {
+ switch (val) {
+ case ABM_SYSFS_CONTROL:
+ dm_new_state->abm_sysfs_forbidden = false;
+ break;
+ case ABM_LEVEL_OFF:
+ dm_new_state->abm_sysfs_forbidden = true;
+ dm_new_state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE;
+ break;
+ default:
+ dm_new_state->abm_sysfs_forbidden = true;
+ dm_new_state->abm_level = val;
+ }
+ ret = 0;
+ }
+
+ return ret;
+}
+
+int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector,
+ const struct drm_connector_state *state,
+ struct drm_property *property,
+ uint64_t *val)
+{
+ struct drm_device *dev = connector->dev;
+ struct amdgpu_device *adev = drm_to_adev(dev);
+ struct dm_connector_state *dm_state =
+ to_dm_connector_state(state);
+ int ret = -EINVAL;
+
+ if (property == dev->mode_config.scaling_mode_property) {
+ switch (dm_state->scaling) {
+ case RMX_CENTER:
+ *val = DRM_MODE_SCALE_CENTER;
+ break;
+ case RMX_ASPECT:
+ *val = DRM_MODE_SCALE_ASPECT;
+ break;
+ case RMX_FULL:
+ *val = DRM_MODE_SCALE_FULLSCREEN;
+ break;
+ case RMX_OFF:
+ default:
+ *val = DRM_MODE_SCALE_NONE;
+ break;
+ }
+ ret = 0;
+ } else if (property == adev->mode_info.underscan_hborder_property) {
+ *val = dm_state->underscan_hborder;
+ ret = 0;
+ } else if (property == adev->mode_info.underscan_vborder_property) {
+ *val = dm_state->underscan_vborder;
+ ret = 0;
+ } else if (property == adev->mode_info.underscan_property) {
+ *val = dm_state->underscan_enable;
+ ret = 0;
+ } else if (property == adev->mode_info.abm_level_property) {
+ if (!dm_state->abm_sysfs_forbidden)
+ *val = ABM_SYSFS_CONTROL;
+ else
+ *val = (dm_state->abm_level != ABM_LEVEL_IMMEDIATE_DISABLE) ?
+ dm_state->abm_level : 0;
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static void amdgpu_dm_connector_unregister(struct drm_connector *connector)
+{
+ struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector);
+
+ if (amdgpu_dm_should_create_sysfs(amdgpu_dm_connector))
+ sysfs_remove_group(&connector->kdev->kobj, &amdgpu_group);
+
+ cec_notifier_conn_unregister(amdgpu_dm_connector->notifier);
+ drm_dp_aux_unregister(&amdgpu_dm_connector->dm_dp_aux.aux);
+}
+
+static void amdgpu_dm_connector_destroy(struct drm_connector *connector)
+{
+ struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+ struct amdgpu_device *adev = drm_to_adev(connector->dev);
+ struct amdgpu_display_manager *dm = &adev->dm;
+
+ /*
+ * Call only if mst_mgr was initialized before since it's not done
+ * for all connector types.
+ */
+ if (aconnector->mst_mgr.dev)
+ drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr);
+
+ /* Cancel and flush any pending HDMI HPD debounce work */
+ if (aconnector->hdmi_hpd_debounce_delay_ms) {
+ cancel_delayed_work_sync(&aconnector->hdmi_hpd_debounce_work);
+ if (aconnector->hdmi_prev_sink) {
+ dc_sink_release(aconnector->hdmi_prev_sink);
+ aconnector->hdmi_prev_sink = NULL;
+ }
+ }
+
+ if (aconnector->bl_idx != -1) {
+ backlight_device_unregister(dm->backlight_dev[aconnector->bl_idx]);
+ dm->backlight_dev[aconnector->bl_idx] = NULL;
+ }
+
+ if (aconnector->dc_em_sink)
+ dc_sink_release(aconnector->dc_em_sink);
+ aconnector->dc_em_sink = NULL;
+ if (aconnector->dc_sink)
+ dc_sink_release(aconnector->dc_sink);
+ aconnector->dc_sink = NULL;
+
+ drm_dp_cec_unregister_connector(&aconnector->dm_dp_aux.aux);
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+ kfree(aconnector->dm_dp_aux.aux.name);
+
+ kfree(connector);
+}
+
+void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector)
+{
+ struct dm_connector_state *state =
+ to_dm_connector_state(connector->state);
+
+ if (connector->state)
+ __drm_atomic_helper_connector_destroy_state(connector->state);
+
+ kfree(state);
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+
+ if (state) {
+ state->scaling = RMX_OFF;
+ state->underscan_enable = false;
+ state->underscan_hborder = 0;
+ state->underscan_vborder = 0;
+ state->base.max_requested_bpc = 8;
+ state->vcpi_slots = 0;
+ state->pbn = 0;
+
+ if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
+ if (amdgpu_dm_abm_level <= 0)
+ state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE;
+ else
+ state->abm_level = amdgpu_dm_abm_level;
+ }
+
+ __drm_atomic_helper_connector_reset(connector, &state->base);
+ }
+}
+
+struct drm_connector_state *
+amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector)
+{
+ struct dm_connector_state *state =
+ to_dm_connector_state(connector->state);
+
+ struct dm_connector_state *new_state =
+ kmemdup(state, sizeof(*state), GFP_KERNEL);
+
+ if (!new_state)
+ return NULL;
+
+ __drm_atomic_helper_connector_duplicate_state(connector, &new_state->base);
+
+ new_state->freesync_capable = state->freesync_capable;
+ new_state->abm_level = state->abm_level;
+ new_state->scaling = state->scaling;
+ new_state->underscan_enable = state->underscan_enable;
+ new_state->underscan_hborder = state->underscan_hborder;
+ new_state->underscan_vborder = state->underscan_vborder;
+ new_state->vcpi_slots = state->vcpi_slots;
+ new_state->pbn = state->pbn;
+ return &new_state->base;
+}
+
+static int
+amdgpu_dm_connector_late_register(struct drm_connector *connector)
+{
+ struct amdgpu_dm_connector *amdgpu_dm_connector =
+ to_amdgpu_dm_connector(connector);
+ int r;
+
+ if (amdgpu_dm_should_create_sysfs(amdgpu_dm_connector)) {
+ r = sysfs_create_group(&connector->kdev->kobj,
+ &amdgpu_group);
+ if (r)
+ return r;
+ }
+
+ amdgpu_dm_register_backlight_device(amdgpu_dm_connector);
+
+ if ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) ||
+ (connector->connector_type == DRM_MODE_CONNECTOR_eDP)) {
+ amdgpu_dm_connector->dm_dp_aux.aux.dev = connector->kdev;
+ r = drm_dp_aux_register(&amdgpu_dm_connector->dm_dp_aux.aux);
+ if (r)
+ return r;
+ }
+
+#if defined(CONFIG_DEBUG_FS)
+ connector_debugfs_init(amdgpu_dm_connector);
+#endif
+
+ return 0;
+}
+
+static void amdgpu_dm_connector_funcs_force(struct drm_connector *connector)
+{
+ struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+ struct dc_link *dc_link = aconnector->dc_link;
+ struct dc_sink *dc_em_sink = aconnector->dc_em_sink;
+ const struct drm_edid *drm_edid;
+ struct i2c_adapter *ddc;
+ struct drm_device *dev = connector->dev;
+
+ if (dc_link && dc_link->aux_mode)
+ ddc = &aconnector->dm_dp_aux.aux.ddc;
+ else
+ ddc = &aconnector->i2c->base;
+
+ drm_edid = drm_edid_read_ddc(connector, ddc);
+ drm_edid_connector_update(connector, drm_edid);
+ if (!drm_edid) {
+ drm_err(dev, "No EDID found on connector: %s.\n", connector->name);
+ return;
+ }
+
+ aconnector->drm_edid = drm_edid;
+ /* Update emulated (virtual) sink's EDID */
+ if (dc_em_sink && dc_link) {
+ /* FIXME: Get rid of drm_edid_raw() */
+ const struct edid *edid = drm_edid_raw(drm_edid);
+
+ memset(&dc_em_sink->edid_caps, 0, sizeof(struct dc_edid_caps));
+ memmove(dc_em_sink->dc_edid.raw_edid, edid,
+ (edid->extensions + 1) * EDID_LENGTH);
+ dm_helpers_parse_edid_caps(
+ dc_link,
+ &dc_em_sink->dc_edid,
+ &dc_em_sink->edid_caps);
+ }
+}
+
+static const struct drm_connector_funcs amdgpu_dm_connector_funcs = {
+ .reset = amdgpu_dm_connector_funcs_reset,
+ .detect = amdgpu_dm_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = amdgpu_dm_connector_destroy,
+ .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+ .atomic_set_property = amdgpu_dm_connector_atomic_set_property,
+ .atomic_get_property = amdgpu_dm_connector_atomic_get_property,
+ .late_register = amdgpu_dm_connector_late_register,
+ .early_unregister = amdgpu_dm_connector_unregister,
+ .force = amdgpu_dm_connector_funcs_force
+};
+
+static int get_modes(struct drm_connector *connector)
+{
+ return amdgpu_dm_connector_get_modes(connector);
+}
+
+static void create_eml_sink(struct amdgpu_dm_connector *aconnector)
+{
+ struct drm_connector *connector = &aconnector->base;
+ struct dc_link *dc_link = aconnector->dc_link;
+ struct dc_sink_init_data init_params = {
+ .link = aconnector->dc_link,
+ .sink_signal = SIGNAL_TYPE_VIRTUAL
+ };
+ const struct drm_edid *drm_edid;
+ const struct edid *edid;
+ struct i2c_adapter *ddc;
+
+ if (dc_link && dc_link->aux_mode)
+ ddc = &aconnector->dm_dp_aux.aux.ddc;
+ else
+ ddc = &aconnector->i2c->base;
+
+ drm_edid = drm_edid_read_ddc(connector, ddc);
+ drm_edid_connector_update(connector, drm_edid);
+ if (!drm_edid) {
+ drm_err(connector->dev, "No EDID found on connector: %s.\n", connector->name);
+ return;
+ }
+
+ if (connector->display_info.is_hdmi)
+ init_params.sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+
+ aconnector->drm_edid = drm_edid;
+
+ /* FIXME: Get rid of drm_edid_raw() */
+ edid = drm_edid_raw(drm_edid);
+ aconnector->dc_em_sink = dc_link_add_remote_sink(
+ aconnector->dc_link,
+ (uint8_t *)edid,
+ (edid->extensions + 1) * EDID_LENGTH,
+ &init_params);
+
+ if (aconnector->base.force == DRM_FORCE_ON) {
+ aconnector->dc_sink = aconnector->dc_link->local_sink ?
+ aconnector->dc_link->local_sink :
+ aconnector->dc_em_sink;
+ if (aconnector->dc_sink)
+ dc_sink_retain(aconnector->dc_sink);
+ }
+}
+
+static void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector)
+{
+ struct dc_link *link = (struct dc_link *)aconnector->dc_link;
+
+ /*
+ * In case of headless boot with force on for DP managed connector
+ * Those settings have to be != 0 to get initial modeset
+ */
+ if (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT) {
+ link->verified_link_cap.lane_count = LANE_COUNT_FOUR;
+ link->verified_link_cap.link_rate = LINK_RATE_HIGH2;
+ }
+
+ create_eml_sink(aconnector);
+}
+
+static enum dc_status dm_validate_stream_and_context(struct dc *dc,
+ struct dc_stream_state *stream)
+{
+ enum dc_status dc_result = DC_ERROR_UNEXPECTED;
+ struct dc_plane_state *dc_plane_state = NULL;
+ struct dc_state *dc_state = NULL;
+
+ if (!stream)
+ goto cleanup;
+
+ dc_plane_state = dc_create_plane_state(dc);
+ if (!dc_plane_state)
+ goto cleanup;
+
+ dc_state = dc_state_create(dc, NULL);
+ if (!dc_state)
+ goto cleanup;
+
+ /* populate stream to plane */
+ dc_plane_state->src_rect.height = stream->src.height;
+ dc_plane_state->src_rect.width = stream->src.width;
+ dc_plane_state->dst_rect.height = stream->src.height;
+ dc_plane_state->dst_rect.width = stream->src.width;
+ dc_plane_state->clip_rect.height = stream->src.height;
+ dc_plane_state->clip_rect.width = stream->src.width;
+ dc_plane_state->plane_size.surface_pitch = ((stream->src.width + 255) / 256) * 256;
+ dc_plane_state->plane_size.surface_size.height = stream->src.height;
+ dc_plane_state->plane_size.surface_size.width = stream->src.width;
+ dc_plane_state->plane_size.chroma_size.height = stream->src.height;
+ dc_plane_state->plane_size.chroma_size.width = stream->src.width;
+ dc_plane_state->format = SURFACE_PIXEL_FORMAT_GRPH_ARGB8888;
+ dc_plane_state->tiling_info.gfx9.swizzle = DC_SW_UNKNOWN;
+ dc_plane_state->rotation = ROTATION_ANGLE_0;
+ dc_plane_state->is_tiling_rotated = false;
+ dc_plane_state->tiling_info.gfx8.array_mode = DC_ARRAY_LINEAR_GENERAL;
+
+ dc_result = dc_validate_stream(dc, stream);
+ if (dc_result == DC_OK)
+ dc_result = dc_validate_plane(dc, dc_plane_state);
+
+ if (dc_result == DC_OK)
+ dc_result = dc_state_add_stream(dc, dc_state, stream);
+
+ if (dc_result == DC_OK && !dc_state_add_plane(
+ dc,
+ stream,
+ dc_plane_state,
+ dc_state))
+ dc_result = DC_FAIL_ATTACH_SURFACES;
+
+ if (dc_result == DC_OK)
+ dc_result = dc_validate_global_state(dc, dc_state, DC_VALIDATE_MODE_ONLY);
+
+cleanup:
+ if (dc_state)
+ dc_state_release(dc_state);
+
+ if (dc_plane_state)
+ dc_plane_state_release(dc_plane_state);
+
+ return dc_result;
+}
+
+struct dc_stream_state *
+amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
+ const struct drm_display_mode *drm_mode,
+ const struct dm_connector_state *dm_state,
+ const struct dc_stream_state *old_stream)
+{
+ struct amdgpu_dm_connector *aconnector = NULL;
+ struct amdgpu_device *adev = drm_to_adev(connector->dev);
+ struct dc_stream_state *stream;
+ const struct drm_connector_state *drm_state = dm_state ? &dm_state->base : NULL;
+ int requested_bpc = drm_state ? drm_state->max_requested_bpc : 8;
+ enum dc_status dc_result = DC_OK;
+ uint8_t bpc_limit = 6;
+
+ if (!dm_state)
+ return NULL;
+
+ if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
+ aconnector = to_amdgpu_dm_connector(connector);
+
+ if (aconnector &&
+ (aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_TYPE_A ||
+ aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_FRL ||
+ aconnector->dc_link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER))
+ bpc_limit = 8;
+
+ do {
+ drm_dbg_kms(connector->dev, "Trying with %d bpc\n", requested_bpc);
+ stream = create_stream_for_sink(connector, drm_mode,
+ dm_state, old_stream,
+ requested_bpc);
+ if (stream == NULL) {
+ drm_err(adev_to_drm(adev), "Failed to create stream for sink!\n");
+ break;
+ }
+
+ dc_result = dc_validate_stream(adev->dm.dc, stream);
+
+ if (!aconnector) /* writeback connector */
+ return stream;
+
+ if (dc_result == DC_OK && stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST)
+ dc_result = dm_dp_mst_is_port_support_mode(aconnector, stream);
+
+ if (dc_result == DC_OK)
+ dc_result = dm_validate_stream_and_context(adev->dm.dc, stream);
+
+ if (dc_result != DC_OK) {
+ drm_dbg_kms(connector->dev, "Pruned mode %d x %d (clk %d) %s %s -- %s\n",
+ drm_mode->hdisplay,
+ drm_mode->vdisplay,
+ drm_mode->clock,
+ dc_pixel_encoding_to_str(stream->timing.pixel_encoding),
+ dc_color_depth_to_str(stream->timing.display_color_depth),
+ dc_status_to_str(dc_result));
+
+ dc_stream_release(stream);
+ stream = NULL;
+ requested_bpc -= 2; /* lower bpc to retry validation */
+ }
+
+ } while (stream == NULL && requested_bpc >= bpc_limit);
+
+ switch (dc_result) {
+ /*
+ * If we failed to validate DP bandwidth stream with the requested RGB color depth,
+ * we try to fallback and configure in order:
+ * YUV422 (8bpc, 6bpc)
+ * YUV420 (8bpc, 6bpc)
+ */
+ case DC_FAIL_ENC_VALIDATE:
+ case DC_EXCEED_DONGLE_CAP:
+ case DC_NO_DP_LINK_BANDWIDTH:
+ /* recursively entered twice and already tried both YUV422 and YUV420 */
+ if (aconnector->force_yuv422_output && aconnector->force_yuv420_output)
+ break;
+ /* first failure; try YUV422 */
+ if (!aconnector->force_yuv422_output) {
+ drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV422\n",
+ __func__, __LINE__, dc_result);
+ aconnector->force_yuv422_output = true;
+ /* recursively entered and YUV422 failed, try YUV420 */
+ } else if (!aconnector->force_yuv420_output) {
+ drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV420\n",
+ __func__, __LINE__, dc_result);
+ aconnector->force_yuv420_output = true;
+ }
+ stream = amdgpu_dm_create_validate_stream_for_sink(connector, drm_mode,
+ dm_state, old_stream);
+ aconnector->force_yuv422_output = false;
+ aconnector->force_yuv420_output = false;
+ break;
+ case DC_OK:
+ break;
+ default:
+ drm_dbg_kms(connector->dev, "%s:%d Unhandled validation failure %d\n",
+ __func__, __LINE__, dc_result);
+ break;
+ }
+
+ return stream;
+}
+
+enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector,
+ const struct drm_display_mode *mode)
+{
+ int result = MODE_ERROR;
+ struct dc_sink *dc_sink;
+ struct drm_display_mode *test_mode;
+ /* TODO: Unhardcode stream count */
+ struct dc_stream_state *stream;
+ /* we always have an amdgpu_dm_connector here since we got
+ * here via the amdgpu_dm_connector_helper_funcs
+ */
+ struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+
+ if ((mode->flags & DRM_MODE_FLAG_INTERLACE) ||
+ (mode->flags & DRM_MODE_FLAG_DBLSCAN))
+ return result;
+
+ /*
+ * Only run this the first time mode_valid is called to initilialize
+ * EDID mgmt
+ */
+ if (aconnector->base.force != DRM_FORCE_UNSPECIFIED &&
+ !aconnector->dc_em_sink)
+ handle_edid_mgmt(aconnector);
+
+ dc_sink = to_amdgpu_dm_connector(connector)->dc_sink;
+
+ if (dc_sink == NULL && aconnector->base.force != DRM_FORCE_ON_DIGITAL &&
+ aconnector->base.force != DRM_FORCE_ON) {
+ drm_err(connector->dev, "dc_sink is NULL!\n");
+ goto fail;
+ }
+
+ test_mode = drm_mode_duplicate(connector->dev, mode);
+ if (!test_mode)
+ goto fail;
+
+ drm_mode_set_crtcinfo(test_mode, 0);
+
+ stream = amdgpu_dm_create_validate_stream_for_sink(connector, test_mode,
+ to_dm_connector_state(connector->state),
+ NULL);
+ drm_mode_destroy(connector->dev, test_mode);
+ if (stream) {
+ dc_stream_release(stream);
+ result = MODE_OK;
+ }
+
+fail:
+ /* TODO: error handling*/
+ return result;
+}
+
+int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state,
+ struct dc_info_packet *out)
+{
+ struct hdmi_drm_infoframe frame;
+ unsigned char buf[30]; /* 26 + 4 */
+ ssize_t len;
+ int ret, i;
+
+ memset(out, 0, sizeof(*out));
+
+ if (!state->hdr_output_metadata)
+ return 0;
+
+ ret = drm_hdmi_infoframe_set_hdr_metadata(&frame, state);
+ if (ret)
+ return ret;
+
+ len = hdmi_drm_infoframe_pack_only(&frame, buf, sizeof(buf));
+ if (len < 0)
+ return (int)len;
+
+ /* Static metadata is a fixed 26 bytes + 4 byte header. */
+ if (len != 30)
+ return -EINVAL;
+
+ /* Prepare the infopacket for DC. */
+ switch (state->connector->connector_type) {
+ case DRM_MODE_CONNECTOR_HDMIA:
+ out->hb0 = 0x87; /* type */
+ out->hb1 = 0x01; /* version */
+ out->hb2 = 0x1A; /* length */
+ out->sb[0] = buf[3]; /* checksum */
+ i = 1;
+ break;
+
+ case DRM_MODE_CONNECTOR_DisplayPort:
+ case DRM_MODE_CONNECTOR_eDP:
+ out->hb0 = 0x00; /* sdp id, zero */
+ out->hb1 = 0x87; /* type */
+ out->hb2 = 0x1D; /* payload len - 1 */
+ out->hb3 = (0x13 << 2); /* sdp version */
+ out->sb[0] = 0x01; /* version */
+ out->sb[1] = 0x1A; /* length */
+ i = 2;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ memcpy(&out->sb[i], &buf[4], 26);
+ out->valid = true;
+
+ print_hex_dump(KERN_DEBUG, "HDR SB:", DUMP_PREFIX_NONE, 16, 1, out->sb,
+ sizeof(out->sb), false);
+
+ return 0;
+}
+
+static int
+amdgpu_dm_connector_atomic_check(struct drm_connector *conn,
+ struct drm_atomic_state *state)
+{
+ struct drm_connector_state *new_con_state =
+ drm_atomic_get_new_connector_state(state, conn);
+ struct drm_connector_state *old_con_state =
+ drm_atomic_get_old_connector_state(state, conn);
+ struct drm_crtc *crtc = new_con_state->crtc;
+ struct drm_crtc_state *new_crtc_state;
+ struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(conn);
+ int ret;
+
+ if (WARN_ON(unlikely(!old_con_state || !new_con_state)))
+ return -EINVAL;
+
+ trace_amdgpu_dm_connector_atomic_check(new_con_state);
+
+ if (conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort) {
+ ret = drm_dp_mst_root_conn_atomic_check(new_con_state, &aconn->mst_mgr);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (!crtc)
+ return 0;
+
+ if (new_con_state->privacy_screen_sw_state != old_con_state->privacy_screen_sw_state) {
+ new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (IS_ERR(new_crtc_state))
+ return PTR_ERR(new_crtc_state);
+
+ new_crtc_state->mode_changed = true;
+ }
+
+ if (new_con_state->colorspace != old_con_state->colorspace) {
+ new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (IS_ERR(new_crtc_state))
+ return PTR_ERR(new_crtc_state);
+
+ new_crtc_state->mode_changed = true;
+ }
+
+ if (new_con_state->content_type != old_con_state->content_type) {
+ new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (IS_ERR(new_crtc_state))
+ return PTR_ERR(new_crtc_state);
+
+ new_crtc_state->mode_changed = true;
+ }
+
+ if (!drm_connector_atomic_hdr_metadata_equal(old_con_state, new_con_state)) {
+ struct dc_info_packet hdr_infopacket;
+
+ ret = amdgpu_dm_fill_hdr_info_packet(new_con_state, &hdr_infopacket);
+ if (ret)
+ return ret;
+
+ new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (IS_ERR(new_crtc_state))
+ return PTR_ERR(new_crtc_state);
+
+ /*
+ * DC considers the stream backends changed if the
+ * static metadata changes. Forcing the modeset also
+ * gives a simple way for userspace to switch from
+ * 8bpc to 10bpc when setting the metadata to enter
+ * or exit HDR.
+ *
+ * Changing the static metadata after it's been
+ * set is permissible, however. So only force a
+ * modeset if we're entering or exiting HDR.
+ */
+ new_crtc_state->mode_changed = new_crtc_state->mode_changed ||
+ !old_con_state->hdr_output_metadata ||
+ !new_con_state->hdr_output_metadata;
+ }
+
+ return 0;
+}
+
+static const struct drm_connector_helper_funcs
+amdgpu_dm_connector_helper_funcs = {
+ /*
+ * If hotplugging a second bigger display in FB Con mode, bigger resolution
+ * modes will be filtered by drm_mode_validate_size(), and those modes
+ * are missing after user start lightdm. So we need to renew modes list.
+ * in get_modes call back, not just return the modes count
+ */
+ .get_modes = get_modes,
+ .mode_valid = amdgpu_dm_connector_mode_valid,
+ .atomic_check = amdgpu_dm_connector_atomic_check,
+};
+
+int amdgpu_dm_convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth)
+{
+ switch (display_color_depth) {
+ case COLOR_DEPTH_666:
+ return 6;
+ case COLOR_DEPTH_888:
+ return 8;
+ case COLOR_DEPTH_101010:
+ return 10;
+ case COLOR_DEPTH_121212:
+ return 12;
+ case COLOR_DEPTH_141414:
+ return 14;
+ case COLOR_DEPTH_161616:
+ return 16;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int to_drm_connector_type(enum signal_type st, uint32_t connector_id)
+{
+ switch (st) {
+ case SIGNAL_TYPE_HDMI_TYPE_A:
+ return DRM_MODE_CONNECTOR_HDMIA;
+ case SIGNAL_TYPE_EDP:
+ return DRM_MODE_CONNECTOR_eDP;
+ case SIGNAL_TYPE_LVDS:
+ return DRM_MODE_CONNECTOR_LVDS;
+ case SIGNAL_TYPE_RGB:
+ return DRM_MODE_CONNECTOR_VGA;
+ case SIGNAL_TYPE_DISPLAY_PORT:
+ case SIGNAL_TYPE_DISPLAY_PORT_MST:
+ return DRM_MODE_CONNECTOR_DisplayPort;
+ case SIGNAL_TYPE_DVI_DUAL_LINK:
+ case SIGNAL_TYPE_DVI_SINGLE_LINK:
+ if (connector_id == CONNECTOR_ID_SINGLE_LINK_DVII ||
+ connector_id == CONNECTOR_ID_DUAL_LINK_DVII)
+ return DRM_MODE_CONNECTOR_DVII;
+
+ return DRM_MODE_CONNECTOR_DVID;
+ case SIGNAL_TYPE_VIRTUAL:
+ return DRM_MODE_CONNECTOR_VIRTUAL;
+
+ default:
+ return DRM_MODE_CONNECTOR_Unknown;
+ }
+}
+
+static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector)
+{
+ struct drm_encoder *encoder;
+
+ /* There is only one encoder per connector */
+ drm_connector_for_each_possible_encoder(connector, encoder)
+ return encoder;
+
+ return NULL;
+}
+
+static void amdgpu_dm_get_native_mode(struct drm_connector *connector)
+{
+ struct drm_encoder *encoder;
+ struct amdgpu_encoder *amdgpu_encoder;
+
+ encoder = amdgpu_dm_connector_to_encoder(connector);
+
+ if (encoder == NULL)
+ return;
+
+ amdgpu_encoder = to_amdgpu_encoder(encoder);
+
+ amdgpu_encoder->native_mode.clock = 0;
+
+ if (!list_empty(&connector->probed_modes)) {
+ struct drm_display_mode *preferred_mode = NULL;
+
+ list_for_each_entry(preferred_mode,
+ &connector->probed_modes,
+ head) {
+ if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED)
+ amdgpu_encoder->native_mode = *preferred_mode;
+
+ break;
+ }
+
+ }
+}
+
+static struct drm_display_mode *
+amdgpu_dm_create_common_mode(struct drm_encoder *encoder,
+ const char *name,
+ int hdisplay, int vdisplay)
+{
+ struct drm_device *dev = encoder->dev;
+ struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
+ struct drm_display_mode *mode = NULL;
+ struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
+
+ mode = drm_mode_duplicate(dev, native_mode);
+
+ if (mode == NULL)
+ return NULL;
+
+ mode->hdisplay = hdisplay;
+ mode->vdisplay = vdisplay;
+ mode->type &= ~DRM_MODE_TYPE_PREFERRED;
+ strscpy(mode->name, name, DRM_DISPLAY_MODE_LEN);
+
+ return mode;
+
+}
+
+static const struct amdgpu_dm_mode_size {
+ char name[DRM_DISPLAY_MODE_LEN];
+ int w;
+ int h;
+} common_modes[] = {
+ { "640x480", 640, 480},
+ { "800x600", 800, 600},
+ { "1024x768", 1024, 768},
+ { "1280x720", 1280, 720},
+ { "1280x800", 1280, 800},
+ {"1280x1024", 1280, 1024},
+ { "1440x900", 1440, 900},
+ {"1680x1050", 1680, 1050},
+ {"1600x1200", 1600, 1200},
+ {"1920x1080", 1920, 1080},
+ {"1920x1200", 1920, 1200}
+};
+
+static void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder);
+ struct drm_display_mode *mode = NULL;
+ struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode;
+ struct amdgpu_dm_connector *amdgpu_dm_connector =
+ to_amdgpu_dm_connector(connector);
+ int i;
+ int n;
+
+ if ((connector->connector_type != DRM_MODE_CONNECTOR_eDP) &&
+ (connector->connector_type != DRM_MODE_CONNECTOR_LVDS))
+ return;
+
+ n = ARRAY_SIZE(common_modes);
+
+ for (i = 0; i < n; i++) {
+ struct drm_display_mode *curmode = NULL;
+ bool mode_existed = false;
+
+ if (common_modes[i].w > native_mode->hdisplay ||
+ common_modes[i].h > native_mode->vdisplay ||
+ (common_modes[i].w == native_mode->hdisplay &&
+ common_modes[i].h == native_mode->vdisplay))
+ continue;
+
+ list_for_each_entry(curmode, &connector->probed_modes, head) {
+ if (common_modes[i].w == curmode->hdisplay &&
+ common_modes[i].h == curmode->vdisplay) {
+ mode_existed = true;
+ break;
+ }
+ }
+
+ if (mode_existed)
+ continue;
+
+ mode = amdgpu_dm_create_common_mode(encoder,
+ common_modes[i].name, common_modes[i].w,
+ common_modes[i].h);
+ if (!mode)
+ continue;
+
+ drm_mode_probed_add(connector, mode);
+ amdgpu_dm_connector->num_modes++;
+ }
+}
+
+void amdgpu_set_panel_orientation(struct drm_connector *connector)
+{
+ struct drm_encoder *encoder;
+ struct amdgpu_encoder *amdgpu_encoder;
+ const struct drm_display_mode *native_mode;
+
+ if (connector->connector_type != DRM_MODE_CONNECTOR_eDP &&
+ connector->connector_type != DRM_MODE_CONNECTOR_LVDS)
+ return;
+
+ mutex_lock(&connector->dev->mode_config.mutex);
+ amdgpu_dm_connector_get_modes(connector);
+ mutex_unlock(&connector->dev->mode_config.mutex);
+
+ encoder = amdgpu_dm_connector_to_encoder(connector);
+ if (!encoder)
+ return;
+
+ amdgpu_encoder = to_amdgpu_encoder(encoder);
+
+ native_mode = &amdgpu_encoder->native_mode;
+ if (native_mode->hdisplay == 0 || native_mode->vdisplay == 0)
+ return;
+
+ drm_connector_set_panel_orientation_with_quirk(connector,
+ DRM_MODE_PANEL_ORIENTATION_UNKNOWN,
+ native_mode->hdisplay,
+ native_mode->vdisplay);
+}
+
+static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector,
+ const struct drm_edid *drm_edid)
+{
+ struct amdgpu_dm_connector *amdgpu_dm_connector =
+ to_amdgpu_dm_connector(connector);
+
+ if (drm_edid) {
+ /* empty probed_modes */
+ INIT_LIST_HEAD(&connector->probed_modes);
+ amdgpu_dm_connector->num_modes =
+ drm_edid_connector_add_modes(connector);
+
+ /* sorting the probed modes before calling function
+ * amdgpu_dm_get_native_mode() since EDID can have
+ * more than one preferred mode. The modes that are
+ * later in the probed mode list could be of higher
+ * and preferred resolution. For example, 3840x2160
+ * resolution in base EDID preferred timing and 4096x2160
+ * preferred resolution in DID extension block later.
+ */
+ drm_mode_sort(&connector->probed_modes);
+ amdgpu_dm_get_native_mode(connector);
+
+ /* Freesync capabilities are reset by calling
+ * drm_edid_connector_add_modes() and need to be
+ * restored here.
+ */
+ amdgpu_dm_update_freesync_caps(connector, drm_edid, false);
+ } else {
+ amdgpu_dm_connector->num_modes = 0;
+ }
+}
+
+static bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector,
+ struct drm_display_mode *mode)
+{
+ struct drm_display_mode *m;
+
+ list_for_each_entry(m, &aconnector->base.probed_modes, head) {
+ if (drm_mode_equal(m, mode))
+ return true;
+ }
+
+ return false;
+}
+
+static uint add_fs_modes(struct amdgpu_dm_connector *aconnector)
+{
+ const struct drm_display_mode *m;
+ struct drm_display_mode *new_mode;
+ uint i;
+ u32 new_modes_count = 0;
+
+ /* Standard FPS values
+ *
+ * 23.976 - TV/NTSC
+ * 24 - Cinema
+ * 25 - TV/PAL
+ * 29.97 - TV/NTSC
+ * 30 - TV/NTSC
+ * 48 - Cinema HFR
+ * 50 - TV/PAL
+ * 60 - Commonly used
+ * 48,72,96,120 - Multiples of 24
+ */
+ static const u32 common_rates[] = {
+ 23976, 24000, 25000, 29970, 30000,
+ 48000, 50000, 60000, 72000, 96000, 120000
+ };
+
+ /*
+ * Find mode with highest refresh rate with the same resolution
+ * as the preferred mode. Some monitors report a preferred mode
+ * with lower resolution than the highest refresh rate supported.
+ */
+
+ m = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true);
+ if (!m)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(common_rates); i++) {
+ u64 target_vtotal, target_vtotal_diff;
+ u64 num, den;
+
+ if (drm_mode_vrefresh(m) * 1000 < common_rates[i])
+ continue;
+
+ if (common_rates[i] < aconnector->min_vfreq * 1000 ||
+ common_rates[i] > aconnector->max_vfreq * 1000)
+ continue;
+
+ num = (unsigned long long)m->clock * 1000 * 1000;
+ den = common_rates[i] * (unsigned long long)m->htotal;
+ target_vtotal = div_u64(num, den);
+ target_vtotal_diff = target_vtotal - m->vtotal;
+
+ /* Check for illegal modes */
+ if (m->vsync_start + target_vtotal_diff < m->vdisplay ||
+ m->vsync_end + target_vtotal_diff < m->vsync_start ||
+ m->vtotal + target_vtotal_diff < m->vsync_end)
+ continue;
+
+ new_mode = drm_mode_duplicate(aconnector->base.dev, m);
+ if (!new_mode)
+ goto out;
+
+ new_mode->vtotal += (u16)target_vtotal_diff;
+ new_mode->vsync_start += (u16)target_vtotal_diff;
+ new_mode->vsync_end += (u16)target_vtotal_diff;
+ new_mode->type &= ~DRM_MODE_TYPE_PREFERRED;
+ new_mode->type |= DRM_MODE_TYPE_DRIVER;
+
+ if (!is_duplicate_mode(aconnector, new_mode)) {
+ drm_mode_probed_add(&aconnector->base, new_mode);
+ new_modes_count += 1;
+ } else
+ drm_mode_destroy(aconnector->base.dev, new_mode);
+ }
+ out:
+ return new_modes_count;
+}
+
+static void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector,
+ const struct drm_edid *drm_edid)
+{
+ struct amdgpu_dm_connector *amdgpu_dm_connector =
+ to_amdgpu_dm_connector(connector);
+
+ if (!(amdgpu_freesync_vid_mode && drm_edid))
+ return;
+
+ if (!amdgpu_dm_connector->dc_sink || !amdgpu_dm_connector->dc_link)
+ return;
+
+ if (!dc_supports_vrr(amdgpu_dm_connector->dc_sink->ctx->dce_version))
+ return;
+
+ if (dc_connector_supports_analog(amdgpu_dm_connector->dc_link->link_id.id) &&
+ amdgpu_dm_connector->dc_sink->edid_caps.analog)
+ return;
+
+ if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10)
+ amdgpu_dm_connector->num_modes +=
+ add_fs_modes(amdgpu_dm_connector);
+}
+
+static int amdgpu_dm_connector_get_modes(struct drm_connector *connector)
+{
+ struct amdgpu_dm_connector *amdgpu_dm_connector =
+ to_amdgpu_dm_connector(connector);
+ struct dc_link *dc_link = amdgpu_dm_connector->dc_link;
+ struct drm_encoder *encoder;
+ const struct drm_edid *drm_edid = amdgpu_dm_connector->drm_edid;
+ struct dc_link_settings *verified_link_cap = &dc_link->verified_link_cap;
+ const struct dc *dc = dc_link->dc;
+
+ encoder = amdgpu_dm_connector_to_encoder(connector);
+
+ if (!drm_edid) {
+ amdgpu_dm_connector->num_modes =
+ drm_add_modes_noedid(connector, 640, 480);
+ if (dc->link_srv->dp_get_encoding_format(verified_link_cap) == DP_128b_132b_ENCODING)
+ amdgpu_dm_connector->num_modes +=
+ drm_add_modes_noedid(connector, 1920, 1080);
+
+ if (amdgpu_dm_connector->dc_sink &&
+ amdgpu_dm_connector->dc_sink->edid_caps.analog &&
+ dc_connector_supports_analog(dc_link->link_id.id)) {
+ /* Analog monitor connected by DAC load detection.
+ * Add common modes. It will be up to the user to select one that works.
+ */
+ for (int i = 0; i < ARRAY_SIZE(common_modes); i++)
+ amdgpu_dm_connector->num_modes += drm_add_modes_noedid(
+ connector, common_modes[i].w, common_modes[i].h);
+ }
+ } else {
+ amdgpu_dm_connector_ddc_get_modes(connector, drm_edid);
+ if (encoder)
+ amdgpu_dm_connector_add_common_modes(encoder, connector);
+ amdgpu_dm_connector_add_freesync_modes(connector, drm_edid);
+ }
+ amdgpu_dm_fbc_init(connector);
+
+ return amdgpu_dm_connector->num_modes;
+}
+
+static const u32 supported_colorspaces =
+ BIT(DRM_MODE_COLORIMETRY_BT709_YCC) |
+ BIT(DRM_MODE_COLORIMETRY_OPRGB) |
+ BIT(DRM_MODE_COLORIMETRY_BT2020_RGB) |
+ BIT(DRM_MODE_COLORIMETRY_BT2020_YCC);
+
+static void hdmi_frl_status_polling_work(struct work_struct *work)
+{
+ struct amdgpu_display_manager *dm =
+ container_of(to_delayed_work(work), struct amdgpu_display_manager,
+ hdmi_frl_status_polling_work);
+ struct dc *dc = dm->dc;
+ struct dc_link *dc_link;
+ bool link_update = false;
+
+ for (int i = 0; i < MAX_LINKS; i++) {
+ dc_link = dc->links[i];
+
+
+ if (!dc_link || !dc_link->local_sink)
+ continue;
+
+ if (!dc_is_hdmi_signal(dc_link->connector_signal))
+ continue;
+
+ if (dc_link->connector_signal != SIGNAL_TYPE_HDMI_FRL)
+ continue;
+
+ link_update = dc_link_frl_poll_status_flag(dc_link);
+ if (link_update) {
+ mutex_lock(&dm->dc_lock);
+ dc_link_detect(dc_link, DETECT_REASON_RETRAIN);
+ mutex_unlock(&dm->dc_lock);
+ }
+ }
+
+ queue_delayed_work(dm->hdmi_frl_status_polling_wq,
+ &dm->hdmi_frl_status_polling_work,
+ msecs_to_jiffies(dm->hdmi_frl_status_polling_delay_ms));
+}
+
+void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
+ struct amdgpu_dm_connector *aconnector,
+ int connector_type,
+ struct dc_link *link,
+ int link_index)
+{
+ struct amdgpu_device *adev = drm_to_adev(dm->ddev);
+
+ /*
+ * Some of the properties below require access to state, like bpc.
+ * Allocate some default initial connector state with our reset helper.
+ */
+ if (aconnector->base.funcs->reset)
+ aconnector->base.funcs->reset(&aconnector->base);
+
+ aconnector->connector_id = link_index;
+ aconnector->bl_idx = -1;
+ aconnector->dc_link = link;
+ aconnector->base.interlace_allowed = false;
+ aconnector->base.doublescan_allowed = false;
+ aconnector->base.stereo_allowed = false;
+ aconnector->base.dpms = DRM_MODE_DPMS_OFF;
+ aconnector->hpd.hpd = AMDGPU_HPD_NONE; /* not used */
+ aconnector->audio_inst = -1;
+ aconnector->pack_sdp_v1_3 = false;
+ aconnector->as_type = ADAPTIVE_SYNC_TYPE_NONE;
+ memset(&aconnector->vsdb_info, 0, sizeof(aconnector->vsdb_info));
+ mutex_init(&aconnector->hpd_lock);
+ mutex_init(&aconnector->handle_mst_msg_ready);
+
+ /*
+ * If HDMI HPD debounce delay is set, use the minimum between selected
+ * value and AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS
+ */
+ if (amdgpu_hdmi_hpd_debounce_delay_ms) {
+ aconnector->hdmi_hpd_debounce_delay_ms = min(amdgpu_hdmi_hpd_debounce_delay_ms,
+ AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS);
+ INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, amdgpu_dm_hdmi_hpd_debounce_work);
+ aconnector->hdmi_prev_sink = NULL;
+ } else {
+ aconnector->hdmi_hpd_debounce_delay_ms = 0;
+ }
+
+ dm->hdmi_frl_status_polling_delay_ms = 200;
+ INIT_DELAYED_WORK(&dm->hdmi_frl_status_polling_work, hdmi_frl_status_polling_work);
+ /*
+ * configure support HPD hot plug connector_>polled default value is 0
+ * which means HPD hot plug not supported
+ */
+ switch (connector_type) {
+ case DRM_MODE_CONNECTOR_HDMIA:
+ aconnector->base.polled = DRM_CONNECTOR_POLL_HPD;
+ aconnector->base.ycbcr_420_allowed =
+ link->link_enc->features.hdmi_ycbcr420_supported ? true : false;
+ break;
+ case DRM_MODE_CONNECTOR_DisplayPort:
+ aconnector->base.polled = DRM_CONNECTOR_POLL_HPD;
+ link->link_enc = link_enc_cfg_get_link_enc(link);
+ ASSERT(link->link_enc);
+ if (link->link_enc)
+ aconnector->base.ycbcr_420_allowed =
+ link->link_enc->features.dp_ycbcr420_supported ? true : false;
+ break;
+ case DRM_MODE_CONNECTOR_DVID:
+ aconnector->base.polled = DRM_CONNECTOR_POLL_HPD;
+ break;
+ case DRM_MODE_CONNECTOR_DVII:
+ case DRM_MODE_CONNECTOR_VGA:
+ aconnector->base.polled =
+ DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
+ break;
+ default:
+ break;
+ }
+
+ drm_object_attach_property(&aconnector->base.base,
+ dm->ddev->mode_config.scaling_mode_property,
+ DRM_MODE_SCALE_NONE);
+
+ if (connector_type == DRM_MODE_CONNECTOR_HDMIA
+ || (connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root))
+ drm_connector_attach_broadcast_rgb_property(&aconnector->base);
+
+ drm_object_attach_property(&aconnector->base.base,
+ adev->mode_info.underscan_property,
+ UNDERSCAN_OFF);
+ drm_object_attach_property(&aconnector->base.base,
+ adev->mode_info.underscan_hborder_property,
+ 0);
+ drm_object_attach_property(&aconnector->base.base,
+ adev->mode_info.underscan_vborder_property,
+ 0);
+
+ if (!aconnector->mst_root)
+ drm_connector_attach_max_bpc_property(&aconnector->base, 8, 16);
+
+ aconnector->base.state->max_bpc = 16;
+ aconnector->base.state->max_requested_bpc = aconnector->base.state->max_bpc;
+
+ if (connector_type == DRM_MODE_CONNECTOR_HDMIA) {
+ /* Content Type is currently only implemented for HDMI. */
+ drm_connector_attach_content_type_property(&aconnector->base);
+ }
+
+ if (connector_type == DRM_MODE_CONNECTOR_HDMIA) {
+ if (!drm_mode_create_hdmi_colorspace_property(&aconnector->base, supported_colorspaces))
+ drm_connector_attach_colorspace_property(&aconnector->base);
+ } else if ((connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root) ||
+ connector_type == DRM_MODE_CONNECTOR_eDP) {
+ if (!drm_mode_create_dp_colorspace_property(&aconnector->base, supported_colorspaces))
+ drm_connector_attach_colorspace_property(&aconnector->base);
+ }
+
+ if (connector_type == DRM_MODE_CONNECTOR_HDMIA ||
+ connector_type == DRM_MODE_CONNECTOR_DisplayPort ||
+ connector_type == DRM_MODE_CONNECTOR_eDP) {
+ drm_connector_attach_hdr_output_metadata_property(&aconnector->base);
+
+ if (!aconnector->mst_root)
+ drm_connector_attach_vrr_capable_property(&aconnector->base);
+
+
+ if (adev->dm.hdcp_workqueue)
+ drm_connector_attach_content_protection_property(&aconnector->base, true);
+ }
+
+ if (connector_type == DRM_MODE_CONNECTOR_eDP) {
+ struct drm_privacy_screen *privacy_screen;
+
+ drm_connector_attach_panel_type_property(&aconnector->base);
+
+ privacy_screen = drm_privacy_screen_get(adev_to_drm(adev)->dev, NULL);
+ if (!IS_ERR(privacy_screen)) {
+ drm_connector_attach_privacy_screen_provider(&aconnector->base,
+ privacy_screen);
+ } else if (PTR_ERR(privacy_screen) != -ENODEV) {
+ drm_warn(adev_to_drm(adev), "Error getting privacy-screen\n");
+ }
+ }
+}
+
+static int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct amdgpu_i2c_adapter *i2c = i2c_get_adapdata(i2c_adap);
+ struct ddc_service *ddc_service = i2c->ddc_service;
+ struct i2c_command cmd;
+ int i;
+ int result = -EIO;
+
+ if (!ddc_service->ddc_pin)
+ return result;
+
+ cmd.payloads = kcalloc(num, sizeof(struct i2c_payload), GFP_KERNEL);
+
+ if (!cmd.payloads)
+ return result;
+
+ cmd.number_of_payloads = num;
+ cmd.engine = I2C_COMMAND_ENGINE_DEFAULT;
+ cmd.speed = 100;
+
+ for (i = 0; i < num; i++) {
+ cmd.payloads[i].write = !(msgs[i].flags & I2C_M_RD);
+ cmd.payloads[i].address = msgs[i].addr;
+ cmd.payloads[i].length = msgs[i].len;
+ cmd.payloads[i].data = msgs[i].buf;
+ }
+
+ if (i2c->oem) {
+ if (dc_submit_i2c_oem(
+ ddc_service->ctx->dc,
+ &cmd))
+ result = num;
+ } else {
+ if (dc_submit_i2c(
+ ddc_service->ctx->dc,
+ ddc_service->link->link_index,
+ &cmd))
+ result = num;
+ }
+
+ kfree(cmd.payloads);
+ return result;
+}
+
+static u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm amdgpu_dm_i2c_algo = {
+ .master_xfer = amdgpu_dm_i2c_xfer,
+ .functionality = amdgpu_dm_i2c_func,
+};
+
+struct amdgpu_i2c_adapter *
+amdgpu_dm_create_i2c(struct ddc_service *ddc_service, bool oem)
+{
+ struct amdgpu_device *adev = ddc_service->ctx->driver_context;
+ struct amdgpu_i2c_adapter *i2c;
+
+ i2c = kzalloc(sizeof(struct amdgpu_i2c_adapter), GFP_KERNEL);
+ if (!i2c)
+ return NULL;
+ i2c->base.owner = THIS_MODULE;
+ i2c->base.dev.parent = &adev->pdev->dev;
+ i2c->base.algo = &amdgpu_dm_i2c_algo;
+ if (oem)
+ snprintf(i2c->base.name, sizeof(i2c->base.name), "AMDGPU DM i2c OEM bus");
+ else
+ snprintf(i2c->base.name, sizeof(i2c->base.name), "AMDGPU DM i2c hw bus %d",
+ ddc_service->link->link_index);
+ i2c_set_adapdata(&i2c->base, i2c);
+ i2c->ddc_service = ddc_service;
+ i2c->oem = oem;
+
+ return i2c;
+}
+
+int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector)
+{
+ struct cec_connector_info conn_info;
+ struct drm_device *ddev = aconnector->base.dev;
+ struct device *hdmi_dev = ddev->dev;
+
+ if (amdgpu_dc_debug_mask & DC_DISABLE_HDMI_CEC) {
+ drm_info(ddev, "HDMI-CEC feature masked\n");
+ return -EINVAL;
+ }
+
+ cec_fill_conn_info_from_drm(&conn_info, &aconnector->base);
+ aconnector->notifier =
+ cec_notifier_conn_register(hdmi_dev, NULL, &conn_info);
+ if (!aconnector->notifier) {
+ drm_err(ddev, "Failed to create cec notifier\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/*
+ * Note: this function assumes that dc_link_detect() was called for the
+ * dc_link which will be represented by this aconnector.
+ */
+int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm,
+ struct amdgpu_dm_connector *aconnector,
+ u32 link_index,
+ struct amdgpu_encoder *aencoder)
+{
+ int res = 0;
+ int connector_type;
+ struct dc *dc = dm->dc;
+ struct dc_link *link = dc_get_link_at_index(dc, link_index);
+ struct amdgpu_i2c_adapter *i2c;
+
+ /* Not needed for writeback connector */
+ link->priv = aconnector;
+
+
+ i2c = amdgpu_dm_create_i2c(link->ddc, false);
+ if (!i2c) {
+ drm_err(adev_to_drm(dm->adev), "Failed to create i2c adapter data\n");
+ return -ENOMEM;
+ }
+
+ aconnector->i2c = i2c;
+ res = devm_i2c_add_adapter(dm->adev->dev, &i2c->base);
+
+ if (res) {
+ drm_err(adev_to_drm(dm->adev), "Failed to register hw i2c %d\n", link->link_index);
+ goto out_free;
+ }
+
+ connector_type = to_drm_connector_type(link->connector_signal, link->link_id.id);
+
+ res = drm_connector_init_with_ddc(
+ dm->ddev,
+ &aconnector->base,
+ &amdgpu_dm_connector_funcs,
+ connector_type,
+ &i2c->base);
+
+ if (res) {
+ drm_err(adev_to_drm(dm->adev), "connector_init failed\n");
+ aconnector->connector_id = -1;
+ goto out_free;
+ }
+
+ drm_connector_helper_add(
+ &aconnector->base,
+ &amdgpu_dm_connector_helper_funcs);
+
+ amdgpu_dm_connector_init_helper(
+ dm,
+ aconnector,
+ connector_type,
+ link,
+ link_index);
+
+ drm_connector_attach_encoder(
+ &aconnector->base, &aencoder->base);
+
+ if (connector_type == DRM_MODE_CONNECTOR_HDMIA ||
+ connector_type == DRM_MODE_CONNECTOR_HDMIB)
+ amdgpu_dm_initialize_hdmi_connector(aconnector);
+
+ if (connector_type == DRM_MODE_CONNECTOR_DisplayPort
+ || connector_type == DRM_MODE_CONNECTOR_eDP)
+ amdgpu_dm_initialize_dp_connector(dm, aconnector, link->link_index);
+
+out_free:
+ if (res) {
+ kfree(i2c);
+ aconnector->i2c = NULL;
+ }
+ return res;
+}
+
+static int dm_force_atomic_commit(struct drm_connector *connector)
+{
+ int ret = 0;
+ struct drm_device *ddev = connector->dev;
+ struct drm_atomic_state *state = drm_atomic_state_alloc(ddev);
+ struct amdgpu_crtc *disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc);
+ struct drm_plane *plane = disconnected_acrtc->base.primary;
+ struct drm_connector_state *conn_state;
+ struct drm_crtc_state *crtc_state;
+ struct drm_plane_state *plane_state;
+
+ if (!state)
+ return -ENOMEM;
+
+ state->acquire_ctx = ddev->mode_config.acquire_ctx;
+
+ /* Construct an atomic state to restore previous display setting */
+
+ /*
+ * Attach connectors to drm_atomic_state
+ */
+ conn_state = drm_atomic_get_connector_state(state, connector);
+
+ /* Check for error in getting connector state */
+ if (IS_ERR(conn_state)) {
+ ret = PTR_ERR(conn_state);
+ goto out;
+ }
+
+ /* Attach crtc to drm_atomic_state*/
+ crtc_state = drm_atomic_get_crtc_state(state, &disconnected_acrtc->base);
+
+ /* Check for error in getting crtc state */
+ if (IS_ERR(crtc_state)) {
+ ret = PTR_ERR(crtc_state);
+ goto out;
+ }
+
+ /* force a restore */
+ crtc_state->mode_changed = true;
+
+ /* Attach plane to drm_atomic_state */
+ plane_state = drm_atomic_get_plane_state(state, plane);
+
+ /* Check for error in getting plane state */
+ if (IS_ERR(plane_state)) {
+ ret = PTR_ERR(plane_state);
+ goto out;
+ }
+
+ /* Call commit internally with the state we just constructed */
+ ret = drm_atomic_commit(state);
+
+out:
+ drm_atomic_state_put(state);
+ if (ret)
+ drm_err(ddev, "Restoring old state failed with %i\n", ret);
+
+ return ret;
+}
+
+/*
+ * This function handles all cases when set mode does not come upon hotplug.
+ * This includes when a display is unplugged then plugged back into the
+ * same port and when running without usermode desktop manager support
+ */
+void dm_restore_drm_connector_state(struct drm_device *dev,
+ struct drm_connector *connector)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct amdgpu_crtc *disconnected_acrtc;
+ struct dm_crtc_state *acrtc_state;
+
+ if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ return;
+
+ aconnector = to_amdgpu_dm_connector(connector);
+
+ if (!aconnector->dc_sink || !connector->state || !connector->encoder)
+ return;
+
+ disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc);
+ if (!disconnected_acrtc)
+ return;
+
+ acrtc_state = to_dm_crtc_state(disconnected_acrtc->base.state);
+ if (!acrtc_state->stream)
+ return;
+
+ /*
+ * If the previous sink is not released and different from the current,
+ * we deduce we are in a state where we can not rely on usermode call
+ * to turn on the display, so we do it here
+ */
+ if (acrtc_state->stream->sink != aconnector->dc_sink)
+ dm_force_atomic_commit(&aconnector->base);
+}
+
+static bool dm_edid_parser_send_cea(struct amdgpu_display_manager *dm,
+ unsigned int offset,
+ unsigned int total_length,
+ u8 *data,
+ unsigned int length,
+ struct amdgpu_hdmi_vsdb_info *vsdb)
+{
+ bool res;
+ union dmub_rb_cmd cmd;
+ struct dmub_cmd_send_edid_cea *input;
+ struct dmub_cmd_edid_cea_output *output;
+
+ if (length > DMUB_EDID_CEA_DATA_CHUNK_BYTES)
+ return false;
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ input = &cmd.edid_cea.data.input;
+
+ cmd.edid_cea.header.type = DMUB_CMD__EDID_CEA;
+ cmd.edid_cea.header.sub_type = 0;
+ cmd.edid_cea.header.payload_bytes =
+ sizeof(cmd.edid_cea) - sizeof(cmd.edid_cea.header);
+ input->offset = offset;
+ input->length = length;
+ input->cea_total_length = total_length;
+ memcpy(input->payload, data, length);
+
+ res = dc_wake_and_execute_dmub_cmd(dm->dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY);
+ if (!res) {
+ drm_err(adev_to_drm(dm->adev), "EDID CEA parser failed\n");
+ return false;
+ }
+
+ output = &cmd.edid_cea.data.output;
+
+ if (output->type == DMUB_CMD__EDID_CEA_ACK) {
+ if (!output->ack.success) {
+ drm_err(adev_to_drm(dm->adev), "EDID CEA ack failed at offset %d\n",
+ output->ack.offset);
+ }
+ } else if (output->type == DMUB_CMD__EDID_CEA_AMD_VSDB) {
+ if (!output->amd_vsdb.vsdb_found)
+ return false;
+
+ vsdb->freesync_supported = output->amd_vsdb.freesync_supported;
+ vsdb->amd_vsdb_version = output->amd_vsdb.amd_vsdb_version;
+ vsdb->min_refresh_rate_hz = output->amd_vsdb.min_frame_rate;
+ vsdb->max_refresh_rate_hz = output->amd_vsdb.max_frame_rate;
+ vsdb->freesync_mccs_vcp_code = output->amd_vsdb.freesync_mccs_vcp_code;
+ } else {
+ drm_warn(adev_to_drm(dm->adev), "Unknown EDID CEA parser results\n");
+ return false;
+ }
+
+ return true;
+}
+
+static bool parse_edid_cea_dmcu(struct amdgpu_display_manager *dm,
+ u8 *edid_ext, int len,
+ struct amdgpu_hdmi_vsdb_info *vsdb_info)
+{
+ int i;
+
+ /* send extension block to DMCU for parsing */
+ for (i = 0; i < len; i += 8) {
+ bool res;
+ int offset;
+
+ /* send 8 bytes a time */
+ if (!dc_edid_parser_send_cea(dm->dc, i, len, &edid_ext[i], 8))
+ return false;
+
+ if (i+8 == len) {
+ /* EDID block sent completed, expect result */
+ int version, min_rate, max_rate;
+
+ res = dc_edid_parser_recv_amd_vsdb(dm->dc, &version, &min_rate, &max_rate);
+ if (res) {
+ /* amd vsdb found */
+ vsdb_info->freesync_supported = 1;
+ vsdb_info->amd_vsdb_version = version;
+ vsdb_info->min_refresh_rate_hz = min_rate;
+ vsdb_info->max_refresh_rate_hz = max_rate;
+ /* Not enabled on DMCU*/
+ vsdb_info->freesync_mccs_vcp_code = 0;
+ return true;
+ }
+ /* not amd vsdb */
+ return false;
+ }
+
+ /* check for ack*/
+ res = dc_edid_parser_recv_cea_ack(dm->dc, &offset);
+ if (!res)
+ return false;
+ }
+
+ return false;
+}
+
+static bool parse_edid_cea_dmub(struct amdgpu_display_manager *dm,
+ u8 *edid_ext, int len,
+ struct amdgpu_hdmi_vsdb_info *vsdb_info)
+{
+ int i;
+
+ /* send extension block to DMCU for parsing */
+ for (i = 0; i < len; i += 8) {
+ /* send 8 bytes a time */
+ if (!dm_edid_parser_send_cea(dm, i, len, &edid_ext[i], 8, vsdb_info))
+ return false;
+ }
+
+ return vsdb_info->freesync_supported;
+}
+
+static bool parse_edid_cea(struct amdgpu_dm_connector *aconnector,
+ u8 *edid_ext, int len,
+ struct amdgpu_hdmi_vsdb_info *vsdb_info)
+{
+ struct amdgpu_device *adev = drm_to_adev(aconnector->base.dev);
+ bool ret;
+
+ mutex_lock(&adev->dm.dc_lock);
+ if (adev->dm.dmub_srv)
+ ret = parse_edid_cea_dmub(&adev->dm, edid_ext, len, vsdb_info);
+ else
+ ret = parse_edid_cea_dmcu(&adev->dm, edid_ext, len, vsdb_info);
+ mutex_unlock(&adev->dm.dc_lock);
+ return ret;
+}
+
+static void parse_edid_displayid_vrr(struct drm_connector *connector,
+ const struct edid *edid)
+{
+ u8 *edid_ext = NULL;
+ int i;
+ int j = 0;
+ u16 min_vfreq;
+ u16 max_vfreq;
+
+ if (!edid || !edid->extensions)
+ return;
+
+ /* Find DisplayID extension */
+ for (i = 0; i < edid->extensions; i++) {
+ edid_ext = (void *)(edid + (i + 1));
+ if (edid_ext[0] == DISPLAYID_EXT)
+ break;
+ }
+
+ if (i == edid->extensions)
+ return;
+
+ while (j < EDID_LENGTH) {
+ /* Get dynamic video timing range from DisplayID if available */
+ if (EDID_LENGTH - j > 13 && edid_ext[j] == 0x25 &&
+ (edid_ext[j+1] & 0xFE) == 0 && (edid_ext[j+2] == 9)) {
+ min_vfreq = edid_ext[j+9];
+ if (edid_ext[j+1] & 7)
+ max_vfreq = edid_ext[j+10] + ((edid_ext[j+11] & 3) << 8);
+ else
+ max_vfreq = edid_ext[j+10];
+
+ if (max_vfreq && min_vfreq) {
+ connector->display_info.monitor_range.max_vfreq = max_vfreq;
+ connector->display_info.monitor_range.min_vfreq = min_vfreq;
+
+ return;
+ }
+ }
+ j++;
+ }
+}
+
+static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector,
+ struct amdgpu_hdmi_vsdb_info *vsdb_info)
+{
+ struct drm_connector *connector = &aconnector->base;
+
+ vsdb_info->replay_mode = connector->display_info.amd_vsdb.replay_mode;
+ vsdb_info->amd_vsdb_version = connector->display_info.amd_vsdb.version;
+
+ return connector->display_info.amd_vsdb.version != 0;
+}
+
+static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector,
+ const struct edid *edid,
+ struct amdgpu_hdmi_vsdb_info *vsdb_info)
+{
+ u8 *edid_ext = NULL;
+ int i;
+ bool valid_vsdb_found = false;
+
+ /*----- drm_find_cea_extension() -----*/
+ /* No EDID or EDID extensions */
+ if (edid == NULL || edid->extensions == 0)
+ return -ENODEV;
+
+ /* Find CEA extension */
+ for (i = 0; i < edid->extensions; i++) {
+ edid_ext = (uint8_t *)edid + EDID_LENGTH * (i + 1);
+ if (edid_ext[0] == CEA_EXT)
+ break;
+ }
+
+ if (i == edid->extensions)
+ return -ENODEV;
+
+ /*----- cea_db_offsets() -----*/
+ if (edid_ext[0] != CEA_EXT)
+ return -ENODEV;
+
+ valid_vsdb_found = parse_edid_cea(aconnector, edid_ext, EDID_LENGTH, vsdb_info);
+
+ return valid_vsdb_found ? i : -ENODEV;
+}
+
+/**
+ * amdgpu_dm_update_freesync_caps - Update Freesync capabilities
+ *
+ * @connector: Connector to query.
+ * @drm_edid: DRM EDID from monitor
+ * @do_mccs: Controls whether MCCS (Monitor Control Command Set) over
+ * DDC (Display Data Channel) transactions are performed. When true,
+ * the driver queries the monitor to get or update additional FreeSync
+ * capability information. When false, these transactions are skipped.
+ *
+ * Amdgpu supports Freesync in DP and HDMI displays, and it is required to keep
+ * track of some of the display information in the internal data struct used by
+ * amdgpu_dm. This function checks which type of connector we need to set the
+ * FreeSync parameters.
+ */
+void amdgpu_dm_update_freesync_caps(struct drm_connector *connector,
+ const struct drm_edid *drm_edid, bool do_mccs)
+{
+ int i = 0;
+ struct amdgpu_dm_connector *amdgpu_dm_connector =
+ to_amdgpu_dm_connector(connector);
+ struct dm_connector_state *dm_con_state = NULL;
+ struct dc_sink *sink;
+ struct amdgpu_device *adev = drm_to_adev(connector->dev);
+ struct amdgpu_hdmi_vsdb_info vsdb_info = {0};
+ const struct edid *edid;
+ bool freesync_capable = false;
+ enum adaptive_sync_type as_type = ADAPTIVE_SYNC_TYPE_NONE;
+
+ if (!connector->state) {
+ drm_err(adev_to_drm(adev), "%s - Connector has no state", __func__);
+ goto update;
+ }
+
+ sink = amdgpu_dm_connector->dc_sink ?
+ amdgpu_dm_connector->dc_sink :
+ amdgpu_dm_connector->dc_em_sink;
+
+ drm_edid_connector_update(connector, drm_edid);
+
+ if (!drm_edid || !sink) {
+ dm_con_state = to_dm_connector_state(connector->state);
+
+ amdgpu_dm_connector->min_vfreq = 0;
+ amdgpu_dm_connector->max_vfreq = 0;
+ freesync_capable = false;
+
+ goto update;
+ }
+
+ dm_con_state = to_dm_connector_state(connector->state);
+
+ if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version))
+ goto update;
+
+ /* FIXME: Get rid of drm_edid_raw() */
+ edid = drm_edid_raw(drm_edid);
+
+ /* Some eDP panels only have the refresh rate range info in DisplayID */
+ if ((connector->display_info.monitor_range.min_vfreq == 0 ||
+ connector->display_info.monitor_range.max_vfreq == 0))
+ parse_edid_displayid_vrr(connector, edid);
+
+ if (edid && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT ||
+ sink->sink_signal == SIGNAL_TYPE_EDP)) {
+ if (amdgpu_dm_connector->dc_link &&
+ amdgpu_dm_connector->dc_link->dpcd_caps.allow_invalid_MSA_timing_param) {
+ amdgpu_dm_connector->min_vfreq = connector->display_info.monitor_range.min_vfreq;
+ amdgpu_dm_connector->max_vfreq = connector->display_info.monitor_range.max_vfreq;
+ if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10)
+ freesync_capable = true;
+ }
+
+ get_amd_vsdb(amdgpu_dm_connector, &vsdb_info);
+
+ if (vsdb_info.replay_mode) {
+ amdgpu_dm_connector->vsdb_info.replay_mode = vsdb_info.replay_mode;
+ amdgpu_dm_connector->vsdb_info.amd_vsdb_version = vsdb_info.amd_vsdb_version;
+ amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP;
+ }
+
+ } else if (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) {
+ i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info);
+ if (i >= 0) {
+ amdgpu_dm_connector->vsdb_info = vsdb_info;
+ sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code;
+
+ if (vsdb_info.freesync_supported) {
+ amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz;
+ amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz;
+ if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10)
+ freesync_capable = true;
+
+ connector->display_info.monitor_range.min_vfreq = vsdb_info.min_refresh_rate_hz;
+ connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz;
+ }
+ }
+ }
+
+ if (amdgpu_dm_connector->dc_link)
+ as_type = dm_get_adaptive_sync_support_type(amdgpu_dm_connector->dc_link);
+
+ if (as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) {
+ i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info);
+ if (i >= 0) {
+ amdgpu_dm_connector->vsdb_info = vsdb_info;
+ sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code;
+
+ if (vsdb_info.freesync_supported && vsdb_info.amd_vsdb_version > 0) {
+ amdgpu_dm_connector->pack_sdp_v1_3 = true;
+ amdgpu_dm_connector->as_type = as_type;
+
+ amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz;
+ amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz;
+ if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10)
+ freesync_capable = true;
+
+ connector->display_info.monitor_range.min_vfreq = vsdb_info.min_refresh_rate_hz;
+ connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz;
+ }
+ }
+ }
+
+ /* Handle MCCS */
+ if (do_mccs)
+ dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink);
+
+ if ((sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A ||
+ as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) &&
+ (!sink->edid_caps.freesync_vcp_code ||
+ (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported)))
+ freesync_capable = false;
+
+ if (do_mccs && sink->mccs_caps.freesync_supported && freesync_capable)
+ dm_helpers_mccs_vcp_set(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink);
+
+update:
+ if (dm_con_state)
+ dm_con_state->freesync_capable = freesync_capable;
+
+ if (connector->state && amdgpu_dm_connector->dc_link && !freesync_capable &&
+ amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported) {
+ amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported = false;
+ amdgpu_dm_connector->dc_link->replay_settings.replay_feature_enabled = false;
+ }
+
+ if (connector->vrr_capable_property)
+ drm_connector_set_vrr_capable_property(connector,
+ freesync_capable);
+}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
new file mode 100644
index 000000000000..0e8e4756cf17
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
@@ -0,0 +1,147 @@
+/* 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_CONNECTOR_H__
+#define __AMDGPU_DM_CONNECTOR_H__
+
+struct amdgpu_device;
+struct amdgpu_dm_connector;
+struct amdgpu_display_manager;
+struct amdgpu_encoder;
+struct amdgpu_i2c_adapter;
+struct dc_crtc_timing;
+struct dc_link;
+struct dc_state;
+struct dc_stream_state;
+struct ddc_service;
+struct dm_connector_state;
+struct drm_atomic_state;
+struct drm_device;
+struct drm_encoder_helper_funcs;
+struct drm_connector;
+struct drm_connector_state;
+struct drm_crtc;
+struct drm_device;
+struct drm_display_mode;
+struct drm_edid;
+struct drm_property;
+
+void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector);
+
+struct drm_connector_state *
+amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector);
+
+int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector,
+ struct drm_connector_state *connector_state,
+ struct drm_property *property,
+ uint64_t val);
+
+int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector,
+ const struct drm_connector_state *state,
+ struct drm_property *property,
+ uint64_t *val);
+
+void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
+ struct amdgpu_dm_connector *aconnector,
+ int connector_type,
+ struct dc_link *link,
+ int link_index);
+
+enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector,
+ const struct drm_display_mode *mode);
+
+void dm_restore_drm_connector_state(struct drm_device *dev,
+ struct drm_connector *connector);
+
+void amdgpu_dm_update_freesync_caps(struct drm_connector *connector,
+ const struct drm_edid *drm_edid,
+ bool do_mccs);
+
+void amdgpu_dm_update_connector_after_detect(
+ struct amdgpu_dm_connector *aconnector);
+
+void amdgpu_dm_hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector);
+int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector);
+
+struct drm_connector *
+amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_state *state,
+ struct drm_crtc *crtc);
+
+int amdgpu_dm_convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth);
+
+struct dc_stream_state *
+amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
+ const struct drm_display_mode *drm_mode,
+ const struct dm_connector_state *dm_state,
+ const struct dc_stream_state *old_stream);
+
+int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm,
+ struct amdgpu_dm_connector *amdgpu_dm_connector,
+ u32 link_index,
+ struct amdgpu_encoder *amdgpu_encoder);
+
+void amdgpu_dm_s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend);
+
+int amdgpu_dm_detect_mst_link_for_all_connectors(struct drm_device *dev);
+
+void amdgpu_set_panel_orientation(struct drm_connector *connector);
+
+enum dc_color_depth
+amdgpu_dm_convert_color_depth_from_display_info(const struct drm_connector *connector,
+ bool is_y420, int requested_bpc);
+
+void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev,
+ const struct drm_display_mode *mode,
+ const struct dm_connector_state *dm_state,
+ struct dc_stream_state *stream);
+
+bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode,
+ struct amdgpu_dm_connector *aconnector);
+
+int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state,
+ struct dc_info_packet *out);
+
+enum dc_color_space
+amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing,
+ const struct drm_connector_state *connector_state);
+
+struct drm_display_mode *
+amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
+ bool use_probed_modes);
+
+struct amdgpu_i2c_adapter *
+amdgpu_dm_create_i2c(struct ddc_service *ddc_service, bool oem);
+
+#define DDC_MANUFACTURERNAME_SAMSUNG 0x2D4C
+
+/* Encoder functions */
+extern const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs;
+int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev);
+int amdgpu_dm_encoder_init(struct drm_device *dev,
+ struct amdgpu_encoder *aencoder,
+ uint32_t link_index);
+
+#endif /* __AMDGPU_DM_CONNECTOR_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
index 2fb3ef7a54ff..096a855a7304 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
@@ -2971,7 +2971,7 @@ static ssize_t hdmi_cec_state_write(struct file *f, const char __user *buf,
ret = amdgpu_dm_initialize_hdmi_connector(aconnector);
if (ret)
return ret;
- hdmi_cec_set_edid(aconnector);
+ amdgpu_dm_hdmi_cec_set_edid(aconnector);
} else {
if (!aconnector->notifier)
return -EINVAL;
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
index 9a70d1487b62..09a6a1deb5db 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
@@ -1784,7 +1784,7 @@ int pre_validate_dsc(struct drm_atomic_state *state,
dm_old_crtc_state = to_dm_crtc_state(state->crtcs[ind].old_state);
local_dc_state->streams[i] =
- create_validate_stream_for_sink(connector,
+ amdgpu_dm_create_validate_stream_for_sink(connector,
&state->crtcs[ind].new_state->mode,
dm_new_conn_state,
dm_old_crtc_state->stream);
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 12/32] drm/amd/display: Fix conflicting types for dc_plane_cm functions
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (10 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 11/32] drm/amd/display: Extract connector and encoder code to amdgpu_dm_connector Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 13/32] drm/amd/display: Add PSR Active VTotal Control capability Chenyu Chen
` (20 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Rafal Ostrowski, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
[WHAT]
Update declarations of amdgpu_dm_atomic_lut3d,
amdgpu_dm_atomic_shaper_lut, and amdgpu_dm_atomic_blend_lut in
amdgpu_dm_color.h to use struct dc_plane_cm * matching the
definitions in amdgpu_dm_color.c. Also update the KUnit tests to
allocate struct dc_plane_cm and access the embedded sub-structs.
This patch fixes an issue in the previous patch in this series,
"drm/amd/display: Introduce dc_plane_cm and migrate surface update color path".
Cc: Rafal Ostrowski <rafal.ostrowski@amd.com>
Assisted-by: Copilot:Claude-Opus-4.6
Reviewed-by: Aurabindo Pillai <aurabindo.pillai@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_color.h | 8 +--
.../amdgpu_dm/tests/amdgpu_dm_color_test.c | 64 +++++++++----------
2 files changed, 36 insertions(+), 36 deletions(-)
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
index e4f53b7bc753..8dbbcb3ab156 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h
@@ -87,10 +87,10 @@ void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut,
struct tetrahedral_params *params,
bool use_tetrahedral_9,
int bit_depth);
-struct dc_3dlut;
+struct dc_plane_cm;
void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut3d,
uint32_t drm_lut3d_size,
- struct dc_3dlut *lut);
+ struct dc_plane_cm *cm);
int __set_colorop_3dlut(const struct drm_color_lut32 *drm_lut3d,
uint32_t drm_lut3d_size,
struct dc_3dlut *lut);
@@ -105,12 +105,12 @@ int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *shaper_lut,
bool has_rom,
enum dc_transfer_func_predefined tf,
uint32_t shaper_size,
- struct dc_transfer_func *func_shaper);
+ struct dc_plane_cm *cm);
int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut,
bool has_rom,
enum dc_transfer_func_predefined tf,
uint32_t blend_size,
- struct dc_transfer_func *func_blend);
+ struct dc_plane_cm *cm);
int __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state,
struct drm_colorop_state *colorop_state);
#endif
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
index f943361b70e8..d64c7da20f2c 100644
--- 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
@@ -1159,19 +1159,19 @@ static void dm_test_verify_lut_sizes_invalid_degamma_valid_gamma(struct kunit *t
*/
static void dm_test_atomic_lut3d_zero_size(struct kunit *test)
{
- struct dc_3dlut *lut;
+ struct dc_plane_cm *cm;
u32 initialized;
- lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, lut);
+ cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, cm);
/* Pre-set initialized so we can confirm it is cleared */
- lut->state.bits.initialized = 1;
+ cm->lut3d_func.state.bits.initialized = 1;
- amdgpu_dm_atomic_lut3d(NULL, 0, lut);
+ amdgpu_dm_atomic_lut3d(NULL, 0, cm);
/* Copy bit-field: typeof cannot be applied to a bit-field */
- initialized = lut->state.bits.initialized;
+ initialized = cm->lut3d_func.state.bits.initialized;
KUNIT_EXPECT_EQ(test, initialized, 0U);
}
@@ -1183,22 +1183,22 @@ static void dm_test_atomic_lut3d_nonzero_state_bits(struct kunit *test)
{
const uint32_t lut3d_size = 5;
struct drm_color_lut *lut_data;
- struct dc_3dlut *lut;
+ struct dc_plane_cm *cm;
u32 initialized;
lut_data = kunit_kcalloc(test, lut3d_size, sizeof(*lut_data), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, lut_data);
- lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, lut);
+ cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, cm);
- amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, lut);
+ amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, cm);
/* Copy bit-field: typeof cannot be applied to a bit-field */
- initialized = lut->state.bits.initialized;
+ initialized = cm->lut3d_func.state.bits.initialized;
KUNIT_EXPECT_EQ(test, initialized, 1U);
- KUNIT_EXPECT_FALSE(test, lut->lut_3d.use_tetrahedral_9);
- KUNIT_EXPECT_TRUE(test, lut->lut_3d.use_12bits);
+ KUNIT_EXPECT_FALSE(test, cm->lut3d_func.lut_3d.use_tetrahedral_9);
+ KUNIT_EXPECT_TRUE(test, cm->lut3d_func.lut_3d.use_12bits);
}
/**
@@ -1209,29 +1209,29 @@ static void dm_test_atomic_lut3d_data_forwarded(struct kunit *test)
{
const uint32_t lut3d_size = 5;
struct drm_color_lut *lut_data;
- struct dc_3dlut *lut;
+ struct dc_plane_cm *cm;
lut_data = kunit_kcalloc(test, lut3d_size, sizeof(*lut_data), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, lut_data);
- lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, lut);
+ cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, cm);
lut_data[0].red = 0xFFFF;
lut_data[0].green = 0x8000;
lut_data[0].blue = 0x4000;
- amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, lut);
+ amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, cm);
/*
* use_tetrahedral_9 == false → data goes into tetrahedral_17.
* lut[0] maps to lut0[0] (first element of the first group).
*/
- KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].red,
+ KUNIT_EXPECT_EQ(test, cm->lut3d_func.lut_3d.tetrahedral_17.lut0[0].red,
drm_color_lut_extract(0xFFFF, MAX_COLOR_3DLUT_BITDEPTH));
- KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].green,
+ KUNIT_EXPECT_EQ(test, cm->lut3d_func.lut_3d.tetrahedral_17.lut0[0].green,
drm_color_lut_extract(0x8000, MAX_COLOR_3DLUT_BITDEPTH));
- KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].blue,
+ KUNIT_EXPECT_EQ(test, cm->lut3d_func.lut_3d.tetrahedral_17.lut0[0].blue,
drm_color_lut_extract(0x4000, MAX_COLOR_3DLUT_BITDEPTH));
}
@@ -1398,19 +1398,19 @@ static void dm_test_set_atomic_regamma_bypass(struct kunit *test)
*/
static void dm_test_atomic_shaper_lut_bypass(struct kunit *test)
{
- struct dc_transfer_func *func_shaper;
+ struct dc_plane_cm *cm;
- func_shaper = kunit_kzalloc(test, sizeof(*func_shaper), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, func_shaper);
+ cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, cm);
/* size=0 and tf=LINEAR: must take the bypass branch */
KUNIT_EXPECT_EQ(test,
amdgpu_dm_atomic_shaper_lut(NULL, false,
TRANSFER_FUNCTION_LINEAR,
- 0, func_shaper),
+ 0, cm),
0);
- KUNIT_EXPECT_EQ(test, (int)func_shaper->type, (int)TF_TYPE_BYPASS);
- KUNIT_EXPECT_EQ(test, (int)func_shaper->tf, (int)TRANSFER_FUNCTION_LINEAR);
+ KUNIT_EXPECT_EQ(test, (int)cm->shaper_func.type, (int)TF_TYPE_BYPASS);
+ KUNIT_EXPECT_EQ(test, (int)cm->shaper_func.tf, (int)TRANSFER_FUNCTION_LINEAR);
}
/**
@@ -1419,19 +1419,19 @@ static void dm_test_atomic_shaper_lut_bypass(struct kunit *test)
*/
static void dm_test_atomic_blend_lut_bypass(struct kunit *test)
{
- struct dc_transfer_func *func_blend;
+ struct dc_plane_cm *cm;
- func_blend = kunit_kzalloc(test, sizeof(*func_blend), GFP_KERNEL);
- KUNIT_ASSERT_NOT_NULL(test, func_blend);
+ cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, cm);
/* size=0 and tf=LINEAR: must take the bypass branch */
KUNIT_EXPECT_EQ(test,
amdgpu_dm_atomic_blend_lut(NULL, false,
TRANSFER_FUNCTION_LINEAR,
- 0, func_blend),
+ 0, cm),
0);
- KUNIT_EXPECT_EQ(test, (int)func_blend->type, (int)TF_TYPE_BYPASS);
- KUNIT_EXPECT_EQ(test, (int)func_blend->tf, (int)TRANSFER_FUNCTION_LINEAR);
+ KUNIT_EXPECT_EQ(test, (int)cm->blend_func.type, (int)TF_TYPE_BYPASS);
+ KUNIT_EXPECT_EQ(test, (int)cm->blend_func.tf, (int)TRANSFER_FUNCTION_LINEAR);
}
/* ---- Tests for __set_colorop_in_tf_1d_curve ---- */
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 13/32] drm/amd/display: Add PSR Active VTotal Control capability
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (11 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 12/32] drm/amd/display: Fix conflicting types for dc_plane_cm functions Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 14/32] drm/amd/display: Enable pstate for DCN4 non-emulation builds Chenyu Chen
` (19 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Robin Chen, Aric Cyr,
Chenyu Chen
From: Robin Chen <robin.chen@amd.com>
[WHY]
The PSRSU-RC capability should be populated in DC during edp detection.
Reviewed-by: Aric Cyr <aric.cyr@amd.com>
Signed-off-by: Robin Chen <robin.chen@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
drivers/gpu/drm/amd/display/dc/dc_dp_types.h | 1 +
.../drm/amd/display/dc/link/protocols/link_dp_capability.c | 6 +++++-
drivers/gpu/drm/amd/display/include/ddc_service_types.h | 1 +
3 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/dc/dc_dp_types.h b/drivers/gpu/drm/amd/display/dc/dc_dp_types.h
index fbef0dc743ff..d0ba9ad67a3e 100644
--- a/drivers/gpu/drm/amd/display/dc/dc_dp_types.h
+++ b/drivers/gpu/drm/amd/display/dc/dc_dp_types.h
@@ -1146,6 +1146,7 @@ struct edp_psr_info {
union edp_psr_dpcd_caps psr_dpcd_caps;
uint8_t psr2_su_y_granularity_cap;
uint8_t force_psrsu_cap;
+ uint8_t psr_active_vtotal_control_cap;
};
struct replay_info {
diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c
index 47abb4066709..d47aefecfc2d 100644
--- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c
+++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c
@@ -2242,10 +2242,14 @@ void detect_edp_sink_caps(struct dc_link *link)
/*
* ALPM is only valid for eDP v1.4 or higher.
*/
- if (link->dpcd_caps.dpcd_rev.raw >= DP_EDP_14)
+ if (link->dpcd_caps.dpcd_rev.raw >= DP_EDP_14) {
core_link_read_dpcd(link, DP_RECEIVER_ALPM_CAP,
&link->dpcd_caps.alpm_caps.raw,
sizeof(link->dpcd_caps.alpm_caps.raw));
+ core_link_read_dpcd(link, DP_SINK_PSR_ACTIVE_VTOTAL_CONTROL_CAP,
+ &link->dpcd_caps.psr_info.psr_active_vtotal_control_cap,
+ sizeof(link->dpcd_caps.psr_info.psr_active_vtotal_control_cap));
+ }
/*
* Read REPLAY info
diff --git a/drivers/gpu/drm/amd/display/include/ddc_service_types.h b/drivers/gpu/drm/amd/display/include/ddc_service_types.h
index 53210e3aa0e0..827e9bd7c5cf 100644
--- a/drivers/gpu/drm/amd/display/include/ddc_service_types.h
+++ b/drivers/gpu/drm/amd/display/include/ddc_service_types.h
@@ -45,6 +45,7 @@
#define DP_DEVICE_ID_BA4159 0xBA4159
#define DP_FORCE_PSRSU_CAPABILITY 0x40F
+#define DP_SINK_PSR_ACTIVE_VTOTAL_CONTROL_CAP 0x370
#define DP_SINK_PSR_ACTIVE_VTOTAL 0x373
#define DP_SINK_PSR_ACTIVE_VTOTAL_CONTROL_MODE 0x375
#define DP_SOURCE_PSR_ACTIVE_VTOTAL 0x376
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 14/32] drm/amd/display: Enable pstate for DCN4 non-emulation builds
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (12 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 13/32] drm/amd/display: Add PSR Active VTotal Control capability Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:44 ` [PATCH 15/32] drm/amd/display: Refactor surface_update_flags to flat struct with helpers Chenyu Chen
` (18 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Gabe Teeger, Matthew Stewart,
Chenyu Chen
From: Gabe Teeger <gabe.teeger@amd.com>
[Why]
Pstate was disabled during bring-up to avoid interference. Now that
bring-up is complete it can be enabled for non-emulation builds.
[How]
Set pstate_enabled to true in debug_defaults_drv for
non-emulation DCN4 builds.
Reviewed-by: Matthew Stewart <matthew.stewart2@amd.com>
Signed-off-by: Gabe Teeger <gabe.teeger@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c
index 527d17f29f3b..669bd5eb4c8f 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c
@@ -757,7 +757,7 @@ static const struct dc_debug_options debug_defaults_drv = {
.underflow_assert_delay_us = 0xFFFFFFFF,
.dwb_fi_phase = -1, // -1 = disable,
.dmub_command_table = true,
- .pstate_enabled = false,
+ .pstate_enabled = true,
.enable_mem_low_power = {
.bits = {
.vga = false,
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 15/32] drm/amd/display: Refactor surface_update_flags to flat struct with helpers
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (13 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 14/32] drm/amd/display: Enable pstate for DCN4 non-emulation builds Chenyu Chen
@ 2026-06-10 9:44 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 16/32] drm/amd/display: Add Support for HDMI Compliance Automation Chenyu Chen
` (17 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:44 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Rafal Ostrowski,
Nicholas Kazlauskas, Chenyu Chen
From: Rafal Ostrowski <rafal.ostrowski@amd.com>
[Why]
The union surface_update_flags type uses a union with a raw
uint32_t member to allow bulk clear/set/test operations on the
bitfield. This couples the struct layout to a specific integer
width, breaks when the number of flag bits exceeds 32, and
scatters raw-access patterns across many call sites. Replacing
the union with a plain struct and adding explicit helper
functions makes the intent clearer and prepares the code for
future flag-set expansion.
[How]
Rename union surface_update_flags to struct pipe_update_bits
and remove the union wrapper, the .bits sub-struct, and the
.raw member. Add inline helpers in dc.h:
surface_update_flags_clear(), surface_update_flags_set_full(),
and surface_update_flags_is_any_set() that operate on the new
struct via memset/memcmp. Add stream_update_flags_clear() and
stream_update_flags_set_full() in dc_stream.h for the stream
update flags union. Update all callers: change the type name,
replace .bits.field with .field, replace .raw = 0 with the
clear helper, replace .raw = 0xFFFFFFFF with the set_full
helper, and replace .raw boolean tests with is_any_set.
Reviewed-by: Nicholas Kazlauskas <nicholas.kazlauskas@amd.com>
Signed-off-by: Rafal Ostrowski <rafal.ostrowski@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 5 +-
drivers/gpu/drm/amd/display/dc/core/dc.c | 157 +++++++++---------
.../drm/amd/display/dc/core/dc_hw_sequencer.c | 20 +--
drivers/gpu/drm/amd/display/dc/dc.h | 153 ++++++++++++-----
drivers/gpu/drm/amd/display/dc/dc_stream.h | 29 ++++
.../drm/amd/display/dc/dml/calcs/dcn_calcs.c | 2 +-
.../amd/display/dc/hwss/dce110/dce110_hwseq.c | 8 +-
.../amd/display/dc/hwss/dce60/dce60_hwseq.c | 8 +-
.../amd/display/dc/hwss/dcn10/dcn10_hwseq.c | 62 +++----
.../amd/display/dc/hwss/dcn20/dcn20_hwseq.c | 56 +++----
.../amd/display/dc/hwss/dcn201/dcn201_hwseq.c | 2 +-
.../amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 4 +-
.../amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 72 ++++----
.../amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 2 +-
14 files changed, 345 insertions(+), 235 deletions(-)
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 ebca180847c9..4519c0572963 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -2016,8 +2016,7 @@ static int dm_resume(struct amdgpu_ip_block *ip_block)
for (i = 0; i < dc_state->stream_count; i++) {
dc_state->streams[i]->mode_changed = true;
for (j = 0; j < dc_state->stream_status[i].plane_count; j++) {
- dc_state->stream_status[i].plane_states[j]->update_flags.raw
- = 0xffffffff;
+ dc_pipe_update_bits_set_full(&dc_state->stream_status[i].plane_states[j]->update_bits);
}
}
@@ -6319,7 +6318,7 @@ static int dm_update_plane_state(struct dc *dc,
/* Tell DC to do a full surface update every time there
* is a plane change. Inefficient, but works for now.
*/
- dm_new_plane_state->dc_state->update_flags.bits.full_update = 1;
+ dm_new_plane_state->dc_state->update_bits.full_update = 1;
*lock_and_validation_needed = true;
}
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c
index 1e7269246cea..379d3c97a521 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc.c
@@ -2310,7 +2310,7 @@ static enum dc_status dc_commit_state_no_check(struct dc *dc, struct dc_state *c
for (i = 0; i < context->stream_count; i++) {
uint32_t prev_dsc_changed = context->streams[i]->update_flags.bits.dsc_changed;
- context->streams[i]->update_flags.raw = 0xFFFFFFFF;
+ stream_update_flags_set_full(&context->streams[i]->update_flags);
context->streams[i]->update_flags.bits.dsc_changed = prev_dsc_changed;
}
@@ -2416,7 +2416,7 @@ static enum dc_status dc_commit_state_no_check(struct dc *dc, struct dc_state *c
/* Clear update flags that were set earlier to avoid redundant programming */
for (i = 0; i < context->stream_count; i++) {
- context->streams[i]->update_flags.raw = 0x0;
+ stream_update_flags_clear(&context->streams[i]->update_flags);
}
old_state = dc->current_state;
@@ -2765,7 +2765,7 @@ static bool is_surface_in_context(
static struct surface_update_descriptor get_plane_info_update_type(const struct dc_surface_update *u)
{
- union surface_update_flags *update_flags = &u->surface->update_flags;
+ struct pipe_update_bits *update_bits = &u->surface->update_bits;
struct surface_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE };
if (!u->plane_info)
@@ -2775,37 +2775,37 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct
elevate_update_type(&update_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
if (u->plane_info->color_space != u->surface->color_space) {
- update_flags->bits.color_space_change = 1;
+ update_bits->color_space_change = 1;
elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM);
}
if (u->plane_info->horizontal_mirror != u->surface->horizontal_mirror) {
- update_flags->bits.horizontal_mirror_change = 1;
+ update_bits->horizontal_mirror_change = 1;
elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM);
}
if (u->plane_info->rotation != u->surface->rotation) {
- update_flags->bits.rotation_change = 1;
+ update_bits->rotation_change = 1;
elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
if (u->plane_info->format != u->surface->format) {
- update_flags->bits.pixel_format_change = 1;
+ update_bits->pixel_format_change = 1;
elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
if (u->plane_info->stereo_format != u->surface->stereo_format) {
- update_flags->bits.stereo_format_change = 1;
+ update_bits->stereo_format_change = 1;
elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
if (u->plane_info->per_pixel_alpha != u->surface->per_pixel_alpha) {
- update_flags->bits.per_pixel_alpha_change = 1;
+ update_bits->per_pixel_alpha_change = 1;
elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM);
}
if (u->plane_info->global_alpha_value != u->surface->global_alpha_value) {
- update_flags->bits.global_alpha_change = 1;
+ update_bits->global_alpha_change = 1;
elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM);
}
@@ -2817,7 +2817,7 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct
* stutter period calculation. Triggering a full update will
* recalculate stutter period.
*/
- update_flags->bits.dcc_change = 1;
+ update_bits->dcc_change = 1;
elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
@@ -2826,25 +2826,25 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct
/* different bytes per element will require full bandwidth
* and DML calculation
*/
- update_flags->bits.bpp_change = 1;
+ update_bits->bpp_change = 1;
elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
if (u->plane_info->plane_size.surface_pitch != u->surface->plane_size.surface_pitch
|| u->plane_info->plane_size.chroma_pitch != u->surface->plane_size.chroma_pitch) {
- update_flags->bits.plane_size_change = 1;
+ update_bits->plane_size_change = 1;
elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM);
}
const struct dc_tiling_info *tiling = &u->plane_info->tiling_info;
if (memcmp(tiling, &u->surface->tiling_info, sizeof(*tiling)) != 0) {
- update_flags->bits.swizzle_change = 1;
+ update_bits->swizzle_change = 1;
if (tiling->flags.avoid_full_update_on_tiling_change) {
elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM);
} else {
- update_flags->bits.bandwidth_change = 1;
+ update_bits->bandwidth_change = 1;
elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
}
@@ -2854,10 +2854,10 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct
}
static struct surface_update_descriptor get_scaling_info_update_type(
- const struct dc_check_config *check_config,
- const struct dc_surface_update *u)
+ const struct dc_check_config *check_config,
+ const struct dc_surface_update *u)
{
- union surface_update_flags *update_flags = &u->surface->update_flags;
+ struct pipe_update_bits *update_bits = &u->surface->update_bits;
struct surface_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE };
if (!u->scaling_info)
@@ -2874,26 +2874,26 @@ static struct surface_update_descriptor get_scaling_info_update_type(
|| u->scaling_info->clip_rect.height != u->surface->clip_rect.height
|| u->scaling_info->scaling_quality.integer_scaling !=
u->surface->scaling_quality.integer_scaling) {
- update_flags->bits.scaling_change = 1;
+ update_bits->scaling_change = 1;
elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
if (u->scaling_info->src_rect.width > u->surface->src_rect.width
|| u->scaling_info->src_rect.height > u->surface->src_rect.height)
/* Making src rect bigger requires a bandwidth change */
- update_flags->bits.clock_change = 1;
+ update_bits->clock_change = 1;
if ((u->scaling_info->dst_rect.width < u->surface->dst_rect.width
|| u->scaling_info->dst_rect.height < u->surface->dst_rect.height)
&& (u->scaling_info->dst_rect.width < u->surface->src_rect.width
|| u->scaling_info->dst_rect.height < u->surface->src_rect.height))
/* Making dst rect smaller requires a bandwidth change */
- update_flags->bits.bandwidth_change = 1;
+ update_bits->bandwidth_change = 1;
if (u->scaling_info->src_rect.width > (int)check_config->max_optimizable_video_width &&
(u->scaling_info->clip_rect.width > u->surface->clip_rect.width ||
u->scaling_info->clip_rect.height > u->surface->clip_rect.height))
/* Changing clip size of a large surface may result in MPC slice count change */
- update_flags->bits.bandwidth_change = 1;
+ update_bits->bandwidth_change = 1;
}
if (u->scaling_info->src_rect.x != u->surface->src_rect.x
@@ -2903,7 +2903,7 @@ static struct surface_update_descriptor get_scaling_info_update_type(
|| u->scaling_info->dst_rect.x != u->surface->dst_rect.x
|| u->scaling_info->dst_rect.y != u->surface->dst_rect.y) {
elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM);
- update_flags->bits.position_change = 1;
+ update_bits->position_change = 1;
}
return update_type;
@@ -2914,15 +2914,15 @@ static struct surface_update_descriptor det_surface_update(
struct dc_surface_update *u)
{
struct surface_update_descriptor overall_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE };
- union surface_update_flags *update_flags = &u->surface->update_flags;
+ struct pipe_update_bits *update_bits = &u->surface->update_bits;
if (u->surface->force_full_update) {
- update_flags->raw = 0xFFFFFFFF;
+ dc_pipe_update_bits_set_full(update_bits);
elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
return overall_type;
}
- update_flags->raw = 0; // Reset all flags
+ dc_pipe_update_bits_clear(update_bits);
struct surface_update_descriptor inner_type = get_plane_info_update_type(u);
@@ -2932,47 +2932,47 @@ static struct surface_update_descriptor det_surface_update(
elevate_update_type(&overall_type, inner_type.update_type, inner_type.lock_descriptor);
if (u->flip_addr) {
- update_flags->bits.addr_update = 1;
+ update_bits->addr_update = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
if (u->flip_addr->address.tmz_surface != u->surface->address.tmz_surface) {
- update_flags->bits.tmz_changed = 1;
+ update_bits->tmz_changed = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
}
if (u->in_transfer_func) {
- update_flags->bits.in_transfer_func_change = 1;
+ update_bits->in_transfer_func_change = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM);
}
if (u->input_csc_color_matrix) {
- update_flags->bits.input_csc_change = 1;
+ update_bits->input_csc_change = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
}
if (u->cursor_csc_color_matrix) {
- update_flags->bits.cursor_csc_color_matrix_change = 1;
+ update_bits->cursor_csc_color_matrix_change = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
}
if (u->coeff_reduction_factor) {
- update_flags->bits.coeff_reduction_change = 1;
+ update_bits->coeff_reduction_change = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
}
if (u->gamut_remap_matrix) {
- update_flags->bits.gamut_remap_change = 1;
+ update_bits->gamut_remap_change = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
}
if ((u->cm && u->cm->flags.bits.blend_enable) ||
(u->gamma && dce_use_lut(u->plane_info ? u->plane_info->format : u->surface->format))) {
- update_flags->bits.gamma_change = 1;
+ update_bits->gamma_change = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
}
if (u->cm && (u->cm->flags.bits.lut3d_enable || u->cm->flags.bits.shaper_enable)) {
- update_flags->bits.lut_3d = 1;
+ update_bits->lut_3d = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
}
@@ -2990,19 +2990,19 @@ static struct surface_update_descriptor det_surface_update(
if (u->hdr_mult.value)
if (u->hdr_mult.value != u->surface->hdr_mult.value) {
// TODO: Should be fast?
- update_flags->bits.hdr_mult = 1;
+ update_bits->hdr_mult = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM);
}
if (u->sdr_white_level_nits)
if (u->sdr_white_level_nits != u->surface->sdr_white_level_nits) {
// TODO: Should be fast?
- update_flags->bits.sdr_white_level_nits = 1;
+ update_bits->sdr_white_level_nits = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
if (u->cm_hist_control) {
- update_flags->bits.cm_hist_change = 1;
+ update_bits->cm_hist_change = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM);
}
@@ -3017,7 +3017,7 @@ static struct surface_update_descriptor det_surface_update(
|| u->cm->flags.bits.blend_enable != u->surface->cm.flags.bits.blend_enable
|| u->cm->flags.bits.lut3d_enable != u->surface->cm.flags.bits.lut3d_enable
|| u->cm->flags.bits.lut3d_dma_enable != u->surface->cm.flags.bits.lut3d_dma_enable) {
- update_flags->bits.mcm_transfer_function_enable_change = 1;
+ update_bits->mcm_transfer_function_enable_change = 1;
elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
@@ -3027,17 +3027,17 @@ static struct surface_update_descriptor det_surface_update(
}
}
- if (update_flags->bits.lut_3d &&
+ if (update_bits->lut_3d &&
!u->surface->cm.flags.bits.lut3d_dma_enable) {
elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
if (check_config->enable_legacy_fast_update &&
- (update_flags->bits.gamma_change ||
- update_flags->bits.gamut_remap_change ||
- update_flags->bits.input_csc_change ||
- update_flags->bits.cm_hist_change ||
- update_flags->bits.coeff_reduction_change)) {
+ (update_bits->gamma_change ||
+ update_bits->gamut_remap_change ||
+ update_bits->input_csc_change ||
+ update_bits->cm_hist_change ||
+ update_bits->coeff_reduction_change)) {
elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL);
}
return overall_type;
@@ -3062,7 +3062,7 @@ static void force_immediate_gsl_plane_flip(struct dc *dc, struct dc_surface_upda
if (has_flip_immediate_plane && surface_count > 1) {
for (i = 0; i < surface_count; i++) {
if (updates[i].surface->flip_immediate)
- updates[i].surface->update_flags.bits.addr_update = 1;
+ updates[i].surface->update_bits.addr_update = 1;
}
}
}
@@ -3217,9 +3217,9 @@ struct surface_update_descriptor dc_check_update_surfaces_for_stream(
struct dc_stream_update *stream_update)
{
if (stream_update)
- stream_update->stream->update_flags.raw = 0;
+ stream_update_flags_clear(&stream_update->stream->update_flags);
for (int i = 0; i < surface_count; i++)
- updates[i].surface->update_flags.raw = 0;
+ dc_pipe_update_bits_clear(&updates[i].surface->update_bits);
return check_update_surfaces_for_stream(check_config, updates, surface_count, stream_update);
}
@@ -3766,11 +3766,11 @@ static bool update_planes_and_stream_state(struct dc *dc,
if (update_type == UPDATE_TYPE_FULL) {
if (stream_update) {
uint32_t dsc_changed = stream_update->stream->update_flags.bits.dsc_changed;
- stream_update->stream->update_flags.raw = 0xFFFFFFFF;
+ stream_update_flags_set_full(&stream_update->stream->update_flags);
stream_update->stream->update_flags.bits.dsc_changed = dsc_changed;
}
for (i = 0; i < surface_count; i++)
- srf_updates[i].surface->update_flags.raw = 0xFFFFFFFF;
+ dc_pipe_update_bits_set_full(&srf_updates[i].surface->update_bits);
}
if (update_type >= update_surface_trace_level)
@@ -3819,7 +3819,7 @@ static bool update_planes_and_stream_state(struct dc *dc,
if (update_type != UPDATE_TYPE_MED)
continue;
- if (surface->update_flags.bits.position_change) {
+ if (surface->update_bits.position_change) {
for (j = 0; j < dc->res_pool->pipe_count; j++) {
struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j];
@@ -4602,14 +4602,23 @@ static void build_dmub_update_dirty_rect(
}
}
-static bool check_address_only_update(union surface_update_flags update_flags)
+/**
+ * dc_check_address_only_update - Check if addr_update is the sole flag set
+ *
+ * @update_bits: The pipe update bits to check
+ *
+ * Determines whether an update contains only an address change with no other
+ * pending updates.
+ *
+ * Return: %true if addr_update is the sole bit set, %false otherwise.
+ */
+bool dc_check_address_only_update(struct pipe_update_bits update_bits)
{
- union surface_update_flags addr_only_update_flags;
- addr_only_update_flags.raw = 0;
- addr_only_update_flags.bits.addr_update = 1;
+ struct pipe_update_bits check = update_bits; /* 1. Copy all flags from input */
- return update_flags.bits.addr_update &&
- !(update_flags.raw & ~addr_only_update_flags.raw);
+ check.addr_update = 0; /* 2. Zero the addr_update bit in the copy */
+ return update_bits.addr_update && /* 3. Check addr_update was set in original */
+ !dc_pipe_update_bits_is_any_set(&check); /* 4. Check no other bits remain in the copy */
}
/**
@@ -4669,7 +4678,7 @@ static void commit_plane_for_stream_offload_fams2_flip(struct dc *dc,
continue;
/* update pipe context for plane */
- if (pipe_ctx->plane_state->update_flags.bits.addr_update)
+ if (pipe_ctx->plane_state->update_bits.addr_update)
dc->hwss.update_plane_addr(dc, pipe_ctx);
}
}
@@ -4707,8 +4716,8 @@ static void commit_planes_for_stream_fast(struct dc *dc,
should_offload_fams2_flip = true;
for (i = 0; i < surface_count; i++) {
if (srf_updates[i].surface &&
- srf_updates[i].surface->update_flags.raw &&
- !check_address_only_update(srf_updates[i].surface->update_flags)) {
+ dc_pipe_update_bits_is_any_set(&srf_updates[i].surface->update_bits) &&
+ !dc_check_address_only_update(srf_updates[i].surface->update_bits)) {
/* more than address update, need to acquire FAMS2 lock */
should_offload_fams2_flip = false;
break;
@@ -4799,7 +4808,7 @@ static void commit_planes_for_stream_fast(struct dc *dc,
* so no need to clear here.
*/
if (top_pipe_to_program->stream)
- top_pipe_to_program->stream->update_flags.raw = 0;
+ stream_update_flags_clear(&top_pipe_to_program->stream->update_flags);
}
static void commit_planes_for_stream(struct dc *dc,
@@ -5137,7 +5146,7 @@ static void commit_planes_for_stream(struct dc *dc,
dc->hwss.program_triplebuffer(
dc, pipe_ctx, pipe_ctx->plane_state->triplebuffer_flips);
}
- if (pipe_ctx->plane_state->update_flags.bits.addr_update)
+ if (pipe_ctx->plane_state->update_bits.addr_update)
dc->hwss.update_plane_addr(dc, pipe_ctx);
}
}
@@ -5228,7 +5237,7 @@ static void commit_planes_for_stream(struct dc *dc,
if (pipe_ctx->bottom_pipe || pipe_ctx->next_odm_pipe ||
!pipe_ctx->stream || !should_update_pipe_for_stream(context, pipe_ctx, stream) ||
- !pipe_ctx->plane_state->update_flags.bits.addr_update ||
+ !pipe_ctx->plane_state->update_bits.addr_update ||
pipe_ctx->plane_state->skip_manual_trigger)
continue;
@@ -5667,7 +5676,7 @@ static bool commit_minimal_transition_state(struct dc *dc,
/* force full surface update */
for (i = 0; i < dc->current_state->stream_count; i++) {
for (j = 0; j < (unsigned int)dc->current_state->stream_status[i].plane_count; j++) {
- dc->current_state->stream_status[i].plane_states[j]->update_flags.raw = 0xFFFFFFFF;
+ dc_pipe_update_bits_set_full(&dc->current_state->stream_status[i].plane_states[j]->update_bits);
}
}
@@ -6108,17 +6117,17 @@ static bool update_planes_and_stream_v3(struct dc *dc,
return true;
}
-static void clear_update_flags(struct dc_surface_update *srf_updates,
+static void clear_update_bits(struct dc_surface_update *srf_updates,
int surface_count, struct dc_stream_state *stream)
{
int i;
if (stream)
- stream->update_flags.raw = 0;
+ stream_update_flags_clear(&stream->update_flags);
for (i = 0; i < surface_count; i++)
if (srf_updates[i].surface)
- srf_updates[i].surface->update_flags.raw = 0;
+ dc_pipe_update_bits_clear(&srf_updates[i].surface->update_bits);
}
bool dc_update_planes_and_stream(struct dc *dc,
@@ -6170,7 +6179,7 @@ void dc_commit_updates_for_stream(struct dc *dc,
}
if (ret && dc->ctx->dce_version >= DCN_VERSION_3_2)
- clear_update_flags(srf_updates, surface_count, stream);
+ clear_update_bits(srf_updates, surface_count, stream);
}
uint8_t dc_get_current_stream_count(struct dc *dc)
@@ -7920,7 +7929,7 @@ struct dc_update_scratch_space {
struct dc_stream_state *stream;
struct dc_stream_update *stream_update;
bool update_v3;
- bool do_clear_update_flags;
+ bool do_clear_update_bits;
enum surface_update_type update_type;
struct dc_state *new_context;
enum update_v3_flow flow;
@@ -7963,8 +7972,8 @@ static bool update_planes_and_stream_cleanup_v2(
const struct dc_update_scratch_space *scratch
)
{
- if (scratch->do_clear_update_flags)
- clear_update_flags(scratch->surface_updates, scratch->surface_count, scratch->stream);
+ if (scratch->do_clear_update_bits)
+ clear_update_bits(scratch->surface_updates, scratch->surface_count, scratch->stream);
return false;
}
@@ -8218,8 +8227,8 @@ static bool update_planes_and_stream_cleanup_v3(
ASSERT(false);
}
- if (scratch->do_clear_update_flags)
- clear_update_flags(scratch->surface_updates, scratch->surface_count, scratch->stream);
+ if (scratch->do_clear_update_bits)
+ clear_update_bits(scratch->surface_updates, scratch->surface_count, scratch->stream);
return false;
}
@@ -8242,7 +8251,7 @@ struct dc_update_scratch_space *dc_update_planes_and_stream_init(
.stream = stream,
.stream_update = stream_update,
.update_v3 = version >= DCN_VERSION_4_01 || version == DCN_VERSION_3_2 || version == DCN_VERSION_3_21,
- .do_clear_update_flags = version >= DCN_VERSION_1_0,
+ .do_clear_update_bits = version >= DCN_VERSION_1_0,
};
return scratch;
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c
index 88446817a71f..c7c32c0a6b50 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c
@@ -1028,20 +1028,20 @@ void hwss_build_fast_sequence(struct dc *dc,
current_mpc_pipe = current_pipe;
while (current_mpc_pipe) {
if (current_mpc_pipe->plane_state) {
- if (dc->hwss.set_flip_control_gsl && current_mpc_pipe->plane_state->update_flags.raw) {
+ if (dc->hwss.set_flip_control_gsl && dc_pipe_update_bits_is_any_set(¤t_mpc_pipe->plane_state->update_bits)) {
block_sequence[*num_steps].params.set_flip_control_gsl_params.hubp = current_mpc_pipe->plane_res.hubp;
block_sequence[*num_steps].params.set_flip_control_gsl_params.flip_immediate = current_mpc_pipe->plane_state->flip_immediate;
block_sequence[*num_steps].func = HUBP_SET_FLIP_CONTROL_GSL;
(*num_steps)++;
}
- if (dc->hwss.program_triplebuffer && dc->debug.enable_tri_buf && current_mpc_pipe->plane_state->update_flags.raw) {
+ if (dc->hwss.program_triplebuffer && dc->debug.enable_tri_buf && dc_pipe_update_bits_is_any_set(¤t_mpc_pipe->plane_state->update_bits)) {
block_sequence[*num_steps].params.program_triplebuffer_params.dc = dc;
block_sequence[*num_steps].params.program_triplebuffer_params.pipe_ctx = current_mpc_pipe;
block_sequence[*num_steps].params.program_triplebuffer_params.enableTripleBuffer = current_mpc_pipe->plane_state->triplebuffer_flips;
block_sequence[*num_steps].func = HUBP_PROGRAM_TRIPLEBUFFER;
(*num_steps)++;
}
- if (dc->hwss.update_plane_addr && current_mpc_pipe->plane_state->update_flags.bits.addr_update) {
+ if (dc->hwss.update_plane_addr && current_mpc_pipe->plane_state->update_bits.addr_update) {
if (resource_is_pipe_type(current_mpc_pipe, OTG_MASTER) &&
stream_status->mall_stream_config.type == SUBVP_MAIN) {
block_sequence[*num_steps].params.subvp_save_surf_addr.dc_dmub_srv = dc->ctx->dmub_srv;
@@ -1057,7 +1057,7 @@ void hwss_build_fast_sequence(struct dc *dc,
(*num_steps)++;
}
- if (hws->funcs.set_input_transfer_func && current_mpc_pipe->plane_state->update_flags.bits.gamma_change) {
+ if (hws->funcs.set_input_transfer_func && current_mpc_pipe->plane_state->update_bits.gamma_change) {
block_sequence[*num_steps].params.set_input_transfer_func_params.dc = dc;
block_sequence[*num_steps].params.set_input_transfer_func_params.pipe_ctx = current_mpc_pipe;
block_sequence[*num_steps].params.set_input_transfer_func_params.plane_state = current_mpc_pipe->plane_state;
@@ -1066,23 +1066,23 @@ void hwss_build_fast_sequence(struct dc *dc,
}
if (dc->hwss.program_gamut_remap &&
- (current_mpc_pipe->plane_state->update_flags.bits.gamut_remap_change ||
+ (current_mpc_pipe->plane_state->update_bits.gamut_remap_change ||
current_mpc_pipe->stream->update_flags.bits.gamut_remap)) {
block_sequence[*num_steps].params.program_gamut_remap_params.pipe_ctx = current_mpc_pipe;
block_sequence[*num_steps].func = DPP_PROGRAM_GAMUT_REMAP;
(*num_steps)++;
}
- if (current_mpc_pipe->plane_state->update_flags.bits.input_csc_change) {
+ if (current_mpc_pipe->plane_state->update_bits.input_csc_change) {
block_sequence[*num_steps].params.setup_dpp_params.pipe_ctx = current_mpc_pipe;
block_sequence[*num_steps].func = DPP_SETUP_DPP;
(*num_steps)++;
}
- if (current_mpc_pipe->plane_state->update_flags.bits.coeff_reduction_change) {
+ if (current_mpc_pipe->plane_state->update_bits.coeff_reduction_change) {
block_sequence[*num_steps].params.program_bias_and_scale_params.pipe_ctx = current_mpc_pipe;
block_sequence[*num_steps].func = DPP_PROGRAM_BIAS_AND_SCALE;
(*num_steps)++;
}
- if (current_mpc_pipe->plane_state->update_flags.bits.cm_hist_change) {
+ if (current_mpc_pipe->plane_state->update_bits.cm_hist_change) {
block_sequence[*num_steps].params.control_cm_hist_params.dpp
= current_mpc_pipe->plane_res.dpp;
block_sequence[*num_steps].params.control_cm_hist_params.cm_hist_control
@@ -1095,7 +1095,7 @@ void hwss_build_fast_sequence(struct dc *dc,
if (current_mpc_pipe->plane_res.dpp &&
current_mpc_pipe->plane_res.dpp->funcs->set_cursor_matrix &&
- current_mpc_pipe->plane_state->update_flags.bits.cursor_csc_color_matrix_change) {
+ current_mpc_pipe->plane_state->update_bits.cursor_csc_color_matrix_change) {
block_sequence[*num_steps].params.dpp_set_cursor_matrix_params.dpp = current_mpc_pipe->plane_res.dpp;
block_sequence[*num_steps].params.dpp_set_cursor_matrix_params.color_space = current_mpc_pipe->plane_state->color_space;
block_sequence[*num_steps].params.dpp_set_cursor_matrix_params.cursor_csc_color_matrix = ¤t_mpc_pipe->plane_state->cursor_csc_color_matrix;
@@ -1176,7 +1176,7 @@ void hwss_build_fast_sequence(struct dc *dc,
while (current_mpc_pipe) {
if (!current_mpc_pipe->bottom_pipe && !current_mpc_pipe->next_odm_pipe &&
current_mpc_pipe->stream && current_mpc_pipe->plane_state &&
- current_mpc_pipe->plane_state->update_flags.bits.addr_update &&
+ current_mpc_pipe->plane_state->update_bits.addr_update &&
!current_mpc_pipe->plane_state->skip_manual_trigger) {
if (dc->hwss.program_cursor_offload_now) {
block_sequence[*num_steps].params.program_cursor_update_now_params.dc = dc;
diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h
index 2a47d7ddf53b..2202c8669bf8 100644
--- a/drivers/gpu/drm/amd/display/dc/dc.h
+++ b/drivers/gpu/drm/amd/display/dc/dc.h
@@ -1540,47 +1540,120 @@ struct dc_plane_status {
struct cm_hist cm_hist;
};
-union surface_update_flags {
-
- struct {
- uint32_t addr_update:1;
- /* Medium updates */
- uint32_t dcc_change:1;
- uint32_t color_space_change:1;
- uint32_t horizontal_mirror_change:1;
- uint32_t per_pixel_alpha_change:1;
- uint32_t global_alpha_change:1;
- uint32_t hdr_mult:1;
- uint32_t rotation_change:1;
- uint32_t swizzle_change:1;
- uint32_t scaling_change:1;
- uint32_t position_change:1;
- uint32_t in_transfer_func_change:1;
- uint32_t input_csc_change:1;
- uint32_t coeff_reduction_change:1;
- uint32_t pixel_format_change:1;
- uint32_t plane_size_change:1;
- uint32_t gamut_remap_change:1;
- uint32_t cursor_csc_color_matrix_change:1;
-
- /* Full updates */
- uint32_t new_plane:1;
- uint32_t bpp_change:1;
- uint32_t gamma_change:1;
- uint32_t bandwidth_change:1;
- uint32_t clock_change:1;
- uint32_t stereo_format_change:1;
- uint32_t lut_3d:1;
- uint32_t tmz_changed:1;
- uint32_t mcm_transfer_function_enable_change:1; /* disable or enable MCM transfer func */
- uint32_t full_update:1;
- uint32_t sdr_white_level_nits:1;
- uint32_t cm_hist_change:1;
- } bits;
-
- uint32_t raw;
+struct pipe_update_bits {
+ uint32_t addr_update:1;
+ uint32_t dcc_change:1;
+ uint32_t color_space_change:1;
+ uint32_t horizontal_mirror_change:1;
+ uint32_t per_pixel_alpha_change:1;
+ uint32_t global_alpha_change:1;
+ uint32_t hdr_mult:1;
+ uint32_t rotation_change:1;
+ uint32_t swizzle_change:1;
+ uint32_t scaling_change:1;
+ uint32_t position_change:1;
+ uint32_t in_transfer_func_change:1;
+ uint32_t input_csc_change:1;
+ uint32_t coeff_reduction_change:1;
+ uint32_t pixel_format_change:1;
+ uint32_t plane_size_change:1;
+ uint32_t gamut_remap_change:1;
+ uint32_t cursor_csc_color_matrix_change:1;
+ uint32_t new_plane:1;
+ uint32_t bpp_change:1;
+ uint32_t gamma_change:1;
+ uint32_t bandwidth_change:1;
+ uint32_t clock_change:1;
+ uint32_t stereo_format_change:1;
+ uint32_t lut_3d:1;
+ uint32_t tmz_changed:1;
+ uint32_t mcm_transfer_function_enable_change:1; /* disable or enable MCM transfer func */
+ uint32_t full_update:1;
+ uint32_t sdr_white_level_nits:1;
+ uint32_t cm_hist_change:1;
+ /* NOTE: When adding a new field, also update:
+ * - dc_pipe_update_bits_set_full()
+ * - dc_pipe_update_bits_is_any_set()
+ */
};
+static inline void dc_pipe_update_bits_clear(struct pipe_update_bits *flags)
+{
+ /* memset ensures padding bits are zeroed */
+ memset(flags, 0, sizeof(*flags));
+}
+
+static inline void dc_pipe_update_bits_set_full(struct pipe_update_bits *flags)
+{
+ dc_pipe_update_bits_clear(flags);
+ flags->addr_update = 1;
+ flags->dcc_change = 1;
+ flags->color_space_change = 1;
+ flags->horizontal_mirror_change = 1;
+ flags->per_pixel_alpha_change = 1;
+ flags->global_alpha_change = 1;
+ flags->hdr_mult = 1;
+ flags->rotation_change = 1;
+ flags->swizzle_change = 1;
+ flags->scaling_change = 1;
+ flags->position_change = 1;
+ flags->in_transfer_func_change = 1;
+ flags->input_csc_change = 1;
+ flags->coeff_reduction_change = 1;
+ flags->pixel_format_change = 1;
+ flags->plane_size_change = 1;
+ flags->gamut_remap_change = 1;
+ flags->cursor_csc_color_matrix_change = 1;
+ flags->new_plane = 1;
+ flags->bpp_change = 1;
+ flags->gamma_change = 1;
+ flags->bandwidth_change = 1;
+ flags->clock_change = 1;
+ flags->stereo_format_change = 1;
+ flags->lut_3d = 1;
+ flags->tmz_changed = 1;
+ flags->mcm_transfer_function_enable_change = 1;
+ flags->full_update = 1;
+ flags->sdr_white_level_nits = 1;
+ flags->cm_hist_change = 1;
+}
+
+static inline bool dc_pipe_update_bits_is_any_set(const struct pipe_update_bits *flags)
+{
+ return flags->addr_update ||
+ flags->dcc_change ||
+ flags->color_space_change ||
+ flags->horizontal_mirror_change ||
+ flags->per_pixel_alpha_change ||
+ flags->global_alpha_change ||
+ flags->hdr_mult ||
+ flags->rotation_change ||
+ flags->swizzle_change ||
+ flags->scaling_change ||
+ flags->position_change ||
+ flags->in_transfer_func_change ||
+ flags->input_csc_change ||
+ flags->coeff_reduction_change ||
+ flags->pixel_format_change ||
+ flags->plane_size_change ||
+ flags->gamut_remap_change ||
+ flags->cursor_csc_color_matrix_change ||
+ flags->new_plane ||
+ flags->bpp_change ||
+ flags->gamma_change ||
+ flags->bandwidth_change ||
+ flags->clock_change ||
+ flags->stereo_format_change ||
+ flags->lut_3d ||
+ flags->tmz_changed ||
+ flags->mcm_transfer_function_enable_change ||
+ flags->full_update ||
+ flags->sdr_white_level_nits ||
+ flags->cm_hist_change;
+}
+
+bool dc_check_address_only_update(struct pipe_update_bits update_bits);
+
#define DC_REMOVE_PLANE_POINTERS 1
struct dc_plane_state {
@@ -1637,7 +1710,7 @@ struct dc_plane_state {
bool horizontal_mirror;
unsigned int layer_index;
- union surface_update_flags update_flags;
+ struct pipe_update_bits update_bits;
bool flip_int_enabled;
bool skip_manual_trigger;
diff --git a/drivers/gpu/drm/amd/display/dc/dc_stream.h b/drivers/gpu/drm/amd/display/dc/dc_stream.h
index 4154cd059562..8b164edc9c51 100644
--- a/drivers/gpu/drm/amd/display/dc/dc_stream.h
+++ b/drivers/gpu/drm/amd/display/dc/dc_stream.h
@@ -128,6 +128,35 @@ union stream_update_flags {
uint32_t raw;
};
+static inline void stream_update_flags_clear(union stream_update_flags *flags)
+{
+ flags->raw = 0;
+}
+
+static inline void stream_update_flags_set_full(union stream_update_flags *flags)
+{
+ stream_update_flags_clear(flags);
+ flags->bits.scaling = 1;
+ flags->bits.out_tf = 1;
+ flags->bits.out_csc = 1;
+ flags->bits.abm_level = 1;
+ flags->bits.dpms_off = 1;
+ flags->bits.gamut_remap = 1;
+ flags->bits.wb_update = 1;
+ flags->bits.dsc_changed = 1;
+ flags->bits.mst_bw = 1;
+ flags->bits.crtc_timing_adjust = 1;
+ flags->bits.fams_changed = 1;
+ flags->bits.scaler_sharpener = 1;
+ flags->bits.sharpening_required = 1;
+ flags->bits.cursor_attr = 1;
+ flags->bits.cursor_pos = 1;
+ flags->bits.periodic_interrupt = 1;
+ flags->bits.info_frame = 1;
+ flags->bits.dmdata = 1;
+ flags->bits.dither = 1;
+}
+
struct test_pattern {
enum dp_test_pattern type;
enum dp_test_pattern_color_space color_space;
diff --git a/drivers/gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c b/drivers/gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c
index dcca23d53261..2ad4a2635683 100644
--- a/drivers/gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c
+++ b/drivers/gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c
@@ -1237,7 +1237,7 @@ bool dcn_validate_bandwidth(
if (pipe->plane_state) {
struct pipe_ctx *hsplit_pipe = pipe->bottom_pipe;
- pipe->plane_state->update_flags.bits.full_update = 1;
+ pipe->plane_state->update_bits.full_update = 1;
if (v->dpp_per_plane[input_idx] == 2 ||
((pipe->stream->view_format ==
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c
index 042602c50e35..c9691974bf72 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c
@@ -3166,12 +3166,12 @@ static void dce110_program_front_end_for_pipe(
plane_state->rotation);
/* Moved programming gamma from dc to hwss */
- if (pipe_ctx->plane_state->update_flags.bits.full_update ||
- pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change ||
- pipe_ctx->plane_state->update_flags.bits.gamma_change)
+ if (pipe_ctx->plane_state->update_bits.full_update ||
+ pipe_ctx->plane_state->update_bits.in_transfer_func_change ||
+ pipe_ctx->plane_state->update_bits.gamma_change)
hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state);
- if (pipe_ctx->plane_state->update_flags.bits.full_update)
+ if (pipe_ctx->plane_state->update_bits.full_update)
hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream);
DC_LOG_SURFACE(
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c
index a08e9f9eec17..26aa303b8237 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c
@@ -332,12 +332,12 @@ dce60_program_front_end_for_pipe(
plane_state->rotation);
/* Moved programming gamma from dc to hwss */
- if (pipe_ctx->plane_state->update_flags.bits.full_update ||
- pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change ||
- pipe_ctx->plane_state->update_flags.bits.gamma_change)
+ if (pipe_ctx->plane_state->update_bits.full_update ||
+ pipe_ctx->plane_state->update_bits.in_transfer_func_change ||
+ pipe_ctx->plane_state->update_bits.gamma_change)
hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state);
- if (pipe_ctx->plane_state->update_flags.bits.full_update)
+ if (pipe_ctx->plane_state->update_bits.full_update)
hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream);
DC_LOG_SURFACE(
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c
index 4a5b2bc0b610..f75a1794ae2a 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c
@@ -2981,7 +2981,7 @@ void dcn10_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx)
mpcc_id = hubp->inst;
/* If there is no full update, don't need to touch MPC tree*/
- if (!pipe_ctx->plane_state->update_flags.bits.full_update) {
+ if (!pipe_ctx->plane_state->update_bits.full_update) {
mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id);
dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id);
return;
@@ -3041,7 +3041,7 @@ static void dcn10_update_dchubp_dpp(
/* If request max dpp clk is lower than current dispclk, no need to
* divided by 2
*/
- if (plane_state->update_flags.bits.full_update) {
+ if (plane_state->update_bits.full_update) {
/* new calculated dispclk, dppclk are stored in
* context->bw_ctx.bw.dcn.clk.dispclk_khz / dppclk_khz. current
@@ -3096,7 +3096,7 @@ static void dcn10_update_dchubp_dpp(
* VTG is within DCHUBBUB which is commond block share by each pipe HUBP.
* VTG is 1:1 mapping with OTG. Each pipe HUBP will select which VTG
*/
- if (plane_state->update_flags.bits.full_update) {
+ if (plane_state->update_bits.full_update) {
hubp->funcs->hubp_vtg_sel(hubp, pipe_ctx->stream_res.tg->inst);
hubp->funcs->hubp_setup(
@@ -3113,26 +3113,26 @@ static void dcn10_update_dchubp_dpp(
size.surface_size = pipe_ctx->plane_res.scl_data.viewport;
- if (plane_state->update_flags.bits.full_update ||
- plane_state->update_flags.bits.bpp_change)
+ if (plane_state->update_bits.full_update ||
+ plane_state->update_bits.bpp_change)
dcn10_update_dpp(dpp, plane_state);
- if (plane_state->update_flags.bits.full_update ||
- plane_state->update_flags.bits.per_pixel_alpha_change ||
- plane_state->update_flags.bits.global_alpha_change)
+ if (plane_state->update_bits.full_update ||
+ plane_state->update_bits.per_pixel_alpha_change ||
+ plane_state->update_bits.global_alpha_change)
hws->funcs.update_mpcc(dc, pipe_ctx);
- if (plane_state->update_flags.bits.full_update ||
- plane_state->update_flags.bits.per_pixel_alpha_change ||
- plane_state->update_flags.bits.global_alpha_change ||
- plane_state->update_flags.bits.scaling_change ||
- plane_state->update_flags.bits.position_change) {
+ if (plane_state->update_bits.full_update ||
+ plane_state->update_bits.per_pixel_alpha_change ||
+ plane_state->update_bits.global_alpha_change ||
+ plane_state->update_bits.scaling_change ||
+ plane_state->update_bits.position_change) {
update_scaler(pipe_ctx);
}
- if (plane_state->update_flags.bits.full_update ||
- plane_state->update_flags.bits.scaling_change ||
- plane_state->update_flags.bits.position_change) {
+ if (plane_state->update_bits.full_update ||
+ plane_state->update_bits.scaling_change ||
+ plane_state->update_bits.position_change) {
hubp->funcs->mem_program_viewport(
hubp,
&pipe_ctx->plane_res.scl_data.viewport,
@@ -3150,7 +3150,7 @@ static void dcn10_update_dchubp_dpp(
dc->hwss.set_cursor_sdr_white_level(pipe_ctx);
}
- if (plane_state->update_flags.bits.full_update) {
+ if (plane_state->update_bits.full_update) {
/*gamut remap*/
dc->hwss.program_gamut_remap(pipe_ctx);
@@ -3161,15 +3161,15 @@ static void dcn10_update_dchubp_dpp(
pipe_ctx->stream_res.opp->inst);
}
- if (plane_state->update_flags.bits.full_update ||
- plane_state->update_flags.bits.pixel_format_change ||
- plane_state->update_flags.bits.horizontal_mirror_change ||
- plane_state->update_flags.bits.rotation_change ||
- plane_state->update_flags.bits.swizzle_change ||
- plane_state->update_flags.bits.dcc_change ||
- plane_state->update_flags.bits.bpp_change ||
- plane_state->update_flags.bits.scaling_change ||
- plane_state->update_flags.bits.plane_size_change) {
+ if (plane_state->update_bits.full_update ||
+ plane_state->update_bits.pixel_format_change ||
+ plane_state->update_bits.horizontal_mirror_change ||
+ plane_state->update_bits.rotation_change ||
+ plane_state->update_bits.swizzle_change ||
+ plane_state->update_bits.dcc_change ||
+ plane_state->update_bits.bpp_change ||
+ plane_state->update_bits.scaling_change ||
+ plane_state->update_bits.plane_size_change) {
hubp->funcs->hubp_program_surface_config(
hubp,
plane_state->format,
@@ -3278,16 +3278,16 @@ void dcn10_program_pipe(
hws->funcs.blank_pixel_data(dc, pipe_ctx, blank);
}
- if (pipe_ctx->plane_state->update_flags.bits.full_update)
+ if (pipe_ctx->plane_state->update_bits.full_update)
dcn10_enable_plane(dc, pipe_ctx, context);
dcn10_update_dchubp_dpp(dc, pipe_ctx, context);
hws->funcs.set_hdr_multiplier(pipe_ctx);
- if (pipe_ctx->plane_state->update_flags.bits.full_update ||
- pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change ||
- pipe_ctx->plane_state->update_flags.bits.gamma_change)
+ if (pipe_ctx->plane_state->update_bits.full_update ||
+ pipe_ctx->plane_state->update_bits.in_transfer_func_change ||
+ pipe_ctx->plane_state->update_bits.gamma_change)
hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state);
/* dcn10_translate_regamma_to_hw_format takes 750us to finish
@@ -3296,7 +3296,7 @@ void dcn10_program_pipe(
* Always call this for now since it does memcmp inside before
* doing heavy calculation and programming
*/
- if (pipe_ctx->plane_state->update_flags.bits.full_update)
+ if (pipe_ctx->plane_state->update_bits.full_update)
hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream);
}
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c
index 50d039b3fb43..95e5b6a6ba0f 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c
@@ -1733,10 +1733,10 @@ void dcn20_update_dchubp_dpp(
if (pipe_ctx->update_flags.bits.enable ||
pipe_ctx->update_flags.bits.plane_changed ||
- plane_state->update_flags.bits.bpp_change ||
- plane_state->update_flags.bits.input_csc_change ||
- plane_state->update_flags.bits.color_space_change ||
- plane_state->update_flags.bits.coeff_reduction_change) {
+ plane_state->update_bits.bpp_change ||
+ plane_state->update_bits.input_csc_change ||
+ plane_state->update_bits.color_space_change ||
+ plane_state->update_bits.coeff_reduction_change) {
struct dc_bias_and_scale bns_params = plane_state->bias_and_scale;
// program the input csc
@@ -1760,16 +1760,16 @@ void dcn20_update_dchubp_dpp(
if (pipe_ctx->update_flags.bits.mpcc
|| pipe_ctx->update_flags.bits.plane_changed
- || plane_state->update_flags.bits.global_alpha_change
- || plane_state->update_flags.bits.per_pixel_alpha_change) {
+ || plane_state->update_bits.global_alpha_change
+ || plane_state->update_bits.per_pixel_alpha_change) {
// MPCC inst is equal to pipe index in practice
hws->funcs.update_mpcc(dc, pipe_ctx);
}
if (pipe_ctx->update_flags.bits.scaler ||
- plane_state->update_flags.bits.scaling_change ||
- plane_state->update_flags.bits.position_change ||
- plane_state->update_flags.bits.per_pixel_alpha_change ||
+ plane_state->update_bits.scaling_change ||
+ plane_state->update_bits.position_change ||
+ plane_state->update_bits.per_pixel_alpha_change ||
pipe_ctx->stream->update_flags.bits.scaling) {
pipe_ctx->plane_res.scl_data.lb_params.alpha_en = pipe_ctx->plane_state->per_pixel_alpha;
ASSERT(pipe_ctx->plane_res.scl_data.lb_params.depth == LB_PIXEL_DEPTH_36BPP);
@@ -1779,8 +1779,8 @@ void dcn20_update_dchubp_dpp(
}
if (pipe_ctx->update_flags.bits.viewport ||
- (context == dc->current_state && plane_state->update_flags.bits.position_change) ||
- (context == dc->current_state && plane_state->update_flags.bits.scaling_change) ||
+ (context == dc->current_state && plane_state->update_bits.position_change) ||
+ (context == dc->current_state && plane_state->update_bits.scaling_change) ||
(context == dc->current_state && pipe_ctx->stream->update_flags.bits.scaling)) {
hubp->funcs->mem_program_viewport(
@@ -1812,7 +1812,7 @@ void dcn20_update_dchubp_dpp(
if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.opp_changed
|| pipe_ctx->update_flags.bits.plane_changed
|| pipe_ctx->stream->update_flags.bits.gamut_remap
- || plane_state->update_flags.bits.gamut_remap_change
+ || plane_state->update_bits.gamut_remap_change
|| pipe_ctx->stream->update_flags.bits.out_csc) {
/* dpp/cm gamut remap*/
dc->hwss.program_gamut_remap(pipe_ctx);
@@ -1828,14 +1828,14 @@ void dcn20_update_dchubp_dpp(
if (pipe_ctx->update_flags.bits.enable ||
pipe_ctx->update_flags.bits.plane_changed ||
pipe_ctx->update_flags.bits.opp_changed ||
- plane_state->update_flags.bits.pixel_format_change ||
- plane_state->update_flags.bits.horizontal_mirror_change ||
- plane_state->update_flags.bits.rotation_change ||
- plane_state->update_flags.bits.swizzle_change ||
- plane_state->update_flags.bits.dcc_change ||
- plane_state->update_flags.bits.bpp_change ||
- plane_state->update_flags.bits.scaling_change ||
- plane_state->update_flags.bits.plane_size_change) {
+ plane_state->update_bits.pixel_format_change ||
+ plane_state->update_bits.horizontal_mirror_change ||
+ plane_state->update_bits.rotation_change ||
+ plane_state->update_bits.swizzle_change ||
+ plane_state->update_bits.dcc_change ||
+ plane_state->update_bits.bpp_change ||
+ plane_state->update_bits.scaling_change ||
+ plane_state->update_bits.plane_size_change) {
struct plane_size size = plane_state->plane_size;
size.surface_size = pipe_ctx->plane_res.scl_data.viewport;
@@ -1853,7 +1853,7 @@ void dcn20_update_dchubp_dpp(
if (pipe_ctx->update_flags.bits.enable ||
pipe_ctx->update_flags.bits.plane_changed ||
- plane_state->update_flags.bits.addr_update) {
+ plane_state->update_bits.addr_update) {
if (resource_is_pipe_type(pipe_ctx, OTG_MASTER) &&
pipe_mall_type == SUBVP_MAIN) {
union block_sequence_params params;
@@ -1969,18 +1969,18 @@ static void dcn20_program_pipe(
}
if (pipe_ctx->plane_state && (pipe_ctx->update_flags.raw ||
- pipe_ctx->plane_state->update_flags.raw ||
+ dc_pipe_update_bits_is_any_set(&pipe_ctx->plane_state->update_bits) ||
pipe_ctx->stream->update_flags.raw))
dcn20_update_dchubp_dpp(dc, pipe_ctx, context);
if (pipe_ctx->plane_state && (pipe_ctx->update_flags.bits.enable ||
- pipe_ctx->plane_state->update_flags.bits.hdr_mult))
+ pipe_ctx->plane_state->update_bits.hdr_mult))
hws->funcs.set_hdr_multiplier(pipe_ctx);
if (pipe_ctx->plane_state &&
- (pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change ||
- pipe_ctx->plane_state->update_flags.bits.gamma_change ||
- pipe_ctx->plane_state->update_flags.bits.lut_3d ||
+ (pipe_ctx->plane_state->update_bits.in_transfer_func_change ||
+ pipe_ctx->plane_state->update_bits.gamma_change ||
+ pipe_ctx->plane_state->update_bits.lut_3d ||
pipe_ctx->update_flags.bits.enable))
hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state);
@@ -2186,7 +2186,7 @@ void dcn20_program_front_end_for_ctx(
pipe = &context->res_ctx.pipe_ctx[i];
if (!pipe->top_pipe && !pipe->prev_odm_pipe
&& pipe->stream && pipe->stream->num_wb_info > 0
- && (pipe->update_flags.raw || (pipe->plane_state && pipe->plane_state->update_flags.raw)
+ && (pipe->update_flags.raw || (pipe->plane_state && dc_pipe_update_bits_is_any_set(&pipe->plane_state->update_bits))
|| pipe->stream->update_flags.raw)
&& hws->funcs.program_all_writeback_pipes_in_tree)
hws->funcs.program_all_writeback_pipes_in_tree(dc, pipe->stream, context);
@@ -2998,7 +2998,7 @@ void dcn20_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx)
mpcc_id = hubp->inst;
/* If there is no full update, don't need to touch MPC tree*/
- if (!pipe_ctx->plane_state->update_flags.bits.full_update &&
+ if (!pipe_ctx->plane_state->update_bits.full_update &&
!pipe_ctx->update_flags.bits.mpcc) {
mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id);
dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id);
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c
index ce18d75fd991..7b820bdae55b 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c
@@ -485,7 +485,7 @@ void dcn201_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx)
mpcc_id = dpp_id;
/* If there is no full update, don't need to touch MPC tree*/
- if (!pipe_ctx->plane_state->update_flags.bits.full_update) {
+ if (!pipe_ctx->plane_state->update_bits.full_update) {
dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id);
mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id);
return;
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c
index 34cbd90b2283..1340f673ec3b 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c
@@ -1466,7 +1466,7 @@ void dcn32_update_phantom_vp_position(struct dc *dc,
if (pipe->stream && dc_state_get_pipe_subvp_type(context, pipe) == SUBVP_MAIN &&
dc_state_get_paired_subvp_stream(context, pipe->stream) == phantom_pipe->stream) {
- if (pipe->plane_state && pipe->plane_state->update_flags.bits.position_change) {
+ if (pipe->plane_state && pipe->plane_state->update_bits.position_change) {
phantom_plane->src_rect.x = pipe->plane_state->src_rect.x;
phantom_plane->src_rect.y = pipe->plane_state->src_rect.y;
@@ -1474,7 +1474,7 @@ void dcn32_update_phantom_vp_position(struct dc *dc,
phantom_plane->dst_rect.x = pipe->plane_state->dst_rect.x;
phantom_plane->dst_rect.y = pipe->plane_state->dst_rect.y;
- phantom_pipe->plane_state->update_flags.bits.position_change = 1;
+ phantom_pipe->plane_state->update_bits.position_change = 1;
resource_build_scaling_params(phantom_pipe);
return;
}
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c
index 5da81adf02d1..6d3bcc02af1a 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c
@@ -1421,7 +1421,7 @@ void dcn401_wait_for_dcc_meta_propagation(const struct dc *dc,
if (pipe_ctx->plane_state &&
pipe_ctx->plane_state->dcc.enable &&
pipe_ctx->plane_state->flip_immediate &&
- pipe_ctx->plane_state->update_flags.bits.addr_update) {
+ pipe_ctx->plane_state->update_bits.addr_update) {
is_wait_needed = true;
break;
}
@@ -2268,18 +2268,18 @@ void dcn401_program_pipe(
}
if (pipe_ctx->plane_state && (pipe_ctx->update_flags.raw ||
- pipe_ctx->plane_state->update_flags.raw ||
+ dc_pipe_update_bits_is_any_set(&pipe_ctx->plane_state->update_bits) ||
pipe_ctx->stream->update_flags.raw))
dc->hwss.update_dchubp_dpp(dc, pipe_ctx, context);
if (pipe_ctx->plane_state && (pipe_ctx->update_flags.bits.enable ||
- pipe_ctx->plane_state->update_flags.bits.hdr_mult))
+ pipe_ctx->plane_state->update_bits.hdr_mult))
hws->funcs.set_hdr_multiplier(pipe_ctx);
if (pipe_ctx->plane_state &&
- (pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change ||
- pipe_ctx->plane_state->update_flags.bits.gamma_change ||
- pipe_ctx->plane_state->update_flags.bits.lut_3d ||
+ (pipe_ctx->plane_state->update_bits.in_transfer_func_change ||
+ pipe_ctx->plane_state->update_bits.gamma_change ||
+ pipe_ctx->plane_state->update_bits.lut_3d ||
pipe_ctx->update_flags.bits.enable))
hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state);
@@ -2338,7 +2338,7 @@ void dcn401_program_pipe(
pipe_ctx->stream_res.test_pattern_params.offset);
}
if (pipe_ctx->plane_state
- && pipe_ctx->plane_state->update_flags.bits.cm_hist_change
+ && pipe_ctx->plane_state->update_bits.cm_hist_change
&& hws->funcs.program_cm_hist)
hws->funcs.program_cm_hist(dc, pipe_ctx, pipe_ctx->plane_state);
}
@@ -2419,7 +2419,7 @@ void dcn401_program_pipe_sequence(
}
if (pipe_ctx->plane_state && (pipe_ctx->update_flags.raw ||
- pipe_ctx->plane_state->update_flags.raw ||
+ dc_pipe_update_bits_is_any_set(&pipe_ctx->plane_state->update_bits) ||
pipe_ctx->stream->update_flags.raw)) {
if (dc->hwss.update_dchubp_dpp_sequence)
@@ -2427,15 +2427,15 @@ void dcn401_program_pipe_sequence(
}
if (pipe_ctx->plane_state && (pipe_ctx->update_flags.bits.enable ||
- pipe_ctx->plane_state->update_flags.bits.hdr_mult)) {
+ pipe_ctx->plane_state->update_bits.hdr_mult)) {
hws->funcs.set_hdr_multiplier_sequence(pipe_ctx, seq_state);
}
if (pipe_ctx->plane_state &&
- (pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change ||
- pipe_ctx->plane_state->update_flags.bits.gamma_change ||
- pipe_ctx->plane_state->update_flags.bits.lut_3d ||
+ (pipe_ctx->plane_state->update_bits.in_transfer_func_change ||
+ pipe_ctx->plane_state->update_bits.gamma_change ||
+ pipe_ctx->plane_state->update_bits.lut_3d ||
pipe_ctx->update_flags.bits.enable)) {
hwss_add_dpp_set_input_transfer_func(seq_state, dc, pipe_ctx, pipe_ctx->plane_state);
@@ -2493,7 +2493,7 @@ void dcn401_program_pipe_sequence(
}
if (pipe_ctx->plane_state
- && pipe_ctx->plane_state->update_flags.bits.cm_hist_change
+ && pipe_ctx->plane_state->update_bits.cm_hist_change
&& hws->funcs.program_cm_hist) {
hwss_add_dpp_program_cm_hist(seq_state, pipe_ctx->plane_res.dpp,
@@ -2647,7 +2647,7 @@ void dcn401_program_front_end_for_ctx(
pipe = &context->res_ctx.pipe_ctx[i];
if (!pipe->top_pipe && !pipe->prev_odm_pipe
&& pipe->stream && pipe->stream->num_wb_info > 0
- && (pipe->update_flags.raw || (pipe->plane_state && pipe->plane_state->update_flags.raw)
+ && (pipe->update_flags.raw || (pipe->plane_state && dc_pipe_update_bits_is_any_set(&pipe->plane_state->update_bits))
|| pipe->stream->update_flags.raw)
&& hws->funcs.program_all_writeback_pipes_in_tree)
hws->funcs.program_all_writeback_pipes_in_tree(dc, pipe->stream, context);
@@ -3733,10 +3733,10 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc,
/* Step 7: DPP setup - input CSC and format setup */
if (pipe_ctx->update_flags.bits.enable ||
pipe_ctx->update_flags.bits.plane_changed ||
- plane_state->update_flags.bits.bpp_change ||
- plane_state->update_flags.bits.input_csc_change ||
- plane_state->update_flags.bits.color_space_change ||
- plane_state->update_flags.bits.coeff_reduction_change) {
+ plane_state->update_bits.bpp_change ||
+ plane_state->update_bits.input_csc_change ||
+ plane_state->update_bits.color_space_change ||
+ plane_state->update_bits.coeff_reduction_change) {
hwss_add_dpp_setup_dpp(seq_state, pipe_ctx);
/* Step 8: DPP cursor matrix setup */
@@ -3753,8 +3753,8 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc,
/* Step 10: MPCC updates */
if (pipe_ctx->update_flags.bits.mpcc ||
pipe_ctx->update_flags.bits.plane_changed ||
- plane_state->update_flags.bits.global_alpha_change ||
- plane_state->update_flags.bits.per_pixel_alpha_change) {
+ plane_state->update_bits.global_alpha_change ||
+ plane_state->update_bits.per_pixel_alpha_change) {
/* Check if update_mpcc_sequence is implemented and prefer it over single MPC_UPDATE_MPCC step */
if (hws->funcs.update_mpcc_sequence)
@@ -3763,9 +3763,9 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc,
/* Step 11: DPP scaler setup */
if (pipe_ctx->update_flags.bits.scaler ||
- plane_state->update_flags.bits.scaling_change ||
- plane_state->update_flags.bits.position_change ||
- plane_state->update_flags.bits.per_pixel_alpha_change ||
+ plane_state->update_bits.scaling_change ||
+ plane_state->update_bits.position_change ||
+ plane_state->update_bits.per_pixel_alpha_change ||
pipe_ctx->stream->update_flags.bits.scaling) {
pipe_ctx->plane_res.scl_data.lb_params.alpha_en = pipe_ctx->plane_state->per_pixel_alpha;
ASSERT(pipe_ctx->plane_res.scl_data.lb_params.depth == LB_PIXEL_DEPTH_36BPP);
@@ -3774,8 +3774,8 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc,
/* Step 12: HUBP viewport programming */
if (pipe_ctx->update_flags.bits.viewport ||
- (context == dc->current_state && plane_state->update_flags.bits.position_change) ||
- (context == dc->current_state && plane_state->update_flags.bits.scaling_change) ||
+ (context == dc->current_state && plane_state->update_bits.position_change) ||
+ (context == dc->current_state && plane_state->update_bits.scaling_change) ||
(context == dc->current_state && pipe_ctx->stream->update_flags.bits.scaling)) {
hwss_add_hubp_mem_program_viewport(seq_state, hubp,
&pipe_ctx->plane_res.scl_data.viewport, &pipe_ctx->plane_res.scl_data.viewport_c);
@@ -3807,7 +3807,7 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc,
if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.opp_changed ||
pipe_ctx->update_flags.bits.plane_changed ||
pipe_ctx->stream->update_flags.bits.gamut_remap ||
- plane_state->update_flags.bits.gamut_remap_change ||
+ plane_state->update_bits.gamut_remap_change ||
pipe_ctx->stream->update_flags.bits.out_csc) {
/* Gamut remap */
@@ -3822,14 +3822,14 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc,
if (pipe_ctx->update_flags.bits.enable ||
pipe_ctx->update_flags.bits.plane_changed ||
pipe_ctx->update_flags.bits.opp_changed ||
- plane_state->update_flags.bits.pixel_format_change ||
- plane_state->update_flags.bits.horizontal_mirror_change ||
- plane_state->update_flags.bits.rotation_change ||
- plane_state->update_flags.bits.swizzle_change ||
- plane_state->update_flags.bits.dcc_change ||
- plane_state->update_flags.bits.bpp_change ||
- plane_state->update_flags.bits.scaling_change ||
- plane_state->update_flags.bits.plane_size_change) {
+ plane_state->update_bits.pixel_format_change ||
+ plane_state->update_bits.horizontal_mirror_change ||
+ plane_state->update_bits.rotation_change ||
+ plane_state->update_bits.swizzle_change ||
+ plane_state->update_bits.dcc_change ||
+ plane_state->update_bits.bpp_change ||
+ plane_state->update_bits.scaling_change ||
+ plane_state->update_bits.plane_size_change) {
struct plane_size size = plane_state->plane_size;
size.surface_size = pipe_ctx->plane_res.scl_data.viewport;
@@ -3843,7 +3843,7 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc,
/* Step 19: Update plane address (with SubVP support) */
if (pipe_ctx->update_flags.bits.enable ||
pipe_ctx->update_flags.bits.plane_changed ||
- plane_state->update_flags.bits.addr_update) {
+ plane_state->update_bits.addr_update) {
/* SubVP save surface address if needed */
if (resource_is_pipe_type(pipe_ctx, OTG_MASTER) && pipe_mall_type == SUBVP_MAIN) {
@@ -3916,7 +3916,7 @@ void dcn401_update_mpcc_sequence(struct dc *dc,
mpcc_id = hubp->inst;
/* Step 1: Update blending if no full update needed */
- if (!pipe_ctx->plane_state->update_flags.bits.full_update &&
+ if (!pipe_ctx->plane_state->update_bits.full_update &&
!pipe_ctx->update_flags.bits.mpcc) {
/* Update blending configuration */
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c
index 9cf8b379cb34..f415473517d4 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c
@@ -355,7 +355,7 @@ void dcn42_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx)
mpcc_id = hubp->inst;
/* If there is no full update, don't need to touch MPC tree*/
- if (!pipe_ctx->plane_state->update_flags.bits.full_update &&
+ if (!pipe_ctx->plane_state->update_bits.full_update &&
!pipe_ctx->update_flags.bits.mpcc) {
mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id);
dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id);
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 16/32] drm/amd/display: Add Support for HDMI Compliance Automation
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (14 preceding siblings ...)
2026-06-10 9:44 ` [PATCH 15/32] drm/amd/display: Refactor surface_update_flags to flat struct with helpers Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 14:43 ` Nicolas Frattaroli
2026-06-10 9:45 ` [PATCH 17/32] drm/amd/display: Add KUnit tests for amdgpu_dm Chenyu Chen
` (16 subsequent siblings)
32 siblings, 1 reply; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Fangzhi Zuo, Chenyu Chen
From: Fangzhi Zuo <Jerry.Zuo@amd.com>
Add support to get DUT trained at FRL link rate when working with
Teledyne M41h compliance automation.
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Fangzhi Zuo <Jerry.Zuo@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 3 +
.../display/amdgpu_dm/amdgpu_dm_connector.c | 5 ++
.../amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 67 ++++++++++++++++++-
.../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 6 ++
4 files changed, 80 insertions(+), 1 deletion(-)
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 2940dd5b7348..ba1e11e144f2 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -877,6 +877,9 @@ struct amdgpu_dm_connector {
unsigned int hdmi_hpd_debounce_delay_ms;
struct delayed_work hdmi_hpd_debounce_work;
struct dc_sink *hdmi_prev_sink;
+
+ /* HDMI compliance automation */
+ bool hdmi_comp_auto;
};
static inline void amdgpu_dm_set_mst_status(uint8_t *status,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index 6ef257622f1a..59091ee32099 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -573,6 +573,11 @@ void amdgpu_dm_update_connector_after_detect(
amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true);
amdgpu_dm_update_connector_ext_caps(aconnector);
dm_set_panel_type(aconnector);
+
+ if (aconnector->hdmi_comp_auto) {
+ if (sink->sink_signal != SIGNAL_TYPE_HDMI_FRL)
+ sink->sink_signal = SIGNAL_TYPE_HDMI_FRL;
+ }
} else {
hdmi_cec_unset_edid(aconnector);
drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
index 096a855a7304..95a56e39f452 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
@@ -2982,6 +2982,64 @@ static ssize_t hdmi_cec_state_write(struct file *f, const char __user *buf,
return size;
}
+/**
+ * hdmi_automation_enable - Enable/Disable HDMI automation feature
+ * @f: file structure.
+ * @buf: userspace buffer. set to '1' to enable; '0' to disable automation feature.
+ * @size: size of buffer from userpsace.
+ * @pos: unused.
+ *
+ * Return size on success, error code on failure
+ */
+static ssize_t hdmi_automation_enable(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos)
+{
+ struct amdgpu_dm_connector *aconnector = file_inode(f)->i_private;
+ char *wr_buf = NULL;
+ const uint32_t wr_buf_size = 40;
+ int max_param_num = 1;
+ uint8_t param_nums = 0;
+ long param[2];
+ bool hdmi_comp_auto;
+
+ if (size == 0)
+ return -EINVAL;
+
+ wr_buf = kcalloc(wr_buf_size, sizeof(char), GFP_KERNEL);
+ if (!wr_buf)
+ return -ENOSPC;
+
+ if (parse_write_buffer_into_params(wr_buf, wr_buf_size,
+ (long *)param, buf,
+ max_param_num,
+ ¶m_nums)) {
+ kfree(wr_buf);
+ return -EINVAL;
+ }
+
+ if (param_nums <= 0) {
+ kfree(wr_buf);
+ DRM_DEBUG_DRIVER("user data not be read\n");
+ return -EINVAL;
+ }
+
+ switch (param[0]) {
+ case 0:
+ hdmi_comp_auto = false;
+ break;
+ case 1:
+ default:
+ hdmi_comp_auto = true;
+ break;
+ }
+
+ /* Persist setting across sink re-detection/hotplug. */
+ aconnector->hdmi_comp_auto = hdmi_comp_auto;
+
+ kfree(wr_buf);
+ return size;
+}
+
DEFINE_SHOW_ATTRIBUTE(dp_dsc_fec_support);
DEFINE_SHOW_ATTRIBUTE(dmub_fw_state);
DEFINE_SHOW_ATTRIBUTE(dmub_tracebuffer);
@@ -3099,6 +3157,12 @@ static const struct file_operations dp_mst_link_settings_debugfs_fops = {
.llseek = default_llseek
};
+static const struct file_operations hdmi_automation_debugfs_fops = {
+ .owner = THIS_MODULE,
+ .write = hdmi_automation_enable,
+ .llseek = default_llseek
+};
+
static const struct {
char *name;
const struct file_operations *fops;
@@ -3131,7 +3195,8 @@ static const struct {
const struct file_operations *fops;
} hdmi_debugfs_entries[] = {
{"hdcp_sink_capability", &hdcp_sink_capability_fops},
- {"hdmi_cec_state", &hdmi_cec_state_fops}
+ {"hdmi_cec_state", &hdmi_cec_state_fops},
+ {"hdmi_automation", &hdmi_automation_debugfs_fops}
};
/*
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
index a2d0bb34e639..6350212b9a66 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
@@ -193,6 +193,12 @@ enum dc_edid_status dm_helpers_parse_edid_caps(
__func__, connector->name, edid_caps->frl_dsc_10bpc, edid_caps->frl_dsc_12bpc, \
edid_caps->frl_dsc_all_bpp, edid_caps->frl_dsc_native_420, edid_caps->frl_dsc_max_slices, \
edid_caps->frl_dsc_max_frl_rate, edid_caps->frl_dsc_total_chunk_kbytes);
+ if (aconnector->hdmi_comp_auto) {
+ edid_caps->panel_patch.hdmi_comp_auto = true;
+ link->ctx->dc->debug.force_frl_max = true;
+ link->ctx->dc->debug.force_frl_dsc = true;
+ drm_dbg_driver(connector->dev, "%s: HDMI_FRL [%s] hdmi_comp_auto --> enabled\n", __func__, connector->name);
+ }
}
apply_edid_quirks(link, edid_buf, edid_caps);
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 17/32] drm/amd/display: Add KUnit tests for amdgpu_dm
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (15 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 16/32] drm/amd/display: Add Support for HDMI Compliance Automation Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 18/32] drm/amd/display: Add KUnit tests for amdgpu_dm_backlight Chenyu Chen
` (15 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Add KUnit tests for pure helper functions in amdgpu_dm.c.
Tests cover:
- dm_plane_layer_index_cmp(): equal, ascending, and descending
layer_index ordering
- fill_plane_color_attributes(): RGB plus BT601/BT709/BT2020
full- and limited-range YCbCr, and invalid encoding
- modereset_required(): active vs inactive stream states with
and without a mode change
- dm_get_oriented_plane_size(): 0/90/180/270 degree rotations
- dm_get_plane_scale(): identity, rotated identity, and
division-by-zero guard
- is_scaling_state_different(): identical state, scaling mode
change, and underscan enable/border changes
- is_timing_unchanged_for_freesync(): NULL args, identical
modes, VRR vtotal/vsync shift, and pixel clock change
- set_freesync_fixed_config(): fixed refresh-rate computation
- is_dc_timing_adjust_needed(): pending hw adjust, VRR
active-fixed, VRR active-state toggle, and steady state
- set_multisync_trigger_params(): disabled trigger and
rising/falling edge selection by vsync polarity
- set_master_stream(): highest refresh-rate selection and the
default-to-first-stream case
Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 42 +-
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 21 +
.../amdgpu_dm/amdgpu_dm_kunit_helpers.h | 1 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 1 +
.../display/amdgpu_dm/tests/amdgpu_dm_test.c | 929 ++++++++++++++++++
5 files changed, 979 insertions(+), 15 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
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 4519c0572963..7faf580a3f8d 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -70,6 +70,7 @@
#include "amdgpu_dm_audio.h"
#include "amdgpu_dm_dmub.h"
#include "amdgpu_dm_connector.h"
+#include "amdgpu_dm_kunit_helpers.h"
#include "ivsrcid/ivsrcid_vislands30.h"
@@ -146,7 +147,7 @@ static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context);
static int amdgpu_dm_atomic_check(struct drm_device *dev,
struct drm_atomic_state *state);
-static bool
+STATIC_IFN_KUNIT bool
is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state,
struct drm_crtc_state *new_crtc_state);
@@ -247,8 +248,8 @@ static int dm_soft_reset(struct amdgpu_ip_block *ip_block)
return 0;
}
-static inline bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state,
- struct dm_crtc_state *new_state)
+STATIC_IFN_KUNIT bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state,
+ struct dm_crtc_state *new_state)
{
if (new_state->stream->adjust.timing_adjust_pending)
return true;
@@ -259,13 +260,14 @@ static inline bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state,
else
return false;
}
+EXPORT_IF_KUNIT(is_dc_timing_adjust_needed);
/*
* DC will program planes with their z-order determined by their ordering
* in the dc_surface_updates array. This comparator is used to sort them
* by descending zpos.
*/
-static int dm_plane_layer_index_cmp(const void *a, const void *b)
+STATIC_IFN_KUNIT int dm_plane_layer_index_cmp(const void *a, const void *b)
{
const struct dc_surface_update *sa = (struct dc_surface_update *)a;
const struct dc_surface_update *sb = (struct dc_surface_update *)b;
@@ -273,6 +275,7 @@ static int dm_plane_layer_index_cmp(const void *a, const void *b)
/* Sort by descending dc_plane layer_index (i.e. normalized_zpos) */
return sb->surface->layer_index - sa->surface->layer_index;
}
+EXPORT_IF_KUNIT(dm_plane_layer_index_cmp);
/**
* update_planes_and_stream_adapter() - Send planes to be updated in DC
@@ -2978,12 +2981,13 @@ static int dm_early_init(struct amdgpu_ip_block *ip_block)
return dm_init_microcode(adev);
}
-static bool modereset_required(struct drm_crtc_state *crtc_state)
+STATIC_IFN_KUNIT bool modereset_required(struct drm_crtc_state *crtc_state)
{
return !crtc_state->active && drm_atomic_crtc_needs_modeset(crtc_state);
}
+EXPORT_IF_KUNIT(modereset_required);
-static int
+STATIC_IFN_KUNIT int
fill_plane_color_attributes(const struct drm_plane_state *plane_state,
const enum surface_pixel_format format,
enum dc_color_space *color_space)
@@ -3030,6 +3034,7 @@ fill_plane_color_attributes(const struct drm_plane_state *plane_state,
return 0;
}
+EXPORT_IF_KUNIT(fill_plane_color_attributes);
static int
fill_dc_plane_info_and_addr(struct amdgpu_device *adev,
@@ -3598,7 +3603,7 @@ static void dm_update_pflip_irq_state(struct amdgpu_device *adev,
amdgpu_irq_update(adev, &adev->pageflip_irq, irq_type);
}
-static bool
+STATIC_IFN_KUNIT bool
is_scaling_state_different(const struct dm_connector_state *dm_state,
const struct dm_connector_state *old_dm_state)
{
@@ -3615,6 +3620,7 @@ is_scaling_state_different(const struct dm_connector_state *dm_state,
return true;
return false;
}
+EXPORT_IF_KUNIT(is_scaling_state_different);
static bool is_content_protection_different(struct drm_crtc_state *new_crtc_state,
struct drm_crtc_state *old_crtc_state,
@@ -5077,7 +5083,7 @@ static int amdgpu_dm_atomic_setup_commit(struct drm_atomic_state *state)
return 0;
}
-static void set_multisync_trigger_params(
+STATIC_IFN_KUNIT void set_multisync_trigger_params(
struct dc_stream_state *stream)
{
struct dc_stream_state *master = NULL;
@@ -5090,9 +5096,10 @@ static void set_multisync_trigger_params(
stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_PIXEL;
}
}
+EXPORT_IF_KUNIT(set_multisync_trigger_params);
-static void set_master_stream(struct dc_stream_state *stream_set[],
- int stream_count)
+STATIC_IFN_KUNIT void set_master_stream(struct dc_stream_state *stream_set[],
+ int stream_count)
{
int j, highest_rfr = 0, master_stream = 0;
@@ -5113,6 +5120,7 @@ static void set_master_stream(struct dc_stream_state *stream_set[],
stream_set[j]->triggered_crtc_reset.event_source = stream_set[master_stream];
}
}
+EXPORT_IF_KUNIT(set_master_stream);
static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context)
{
@@ -5558,7 +5566,7 @@ static void reset_freesync_config_for_crtc(
sizeof(new_crtc_state->vrr_infopacket));
}
-static bool
+STATIC_IFN_KUNIT bool
is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state,
struct drm_crtc_state *new_crtc_state)
{
@@ -5587,8 +5595,9 @@ is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state,
return false;
}
+EXPORT_IF_KUNIT(is_timing_unchanged_for_freesync);
-static void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state)
+STATIC_IFN_KUNIT void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state)
{
u64 num, den, res;
struct drm_crtc_state *new_crtc_state = &dm_new_crtc_state->base;
@@ -5602,6 +5611,7 @@ static void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state)
res = div_u64(num, den);
dm_new_crtc_state->freesync_config.fixed_refresh_in_uhz = res;
}
+EXPORT_IF_KUNIT(set_freesync_fixed_config);
static int dm_update_crtc_state(struct amdgpu_display_manager *dm,
struct drm_atomic_state *state,
@@ -6337,8 +6347,8 @@ static int dm_update_plane_state(struct dc *dc,
return ret;
}
-static void dm_get_oriented_plane_size(struct drm_plane_state *plane_state,
- int *src_w, int *src_h)
+STATIC_IFN_KUNIT void dm_get_oriented_plane_size(struct drm_plane_state *plane_state,
+ int *src_w, int *src_h)
{
switch (plane_state->rotation & DRM_MODE_ROTATE_MASK) {
case DRM_MODE_ROTATE_90:
@@ -6354,8 +6364,9 @@ static void dm_get_oriented_plane_size(struct drm_plane_state *plane_state,
break;
}
}
+EXPORT_IF_KUNIT(dm_get_oriented_plane_size);
-static void
+STATIC_IFN_KUNIT void
dm_get_plane_scale(struct drm_plane_state *plane_state,
int *out_plane_scale_w, int *out_plane_scale_h)
{
@@ -6365,6 +6376,7 @@ dm_get_plane_scale(struct drm_plane_state *plane_state,
*out_plane_scale_w = plane_src_w ? plane_state->crtc_w * 1000 / plane_src_w : 0;
*out_plane_scale_h = plane_src_h ? plane_state->crtc_h * 1000 / plane_src_h : 0;
}
+EXPORT_IF_KUNIT(dm_get_plane_scale);
/*
* The normalized_zpos value cannot be used by this iterator directly. It's only
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 ba1e11e144f2..2f4a567412f1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1126,4 +1126,25 @@ void amdgpu_dm_emulated_link_detect(struct dc_link *link);
void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev,
struct dc_sink *sink);
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+int dm_plane_layer_index_cmp(const void *a, const void *b);
+int fill_plane_color_attributes(const struct drm_plane_state *plane_state,
+ const enum surface_pixel_format format,
+ enum dc_color_space *color_space);
+bool modereset_required(struct drm_crtc_state *crtc_state);
+void dm_get_oriented_plane_size(struct drm_plane_state *plane_state,
+ int *src_w, int *src_h);
+void dm_get_plane_scale(struct drm_plane_state *plane_state,
+ int *out_plane_scale_w, int *out_plane_scale_h);
+bool is_scaling_state_different(const struct dm_connector_state *dm_state,
+ const struct dm_connector_state *old_dm_state);
+bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state,
+ struct drm_crtc_state *new_crtc_state);
+void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state);
+bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state,
+ struct dm_crtc_state *new_state);
+void set_multisync_trigger_params(struct dc_stream_state *stream);
+void set_master_stream(struct dc_stream_state *stream_set[], int stream_count);
+#endif
+
#endif /* __AMDGPU_DM_H__ */
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
index 4b2864375105..1f910a6a00c0 100644
--- 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
@@ -10,6 +10,7 @@
#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
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index 4d2eb301c2af..4365d4024f70 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -20,3 +20,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
new file mode 100644
index 000000000000..31194ab42f04
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -0,0 +1,929 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+
+#include "dc.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
+
+/* Tests for dm_plane_layer_index_cmp() */
+
+/**
+ * dm_test_plane_layer_index_cmp_equal - Test Plane layer index cmp equal
+ * @test: The KUnit test context
+ */
+static void dm_test_plane_layer_index_cmp_equal(struct kunit *test)
+{
+ struct dc_plane_state *plane_a;
+ struct dc_plane_state *plane_b;
+ struct dc_surface_update sa, sb;
+
+ plane_a = kunit_kzalloc(test, sizeof(*plane_a), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_a);
+ plane_b = kunit_kzalloc(test, sizeof(*plane_b), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_b);
+
+ plane_a->layer_index = 5;
+ plane_b->layer_index = 5;
+ sa.surface = plane_a;
+ sb.surface = plane_b;
+
+ KUNIT_EXPECT_EQ(test, dm_plane_layer_index_cmp(&sa, &sb), 0);
+}
+
+/**
+ * dm_test_plane_layer_index_cmp_descending - Test Plane layer index cmp descending
+ * @test: The KUnit test context
+ */
+static void dm_test_plane_layer_index_cmp_descending(struct kunit *test)
+{
+ struct dc_plane_state *plane_a;
+ struct dc_plane_state *plane_b;
+ struct dc_surface_update sa, sb;
+
+ plane_a = kunit_kzalloc(test, sizeof(*plane_a), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_a);
+ plane_b = kunit_kzalloc(test, sizeof(*plane_b), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_b);
+
+ plane_a->layer_index = 3;
+ plane_b->layer_index = 7;
+ sa.surface = plane_a;
+ sb.surface = plane_b;
+
+ /* b has higher index, so cmp(a,b) = b - a > 0 (b sorts first) */
+ KUNIT_EXPECT_GT(test, dm_plane_layer_index_cmp(&sa, &sb), 0);
+}
+
+/**
+ * dm_test_plane_layer_index_cmp_ascending - Test Plane layer index cmp ascending
+ * @test: The KUnit test context
+ */
+static void dm_test_plane_layer_index_cmp_ascending(struct kunit *test)
+{
+ struct dc_plane_state *plane_a;
+ struct dc_plane_state *plane_b;
+ struct dc_surface_update sa, sb;
+
+ plane_a = kunit_kzalloc(test, sizeof(*plane_a), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_a);
+ plane_b = kunit_kzalloc(test, sizeof(*plane_b), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_b);
+
+ plane_a->layer_index = 9;
+ plane_b->layer_index = 2;
+ sa.surface = plane_a;
+ sb.surface = plane_b;
+
+ /* a has higher index, so cmp(a,b) = b - a < 0 (a sorts first) */
+ KUNIT_EXPECT_LT(test, dm_plane_layer_index_cmp(&sa, &sb), 0);
+}
+
+/* Tests for fill_plane_color_attributes() */
+
+/**
+ * dm_test_fill_color_attr_rgb_format - Test Fill color attr rgb format
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_color_attr_rgb_format(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ enum dc_color_space color_space = COLOR_SPACE_UNKNOWN;
+ int ret;
+
+ /* RGB format: should return 0 and set SRGB regardless of encoding */
+ plane_state.color_encoding = DRM_COLOR_YCBCR_BT709;
+ plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE;
+
+ ret = fill_plane_color_attributes(&plane_state,
+ SURFACE_PIXEL_FORMAT_GRPH_ARGB8888,
+ &color_space);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_SRGB);
+}
+
+/**
+ * dm_test_fill_color_attr_bt601_full - Test Fill color attr bt601 full
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_color_attr_bt601_full(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ enum dc_color_space color_space = COLOR_SPACE_UNKNOWN;
+ int ret;
+
+ plane_state.color_encoding = DRM_COLOR_YCBCR_BT601;
+ plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE;
+
+ ret = fill_plane_color_attributes(&plane_state,
+ SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
+ &color_space);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_YCBCR601);
+}
+
+/**
+ * dm_test_fill_color_attr_bt601_limited - Test Fill color attr bt601 limited
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_color_attr_bt601_limited(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ enum dc_color_space color_space = COLOR_SPACE_UNKNOWN;
+ int ret;
+
+ plane_state.color_encoding = DRM_COLOR_YCBCR_BT601;
+ plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE;
+
+ ret = fill_plane_color_attributes(&plane_state,
+ SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
+ &color_space);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, (int)color_space,
+ (int)COLOR_SPACE_YCBCR601_LIMITED);
+}
+
+/**
+ * dm_test_fill_color_attr_bt709_full - Test Fill color attr bt709 full
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_color_attr_bt709_full(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ enum dc_color_space color_space = COLOR_SPACE_UNKNOWN;
+ int ret;
+
+ plane_state.color_encoding = DRM_COLOR_YCBCR_BT709;
+ plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE;
+
+ ret = fill_plane_color_attributes(&plane_state,
+ SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
+ &color_space);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_YCBCR709);
+}
+
+/**
+ * dm_test_fill_color_attr_bt709_limited - Test Fill color attr bt709 limited
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_color_attr_bt709_limited(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ enum dc_color_space color_space = COLOR_SPACE_UNKNOWN;
+ int ret;
+
+ plane_state.color_encoding = DRM_COLOR_YCBCR_BT709;
+ plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE;
+
+ ret = fill_plane_color_attributes(&plane_state,
+ SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
+ &color_space);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, (int)color_space,
+ (int)COLOR_SPACE_YCBCR709_LIMITED);
+}
+
+/**
+ * dm_test_fill_color_attr_bt2020_full - Test Fill color attr bt2020 full
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_color_attr_bt2020_full(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ enum dc_color_space color_space = COLOR_SPACE_UNKNOWN;
+ int ret;
+
+ plane_state.color_encoding = DRM_COLOR_YCBCR_BT2020;
+ plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE;
+
+ ret = fill_plane_color_attributes(&plane_state,
+ SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
+ &color_space);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, (int)color_space,
+ (int)COLOR_SPACE_2020_YCBCR_FULL);
+}
+
+/**
+ * dm_test_fill_color_attr_bt2020_limited - Test Fill color attr bt2020 limited
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_color_attr_bt2020_limited(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ enum dc_color_space color_space = COLOR_SPACE_UNKNOWN;
+ int ret;
+
+ plane_state.color_encoding = DRM_COLOR_YCBCR_BT2020;
+ plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE;
+
+ ret = fill_plane_color_attributes(&plane_state,
+ SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
+ &color_space);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, (int)color_space,
+ (int)COLOR_SPACE_2020_YCBCR_LIMITED);
+}
+
+/**
+ * dm_test_fill_color_attr_invalid_encoding - Test Fill color attr invalid encoding
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_color_attr_invalid_encoding(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ enum dc_color_space color_space = COLOR_SPACE_UNKNOWN;
+ int ret;
+
+ plane_state.color_encoding = 99;
+ plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE;
+
+ ret = fill_plane_color_attributes(&plane_state,
+ SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
+ &color_space);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+/* Tests for modereset_required() */
+
+/**
+ * dm_test_modereset_required_when_inactive_and_modeset - Test Modereset required when inactive and modeset
+ * @test: The KUnit test context
+ */
+static void dm_test_modereset_required_when_inactive_and_modeset(struct kunit *test)
+{
+ struct drm_crtc_state crtc_state = { 0 };
+
+ crtc_state.active = false;
+ crtc_state.mode_changed = true;
+
+ KUNIT_EXPECT_TRUE(test, modereset_required(&crtc_state));
+}
+
+/**
+ * dm_test_modereset_not_required_when_active_and_modeset - Test Modereset not required when active and modeset
+ * @test: The KUnit test context
+ */
+static void dm_test_modereset_not_required_when_active_and_modeset(struct kunit *test)
+{
+ struct drm_crtc_state crtc_state = { 0 };
+
+ crtc_state.active = true;
+ crtc_state.mode_changed = true;
+
+ KUNIT_EXPECT_FALSE(test, modereset_required(&crtc_state));
+}
+
+/**
+ * dm_test_modereset_not_required_when_inactive_without_modeset - Test Modereset not required when inactive without modeset
+ * @test: The KUnit test context
+ */
+static void dm_test_modereset_not_required_when_inactive_without_modeset(struct kunit *test)
+{
+ struct drm_crtc_state crtc_state = { 0 };
+
+ crtc_state.active = false;
+ crtc_state.mode_changed = false;
+
+ KUNIT_EXPECT_FALSE(test, modereset_required(&crtc_state));
+}
+
+/* Tests for dm_get_oriented_plane_size() */
+
+/**
+ * dm_test_oriented_plane_size_rotate_0 - Test Oriented plane size rotate 0
+ * @test: The KUnit test context
+ */
+static void dm_test_oriented_plane_size_rotate_0(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int src_w = 0;
+ int src_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_0;
+ plane_state.src_w = 1920 << 16;
+ plane_state.src_h = 1080 << 16;
+
+ dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
+
+ KUNIT_EXPECT_EQ(test, src_w, 1920);
+ KUNIT_EXPECT_EQ(test, src_h, 1080);
+}
+
+/**
+ * dm_test_oriented_plane_size_rotate_90 - Test Oriented plane size rotate 90
+ * @test: The KUnit test context
+ */
+static void dm_test_oriented_plane_size_rotate_90(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int src_w = 0;
+ int src_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_90;
+ plane_state.src_w = 1920 << 16;
+ plane_state.src_h = 1080 << 16;
+
+ dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
+
+ KUNIT_EXPECT_EQ(test, src_w, 1080);
+ KUNIT_EXPECT_EQ(test, src_h, 1920);
+}
+
+/**
+ * dm_test_oriented_plane_size_rotate_180 - Test Oriented plane size rotate 180
+ * @test: The KUnit test context
+ */
+static void dm_test_oriented_plane_size_rotate_180(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int src_w = 0;
+ int src_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_180;
+ plane_state.src_w = 1920 << 16;
+ plane_state.src_h = 1080 << 16;
+
+ dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
+
+ KUNIT_EXPECT_EQ(test, src_w, 1920);
+ KUNIT_EXPECT_EQ(test, src_h, 1080);
+}
+
+/**
+ * dm_test_oriented_plane_size_rotate_270 - Test Oriented plane size rotate 270
+ * @test: The KUnit test context
+ */
+static void dm_test_oriented_plane_size_rotate_270(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int src_w = 0;
+ int src_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_270;
+ plane_state.src_w = 1920 << 16;
+ plane_state.src_h = 1080 << 16;
+
+ dm_get_oriented_plane_size(&plane_state, &src_w, &src_h);
+
+ KUNIT_EXPECT_EQ(test, src_w, 1080);
+ KUNIT_EXPECT_EQ(test, src_h, 1920);
+}
+
+/* Tests for dm_get_plane_scale() */
+
+/**
+ * dm_test_get_plane_scale_identity - Test Get plane scale identity
+ * @test: The KUnit test context
+ */
+static void dm_test_get_plane_scale_identity(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int scale_w = 0;
+ int scale_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_0;
+ plane_state.src_w = 1920 << 16;
+ plane_state.src_h = 1080 << 16;
+ plane_state.crtc_w = 1920;
+ plane_state.crtc_h = 1080;
+
+ dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
+
+ KUNIT_EXPECT_EQ(test, scale_w, 1000);
+ KUNIT_EXPECT_EQ(test, scale_h, 1000);
+}
+
+/**
+ * dm_test_get_plane_scale_rotate_90_identity - Test Get plane scale rotate 90 identity
+ * @test: The KUnit test context
+ */
+static void dm_test_get_plane_scale_rotate_90_identity(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int scale_w = 0;
+ int scale_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_90;
+ plane_state.src_w = 1920 << 16;
+ plane_state.src_h = 1080 << 16;
+ plane_state.crtc_w = 1080;
+ plane_state.crtc_h = 1920;
+
+ dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
+
+ KUNIT_EXPECT_EQ(test, scale_w, 1000);
+ KUNIT_EXPECT_EQ(test, scale_h, 1000);
+}
+
+/**
+ * dm_test_get_plane_scale_zero_src_width - Test Get plane scale zero src width
+ * @test: The KUnit test context
+ */
+static void dm_test_get_plane_scale_zero_src_width(struct kunit *test)
+{
+ struct drm_plane_state plane_state = { 0 };
+ int scale_w = 0;
+ int scale_h = 0;
+
+ plane_state.rotation = DRM_MODE_ROTATE_0;
+ plane_state.src_w = 0;
+ plane_state.src_h = 1080 << 16;
+ plane_state.crtc_w = 100;
+ plane_state.crtc_h = 200;
+
+ dm_get_plane_scale(&plane_state, &scale_w, &scale_h);
+
+ KUNIT_EXPECT_EQ(test, scale_w, 0);
+ KUNIT_EXPECT_EQ(test, scale_h, 185);
+}
+
+/* Tests for is_scaling_state_different() */
+
+/**
+ * dm_test_scaling_state_same - Test identical scaling states compare equal
+ * @test: The KUnit test context
+ */
+static void dm_test_scaling_state_same(struct kunit *test)
+{
+ struct dm_connector_state a = { 0 };
+ struct dm_connector_state b = { 0 };
+
+ a.scaling = RMX_FULL;
+ a.underscan_enable = false;
+ b = a;
+
+ KUNIT_EXPECT_FALSE(test, is_scaling_state_different(&a, &b));
+}
+
+/**
+ * dm_test_scaling_state_scaling_changed - Test differing scaling mode is detected
+ * @test: The KUnit test context
+ */
+static void dm_test_scaling_state_scaling_changed(struct kunit *test)
+{
+ struct dm_connector_state a = { 0 };
+ struct dm_connector_state b = { 0 };
+
+ a.scaling = RMX_FULL;
+ b.scaling = RMX_CENTER;
+
+ KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&a, &b));
+}
+
+/**
+ * dm_test_scaling_state_underscan_enabled - Test enabling underscan with borders differs
+ * @test: The KUnit test context
+ */
+static void dm_test_scaling_state_underscan_enabled(struct kunit *test)
+{
+ struct dm_connector_state old_state = { 0 };
+ struct dm_connector_state new_state = { 0 };
+
+ /* new enables underscan with non-zero borders, old has it disabled */
+ new_state.underscan_enable = true;
+ new_state.underscan_hborder = 16;
+ new_state.underscan_vborder = 16;
+ old_state.underscan_enable = false;
+
+ KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&new_state, &old_state));
+}
+
+/**
+ * dm_test_scaling_state_underscan_border_changed - Test changed underscan borders differ
+ * @test: The KUnit test context
+ */
+static void dm_test_scaling_state_underscan_border_changed(struct kunit *test)
+{
+ struct dm_connector_state a = { 0 };
+ struct dm_connector_state b = { 0 };
+
+ a.underscan_enable = true;
+ a.underscan_hborder = 16;
+ a.underscan_vborder = 16;
+ b = a;
+ b.underscan_hborder = 32;
+
+ KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&a, &b));
+}
+
+/* Tests for is_timing_unchanged_for_freesync() */
+
+/**
+ * dm_test_timing_unchanged_null_args - Test NULL crtc states return false
+ * @test: The KUnit test context
+ */
+static void dm_test_timing_unchanged_null_args(struct kunit *test)
+{
+ struct drm_crtc_state crtc_state = { 0 };
+
+ KUNIT_EXPECT_FALSE(test,
+ is_timing_unchanged_for_freesync(NULL, &crtc_state));
+ KUNIT_EXPECT_FALSE(test,
+ is_timing_unchanged_for_freesync(&crtc_state, NULL));
+}
+
+/**
+ * dm_test_timing_unchanged_identical_modes - Test identical modes are not "unchanged"
+ * @test: The KUnit test context
+ *
+ * The helper only returns true when vtotal/vsync shift (vrr) while the rest
+ * of the timing stays fixed, so identical modes must return false.
+ */
+static void dm_test_timing_unchanged_identical_modes(struct kunit *test)
+{
+ struct drm_crtc_state old_state = { 0 };
+ struct drm_crtc_state new_state = { 0 };
+
+ old_state.mode.clock = 148500;
+ old_state.mode.hdisplay = 1920;
+ old_state.mode.vdisplay = 1080;
+ old_state.mode.htotal = 2200;
+ old_state.mode.vtotal = 1125;
+ new_state.mode = old_state.mode;
+
+ KUNIT_EXPECT_FALSE(test,
+ is_timing_unchanged_for_freesync(&old_state, &new_state));
+}
+
+/**
+ * dm_test_timing_unchanged_vrr_shift - Test vrr-style vtotal/vsync shift is detected
+ * @test: The KUnit test context
+ */
+static void dm_test_timing_unchanged_vrr_shift(struct kunit *test)
+{
+ struct drm_crtc_state old_state = { 0 };
+ struct drm_crtc_state new_state = { 0 };
+
+ old_state.mode.clock = 148500;
+ old_state.mode.hdisplay = 1920;
+ old_state.mode.vdisplay = 1080;
+ old_state.mode.htotal = 2200;
+ old_state.mode.vtotal = 1125;
+ old_state.mode.hsync_start = 2008;
+ old_state.mode.vsync_start = 1084;
+ old_state.mode.hsync_end = 2052;
+ old_state.mode.vsync_end = 1089;
+
+ /* Same horizontal timing, vertical totals/sync shifted by 125 lines */
+ new_state.mode = old_state.mode;
+ new_state.mode.vtotal = 1250;
+ new_state.mode.vsync_start = 1209;
+ new_state.mode.vsync_end = 1214;
+
+ KUNIT_EXPECT_TRUE(test,
+ is_timing_unchanged_for_freesync(&old_state, &new_state));
+}
+
+/**
+ * dm_test_timing_unchanged_clock_changed - Test pixel clock change returns false
+ * @test: The KUnit test context
+ */
+static void dm_test_timing_unchanged_clock_changed(struct kunit *test)
+{
+ struct drm_crtc_state old_state = { 0 };
+ struct drm_crtc_state new_state = { 0 };
+
+ old_state.mode.clock = 148500;
+ old_state.mode.htotal = 2200;
+ old_state.mode.vtotal = 1125;
+ old_state.mode.vsync_start = 1084;
+ old_state.mode.vsync_end = 1089;
+
+ new_state.mode = old_state.mode;
+ new_state.mode.clock = 297000;
+ new_state.mode.vtotal = 1250;
+ new_state.mode.vsync_start = 1209;
+ new_state.mode.vsync_end = 1214;
+
+ KUNIT_EXPECT_FALSE(test,
+ is_timing_unchanged_for_freesync(&old_state, &new_state));
+}
+
+/* Tests for set_freesync_fixed_config() */
+
+/**
+ * dm_test_set_freesync_fixed_config_60hz - Test fixed refresh computed for 1080p60
+ * @test: The KUnit test context
+ */
+static void dm_test_set_freesync_fixed_config_60hz(struct kunit *test)
+{
+ struct dm_crtc_state dm_crtc_state = { 0 };
+
+ dm_crtc_state.base.mode.clock = 148500;
+ dm_crtc_state.base.mode.htotal = 2200;
+ dm_crtc_state.base.mode.vtotal = 1125;
+
+ set_freesync_fixed_config(&dm_crtc_state);
+
+ KUNIT_EXPECT_EQ(test, (int)dm_crtc_state.freesync_config.state,
+ (int)VRR_STATE_ACTIVE_FIXED);
+ /* 148500 kHz / (2200 * 1125) = 60 Hz = 60000000 uHz */
+ KUNIT_EXPECT_EQ(test, dm_crtc_state.freesync_config.fixed_refresh_in_uhz,
+ 60000000U);
+}
+
+/* Tests for is_dc_timing_adjust_needed() */
+
+/**
+ * dm_test_dc_timing_adjust_pending - Test a pending hw timing adjust forces true
+ * @test: The KUnit test context
+ */
+static void dm_test_dc_timing_adjust_pending(struct kunit *test)
+{
+ struct dm_crtc_state *old_state, *new_state;
+ struct dc_stream_state *stream;
+
+ old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
+ new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+
+ new_state->stream = stream;
+ stream->adjust.timing_adjust_pending = 1;
+
+ KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state));
+}
+
+/**
+ * dm_test_dc_timing_adjust_active_fixed - Test VRR active-fixed forces true
+ * @test: The KUnit test context
+ */
+static void dm_test_dc_timing_adjust_active_fixed(struct kunit *test)
+{
+ struct dm_crtc_state *old_state, *new_state;
+ struct dc_stream_state *stream;
+
+ old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
+ new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+
+ new_state->stream = stream;
+ new_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED;
+
+ KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state));
+}
+
+/**
+ * dm_test_dc_timing_adjust_vrr_toggle - Test a change in vrr active state forces true
+ * @test: The KUnit test context
+ */
+static void dm_test_dc_timing_adjust_vrr_toggle(struct kunit *test)
+{
+ struct dm_crtc_state *old_state, *new_state;
+ struct dc_stream_state *stream;
+
+ old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
+ new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+
+ new_state->stream = stream;
+ old_state->freesync_config.state = VRR_STATE_ACTIVE_VARIABLE;
+ new_state->freesync_config.state = VRR_STATE_INACTIVE;
+
+ KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state));
+}
+
+/**
+ * dm_test_dc_timing_adjust_not_needed - Test steady-state timing needs no adjust
+ * @test: The KUnit test context
+ */
+static void dm_test_dc_timing_adjust_not_needed(struct kunit *test)
+{
+ struct dm_crtc_state *old_state, *new_state;
+ struct dc_stream_state *stream;
+
+ old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state);
+ new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+
+ new_state->stream = stream;
+ old_state->freesync_config.state = VRR_STATE_INACTIVE;
+ new_state->freesync_config.state = VRR_STATE_INACTIVE;
+
+ KUNIT_EXPECT_FALSE(test, is_dc_timing_adjust_needed(old_state, new_state));
+}
+
+/* Tests for set_multisync_trigger_params() */
+
+/**
+ * dm_test_multisync_trigger_disabled - Test disabled reset leaves params untouched
+ * @test: The KUnit test context
+ */
+static void dm_test_multisync_trigger_disabled(struct kunit *test)
+{
+ struct dc_stream_state *stream;
+
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+
+ stream->triggered_crtc_reset.enabled = false;
+ stream->triggered_crtc_reset.event = CRTC_EVENT_VSYNC_FALLING;
+ stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_LINE;
+
+ set_multisync_trigger_params(stream);
+
+ /* Nothing should change when the reset trigger is disabled */
+ KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.event,
+ (int)CRTC_EVENT_VSYNC_FALLING);
+ KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.delay,
+ (int)TRIGGER_DELAY_NEXT_LINE);
+}
+
+/**
+ * dm_test_multisync_trigger_rising - Test positive vsync polarity selects rising edge
+ * @test: The KUnit test context
+ */
+static void dm_test_multisync_trigger_rising(struct kunit *test)
+{
+ struct dc_stream_state *stream;
+ struct dc_stream_state *master;
+
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+ master = kunit_kzalloc(test, sizeof(*master), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, master);
+
+ master->timing.flags.VSYNC_POSITIVE_POLARITY = 1;
+ stream->triggered_crtc_reset.enabled = true;
+ stream->triggered_crtc_reset.event_source = master;
+
+ set_multisync_trigger_params(stream);
+
+ KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.event,
+ (int)CRTC_EVENT_VSYNC_RISING);
+ KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.delay,
+ (int)TRIGGER_DELAY_NEXT_PIXEL);
+}
+
+/**
+ * dm_test_multisync_trigger_falling - Test negative vsync polarity selects falling edge
+ * @test: The KUnit test context
+ */
+static void dm_test_multisync_trigger_falling(struct kunit *test)
+{
+ struct dc_stream_state *stream;
+ struct dc_stream_state *master;
+
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+ master = kunit_kzalloc(test, sizeof(*master), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, master);
+
+ master->timing.flags.VSYNC_POSITIVE_POLARITY = 0;
+ stream->triggered_crtc_reset.enabled = true;
+ stream->triggered_crtc_reset.event_source = master;
+
+ set_multisync_trigger_params(stream);
+
+ KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.event,
+ (int)CRTC_EVENT_VSYNC_FALLING);
+ KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.delay,
+ (int)TRIGGER_DELAY_NEXT_PIXEL);
+}
+
+/* Tests for set_master_stream() */
+
+/**
+ * dm_test_master_stream_highest_refresh - Test highest refresh-rate stream becomes master
+ * @test: The KUnit test context
+ */
+static void dm_test_master_stream_highest_refresh(struct kunit *test)
+{
+ struct dc_stream_state *stream0, *stream1;
+ struct dc_stream_state *stream_set[2];
+
+ stream0 = kunit_kzalloc(test, sizeof(*stream0), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream0);
+ stream1 = kunit_kzalloc(test, sizeof(*stream1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream1);
+ stream_set[0] = stream0;
+ stream_set[1] = stream1;
+
+ /* stream0: 60Hz, stream1: 120Hz -> stream1 is master */
+ stream0->triggered_crtc_reset.enabled = true;
+ stream0->timing.pix_clk_100hz = 1485000;
+ stream0->timing.h_total = 2200;
+ stream0->timing.v_total = 1125;
+
+ stream1->triggered_crtc_reset.enabled = true;
+ stream1->timing.pix_clk_100hz = 2970000;
+ stream1->timing.h_total = 2200;
+ stream1->timing.v_total = 1125;
+
+ set_master_stream(stream_set, 2);
+
+ KUNIT_EXPECT_PTR_EQ(test, stream0->triggered_crtc_reset.event_source,
+ stream1);
+ KUNIT_EXPECT_PTR_EQ(test, stream1->triggered_crtc_reset.event_source,
+ stream1);
+}
+
+/**
+ * dm_test_master_stream_defaults_to_first - Test default master when none triggered
+ * @test: The KUnit test context
+ *
+ * When no stream has the reset trigger enabled, master_stream stays 0 and all
+ * streams point at the first stream as their event source.
+ */
+static void dm_test_master_stream_defaults_to_first(struct kunit *test)
+{
+ struct dc_stream_state *stream0, *stream1;
+ struct dc_stream_state *stream_set[2];
+
+ stream0 = kunit_kzalloc(test, sizeof(*stream0), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream0);
+ stream1 = kunit_kzalloc(test, sizeof(*stream1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream1);
+ stream_set[0] = stream0;
+ stream_set[1] = stream1;
+
+ set_master_stream(stream_set, 2);
+
+ KUNIT_EXPECT_PTR_EQ(test, stream0->triggered_crtc_reset.event_source,
+ stream0);
+ KUNIT_EXPECT_PTR_EQ(test, stream1->triggered_crtc_reset.event_source,
+ stream0);
+}
+
+static struct kunit_case amdgpu_dm_tests[] = {
+ /* dm_plane_layer_index_cmp */
+ KUNIT_CASE(dm_test_plane_layer_index_cmp_equal),
+ KUNIT_CASE(dm_test_plane_layer_index_cmp_descending),
+ KUNIT_CASE(dm_test_plane_layer_index_cmp_ascending),
+ /* fill_plane_color_attributes */
+ KUNIT_CASE(dm_test_fill_color_attr_rgb_format),
+ KUNIT_CASE(dm_test_fill_color_attr_bt601_full),
+ KUNIT_CASE(dm_test_fill_color_attr_bt601_limited),
+ KUNIT_CASE(dm_test_fill_color_attr_bt709_full),
+ KUNIT_CASE(dm_test_fill_color_attr_bt709_limited),
+ KUNIT_CASE(dm_test_fill_color_attr_bt2020_full),
+ KUNIT_CASE(dm_test_fill_color_attr_bt2020_limited),
+ KUNIT_CASE(dm_test_fill_color_attr_invalid_encoding),
+ /* modereset_required */
+ KUNIT_CASE(dm_test_modereset_required_when_inactive_and_modeset),
+ KUNIT_CASE(dm_test_modereset_not_required_when_active_and_modeset),
+ KUNIT_CASE(dm_test_modereset_not_required_when_inactive_without_modeset),
+ /* dm_get_oriented_plane_size */
+ KUNIT_CASE(dm_test_oriented_plane_size_rotate_0),
+ KUNIT_CASE(dm_test_oriented_plane_size_rotate_90),
+ KUNIT_CASE(dm_test_oriented_plane_size_rotate_180),
+ KUNIT_CASE(dm_test_oriented_plane_size_rotate_270),
+ /* dm_get_plane_scale */
+ KUNIT_CASE(dm_test_get_plane_scale_identity),
+ KUNIT_CASE(dm_test_get_plane_scale_rotate_90_identity),
+ KUNIT_CASE(dm_test_get_plane_scale_zero_src_width),
+ /* is_scaling_state_different */
+ KUNIT_CASE(dm_test_scaling_state_same),
+ KUNIT_CASE(dm_test_scaling_state_scaling_changed),
+ KUNIT_CASE(dm_test_scaling_state_underscan_enabled),
+ KUNIT_CASE(dm_test_scaling_state_underscan_border_changed),
+ /* is_timing_unchanged_for_freesync */
+ KUNIT_CASE(dm_test_timing_unchanged_null_args),
+ KUNIT_CASE(dm_test_timing_unchanged_identical_modes),
+ KUNIT_CASE(dm_test_timing_unchanged_vrr_shift),
+ KUNIT_CASE(dm_test_timing_unchanged_clock_changed),
+ /* set_freesync_fixed_config */
+ KUNIT_CASE(dm_test_set_freesync_fixed_config_60hz),
+ /* is_dc_timing_adjust_needed */
+ KUNIT_CASE(dm_test_dc_timing_adjust_pending),
+ KUNIT_CASE(dm_test_dc_timing_adjust_active_fixed),
+ KUNIT_CASE(dm_test_dc_timing_adjust_vrr_toggle),
+ KUNIT_CASE(dm_test_dc_timing_adjust_not_needed),
+ /* set_multisync_trigger_params */
+ KUNIT_CASE(dm_test_multisync_trigger_disabled),
+ KUNIT_CASE(dm_test_multisync_trigger_rising),
+ KUNIT_CASE(dm_test_multisync_trigger_falling),
+ /* set_master_stream */
+ KUNIT_CASE(dm_test_master_stream_highest_refresh),
+ KUNIT_CASE(dm_test_master_stream_defaults_to_first),
+ {}
+};
+
+static struct kunit_suite amdgpu_dm_test_suite = {
+ .name = "amdgpu_dm",
+ .test_cases = amdgpu_dm_tests,
+};
+
+kunit_test_suite(amdgpu_dm_test_suite);
+
+MODULE_AUTHOR("AMD");
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm");
+MODULE_LICENSE("Dual MIT/GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 18/32] drm/amd/display: Add KUnit tests for amdgpu_dm_backlight
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (16 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 17/32] drm/amd/display: Add KUnit tests for amdgpu_dm Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 19/32] drm/amd/display: Add KUnit tests for amdgpu_dm_audio Chenyu Chen
` (14 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Add KUnit tests for the backlight helpers in amdgpu_dm_backlight.c.
Tests cover:
- amdgpu_dm_update_backlight_caps(): short-circuit on populated caps
and default value assignment
- get_brightness_range(): NULL, PWM-only, and AUX backlight paths
- convert_brightness_to_user(): minimum clamp, maximum passthrough,
and mid-range rescaling
- convert_brightness_from_user(): linear rescaling, AUX path, and
custom-curve mapping
- convert_custom_brightness(): exact match, below-first, interpolation,
above-last, single data point, zero lower luminance, and the
debug-mask and no-data-point guards
- amdgpu_dm_update_connector_ext_caps(): negative bl_idx and non-eDP
early returns, OLED defaults, luminance range copy, and the
amdgpu_backlight force-AUX/force-PWM overrides
- amdgpu_dm_should_create_sysfs(): forced ABM, non-eDP, missing
backlight index, and AUX vs PWM backlight
- amdgpu_dm_setup_backlight_device(): non-eDP/LVDS skip, disconnected
link skip, eDP-count limit, and the successful eDP setup path
Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../display/amdgpu_dm/amdgpu_dm_backlight.c | 71 +-
.../display/amdgpu_dm/amdgpu_dm_backlight.h | 18 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 2 +
.../tests/amdgpu_dm_backlight_test.c | 1128 +++++++++++++++++
4 files changed, 1210 insertions(+), 9 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
index 3770e8dafdbf..f101aed75bb3 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
@@ -47,6 +47,7 @@
#include "amdgpu_dm_trace.h"
#include "amd_shared.h"
+#include "amdgpu_dm_kunit_helpers.h"
#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12
#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255
@@ -92,9 +93,11 @@ void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm,
caps->caps_valid = true;
#endif
}
+EXPORT_IF_KUNIT(amdgpu_dm_update_backlight_caps);
-static int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,
- unsigned int *min, unsigned int *max)
+STATIC_IFN_KUNIT
+int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,
+ unsigned int *min, unsigned int *max)
{
if (!caps)
return 0;
@@ -110,6 +113,7 @@ static int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,
}
return 1;
}
+EXPORT_IF_KUNIT(get_brightness_range);
/* Rescale from [min..max] to [0..AMDGPU_MAX_BL_LEVEL] */
static inline u32 scale_input_to_fw(int min, int max, u64 input)
@@ -123,9 +127,10 @@ static inline u32 scale_fw_to_input(int min, int max, u64 input)
return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL);
}
-static void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
- unsigned int min, unsigned int max,
- uint32_t *user_brightness)
+STATIC_IFN_KUNIT
+void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
+ unsigned int min, unsigned int max,
+ uint32_t *user_brightness)
{
u32 brightness = scale_input_to_fw(min, max, *user_brightness);
u8 lower_signal, upper_signal, upper_lum, lower_lum, lum;
@@ -187,8 +192,11 @@ static void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *cap
DIV_ROUND_CLOSEST(lum * brightness, 101));
}
-static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
- uint32_t brightness)
+EXPORT_IF_KUNIT(convert_custom_brightness);
+
+STATIC_IFN_KUNIT
+u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
+ uint32_t brightness)
{
unsigned int min, max;
@@ -201,8 +209,11 @@ static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *c
return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness, max);
}
-static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,
- uint32_t brightness)
+EXPORT_IF_KUNIT(convert_brightness_from_user);
+
+STATIC_IFN_KUNIT
+u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,
+ uint32_t brightness)
{
unsigned int min, max;
@@ -215,6 +226,7 @@ static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *cap
return DIV_ROUND_CLOSEST_ULL((u64)max * (brightness - min),
max - min);
}
+EXPORT_IF_KUNIT(convert_brightness_to_user);
static struct dc_stream_state *dm_find_stream_with_link(
struct amdgpu_display_manager *dm,
@@ -529,6 +541,7 @@ void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector)
}
}
}
+EXPORT_IF_KUNIT(amdgpu_dm_update_connector_ext_caps);
void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm,
struct amdgpu_dm_connector *aconnector)
@@ -561,6 +574,7 @@ void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm,
dm->adev->mode_info.abm_level_property,
ABM_SYSFS_CONTROL);
}
+EXPORT_IF_KUNIT(amdgpu_dm_setup_backlight_device);
/**
* DOC: panel power savings
@@ -658,3 +672,42 @@ amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *amdgpu_dm_connector)
return true;
}
+EXPORT_IF_KUNIT(amdgpu_dm_should_create_sysfs);
+
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+uint amdgpu_dm_get_dc_debug_mask(void)
+{
+ return amdgpu_dc_debug_mask;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_get_dc_debug_mask);
+
+void amdgpu_dm_set_dc_debug_mask(uint val)
+{
+ amdgpu_dc_debug_mask = val;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_set_dc_debug_mask);
+
+int amdgpu_dm_get_abm_level_param(void)
+{
+ return amdgpu_dm_abm_level;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_get_abm_level_param);
+
+void amdgpu_dm_set_abm_level_param(int val)
+{
+ amdgpu_dm_abm_level = val;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_set_abm_level_param);
+
+int amdgpu_dm_get_backlight_param(void)
+{
+ return amdgpu_backlight;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_get_backlight_param);
+
+void amdgpu_dm_set_backlight_param(int val)
+{
+ amdgpu_backlight = val;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_set_backlight_param);
+#endif
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
index acff23f9feef..5234da6ae484 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
@@ -41,4 +41,22 @@ bool amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *aconnector);
extern const struct attribute_group amdgpu_group;
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,
+ unsigned int *min, unsigned int *max);
+void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
+ unsigned int min, unsigned int max,
+ uint32_t *user_brightness);
+u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
+ uint32_t brightness);
+u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,
+ uint32_t brightness);
+uint amdgpu_dm_get_dc_debug_mask(void);
+void amdgpu_dm_set_dc_debug_mask(uint val);
+int amdgpu_dm_get_abm_level_param(void);
+void amdgpu_dm_set_abm_level_param(int val);
+int amdgpu_dm_get_backlight_param(void);
+void amdgpu_dm_set_backlight_param(int val);
+#endif
+
#endif /* __AMDGPU_DM_BACKLIGHT_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index 4365d4024f70..ddd9fce66232 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -8,12 +8,14 @@ ccflags-y += -I$(src)/../../include
ccflags-y += -I$(src)/../../modules/inc
ccflags-y += -I$(src)/../../dc
ccflags-y += -I$(src)/../../../amdgpu
+ccflags-y += -I$(src)/../../../amdkfd
ccflags-y += -I$(src)/../../../include
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_backlight_test.o
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_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
new file mode 100644
index 000000000000..2f4293cfd478
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
@@ -0,0 +1,1128 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_backlight.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+
+#include "dc.h"
+#include "amdgpu.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_backlight.h"
+#include "amd_shared.h"
+
+struct dm_backlight_connector_fixture {
+ struct amdgpu_device *adev;
+ struct amdgpu_dm_connector *aconnector;
+ struct dc_link *link;
+};
+
+static struct amdgpu_display_manager *alloc_test_dm(struct kunit *test)
+{
+ struct amdgpu_display_manager *dm;
+
+ dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, dm);
+
+ return dm;
+}
+
+static void setup_test_connector(struct kunit *test,
+ struct dm_backlight_connector_fixture *fixture,
+ int bl_idx, enum signal_type signal)
+{
+ fixture->adev = kunit_kzalloc(test, sizeof(*fixture->adev), GFP_KERNEL);
+ fixture->aconnector = kunit_kzalloc(test, sizeof(*fixture->aconnector), GFP_KERNEL);
+ fixture->link = kunit_kzalloc(test, sizeof(*fixture->link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->aconnector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->link);
+
+ fixture->aconnector->bl_idx = bl_idx;
+ fixture->aconnector->dc_link = fixture->link;
+ fixture->aconnector->base.dev = &fixture->adev->ddev;
+ fixture->link->connector_signal = signal;
+}
+
+/* Tests for amdgpu_dm_update_backlight_caps() */
+
+/**
+ * dm_test_backlight_caps_valid_short_circuit - Test Backlight caps valid short circuit
+ * @test: The KUnit test context
+ */
+static void dm_test_backlight_caps_valid_short_circuit(struct kunit *test)
+{
+ struct amdgpu_display_manager *dm = alloc_test_dm(test);
+ struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0];
+
+ caps->caps_valid = true;
+ caps->aux_support = false;
+ caps->min_input_signal = 42;
+ caps->max_input_signal = 199;
+
+ amdgpu_dm_update_backlight_caps(dm, 0);
+
+ KUNIT_EXPECT_TRUE(test, caps->caps_valid);
+ KUNIT_EXPECT_EQ(test, caps->min_input_signal, 42);
+ KUNIT_EXPECT_EQ(test, caps->max_input_signal, 199);
+}
+
+#if !defined(CONFIG_ACPI)
+
+/**
+ * dm_test_backlight_caps_aux_support_noop - Test Backlight caps aux support noop
+ * @test: The KUnit test context
+ */
+static void dm_test_backlight_caps_aux_support_noop(struct kunit *test)
+{
+ struct amdgpu_display_manager *dm = alloc_test_dm(test);
+ struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0];
+
+ caps->caps_valid = false;
+ caps->aux_support = true;
+ caps->min_input_signal = 11;
+ caps->max_input_signal = 222;
+
+ amdgpu_dm_update_backlight_caps(dm, 0);
+
+ KUNIT_EXPECT_FALSE(test, caps->caps_valid);
+ KUNIT_EXPECT_EQ(test, caps->min_input_signal, 11);
+ KUNIT_EXPECT_EQ(test, caps->max_input_signal, 222);
+}
+
+/**
+ * dm_test_backlight_caps_non_aux_sets_defaults - Test Backlight caps non aux sets defaults
+ * @test: The KUnit test context
+ */
+static void dm_test_backlight_caps_non_aux_sets_defaults(struct kunit *test)
+{
+ struct amdgpu_display_manager *dm = alloc_test_dm(test);
+ struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0];
+
+ caps->caps_valid = false;
+ caps->aux_support = false;
+ caps->min_input_signal = 0;
+ caps->max_input_signal = 0;
+
+ amdgpu_dm_update_backlight_caps(dm, 0);
+
+ KUNIT_EXPECT_TRUE(test, caps->caps_valid);
+ KUNIT_EXPECT_EQ(test, caps->min_input_signal, 12);
+ KUNIT_EXPECT_EQ(test, caps->max_input_signal, 255);
+}
+#endif
+
+/* Tests for get_brightness_range() */
+
+/**
+ * dm_test_brightness_range_null_caps - Test Brightness range null caps
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_range_null_caps(struct kunit *test)
+{
+ unsigned int min = 99, max = 99;
+
+ KUNIT_EXPECT_EQ(test, get_brightness_range(NULL, &min, &max), 0);
+ /* min/max should remain untouched */
+ KUNIT_EXPECT_EQ(test, min, 99U);
+ KUNIT_EXPECT_EQ(test, max, 99U);
+}
+
+/**
+ * dm_test_brightness_range_pwm - Test Brightness range pwm
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_range_pwm(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max;
+
+ caps.aux_support = false;
+ caps.min_input_signal = 12;
+ caps.max_input_signal = 255;
+
+ KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1);
+ /* 0x101 * 12 = 3084, 0x101 * 255 = 65535 */
+ KUNIT_EXPECT_EQ(test, min, 0x101U * 12);
+ KUNIT_EXPECT_EQ(test, max, 0x101U * 255);
+}
+
+/**
+ * dm_test_brightness_range_aux - Test Brightness range aux
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_range_aux(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max;
+
+ caps.aux_support = true;
+ caps.aux_min_input_signal = 1;
+ caps.aux_max_input_signal = 512;
+
+ KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1);
+ /* millinits: 1000 * value */
+ KUNIT_EXPECT_EQ(test, min, 1000U);
+ KUNIT_EXPECT_EQ(test, max, 512000U);
+}
+
+/* Tests for convert_brightness_to_user() */
+
+/**
+ * dm_test_brightness_to_user_null_caps - Test Brightness to user null caps
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_to_user_null_caps(struct kunit *test)
+{
+ /*
+ * With NULL caps, get_brightness_range fails → passthrough.
+ * We simulate this by passing a zeroed caps struct where
+ * max_input_signal=0 makes max=0 and the function hits
+ * get_brightness_range returning 0 since caps is NULL.
+ */
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_user(NULL, 42), 42U);
+}
+
+/**
+ * dm_test_brightness_to_user_below_min - Test Brightness to user below min
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_to_user_below_min(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+
+ caps.aux_support = false;
+ caps.min_input_signal = 12;
+ caps.max_input_signal = 255;
+
+ /* brightness < min (0x101*12 = 3084), should return 0 */
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, 100), 0U);
+}
+
+/**
+ * dm_test_brightness_to_user_at_max - Test Brightness to user at max
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_to_user_at_max(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max;
+
+ caps.aux_support = false;
+ caps.min_input_signal = 12;
+ caps.max_input_signal = 255;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /* At max → should return max */
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, max), max);
+}
+
+/**
+ * dm_test_brightness_to_user_at_min - Test Brightness to user at min
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_to_user_at_min(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max;
+
+ caps.aux_support = false;
+ caps.min_input_signal = 12;
+ caps.max_input_signal = 255;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /* At min → should return 0 */
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, min), 0U);
+}
+
+/**
+ * dm_test_brightness_to_user_midpoint_pwm - Test Brightness to user midpoint pwm
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_to_user_midpoint_pwm(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max, mid_hw, result;
+ u64 expected;
+
+ caps.aux_support = false;
+ caps.min_input_signal = 12;
+ caps.max_input_signal = 255;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /* midpoint of hw range */
+ mid_hw = min + (max - min) / 2;
+ /* expected = DIV_ROUND_CLOSEST_ULL((u64)max * (mid_hw - min), max - min) */
+ expected = DIV_ROUND_CLOSEST_ULL((u64)max * (mid_hw - min), max - min);
+ result = convert_brightness_to_user(&caps, mid_hw);
+
+ KUNIT_EXPECT_EQ(test, result, (u32)expected);
+}
+
+/* Tests for convert_brightness_from_user() — no custom curve */
+
+/**
+ * dm_test_brightness_from_user_null_caps - Test Brightness from user null caps
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_from_user_null_caps(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(NULL, 100), 100U);
+}
+
+/**
+ * dm_test_brightness_from_user_zero - Test Brightness from user zero
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_from_user_zero(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max;
+
+ caps.aux_support = false;
+ caps.min_input_signal = 12;
+ caps.max_input_signal = 255;
+ /* no custom curve */
+ caps.data_points = 0;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /* brightness=0 → min + 0 = min */
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)min);
+}
+
+/**
+ * dm_test_brightness_from_user_max - Test Brightness from user max
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_from_user_max(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max;
+
+ caps.aux_support = false;
+ caps.min_input_signal = 12;
+ caps.max_input_signal = 255;
+ caps.data_points = 0;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /*
+ * brightness=max → min + DIV_ROUND_CLOSEST((max-min)*max, max)
+ * = min + (max - min) = max
+ */
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)max);
+}
+
+/**
+ * dm_test_brightness_from_user_aux - Test Brightness from user aux
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_from_user_aux(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max;
+
+ caps.aux_support = true;
+ caps.aux_min_input_signal = 1;
+ caps.aux_max_input_signal = 512;
+ caps.data_points = 0;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /* brightness=0 → min */
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)min);
+ /* brightness=max → max */
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)max);
+}
+
+/* Tests for convert_custom_brightness() */
+
+/**
+ * dm_test_custom_brightness_no_data_points - Test Custom brightness no data points
+ * @test: The KUnit test context
+ */
+static void dm_test_custom_brightness_no_data_points(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint32_t brightness = 128;
+ uint32_t saved = brightness;
+
+ caps.data_points = 0;
+
+ convert_custom_brightness(&caps, 3084, 65535, &brightness);
+
+ /* No data points → no-op */
+ KUNIT_EXPECT_EQ(test, brightness, saved);
+}
+
+/**
+ * dm_test_custom_brightness_debug_mask_disables - Test Custom brightness debug mask disables
+ * @test: The KUnit test context
+ */
+static void dm_test_custom_brightness_debug_mask_disables(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint32_t brightness = 128;
+ uint32_t saved = brightness;
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+
+ caps.data_points = 3;
+ caps.luminance_data[0].input_signal = 50;
+ caps.luminance_data[0].luminance = 10;
+
+ /* Set the disable flag */
+ amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() | DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+
+ convert_custom_brightness(&caps, 3084, 65535, &brightness);
+
+ /* Should be no-op due to debug mask */
+ KUNIT_EXPECT_EQ(test, brightness, saved);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_custom_brightness_exact_match - Test Custom brightness exact match
+ * @test: The KUnit test context
+ */
+static void dm_test_custom_brightness_exact_match(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint32_t brightness;
+ unsigned int min, max;
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+
+ amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+
+ caps.aux_support = false;
+ caps.min_input_signal = 0;
+ caps.max_input_signal = 255;
+ caps.data_points = 3;
+ caps.luminance_data[0].input_signal = 50;
+ caps.luminance_data[0].luminance = 20;
+ caps.luminance_data[1].input_signal = 128;
+ caps.luminance_data[1].luminance = 50;
+ caps.luminance_data[2].input_signal = 200;
+ caps.luminance_data[2].luminance = 90;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /*
+ * Set brightness so that scale_input_to_fw yields exactly 128.
+ * scale_input_to_fw(min, max, x) = DIV_ROUND_CLOSEST(x * 255, max - min)
+ * With min=0, max=0x101*255=65535:
+ * We need x such that DIV_ROUND_CLOSEST(x * 255, 65535) = 128
+ * → x = 128 * 65535 / 255 = 32896
+ */
+ brightness = 32896;
+
+ convert_custom_brightness(&caps, min, max, &brightness);
+
+ /*
+ * Exact match: lum=50, brightness_scaled=128
+ * result = scale_fw_to_input(min, max, DIV_ROUND_CLOSEST(50*128, 101))
+ * = scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(6400, 101))
+ * = scale_fw_to_input(0, 65535, 63)
+ * = 0 + DIV_ROUND_CLOSEST(63 * 65535, 255) = 16191 (approx)
+ */
+ KUNIT_EXPECT_TRUE(test, brightness != 32896);
+ KUNIT_EXPECT_TRUE(test, brightness < 32896);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_custom_brightness_below_first - Test Custom brightness below first
+ * @test: The KUnit test context
+ */
+static void dm_test_custom_brightness_below_first(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint32_t brightness;
+ unsigned int min, max;
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+
+ amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+
+ caps.aux_support = false;
+ caps.min_input_signal = 0;
+ caps.max_input_signal = 255;
+ caps.data_points = 2;
+ caps.luminance_data[0].input_signal = 100;
+ caps.luminance_data[0].luminance = 40;
+ caps.luminance_data[1].input_signal = 200;
+ caps.luminance_data[1].luminance = 80;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /*
+ * Set brightness low enough that scaled value < 100.
+ * scale_input_to_fw(0, 65535, x) = DIV_ROUND_CLOSEST(x*255, 65535)
+ * For result=50: x = 50*65535/255 = 12850
+ */
+ brightness = 12850;
+
+ convert_custom_brightness(&caps, min, max, &brightness);
+
+ /*
+ * Below first data point: lum = DIV_ROUND_CLOSEST(40 * 50, 100) = 20
+ * Then: scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(20 * 50, 101))
+ * = scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(1000, 101))
+ * = scale_fw_to_input(0, 65535, 10)
+ * The output should be significantly less than input.
+ */
+ KUNIT_EXPECT_TRUE(test, brightness < 12850);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_custom_brightness_interpolation - Test Custom brightness interpolation
+ * @test: The KUnit test context
+ */
+static void dm_test_custom_brightness_interpolation(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint32_t brightness;
+ unsigned int min, max;
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+
+ amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+
+ caps.aux_support = false;
+ caps.min_input_signal = 0;
+ caps.max_input_signal = 255;
+ caps.data_points = 2;
+ caps.luminance_data[0].input_signal = 50;
+ caps.luminance_data[0].luminance = 20;
+ caps.luminance_data[1].input_signal = 200;
+ caps.luminance_data[1].luminance = 80;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /*
+ * Choose a value between data points 50 and 200.
+ * scale_input_to_fw(0, 65535, x) = 125 when x = 125*65535/255 = 32125
+ */
+ brightness = 32125;
+
+ convert_custom_brightness(&caps, min, max, &brightness);
+
+ /*
+ * The function should interpolate between data points and produce
+ * a remapped value different from the input.
+ */
+ KUNIT_EXPECT_TRUE(test, brightness != 32125);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_custom_brightness_above_last - Test Custom brightness above last data point
+ * @test: The KUnit test context
+ */
+static void dm_test_custom_brightness_above_last(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint32_t brightness;
+ unsigned int min, max;
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+
+ amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+
+ caps.aux_support = false;
+ caps.min_input_signal = 0;
+ caps.max_input_signal = 255;
+ caps.data_points = 2;
+ caps.luminance_data[0].input_signal = 50;
+ caps.luminance_data[0].luminance = 20;
+ caps.luminance_data[1].input_signal = 150;
+ caps.luminance_data[1].luminance = 60;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /*
+ * Choose brightness above the last data point (150).
+ * scale_input_to_fw(0, 65535, x) = 220 when x = 220*65535/255 = 56533
+ * After binary search, left >= data_points, clamped → right==left,
+ * so lum = upper_lum = 60.
+ */
+ brightness = 56533;
+
+ convert_custom_brightness(&caps, min, max, &brightness);
+
+ /* Output should differ from input (remapped via curve) */
+ KUNIT_EXPECT_TRUE(test, brightness != 56533);
+ KUNIT_EXPECT_TRUE(test, brightness < 56533);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_custom_brightness_single_data_point - Test Custom brightness with single data point
+ * @test: The KUnit test context
+ */
+static void dm_test_custom_brightness_single_data_point(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint32_t brightness;
+ unsigned int min, max;
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+
+ amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+
+ caps.aux_support = false;
+ caps.min_input_signal = 0;
+ caps.max_input_signal = 255;
+ caps.data_points = 1;
+ caps.luminance_data[0].input_signal = 128;
+ caps.luminance_data[0].luminance = 50;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /*
+ * Brightness below the single data point triggers the
+ * "below first" path: lum = DIV_ROUND_CLOSEST(50 * scaled, 128).
+ * scale_input_to_fw(0, 65535, x) = 64 when x = 64*65535/255 = 16448
+ */
+ brightness = 16448;
+
+ convert_custom_brightness(&caps, min, max, &brightness);
+
+ KUNIT_EXPECT_TRUE(test, brightness < 16448);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_custom_brightness_lower_lum_zero - Test Custom brightness with zero lower luminance
+ * @test: The KUnit test context
+ */
+static void dm_test_custom_brightness_lower_lum_zero(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint32_t brightness;
+ unsigned int min, max;
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+
+ amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+
+ caps.aux_support = false;
+ caps.min_input_signal = 0;
+ caps.max_input_signal = 255;
+ caps.data_points = 2;
+ caps.luminance_data[0].input_signal = 50;
+ caps.luminance_data[0].luminance = 0; /* zero lower luminance */
+ caps.luminance_data[1].input_signal = 200;
+ caps.luminance_data[1].luminance = 80;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /*
+ * Choose brightness between data points to trigger interpolation.
+ * scale_input_to_fw(0, 65535, x) = 125 when x = 125*65535/255 = 32125
+ * With lower_lum == 0, code takes shortcut: lum = upper_lum = 80.
+ */
+ brightness = 32125;
+
+ convert_custom_brightness(&caps, min, max, &brightness);
+
+ /* Should remap; result should differ from input */
+ KUNIT_EXPECT_TRUE(test, brightness != 32125);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_brightness_to_user_above_max - Test Brightness to user above max
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_to_user_above_max(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max, result;
+
+ caps.aux_support = false;
+ caps.min_input_signal = 12;
+ caps.max_input_signal = 255;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /* brightness above max → result > max (linear extrapolation) */
+ result = convert_brightness_to_user(&caps, max + 1000);
+
+ KUNIT_EXPECT_GT(test, result, max);
+}
+
+/**
+ * dm_test_brightness_from_user_midrange - Test Brightness from user mid-range value
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_from_user_midrange(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max;
+ u32 result;
+
+ caps.aux_support = false;
+ caps.min_input_signal = 12;
+ caps.max_input_signal = 255;
+ caps.data_points = 0;
+
+ get_brightness_range(&caps, &min, &max);
+
+ /* Mid-range brightness should map to between min and max */
+ result = convert_brightness_from_user(&caps, max / 2);
+
+ KUNIT_EXPECT_GE(test, result, min);
+ KUNIT_EXPECT_LE(test, result, max);
+}
+
+/**
+ * dm_test_brightness_from_user_with_curve - Test Brightness from user with custom curve active
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_from_user_with_curve(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max;
+ u32 with_curve, without_curve;
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+
+ amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+
+ caps.aux_support = false;
+ caps.min_input_signal = 0;
+ caps.max_input_signal = 255;
+ caps.data_points = 2;
+ caps.luminance_data[0].input_signal = 50;
+ caps.luminance_data[0].luminance = 20;
+ caps.luminance_data[1].input_signal = 200;
+ caps.luminance_data[1].luminance = 80;
+
+ get_brightness_range(&caps, &min, &max);
+
+ with_curve = convert_brightness_from_user(&caps, max / 2);
+
+ /* Now disable the curve and compare */
+ amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() | DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ without_curve = convert_brightness_from_user(&caps, max / 2);
+
+ /* Custom curve should produce a different mapping */
+ KUNIT_EXPECT_NE(test, with_curve, without_curve);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_brightness_range_zero_signals - Test Brightness range with zero min and max signals
+ * @test: The KUnit test context
+ */
+static void dm_test_brightness_range_zero_signals(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min = 99, max = 99;
+
+ caps.aux_support = false;
+ caps.min_input_signal = 0;
+ caps.max_input_signal = 0;
+
+ /* Both signals zero → min=max=0 */
+ KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1);
+ KUNIT_EXPECT_EQ(test, min, 0U);
+ KUNIT_EXPECT_EQ(test, max, 0U);
+}
+
+/* Tests for amdgpu_dm_update_connector_ext_caps() */
+
+/**
+ * dm_test_update_connector_ext_caps_negative_bl_idx - Test negative backlight index early return
+ * @test: The KUnit test context
+ */
+static void dm_test_update_connector_ext_caps_negative_bl_idx(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector);
+
+ aconnector->bl_idx = -1;
+
+ amdgpu_dm_update_connector_ext_caps(aconnector);
+
+ KUNIT_SUCCEED(test);
+}
+
+/**
+ * dm_test_update_connector_ext_caps_non_edp - Test non-eDP connector early return
+ * @test: The KUnit test context
+ */
+static void dm_test_update_connector_ext_caps_non_edp(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+
+ setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_HDMI_TYPE_A);
+ fixture.adev->dm.backlight_caps[0].aux_support = true;
+
+ amdgpu_dm_update_connector_ext_caps(fixture.aconnector);
+
+ KUNIT_EXPECT_TRUE(test, fixture.adev->dm.backlight_caps[0].aux_support);
+ KUNIT_EXPECT_PTR_EQ(test, fixture.adev->dm.backlight_caps[0].ext_caps, NULL);
+}
+
+/**
+ * dm_test_update_connector_ext_caps_oled_defaults - Test OLED eDP defaults to AUX backlight
+ * @test: The KUnit test context
+ */
+static void dm_test_update_connector_ext_caps_oled_defaults(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ int saved_backlight = amdgpu_dm_get_backlight_param();
+
+ amdgpu_dm_set_backlight_param(-1);
+ setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP);
+ fixture.link->dpcd_sink_ext_caps.bits.oled = 1;
+
+ amdgpu_dm_update_connector_ext_caps(fixture.aconnector);
+
+ KUNIT_EXPECT_PTR_EQ(test, fixture.adev->dm.backlight_caps[0].ext_caps,
+ &fixture.link->dpcd_sink_ext_caps);
+ KUNIT_EXPECT_TRUE(test, fixture.adev->dm.backlight_caps[0].aux_support);
+ KUNIT_EXPECT_EQ(test, fixture.link->backlight_control_type,
+ BACKLIGHT_CONTROL_AMD_AUX);
+ KUNIT_EXPECT_EQ(test, fixture.adev->dm.backlight_caps[0].aux_max_input_signal, 512);
+ KUNIT_EXPECT_EQ(test, fixture.adev->dm.backlight_caps[0].aux_min_input_signal, 1);
+
+ amdgpu_dm_set_backlight_param(saved_backlight);
+}
+
+/**
+ * dm_test_update_connector_ext_caps_luminance_values - Test luminance range copy
+ * @test: The KUnit test context
+ */
+static void dm_test_update_connector_ext_caps_luminance_values(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ int saved_backlight = amdgpu_dm_get_backlight_param();
+
+ amdgpu_dm_set_backlight_param(-1);
+ setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP);
+ fixture.aconnector->base.display_info.luminance_range.min_luminance = 2;
+ fixture.aconnector->base.display_info.luminance_range.max_luminance = 400;
+
+ amdgpu_dm_update_connector_ext_caps(fixture.aconnector);
+
+ KUNIT_EXPECT_FALSE(test, fixture.adev->dm.backlight_caps[0].aux_support);
+ KUNIT_EXPECT_EQ(test, fixture.adev->dm.backlight_caps[0].aux_max_input_signal, 400);
+ KUNIT_EXPECT_EQ(test, fixture.adev->dm.backlight_caps[0].aux_min_input_signal, 2);
+
+ amdgpu_dm_set_backlight_param(saved_backlight);
+}
+
+/**
+ * dm_test_update_connector_ext_caps_force_aux - Test module parameter forces AUX backlight
+ * @test: The KUnit test context
+ */
+static void dm_test_update_connector_ext_caps_force_aux(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ int saved_backlight = amdgpu_dm_get_backlight_param();
+
+ amdgpu_dm_set_backlight_param(1);
+ setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP);
+
+ amdgpu_dm_update_connector_ext_caps(fixture.aconnector);
+
+ KUNIT_EXPECT_TRUE(test, fixture.adev->dm.backlight_caps[0].aux_support);
+ KUNIT_EXPECT_EQ(test, fixture.link->backlight_control_type,
+ BACKLIGHT_CONTROL_AMD_AUX);
+
+ amdgpu_dm_set_backlight_param(saved_backlight);
+}
+
+/**
+ * dm_test_update_connector_ext_caps_force_pwm - Test module parameter forces PWM backlight
+ * @test: The KUnit test context
+ */
+static void dm_test_update_connector_ext_caps_force_pwm(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ int saved_backlight = amdgpu_dm_get_backlight_param();
+
+ amdgpu_dm_set_backlight_param(0);
+ setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP);
+ fixture.link->dpcd_sink_ext_caps.bits.oled = 1;
+
+ amdgpu_dm_update_connector_ext_caps(fixture.aconnector);
+
+ KUNIT_EXPECT_FALSE(test, fixture.adev->dm.backlight_caps[0].aux_support);
+ KUNIT_EXPECT_NE(test, fixture.link->backlight_control_type,
+ BACKLIGHT_CONTROL_AMD_AUX);
+
+ amdgpu_dm_set_backlight_param(saved_backlight);
+}
+
+/* Tests for amdgpu_dm_should_create_sysfs() */
+
+/**
+ * dm_test_should_create_sysfs_abm_forced - Test forced ABM disables sysfs
+ * @test: The KUnit test context
+ */
+static void dm_test_should_create_sysfs_abm_forced(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ int saved_abm_level = amdgpu_dm_get_abm_level_param();
+
+ amdgpu_dm_set_abm_level_param(1);
+ setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP);
+ fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP;
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector));
+
+ amdgpu_dm_set_abm_level_param(saved_abm_level);
+}
+
+/**
+ * dm_test_should_create_sysfs_non_edp - Test non-eDP connector disables sysfs
+ * @test: The KUnit test context
+ */
+static void dm_test_should_create_sysfs_non_edp(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ int saved_abm_level = amdgpu_dm_get_abm_level_param();
+
+ amdgpu_dm_set_abm_level_param(-1);
+ setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_HDMI_TYPE_A);
+ fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector));
+
+ amdgpu_dm_set_abm_level_param(saved_abm_level);
+}
+
+/**
+ * dm_test_should_create_sysfs_no_backlight_index - Test eDP without backlight index enables sysfs
+ * @test: The KUnit test context
+ */
+static void dm_test_should_create_sysfs_no_backlight_index(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ int saved_abm_level = amdgpu_dm_get_abm_level_param();
+
+ amdgpu_dm_set_abm_level_param(-1);
+ setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP);
+ fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP;
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector));
+
+ amdgpu_dm_set_abm_level_param(saved_abm_level);
+}
+
+/**
+ * dm_test_should_create_sysfs_aux_backlight - Test AUX backlight disables sysfs
+ * @test: The KUnit test context
+ */
+static void dm_test_should_create_sysfs_aux_backlight(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ int saved_abm_level = amdgpu_dm_get_abm_level_param();
+
+ amdgpu_dm_set_abm_level_param(-1);
+ setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP);
+ fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP;
+ fixture.adev->dm.backlight_caps[0].aux_support = true;
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector));
+
+ amdgpu_dm_set_abm_level_param(saved_abm_level);
+}
+
+/**
+ * dm_test_should_create_sysfs_pwm_backlight - Test PWM backlight enables sysfs
+ * @test: The KUnit test context
+ */
+static void dm_test_should_create_sysfs_pwm_backlight(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ int saved_abm_level = amdgpu_dm_get_abm_level_param();
+
+ amdgpu_dm_set_abm_level_param(-1);
+ setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP);
+ fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP;
+ fixture.adev->dm.backlight_caps[0].aux_support = false;
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector));
+
+ amdgpu_dm_set_abm_level_param(saved_abm_level);
+}
+
+/* Tests for amdgpu_dm_setup_backlight_device() */
+
+/**
+ * dm_test_setup_backlight_device_non_edp - Test non-eDP/LVDS link is skipped
+ * @test: The KUnit test context
+ */
+static void dm_test_setup_backlight_device_non_edp(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ struct amdgpu_display_manager *dm;
+
+ setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_HDMI_TYPE_A);
+ fixture.link->type = dc_connection_single;
+ dm = &fixture.adev->dm;
+ dm->adev = fixture.adev;
+ dm->num_of_edps = 0;
+
+ amdgpu_dm_setup_backlight_device(dm, fixture.aconnector);
+
+ /* Non-eDP/LVDS signal → no backlight setup */
+ KUNIT_EXPECT_EQ(test, dm->num_of_edps, 0);
+ KUNIT_EXPECT_EQ(test, fixture.aconnector->bl_idx, -1);
+}
+
+/**
+ * dm_test_setup_backlight_device_connection_none - Test disconnected link is skipped
+ * @test: The KUnit test context
+ */
+static void dm_test_setup_backlight_device_connection_none(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ struct amdgpu_display_manager *dm;
+
+ setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP);
+ fixture.link->type = dc_connection_none;
+ dm = &fixture.adev->dm;
+ dm->adev = fixture.adev;
+ dm->num_of_edps = 0;
+
+ amdgpu_dm_setup_backlight_device(dm, fixture.aconnector);
+
+ /* Disconnected link → no backlight setup */
+ KUNIT_EXPECT_EQ(test, dm->num_of_edps, 0);
+ KUNIT_EXPECT_EQ(test, fixture.aconnector->bl_idx, -1);
+}
+
+/**
+ * dm_test_setup_backlight_device_max_edps - Test setup is skipped when at eDP limit
+ * @test: The KUnit test context
+ */
+static void dm_test_setup_backlight_device_max_edps(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ struct amdgpu_display_manager *dm;
+
+ setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP);
+ fixture.link->type = dc_connection_single;
+ dm = &fixture.adev->dm;
+ dm->adev = fixture.adev;
+ dm->num_of_edps = AMDGPU_DM_MAX_NUM_EDP;
+
+ amdgpu_dm_setup_backlight_device(dm, fixture.aconnector);
+
+ /* Already at the eDP limit → no additional setup */
+ KUNIT_EXPECT_EQ(test, dm->num_of_edps, AMDGPU_DM_MAX_NUM_EDP);
+ KUNIT_EXPECT_EQ(test, fixture.aconnector->bl_idx, -1);
+}
+
+/**
+ * dm_test_setup_backlight_device_oled_success - Test successful eDP backlight setup
+ * @test: The KUnit test context
+ */
+static void dm_test_setup_backlight_device_oled_success(struct kunit *test)
+{
+ struct dm_backlight_connector_fixture fixture = {};
+ struct amdgpu_display_manager *dm;
+ int saved_backlight = amdgpu_dm_get_backlight_param();
+
+ amdgpu_dm_set_backlight_param(-1);
+ setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP);
+ fixture.link->type = dc_connection_single;
+ /* OLED panel avoids the ABM property attach path */
+ fixture.link->dpcd_sink_ext_caps.bits.oled = 1;
+ dm = &fixture.adev->dm;
+ dm->adev = fixture.adev;
+ dm->num_of_edps = 0;
+
+ amdgpu_dm_setup_backlight_device(dm, fixture.aconnector);
+
+ KUNIT_EXPECT_EQ(test, dm->num_of_edps, 1);
+ KUNIT_EXPECT_EQ(test, fixture.aconnector->bl_idx, 0);
+ KUNIT_EXPECT_PTR_EQ(test, (void *)dm->backlight_link[0],
+ (void *)fixture.link);
+ KUNIT_EXPECT_TRUE(test, dm->backlight_caps[0].aux_support);
+
+ amdgpu_dm_set_backlight_param(saved_backlight);
+}
+
+static struct kunit_case dm_backlight_test_cases[] = {
+ KUNIT_CASE(dm_test_backlight_caps_valid_short_circuit),
+#if !defined(CONFIG_ACPI)
+ KUNIT_CASE(dm_test_backlight_caps_aux_support_noop),
+ KUNIT_CASE(dm_test_backlight_caps_non_aux_sets_defaults),
+#endif
+ /* get_brightness_range */
+ KUNIT_CASE(dm_test_brightness_range_null_caps),
+ KUNIT_CASE(dm_test_brightness_range_pwm),
+ KUNIT_CASE(dm_test_brightness_range_aux),
+ /* convert_brightness_to_user */
+ KUNIT_CASE(dm_test_brightness_to_user_null_caps),
+ KUNIT_CASE(dm_test_brightness_to_user_below_min),
+ KUNIT_CASE(dm_test_brightness_to_user_at_max),
+ KUNIT_CASE(dm_test_brightness_to_user_at_min),
+ KUNIT_CASE(dm_test_brightness_to_user_midpoint_pwm),
+ /* convert_brightness_from_user */
+ KUNIT_CASE(dm_test_brightness_from_user_null_caps),
+ KUNIT_CASE(dm_test_brightness_from_user_zero),
+ KUNIT_CASE(dm_test_brightness_from_user_max),
+ KUNIT_CASE(dm_test_brightness_from_user_aux),
+ /* convert_custom_brightness */
+ KUNIT_CASE(dm_test_custom_brightness_no_data_points),
+ KUNIT_CASE(dm_test_custom_brightness_debug_mask_disables),
+ KUNIT_CASE(dm_test_custom_brightness_exact_match),
+ KUNIT_CASE(dm_test_custom_brightness_below_first),
+ KUNIT_CASE(dm_test_custom_brightness_interpolation),
+ KUNIT_CASE(dm_test_custom_brightness_above_last),
+ KUNIT_CASE(dm_test_custom_brightness_single_data_point),
+ KUNIT_CASE(dm_test_custom_brightness_lower_lum_zero),
+ KUNIT_CASE(dm_test_brightness_to_user_above_max),
+ KUNIT_CASE(dm_test_brightness_from_user_midrange),
+ KUNIT_CASE(dm_test_brightness_from_user_with_curve),
+ KUNIT_CASE(dm_test_brightness_range_zero_signals),
+ /* amdgpu_dm_update_connector_ext_caps */
+ KUNIT_CASE(dm_test_update_connector_ext_caps_negative_bl_idx),
+ KUNIT_CASE(dm_test_update_connector_ext_caps_non_edp),
+ KUNIT_CASE(dm_test_update_connector_ext_caps_oled_defaults),
+ KUNIT_CASE(dm_test_update_connector_ext_caps_luminance_values),
+ KUNIT_CASE(dm_test_update_connector_ext_caps_force_aux),
+ KUNIT_CASE(dm_test_update_connector_ext_caps_force_pwm),
+ /* amdgpu_dm_should_create_sysfs */
+ KUNIT_CASE(dm_test_should_create_sysfs_abm_forced),
+ KUNIT_CASE(dm_test_should_create_sysfs_non_edp),
+ KUNIT_CASE(dm_test_should_create_sysfs_no_backlight_index),
+ KUNIT_CASE(dm_test_should_create_sysfs_aux_backlight),
+ KUNIT_CASE(dm_test_should_create_sysfs_pwm_backlight),
+ /* amdgpu_dm_setup_backlight_device */
+ KUNIT_CASE(dm_test_setup_backlight_device_non_edp),
+ KUNIT_CASE(dm_test_setup_backlight_device_connection_none),
+ KUNIT_CASE(dm_test_setup_backlight_device_max_edps),
+ KUNIT_CASE(dm_test_setup_backlight_device_oled_success),
+ {}
+};
+
+static struct kunit_suite dm_backlight_test_suite = {
+ .name = "amdgpu_dm_backlight",
+ .test_cases = dm_backlight_test_cases,
+};
+
+kunit_test_suite(dm_backlight_test_suite);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_backlight");
+MODULE_AUTHOR("AMD");
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 19/32] drm/amd/display: Add KUnit tests for amdgpu_dm_audio
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (17 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 18/32] drm/amd/display: Add KUnit tests for amdgpu_dm_backlight Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 20/32] drm/amd/display: Add KUnit tests for amdgpu_dm_dmub Chenyu Chen
` (13 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Add KUnit tests for amdgpu_dm_audio.c.
Tests cover:
- amdgpu_dm_audio_init(): early exit when audio is disabled
- amdgpu_dm_audio_fini(): early exit when audio is not enabled
- fill_audio_info(): manufacturer and product ID propagation,
display name copy, speaker allocation flags, CEA revision
gating of audio mode copying (including the zero-mode case),
and latency field propagation
- amdgpu_dm_audio_component_bind()/unbind(): component ops, device,
and audio_component pointer are wired up on bind and cleared on
unbind
- amdgpu_dm_audio_eld_notify(): callback is forwarded with the
correct port and audio pointer, and the no-op guard paths for a
missing component, audio_ops, or pin_eld_notify callback
Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_audio.c | 27 +-
.../amd/display/amdgpu_dm/amdgpu_dm_audio.h | 12 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 1 +
.../amdgpu_dm/tests/amdgpu_dm_audio_test.c | 490 ++++++++++++++++++
4 files changed, 527 insertions(+), 3 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
index 70b7dbded275..1ae121387297 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
@@ -26,6 +26,7 @@
#include "amdgpu.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_audio.h"
+#include "amdgpu_dm_kunit_helpers.h"
#include "dc.h"
#include <linux/component.h>
@@ -83,7 +84,7 @@ static const struct drm_audio_component_ops amdgpu_dm_audio_component_ops = {
.get_eld = amdgpu_dm_audio_component_get_eld,
};
-static int amdgpu_dm_audio_component_bind(struct device *kdev,
+STATIC_IFN_KUNIT int amdgpu_dm_audio_component_bind(struct device *kdev,
struct device *hda_kdev, void *data)
{
struct drm_device *dev = dev_get_drvdata(kdev);
@@ -96,8 +97,9 @@ static int amdgpu_dm_audio_component_bind(struct device *kdev,
return 0;
}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_component_bind);
-static void amdgpu_dm_audio_component_unbind(struct device *kdev,
+STATIC_IFN_KUNIT void amdgpu_dm_audio_component_unbind(struct device *kdev,
struct device *hda_kdev, void *data)
{
struct amdgpu_device *adev = drm_to_adev(dev_get_drvdata(kdev));
@@ -107,6 +109,7 @@ static void amdgpu_dm_audio_component_unbind(struct device *kdev,
acomp->dev = NULL;
adev->dm.audio_component = NULL;
}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_component_unbind);
static const struct component_ops amdgpu_dm_audio_component_bind_ops = {
.bind = amdgpu_dm_audio_component_bind,
@@ -144,6 +147,7 @@ int amdgpu_dm_audio_init(struct amdgpu_device *adev)
return 0;
}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_init);
void amdgpu_dm_audio_fini(struct amdgpu_device *adev)
{
@@ -162,8 +166,9 @@ void amdgpu_dm_audio_fini(struct amdgpu_device *adev)
adev->mode_info.audio.enabled = false;
}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_fini);
-static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin)
+STATIC_IFN_KUNIT void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin)
{
struct drm_audio_component *acomp = adev->dm.audio_component;
@@ -174,6 +179,7 @@ static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin)
pin, -1);
}
}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_eld_notify);
void amdgpu_dm_fill_audio_info(struct audio_info *audio_info,
const struct drm_connector *drm_connector,
@@ -219,6 +225,7 @@ void amdgpu_dm_fill_audio_info(struct audio_info *audio_info,
/* TODO: For DP, video and audio latency should be calculated from DPCD caps */
}
+EXPORT_IF_KUNIT(amdgpu_dm_fill_audio_info);
void amdgpu_dm_commit_audio(struct drm_device *dev,
struct drm_atomic_state *state)
@@ -300,3 +307,17 @@ void amdgpu_dm_commit_audio(struct drm_device *dev,
amdgpu_dm_audio_eld_notify(adev, inst);
}
}
+
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+int amdgpu_dm_audio_get_param(void)
+{
+ return amdgpu_audio;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_get_param);
+
+void amdgpu_dm_audio_set_param(int val)
+{
+ amdgpu_audio = val;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_audio_set_param);
+#endif
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h
index efd412bba178..48391305c3c2 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h
@@ -41,4 +41,16 @@ void amdgpu_dm_fill_audio_info(struct audio_info *audio_info,
const struct drm_connector *drm_connector,
const struct dc_sink *dc_sink);
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+struct device;
+
+int amdgpu_dm_audio_component_bind(struct device *kdev,
+ struct device *hda_kdev, void *data);
+void amdgpu_dm_audio_component_unbind(struct device *kdev,
+ struct device *hda_kdev, void *data);
+void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin);
+int amdgpu_dm_audio_get_param(void);
+void amdgpu_dm_audio_set_param(int val);
+#endif
+
#endif /* __AMDGPU_DM_AUDIO_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index ddd9fce66232..5bb43b3bc439 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -13,6 +13,7 @@ ccflags-y += -I$(src)/../../../include
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_audio_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_backlight_test.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
new file mode 100644
index 000000000000..79ff5d9b3fa5
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_audio.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+
+#include <drm/drm_audio_component.h>
+
+#include "dc.h"
+#include "amdgpu.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_audio.h"
+
+/* Tests for amdgpu_dm_audio_init() */
+
+/**
+ * dm_test_audio_init_disabled - Test audio init exits when audio is disabled
+ * @test: The KUnit test context
+ */
+static void dm_test_audio_init_disabled(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ int saved_audio = amdgpu_dm_audio_get_param();
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ amdgpu_dm_audio_set_param(0);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_audio_init(adev), 0);
+ KUNIT_EXPECT_FALSE(test, adev->mode_info.audio.enabled);
+ KUNIT_EXPECT_FALSE(test, adev->dm.audio_registered);
+
+ amdgpu_dm_audio_set_param(saved_audio);
+}
+
+/* Tests for amdgpu_dm_audio_fini() */
+
+/**
+ * dm_test_audio_fini_without_enabled_audio - Test fini exits when audio is not enabled
+ * @test: The KUnit test context
+ */
+static void dm_test_audio_fini_without_enabled_audio(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ int saved_audio = amdgpu_dm_audio_get_param();
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ amdgpu_dm_audio_set_param(1);
+ adev->mode_info.audio.enabled = false;
+ adev->dm.audio_registered = true;
+
+ amdgpu_dm_audio_fini(adev);
+
+ KUNIT_EXPECT_FALSE(test, adev->mode_info.audio.enabled);
+ KUNIT_EXPECT_TRUE(test, adev->dm.audio_registered);
+
+ amdgpu_dm_audio_set_param(saved_audio);
+}
+
+/* Tests for amdgpu_dm_fill_audio_info() */
+
+/**
+ * dm_test_fill_audio_info_ids_name_flags - Test Fill audio info ids name flags
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_audio_info_ids_name_flags(struct kunit *test)
+{
+ struct audio_info *audio_info;
+ struct drm_connector *connector;
+ struct dc_sink *dc_sink;
+ const char *name = "DM-AUDIO-PANEL";
+
+ audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL);
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink);
+
+ dc_sink->edid_caps.manufacturer_id = 0x1234;
+ dc_sink->edid_caps.product_id = 0xABCD;
+ dc_sink->edid_caps.speaker_flags = 0x5;
+ strscpy(dc_sink->edid_caps.display_name, name,
+ AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS);
+
+ connector->display_info.cea_rev = 1;
+
+ amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink);
+
+ KUNIT_EXPECT_EQ(test, audio_info->manufacture_id, 0x1234U);
+ KUNIT_EXPECT_EQ(test, audio_info->product_id, 0xABCDU);
+ KUNIT_EXPECT_EQ(test, audio_info->flags.all, 0x5U);
+ KUNIT_EXPECT_STREQ(test, audio_info->display_name, name);
+}
+
+/**
+ * dm_test_fill_audio_info_cea_lt_3_skips_modes - Test Fill audio info cea lt 3 skips modes
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_audio_info_cea_lt_3_skips_modes(struct kunit *test)
+{
+ struct audio_info *audio_info;
+ struct drm_connector *connector;
+ struct dc_sink *dc_sink;
+
+ audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL);
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink);
+
+ connector->display_info.cea_rev = 2;
+ dc_sink->edid_caps.audio_mode_count = 2;
+ dc_sink->edid_caps.audio_modes[0].format_code = 1;
+ dc_sink->edid_caps.audio_modes[0].channel_count = 2;
+ dc_sink->edid_caps.audio_modes[0].sample_rate = 0x07;
+ dc_sink->edid_caps.audio_modes[0].sample_size = 16;
+
+ amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink);
+
+ KUNIT_EXPECT_EQ(test, audio_info->mode_count, 0U);
+}
+
+/**
+ * dm_test_fill_audio_info_cea_ge_3_copies_modes - Test Fill audio info cea ge 3 copies modes
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_audio_info_cea_ge_3_copies_modes(struct kunit *test)
+{
+ struct audio_info *audio_info;
+ struct drm_connector *connector;
+ struct dc_sink *dc_sink;
+
+ audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL);
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink);
+
+ connector->display_info.cea_rev = 3;
+ dc_sink->edid_caps.audio_mode_count = 2;
+
+ dc_sink->edid_caps.audio_modes[0].format_code = 1;
+ dc_sink->edid_caps.audio_modes[0].channel_count = 2;
+ dc_sink->edid_caps.audio_modes[0].sample_rate = 0x07;
+ dc_sink->edid_caps.audio_modes[0].sample_size = 16;
+
+ dc_sink->edid_caps.audio_modes[1].format_code = 11;
+ dc_sink->edid_caps.audio_modes[1].channel_count = 6;
+ dc_sink->edid_caps.audio_modes[1].sample_rate = 0x1F;
+ dc_sink->edid_caps.audio_modes[1].sample_size = 24;
+
+ amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink);
+
+ KUNIT_EXPECT_EQ(test, audio_info->mode_count, 2U);
+
+ KUNIT_EXPECT_EQ(test, (int)audio_info->modes[0].format_code, 1);
+ KUNIT_EXPECT_EQ(test, audio_info->modes[0].channel_count, 2);
+ KUNIT_EXPECT_EQ(test, audio_info->modes[0].sample_rates.all, 0x07U);
+ KUNIT_EXPECT_EQ(test, audio_info->modes[0].sample_size, 16);
+
+ KUNIT_EXPECT_EQ(test, (int)audio_info->modes[1].format_code, 11);
+ KUNIT_EXPECT_EQ(test, audio_info->modes[1].channel_count, 6);
+ KUNIT_EXPECT_EQ(test, audio_info->modes[1].sample_rates.all, 0x1FU);
+ KUNIT_EXPECT_EQ(test, audio_info->modes[1].sample_size, 24);
+}
+
+/**
+ * dm_test_fill_audio_info_latency_present - Test Fill audio info latency present
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_audio_info_latency_present(struct kunit *test)
+{
+ struct audio_info *audio_info;
+ struct drm_connector *connector;
+ struct dc_sink *dc_sink;
+
+ audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL);
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink);
+
+ connector->display_info.cea_rev = 3;
+ connector->latency_present[0] = true;
+ connector->video_latency[0] = 11;
+ connector->audio_latency[0] = 22;
+
+ amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink);
+
+ KUNIT_EXPECT_EQ(test, audio_info->video_latency, 11U);
+ KUNIT_EXPECT_EQ(test, audio_info->audio_latency, 22U);
+}
+
+/**
+ * dm_test_fill_audio_info_latency_absent_keeps_zero - Test Fill audio info latency absent keeps zero
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_audio_info_latency_absent_keeps_zero(struct kunit *test)
+{
+ struct audio_info *audio_info;
+ struct drm_connector *connector;
+ struct dc_sink *dc_sink;
+
+ audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL);
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink);
+
+ connector->display_info.cea_rev = 3;
+ connector->latency_present[0] = false;
+ connector->video_latency[0] = 99;
+ connector->audio_latency[0] = 88;
+
+ amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink);
+
+ KUNIT_EXPECT_EQ(test, audio_info->video_latency, 0U);
+ KUNIT_EXPECT_EQ(test, audio_info->audio_latency, 0U);
+}
+
+/**
+ * dm_test_fill_audio_info_cea_ge_3_zero_modes - Test cea >= 3 with zero modes
+ * @test: The KUnit test context
+ *
+ * When cea_rev >= 3 but the sink reports no audio modes, mode_count must be
+ * copied as 0 and no mode entries should be populated.
+ */
+static void dm_test_fill_audio_info_cea_ge_3_zero_modes(struct kunit *test)
+{
+ struct audio_info *audio_info;
+ struct drm_connector *connector;
+ struct dc_sink *dc_sink;
+
+ audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL);
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink);
+
+ connector->display_info.cea_rev = 3;
+ dc_sink->edid_caps.audio_mode_count = 0;
+
+ amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink);
+
+ KUNIT_EXPECT_EQ(test, audio_info->mode_count, 0U);
+ KUNIT_EXPECT_EQ(test, (int)audio_info->modes[0].format_code, 0);
+}
+
+/* Tests for amdgpu_dm_audio_component_bind()/unbind() */
+
+/**
+ * dm_test_audio_component_bind_sets_fields - Test bind wires up audio component
+ * @test: The KUnit test context
+ *
+ * Binding must publish the DRM audio component ops, record the kernel device,
+ * and store the component pointer in the display manager.
+ */
+static void dm_test_audio_component_bind_sets_fields(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct device *kdev;
+ struct drm_audio_component *acomp;
+ int ret;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ kdev = kunit_kzalloc(test, sizeof(*kdev), GFP_KERNEL);
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, kdev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp);
+
+ dev_set_drvdata(kdev, &adev->ddev);
+
+ ret = amdgpu_dm_audio_component_bind(kdev, NULL, acomp);
+
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_NOT_NULL(test, acomp->ops);
+ KUNIT_EXPECT_PTR_EQ(test, acomp->dev, kdev);
+ KUNIT_EXPECT_PTR_EQ(test, adev->dm.audio_component, acomp);
+}
+
+/**
+ * dm_test_audio_component_unbind_clears_fields - Test unbind tears down component
+ * @test: The KUnit test context
+ *
+ * Unbinding must clear the component ops, the kernel device, and the display
+ * manager's stored component pointer.
+ */
+static void dm_test_audio_component_unbind_clears_fields(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct device *kdev;
+ struct drm_audio_component *acomp;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ kdev = kunit_kzalloc(test, sizeof(*kdev), GFP_KERNEL);
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, kdev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp);
+
+ dev_set_drvdata(kdev, &adev->ddev);
+
+ /* Pretend a prior bind already happened. */
+ acomp->dev = kdev;
+ adev->dm.audio_component = acomp;
+
+ amdgpu_dm_audio_component_unbind(kdev, NULL, acomp);
+
+ KUNIT_EXPECT_NULL(test, acomp->ops);
+ KUNIT_EXPECT_NULL(test, acomp->dev);
+ KUNIT_EXPECT_NULL(test, adev->dm.audio_component);
+}
+
+/* Tests for amdgpu_dm_audio_eld_notify() */
+
+static int dm_test_eld_notify_count;
+static int dm_test_eld_notify_port;
+static void *dm_test_eld_notify_ptr;
+
+static void dm_test_pin_eld_notify(void *audio_ptr, int port, int pipe)
+{
+ dm_test_eld_notify_count++;
+ dm_test_eld_notify_port = port;
+ dm_test_eld_notify_ptr = audio_ptr;
+}
+
+/**
+ * dm_test_eld_notify_invokes_callback - Test ELD notify forwards to hda driver
+ * @test: The KUnit test context
+ *
+ * When a component with a pin_eld_notify callback is registered, the notify
+ * helper must invoke it with the audio pointer and the requested pin.
+ */
+static void dm_test_eld_notify_invokes_callback(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct drm_audio_component *acomp;
+ struct drm_audio_component_audio_ops *audio_ops;
+ int marker = 0;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ audio_ops = kunit_kzalloc(test, sizeof(*audio_ops), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_ops);
+
+ audio_ops->audio_ptr = ▮
+ audio_ops->pin_eld_notify = dm_test_pin_eld_notify;
+ acomp->audio_ops = audio_ops;
+ adev->dm.audio_component = acomp;
+
+ dm_test_eld_notify_count = 0;
+ dm_test_eld_notify_port = -100;
+ dm_test_eld_notify_ptr = NULL;
+
+ amdgpu_dm_audio_eld_notify(adev, 7);
+
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1);
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 7);
+ KUNIT_EXPECT_PTR_EQ(test, dm_test_eld_notify_ptr, (void *)&marker);
+}
+
+/**
+ * dm_test_eld_notify_no_component - Test ELD notify is a no-op without component
+ * @test: The KUnit test context
+ *
+ * With no registered audio component, the notify helper must return without
+ * invoking any callback.
+ */
+static void dm_test_eld_notify_no_component(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->dm.audio_component = NULL;
+
+ dm_test_eld_notify_count = 0;
+
+ amdgpu_dm_audio_eld_notify(adev, 3);
+
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0);
+}
+
+/**
+ * dm_test_eld_notify_null_audio_ops - Test ELD notify is a no-op without audio_ops
+ * @test: The KUnit test context
+ *
+ * A component without audio_ops must not trigger any callback.
+ */
+static void dm_test_eld_notify_null_audio_ops(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct drm_audio_component *acomp;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp);
+
+ acomp->audio_ops = NULL;
+ adev->dm.audio_component = acomp;
+
+ dm_test_eld_notify_count = 0;
+
+ amdgpu_dm_audio_eld_notify(adev, 3);
+
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0);
+}
+
+/**
+ * dm_test_eld_notify_null_callback - Test ELD notify is a no-op without callback
+ * @test: The KUnit test context
+ *
+ * audio_ops present but with a NULL pin_eld_notify must not crash or call
+ * anything.
+ */
+static void dm_test_eld_notify_null_callback(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct drm_audio_component *acomp;
+ struct drm_audio_component_audio_ops *audio_ops;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL);
+ audio_ops = kunit_kzalloc(test, sizeof(*audio_ops), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_ops);
+
+ audio_ops->pin_eld_notify = NULL;
+ acomp->audio_ops = audio_ops;
+ adev->dm.audio_component = acomp;
+
+ dm_test_eld_notify_count = 0;
+
+ amdgpu_dm_audio_eld_notify(adev, 3);
+
+ KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0);
+}
+
+static struct kunit_case dm_audio_test_cases[] = {
+ /* amdgpu_dm_audio_init */
+ KUNIT_CASE(dm_test_audio_init_disabled),
+ /* amdgpu_dm_audio_fini */
+ KUNIT_CASE(dm_test_audio_fini_without_enabled_audio),
+ /* amdgpu_dm_fill_audio_info */
+ KUNIT_CASE(dm_test_fill_audio_info_ids_name_flags),
+ KUNIT_CASE(dm_test_fill_audio_info_cea_lt_3_skips_modes),
+ KUNIT_CASE(dm_test_fill_audio_info_cea_ge_3_copies_modes),
+ KUNIT_CASE(dm_test_fill_audio_info_cea_ge_3_zero_modes),
+ KUNIT_CASE(dm_test_fill_audio_info_latency_present),
+ KUNIT_CASE(dm_test_fill_audio_info_latency_absent_keeps_zero),
+ /* amdgpu_dm_audio_component_bind/unbind */
+ KUNIT_CASE(dm_test_audio_component_bind_sets_fields),
+ KUNIT_CASE(dm_test_audio_component_unbind_clears_fields),
+ /* amdgpu_dm_audio_eld_notify */
+ KUNIT_CASE(dm_test_eld_notify_invokes_callback),
+ KUNIT_CASE(dm_test_eld_notify_no_component),
+ KUNIT_CASE(dm_test_eld_notify_null_audio_ops),
+ KUNIT_CASE(dm_test_eld_notify_null_callback),
+ {}
+};
+
+static struct kunit_suite dm_audio_test_suite = {
+ .name = "amdgpu_dm_audio",
+ .test_cases = dm_audio_test_cases,
+};
+
+kunit_test_suite(dm_audio_test_suite);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_audio");
+MODULE_AUTHOR("AMD");
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 20/32] drm/amd/display: Add KUnit tests for amdgpu_dm_dmub
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (18 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 19/32] drm/amd/display: Add KUnit tests for amdgpu_dm_audio Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 21/32] drm/amd/display: Add KUnit tests for amdgpu_dm_connector Chenyu Chen
` (12 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Add KUnit tests for amdgpu_dm_dmub.c covering the following
functions:
- dm_register_dmub_notify_callback(): NULL callback rejection,
out-of-range type, valid registration with offload flag
- dm_dmub_aux_setconfig_callback(): copy and complete on AUX
reply, non-AUX skip, NULL dm_notify, SET_CONFIG reply
- dm_dmub_aux_fused_io_callback(): copy reply and complete,
max ddc_line boundary
- dm_get_default_ips_mode(): IPS mode per DCN version (3.5,
3.5.1, 3.6, 4.2), disabled for older ASICs, default enabled
for unhandled newer ASICs
- dm_dmub_hw_init(): early returns for no dmub_srv, no fb_info,
no firmware
- dm_dmub_hw_resume(): no-op when dmub_srv is NULL
- dm_dmub_sw_init(): returns 0 for unsupported ASIC
- dm_init_microcode(): returns 0 for unsupported ASIC
Assisted-by: Copilot:Claude-Opus-4.6
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 9 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 1 +
.../amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 600 ++++++++++++++++++
3 files changed, 610 insertions(+)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c
index 54b3b884968b..579a435dcefc 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c
@@ -38,6 +38,7 @@
#include "amdgpu_ucode.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_dmub.h"
+#include "amdgpu_dm_kunit_helpers.h"
#include <linux/component.h>
#include <linux/firmware.h>
@@ -80,6 +81,7 @@ void dm_dmub_aux_setconfig_callback(struct amdgpu_device *adev,
if (notify->type == DMUB_NOTIFICATION_AUX_REPLY)
complete(&adev->dm.dmub_aux_transfer_done);
}
+EXPORT_IF_KUNIT(dm_dmub_aux_setconfig_callback);
void dm_dmub_aux_fused_io_callback(struct amdgpu_device *adev,
struct dmub_notification *notify)
@@ -103,6 +105,7 @@ void dm_dmub_aux_fused_io_callback(struct amdgpu_device *adev,
memcpy(sync->reply_data, req, sizeof(*req));
complete(&sync->replied);
}
+EXPORT_IF_KUNIT(dm_dmub_aux_fused_io_callback);
/**
* dm_register_dmub_notify_callback - Sets callback for DMUB notify
@@ -129,6 +132,7 @@ bool dm_register_dmub_notify_callback(struct amdgpu_device *adev,
return true;
}
+EXPORT_IF_KUNIT(dm_register_dmub_notify_callback);
int dm_dmub_hw_init(struct amdgpu_device *adev)
{
@@ -318,6 +322,7 @@ int dm_dmub_hw_init(struct amdgpu_device *adev)
return 0;
}
+EXPORT_IF_KUNIT(dm_dmub_hw_init);
void dm_dmub_hw_resume(struct amdgpu_device *adev)
{
@@ -347,6 +352,7 @@ void dm_dmub_hw_resume(struct amdgpu_device *adev)
drm_err(adev_to_drm(adev), "DMUB interface failed to initialize: status=%d\n", r);
}
}
+EXPORT_IF_KUNIT(dm_dmub_hw_resume);
static enum dmub_status
dm_dmub_send_vbios_gpint_command(struct amdgpu_device *adev,
@@ -460,6 +466,7 @@ enum dmub_ips_disable_type dm_get_default_ips_mode(
return ret;
}
+EXPORT_IF_KUNIT(dm_get_default_ips_mode);
static uint32_t amdgpu_dm_dmub_reg_read(void *ctx, uint32_t address)
{
@@ -678,6 +685,7 @@ int dm_dmub_sw_init(struct amdgpu_device *adev)
return 0;
}
+EXPORT_IF_KUNIT(dm_dmub_sw_init);
int dm_init_microcode(struct amdgpu_device *adev)
{
@@ -750,6 +758,7 @@ int dm_init_microcode(struct amdgpu_device *adev)
"%s", fw_name_dmub);
return r;
}
+EXPORT_IF_KUNIT(dm_init_microcode);
int amdgpu_dm_process_dmub_aux_transfer_sync(
struct dc_context *ctx,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index 5bb43b3bc439..4bd8d1fa0fee 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_audio_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_backlight_test.o
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_dmub_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_dmub_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c
new file mode 100644
index 000000000000..b82dd301a896
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c
@@ -0,0 +1,600 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_dmub.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+
+#include "dc.h"
+#include "dc/inc/core_types.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
+#include "dmub/dmub_srv.h"
+#include "amdgpu_dm_dmub.h"
+
+/* Tests for dm_register_dmub_notify_callback() */
+
+static void dummy_callback(struct amdgpu_device *adev,
+ struct dmub_notification *notify)
+{
+}
+
+/**
+ * dm_test_register_dmub_notify_callback_null_callback - Test null callback is rejected
+ * @test: The KUnit test context
+ */
+static void dm_test_register_dmub_notify_callback_null_callback(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ KUNIT_EXPECT_FALSE(test, dm_register_dmub_notify_callback(adev,
+ DMUB_NOTIFICATION_AUX_REPLY, NULL, false));
+}
+
+/**
+ * dm_test_register_dmub_notify_callback_type_out_of_range - Test out-of-range type is rejected
+ * @test: The KUnit test context
+ */
+static void dm_test_register_dmub_notify_callback_type_out_of_range(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ KUNIT_EXPECT_FALSE(test, dm_register_dmub_notify_callback(adev,
+ AMDGPU_DMUB_NOTIFICATION_MAX, dummy_callback, false));
+}
+
+/**
+ * dm_test_register_dmub_notify_callback_valid - Test Register dmub notify callback valid
+ * @test: The KUnit test context
+ */
+static void dm_test_register_dmub_notify_callback_valid(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ KUNIT_EXPECT_TRUE(test, dm_register_dmub_notify_callback(adev,
+ DMUB_NOTIFICATION_AUX_REPLY, dummy_callback, true));
+
+ KUNIT_EXPECT_TRUE(test,
+ adev->dm.dmub_callback[DMUB_NOTIFICATION_AUX_REPLY] == dummy_callback);
+ KUNIT_EXPECT_TRUE(test,
+ adev->dm.dmub_thread_offload[DMUB_NOTIFICATION_AUX_REPLY]);
+}
+
+/**
+ * dm_test_register_dmub_notify_callback_offload_false - Test registration with offload disabled
+ * @test: The KUnit test context
+ */
+static void dm_test_register_dmub_notify_callback_offload_false(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ KUNIT_EXPECT_TRUE(test, dm_register_dmub_notify_callback(adev,
+ DMUB_NOTIFICATION_HPD, dummy_callback, false));
+
+ KUNIT_EXPECT_TRUE(test,
+ adev->dm.dmub_callback[DMUB_NOTIFICATION_HPD] == dummy_callback);
+ KUNIT_EXPECT_FALSE(test,
+ adev->dm.dmub_thread_offload[DMUB_NOTIFICATION_HPD]);
+}
+
+/* Tests for dm_dmub_aux_setconfig_callback() */
+
+/**
+ * dm_test_dmub_aux_setconfig_callback_copies_and_completes - Test copy and complete on AUX reply
+ * @test: The KUnit test context
+ */
+static void dm_test_dmub_aux_setconfig_callback_copies_and_completes(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dmub_notification *dm_notify;
+ struct dmub_notification notify = {};
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ dm_notify = kunit_kzalloc(test, sizeof(*dm_notify), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_notify);
+
+ init_completion(&adev->dm.dmub_aux_transfer_done);
+ adev->dm.dmub_notify = dm_notify;
+
+ notify.type = DMUB_NOTIFICATION_AUX_REPLY;
+ notify.result = AUX_RET_SUCCESS;
+ notify.aux_reply.command = 0xA5;
+ notify.aux_reply.length = 3;
+ notify.aux_reply.data[0] = 0x11;
+ notify.aux_reply.data[1] = 0x22;
+ notify.aux_reply.data[2] = 0x33;
+
+ dm_dmub_aux_setconfig_callback(adev, ¬ify);
+
+ KUNIT_EXPECT_EQ(test, dm_notify->type, notify.type);
+ KUNIT_EXPECT_EQ(test, dm_notify->result, notify.result);
+ KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.command, notify.aux_reply.command);
+ KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.length, notify.aux_reply.length);
+ KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.data[0], notify.aux_reply.data[0]);
+ KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.data[1], notify.aux_reply.data[1]);
+ KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.data[2], notify.aux_reply.data[2]);
+ KUNIT_EXPECT_TRUE(test, completion_done(&adev->dm.dmub_aux_transfer_done));
+}
+
+/**
+ * dm_test_dmub_aux_setconfig_callback_non_aux_no_complete - Test non-AUX type skips completion
+ * @test: The KUnit test context
+ */
+static void dm_test_dmub_aux_setconfig_callback_non_aux_no_complete(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dmub_notification *dm_notify;
+ struct dmub_notification notify = {};
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ dm_notify = kunit_kzalloc(test, sizeof(*dm_notify), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_notify);
+
+ init_completion(&adev->dm.dmub_aux_transfer_done);
+ adev->dm.dmub_notify = dm_notify;
+
+ notify.type = DMUB_NOTIFICATION_HPD;
+ notify.result = AUX_RET_ERROR_TIMEOUT;
+
+ dm_dmub_aux_setconfig_callback(adev, ¬ify);
+
+ KUNIT_EXPECT_EQ(test, dm_notify->type, notify.type);
+ KUNIT_EXPECT_FALSE(test, completion_done(&adev->dm.dmub_aux_transfer_done));
+}
+
+/**
+ * dm_test_dmub_aux_setconfig_callback_aux_with_null_dm_notify - Test AUX with NULL dm_notify
+ * @test: The KUnit test context
+ */
+static void dm_test_dmub_aux_setconfig_callback_aux_with_null_dm_notify(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dmub_notification notify = {};
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ init_completion(&adev->dm.dmub_aux_transfer_done);
+ adev->dm.dmub_notify = NULL;
+
+ notify.type = DMUB_NOTIFICATION_AUX_REPLY;
+
+ dm_dmub_aux_setconfig_callback(adev, ¬ify);
+
+ KUNIT_EXPECT_TRUE(test, completion_done(&adev->dm.dmub_aux_transfer_done));
+}
+
+/**
+ * dm_test_dmub_aux_setconfig_callback_set_config_reply - Test SET_CONFIG reply copies status
+ * @test: The KUnit test context
+ */
+static void dm_test_dmub_aux_setconfig_callback_set_config_reply(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dmub_notification *dm_notify;
+ struct dmub_notification notify = {};
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ dm_notify = kunit_kzalloc(test, sizeof(*dm_notify), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_notify);
+
+ init_completion(&adev->dm.dmub_aux_transfer_done);
+ adev->dm.dmub_notify = dm_notify;
+
+ notify.type = DMUB_NOTIFICATION_SET_CONFIG_REPLY;
+ notify.sc_status = SET_CONFIG_RX_TIMEOUT;
+
+ dm_dmub_aux_setconfig_callback(adev, ¬ify);
+
+ KUNIT_EXPECT_EQ(test, dm_notify->type, notify.type);
+ KUNIT_EXPECT_EQ(test, dm_notify->sc_status, notify.sc_status);
+ KUNIT_EXPECT_FALSE(test, completion_done(&adev->dm.dmub_aux_transfer_done));
+}
+
+/* Tests for dm_dmub_aux_fused_io_callback() */
+
+/**
+ * dm_test_dmub_aux_fused_io_callback_copies_reply_and_completes - Test copy and complete
+ * @test: The KUnit test context
+ */
+static void dm_test_dmub_aux_fused_io_callback_copies_reply_and_completes(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dmub_notification notify = {};
+ struct dmub_cmd_fused_request *reply;
+ u32 reply_ddc_line;
+ u32 notify_ddc_line;
+ u32 reply_address;
+ u32 notify_address;
+ u32 reply_length;
+ u32 notify_length;
+ uint8_t ddc_line = 2;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ init_completion(&adev->dm.fused_io[ddc_line].replied);
+
+ notify.fused_request.identifier = 0x34;
+ notify.fused_request.status = FUSED_REQUEST_STATUS_SUCCESS;
+ notify.fused_request.u.aux.ddc_line = ddc_line;
+ notify.fused_request.u.aux.address = 0x50;
+ notify.fused_request.u.aux.length = 4;
+
+ dm_dmub_aux_fused_io_callback(adev, ¬ify);
+
+ KUNIT_EXPECT_TRUE(test, completion_done(&adev->dm.fused_io[ddc_line].replied));
+
+ reply = (struct dmub_cmd_fused_request *)adev->dm.fused_io[ddc_line].reply_data;
+ reply_ddc_line = reply->u.aux.ddc_line;
+ notify_ddc_line = notify.fused_request.u.aux.ddc_line;
+ reply_address = reply->u.aux.address;
+ notify_address = notify.fused_request.u.aux.address;
+ reply_length = reply->u.aux.length;
+ notify_length = notify.fused_request.u.aux.length;
+
+ KUNIT_EXPECT_EQ(test, reply->identifier, notify.fused_request.identifier);
+ KUNIT_EXPECT_EQ(test, reply->status, notify.fused_request.status);
+ KUNIT_EXPECT_EQ(test, reply_ddc_line, notify_ddc_line);
+ KUNIT_EXPECT_EQ(test, reply_address, notify_address);
+ KUNIT_EXPECT_EQ(test, reply_length, notify_length);
+}
+
+/**
+ * dm_test_dmub_aux_fused_io_callback_max_ddc_line - Test Dmub aux fused io callback max ddc line
+ * @test: The KUnit test context
+ */
+static void dm_test_dmub_aux_fused_io_callback_max_ddc_line(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dmub_notification notify = {};
+ struct dmub_cmd_fused_request *reply;
+ u32 reply_ddc_line;
+ u32 notify_ddc_line;
+ uint8_t ddc_line;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ ddc_line = ARRAY_SIZE(adev->dm.fused_io) - 1;
+ init_completion(&adev->dm.fused_io[ddc_line].replied);
+
+ notify.fused_request.identifier = 0x56;
+ notify.fused_request.status = FUSED_REQUEST_STATUS_SUCCESS;
+ notify.fused_request.u.aux.ddc_line = ddc_line;
+ notify.fused_request.u.aux.address = 0x50;
+ notify.fused_request.u.aux.length = 1;
+
+ dm_dmub_aux_fused_io_callback(adev, ¬ify);
+
+ KUNIT_EXPECT_TRUE(test, completion_done(&adev->dm.fused_io[ddc_line].replied));
+
+ reply = (struct dmub_cmd_fused_request *)adev->dm.fused_io[ddc_line].reply_data;
+ reply_ddc_line = reply->u.aux.ddc_line;
+ notify_ddc_line = notify.fused_request.u.aux.ddc_line;
+
+ KUNIT_EXPECT_EQ(test, reply->identifier, notify.fused_request.identifier);
+ KUNIT_EXPECT_EQ(test, reply_ddc_line, notify_ddc_line);
+}
+
+/* Tests for dm_get_default_ips_mode() */
+
+/**
+ * dm_test_get_default_ips_mode_dcn35 - Test Get default ips mode dcn35
+ * @test: The KUnit test context
+ */
+static void dm_test_get_default_ips_mode_dcn35(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 5, 0);
+
+ KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev),
+ DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF);
+}
+
+/**
+ * dm_test_get_default_ips_mode_dcn351 - Test Get default ips mode dcn351
+ * @test: The KUnit test context
+ */
+static void dm_test_get_default_ips_mode_dcn351(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 5, 1);
+
+ KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev),
+ DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF);
+}
+
+/**
+ * dm_test_get_default_ips_mode_dcn36 - Test Get default ips mode dcn36
+ * @test: The KUnit test context
+ */
+static void dm_test_get_default_ips_mode_dcn36(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 6, 0);
+
+ KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev),
+ DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF);
+}
+
+/**
+ * dm_test_get_default_ips_mode_dcn42 - Test Get default ips mode dcn42
+ * @test: The KUnit test context
+ */
+static void dm_test_get_default_ips_mode_dcn42(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 2, 0);
+
+ KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev),
+ DMUB_IPS_DISABLE_ALL);
+}
+
+/**
+ * dm_test_get_default_ips_mode_older_than_dcn35 - Test Get default ips mode older than dcn35
+ * @test: The KUnit test context
+ */
+static void dm_test_get_default_ips_mode_older_than_dcn35(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 2, 0);
+
+ KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev),
+ DMUB_IPS_DISABLE_ALL);
+}
+
+/**
+ * dm_test_get_default_ips_mode_newer_default - Test Get default ips mode newer default
+ * @test: The KUnit test context
+ */
+static void dm_test_get_default_ips_mode_newer_default(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ /* DCN 4.0.1 is >= 3.5 but has no explicit case, returns ENABLE */
+ adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 0, 1);
+
+ KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev),
+ DMUB_IPS_ENABLE);
+}
+
+/* Tests for dm_dmub_hw_init() */
+
+/*
+ * Build an amdgpu_device with the minimal dc/res_pool pointers that
+ * dm_dmub_hw_init() and dm_dmub_hw_resume() dereference before their
+ * early-return checks.
+ */
+static struct amdgpu_device *dm_test_alloc_adev_with_dc(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dc *dc;
+ struct resource_pool *res_pool;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc);
+
+ res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool);
+
+ dc->res_pool = res_pool;
+ adev->dm.dc = dc;
+
+ return adev;
+}
+
+/**
+ * dm_test_dmub_hw_init_no_dmub_srv - Test hw init returns 0 when DMUB unsupported
+ * @test: The KUnit test context
+ *
+ * When adev->dm.dmub_srv is NULL the ASIC does not support DMUB and
+ * dm_dmub_hw_init() should return 0 without touching the hardware.
+ */
+static void dm_test_dmub_hw_init_no_dmub_srv(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test);
+
+ adev->dm.dmub_srv = NULL;
+
+ KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0);
+}
+
+/**
+ * dm_test_dmub_hw_init_no_fb_info - Test hw init fails without framebuffer info
+ * @test: The KUnit test context
+ *
+ * With a DMUB service present but no framebuffer info, dm_dmub_hw_init()
+ * should return -EINVAL.
+ */
+static void dm_test_dmub_hw_init_no_fb_info(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test);
+ struct dmub_srv *dmub_srv;
+
+ dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub_srv);
+
+ adev->dm.dmub_srv = dmub_srv;
+ adev->dm.dmub_fb_info = NULL;
+
+ KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), -EINVAL);
+}
+
+/**
+ * dm_test_dmub_hw_init_no_firmware - Test hw init fails without firmware
+ * @test: The KUnit test context
+ *
+ * With a DMUB service and framebuffer info present but no firmware,
+ * dm_dmub_hw_init() should return -EINVAL.
+ */
+static void dm_test_dmub_hw_init_no_firmware(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test);
+ struct dmub_srv *dmub_srv;
+ struct dmub_srv_fb_info *fb_info;
+
+ dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub_srv);
+
+ fb_info = kunit_kzalloc(test, sizeof(*fb_info), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fb_info);
+
+ adev->dm.dmub_srv = dmub_srv;
+ adev->dm.dmub_fb_info = fb_info;
+ adev->dm.dmub_fw = NULL;
+
+ KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), -EINVAL);
+}
+
+/* Tests for dm_dmub_hw_resume() */
+
+/**
+ * dm_test_dmub_hw_resume_no_dmub_srv - Test hw resume is a no-op when DMUB unsupported
+ * @test: The KUnit test context
+ *
+ * When adev->dm.dmub_srv is NULL, dm_dmub_hw_resume() should return early
+ * without dereferencing the (absent) DMUB service.
+ */
+static void dm_test_dmub_hw_resume_no_dmub_srv(struct kunit *test)
+{
+ struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test);
+
+ adev->dm.dmub_srv = NULL;
+
+ /* Must not crash. */
+ dm_dmub_hw_resume(adev);
+}
+
+/* Tests for dm_dmub_sw_init() */
+
+/**
+ * dm_test_dmub_sw_init_unsupported_asic - Test sw init returns 0 for unsupported ASIC
+ * @test: The KUnit test context
+ *
+ * For an IP version with no DMUB support, dm_dmub_sw_init() should return 0
+ * before attempting to access the firmware.
+ */
+static void dm_test_dmub_sw_init_unsupported_asic(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->ip_versions[DCE_HWIP][0] = IP_VERSION(1, 0, 0);
+
+ KUNIT_EXPECT_EQ(test, dm_dmub_sw_init(adev), 0);
+}
+
+/* Tests for dm_init_microcode() */
+
+/**
+ * dm_test_init_microcode_unsupported_asic - Test microcode init returns 0 for unsupported ASIC
+ * @test: The KUnit test context
+ *
+ * For an IP version with no DMUB support, dm_init_microcode() should return 0
+ * without requesting any firmware.
+ */
+static void dm_test_init_microcode_unsupported_asic(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->ip_versions[DCE_HWIP][0] = IP_VERSION(1, 0, 0);
+
+ KUNIT_EXPECT_EQ(test, dm_init_microcode(adev), 0);
+}
+
+static struct kunit_case amdgpu_dm_dmub_tests[] = {
+ /* dm_register_dmub_notify_callback() */
+ KUNIT_CASE(dm_test_register_dmub_notify_callback_null_callback),
+ KUNIT_CASE(dm_test_register_dmub_notify_callback_type_out_of_range),
+ KUNIT_CASE(dm_test_register_dmub_notify_callback_valid),
+ KUNIT_CASE(dm_test_register_dmub_notify_callback_offload_false),
+ /* dm_dmub_aux_setconfig_callback() */
+ KUNIT_CASE(dm_test_dmub_aux_setconfig_callback_copies_and_completes),
+ KUNIT_CASE(dm_test_dmub_aux_setconfig_callback_non_aux_no_complete),
+ KUNIT_CASE(dm_test_dmub_aux_setconfig_callback_aux_with_null_dm_notify),
+ KUNIT_CASE(dm_test_dmub_aux_setconfig_callback_set_config_reply),
+ /* dm_dmub_aux_fused_io_callback() */
+ KUNIT_CASE(dm_test_dmub_aux_fused_io_callback_copies_reply_and_completes),
+ KUNIT_CASE(dm_test_dmub_aux_fused_io_callback_max_ddc_line),
+ /* dm_get_default_ips_mode() */
+ KUNIT_CASE(dm_test_get_default_ips_mode_dcn35),
+ KUNIT_CASE(dm_test_get_default_ips_mode_dcn351),
+ KUNIT_CASE(dm_test_get_default_ips_mode_dcn36),
+ KUNIT_CASE(dm_test_get_default_ips_mode_dcn42),
+ KUNIT_CASE(dm_test_get_default_ips_mode_older_than_dcn35),
+ KUNIT_CASE(dm_test_get_default_ips_mode_newer_default),
+ /* dm_dmub_hw_init() */
+ KUNIT_CASE(dm_test_dmub_hw_init_no_dmub_srv),
+ KUNIT_CASE(dm_test_dmub_hw_init_no_fb_info),
+ KUNIT_CASE(dm_test_dmub_hw_init_no_firmware),
+ /* dm_dmub_hw_resume() */
+ KUNIT_CASE(dm_test_dmub_hw_resume_no_dmub_srv),
+ /* dm_dmub_sw_init() */
+ KUNIT_CASE(dm_test_dmub_sw_init_unsupported_asic),
+ /* dm_init_microcode() */
+ KUNIT_CASE(dm_test_init_microcode_unsupported_asic),
+ {}
+};
+
+static struct kunit_suite amdgpu_dm_dmub_test_suite = {
+ .name = "amdgpu_dm_dmub",
+ .test_cases = amdgpu_dm_dmub_tests,
+};
+
+kunit_test_suite(amdgpu_dm_dmub_test_suite);
+
+MODULE_AUTHOR("AMD");
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_dmub");
+MODULE_LICENSE("Dual MIT/GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 21/32] drm/amd/display: Add KUnit tests for amdgpu_dm_connector
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (19 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 20/32] drm/amd/display: Add KUnit tests for amdgpu_dm_dmub Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 22/32] drm/amd/display: Add KUnit tests for amdgpu_dm_irq Chenyu Chen
` (11 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Add KUnit tests for helper functions in amdgpu_dm_connector.c,
including both pure helper tests and DRM mock-based tests.
Tests cover:
- get_subconnector_type(): all dongle types and unknown default
- get_output_content_type(): all content type mappings and unknown
default
- adjust_colour_depth_from_display_info(): depth reduction from 12bpc
to 10bpc, 16bpc no-fallback, YCbCr420 clock halving, and no-fit
rejection
- get_output_color_space(): RGB full/limited, YCbCr default 709/601,
BT601/709 with Y_ONLY, OPRGB, BT2020 RGB/YCC paths
- convert_dc_color_depth_into_bpc(): all depths and undefined default
- convert_color_depth_from_display_info(): non-Y420 bpc values, Y420
default/10/12/16bpc, requested odd bpc rounding, unsupported bpc,
and requested_bpc capping
- to_drm_connector_type(): HDMI, eDP, LVDS, RGB, DP/MST, DVI single
and dual link DVII/DVID, virtual, and unknown
- is_duplicate_mode(): empty list, match, no-match, and same-size
different-clock cases
- amdgpu_dm_get_encoder_crtc_mask(): 1-6 CRTCs and default
- get_aspect_ratio(): all HDMI picture aspect ratios
- decide_crtc_timing_for_drm_display_mode(): scale enabled, matching
mode, no copy, and no crtc_clock cases
- amdgpu_dm_connector_funcs_reset(): default fields, eDP ABM level
set, and eDP ABM disabled
- amdgpu_dm_connector_atomic_duplicate_state(): field copy
verification
- amdgpu_dm_fill_hdr_info_packet(): null metadata early return and
output zeroing
- amdgpu_dm_connector_atomic_set_property(): scaling center/aspect/
fullscreen/none/unchanged, underscan hborder/vborder/enable, abm
sysfs control/level off/level value, and unknown property -EINVAL
- amdgpu_dm_connector_atomic_get_property(): scaling center/aspect/
full/off, underscan borders, abm sysfs allowed/level/disabled, and
unknown property -EINVAL
- amdgpu_dm_get_highest_refresh_rate_mode(): null writeback, cached
base mode, and preferred mode selection
- amdgpu_dm_is_freesync_video_mode(): null mode, match, and no-match
cases
Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../display/amdgpu_dm/amdgpu_dm_connector.c | 33 +-
.../display/amdgpu_dm/amdgpu_dm_connector.h | 15 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 1 +
.../tests/amdgpu_dm_connector_test.c | 2142 +++++++++++++++++
4 files changed, 2184 insertions(+), 7 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index 59091ee32099..c9377a9e8e6a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -42,6 +42,7 @@
#include "amdgpu_display.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_connector.h"
+#include "amdgpu_dm_kunit_helpers.h"
#include "amdgpu_dm_plane.h"
#include "amdgpu_dm_crtc.h"
#include "amdgpu_dm_wb.h"
@@ -188,6 +189,7 @@ int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev)
return 0x3f;
}
}
+EXPORT_IF_KUNIT(amdgpu_dm_get_encoder_crtc_mask);
int amdgpu_dm_encoder_init(struct drm_device *dev,
struct amdgpu_encoder *aencoder,
@@ -213,7 +215,7 @@ int amdgpu_dm_encoder_init(struct drm_device *dev,
return res;
}
-static enum drm_mode_subconnector get_subconnector_type(struct dc_link *link)
+STATIC_IFN_KUNIT enum drm_mode_subconnector get_subconnector_type(struct dc_link *link)
{
switch (link->dpcd_caps.dongle_type) {
case DISPLAY_DONGLE_NONE:
@@ -231,6 +233,7 @@ static enum drm_mode_subconnector get_subconnector_type(struct dc_link *link)
return DRM_MODE_SUBCONNECTOR_Unknown;
}
}
+EXPORT_IF_KUNIT(get_subconnector_type);
static void update_subconnector_property(struct amdgpu_dm_connector *aconnector)
{
@@ -662,13 +665,15 @@ amdgpu_dm_convert_color_depth_from_display_info(const struct drm_connector *conn
return COLOR_DEPTH_UNDEFINED;
}
}
+EXPORT_IF_KUNIT(amdgpu_dm_convert_color_depth_from_display_info);
-static enum dc_aspect_ratio
+STATIC_IFN_KUNIT enum dc_aspect_ratio
get_aspect_ratio(const struct drm_display_mode *mode_in)
{
/* 1-1 mapping, since both enums follow the HDMI spec. */
return (enum dc_aspect_ratio) mode_in->picture_aspect_ratio;
}
+EXPORT_IF_KUNIT(get_aspect_ratio);
enum dc_color_space
amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing,
@@ -728,8 +733,9 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing,
return color_space;
}
+EXPORT_IF_KUNIT(amdgpu_dm_get_output_color_space);
-static enum display_content_type
+STATIC_IFN_KUNIT enum display_content_type
get_output_content_type(const struct drm_connector_state *connector_state)
{
switch (connector_state->content_type) {
@@ -746,8 +752,9 @@ get_output_content_type(const struct drm_connector_state *connector_state)
return DISPLAY_CONTENT_TYPE_GAME;
}
}
+EXPORT_IF_KUNIT(get_output_content_type);
-static bool adjust_colour_depth_from_display_info(
+STATIC_IFN_KUNIT bool adjust_colour_depth_from_display_info(
struct dc_crtc_timing *timing_out,
const struct drm_display_info *info)
{
@@ -783,6 +790,7 @@ static bool adjust_colour_depth_from_display_info(
} while (--depth > COLOR_DEPTH_666);
return false;
}
+EXPORT_IF_KUNIT(adjust_colour_depth_from_display_info);
static void fill_stream_properties_from_drm_display_mode(
struct dc_stream_state *stream,
@@ -924,7 +932,7 @@ copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode,
dst_mode->crtc_vtotal = src_mode->crtc_vtotal;
}
-static void
+STATIC_IFN_KUNIT void
decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode,
const struct drm_display_mode *native_mode,
bool scale_enabled)
@@ -939,6 +947,7 @@ decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode,
/* no scaling nor amdgpu inserted, no need to patch */
}
}
+EXPORT_IF_KUNIT(decide_crtc_timing_for_drm_display_mode);
static struct dc_sink *
create_fake_sink(struct drm_device *dev, struct dc_link *link)
@@ -1044,6 +1053,7 @@ amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
drm_mode_copy(&aconnector->freesync_vid_base, m_pref);
return m_pref;
}
+EXPORT_IF_KUNIT(amdgpu_dm_get_highest_refresh_rate_mode);
bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode,
@@ -1072,6 +1082,7 @@ bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode,
else
return true;
}
+EXPORT_IF_KUNIT(amdgpu_dm_is_freesync_video_mode);
#if defined(CONFIG_DRM_AMD_DC_FP)
static void update_dsc_caps(struct amdgpu_dm_connector *aconnector,
@@ -1654,6 +1665,7 @@ int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector,
return ret;
}
+EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_set_property);
int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector,
const struct drm_connector_state *state,
@@ -1703,6 +1715,7 @@ int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector,
return ret;
}
+EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_get_property);
static void amdgpu_dm_connector_unregister(struct drm_connector *connector)
{
@@ -1788,6 +1801,7 @@ void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector)
__drm_atomic_helper_connector_reset(connector, &state->base);
}
}
+EXPORT_IF_KUNIT(amdgpu_dm_connector_funcs_reset);
struct drm_connector_state *
amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector)
@@ -1813,6 +1827,7 @@ amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector)
new_state->pbn = state->pbn;
return &new_state->base;
}
+EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_duplicate_state);
static int
amdgpu_dm_connector_late_register(struct drm_connector *connector)
@@ -2240,6 +2255,7 @@ int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state,
return 0;
}
+EXPORT_IF_KUNIT(amdgpu_dm_fill_hdr_info_packet);
static int
amdgpu_dm_connector_atomic_check(struct drm_connector *conn,
@@ -2355,8 +2371,9 @@ int amdgpu_dm_convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_
}
return 0;
}
+EXPORT_IF_KUNIT(amdgpu_dm_convert_dc_color_depth_into_bpc);
-static int to_drm_connector_type(enum signal_type st, uint32_t connector_id)
+STATIC_IFN_KUNIT int to_drm_connector_type(enum signal_type st, uint32_t connector_id)
{
switch (st) {
case SIGNAL_TYPE_HDMI_TYPE_A:
@@ -2384,6 +2401,7 @@ static int to_drm_connector_type(enum signal_type st, uint32_t connector_id)
return DRM_MODE_CONNECTOR_Unknown;
}
}
+EXPORT_IF_KUNIT(to_drm_connector_type);
static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector)
{
@@ -2579,7 +2597,7 @@ static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector,
}
}
-static bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector,
+STATIC_IFN_KUNIT bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector,
struct drm_display_mode *mode)
{
struct drm_display_mode *m;
@@ -2591,6 +2609,7 @@ static bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector,
return false;
}
+EXPORT_IF_KUNIT(is_duplicate_mode);
static uint add_fs_modes(struct amdgpu_dm_connector *aconnector)
{
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
index 0e8e4756cf17..ab6938a0b8ed 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
@@ -34,6 +34,7 @@ struct amdgpu_encoder;
struct amdgpu_i2c_adapter;
struct dc_crtc_timing;
struct dc_link;
+enum signal_type;
struct dc_state;
struct dc_stream_state;
struct ddc_service;
@@ -144,4 +145,18 @@ int amdgpu_dm_encoder_init(struct drm_device *dev,
struct amdgpu_encoder *aencoder,
uint32_t link_index);
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+enum drm_mode_subconnector get_subconnector_type(struct dc_link *link);
+enum display_content_type
+get_output_content_type(const struct drm_connector_state *connector_state);
+bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out,
+ const struct drm_display_info *info);
+
+int to_drm_connector_type(enum signal_type st, uint32_t connector_id);
+bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, struct drm_display_mode *mode);
+enum dc_aspect_ratio get_aspect_ratio(const struct drm_display_mode *mode_in);
+void decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode,
+ const struct drm_display_mode *native_mode,
+ bool scale_enabled);
+#endif
#endif /* __AMDGPU_DM_CONNECTOR_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index 4bd8d1fa0fee..422eef0bfe49 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_audio_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_connector_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_backlight_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_dmub_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
new file mode 100644
index 000000000000..34e40d2a9d2c
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -0,0 +1,2142 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_connector.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_kunit_helpers.h>
+#include <linux/hdmi.h>
+
+#include "dc.h"
+#include "amdgpu.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_display.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_connector.h"
+#include "amdgpu_dm_backlight.h"
+#include "include/grph_object_id.h"
+
+/* Tests for get_subconnector_type() */
+
+/**
+ * dm_test_subconnector_type_none - Test Subconnector type none
+ * @test: The KUnit test context
+ */
+static void dm_test_subconnector_type_none(struct kunit *test)
+{
+ struct dc_link link = {};
+
+ link.dpcd_caps.dongle_type = DISPLAY_DONGLE_NONE;
+ KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Native);
+}
+
+/**
+ * dm_test_subconnector_type_vga - Test Subconnector type vga
+ * @test: The KUnit test context
+ */
+static void dm_test_subconnector_type_vga(struct kunit *test)
+{
+ struct dc_link link = {};
+
+ link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_VGA_CONVERTER;
+ KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_VGA);
+}
+
+/**
+ * dm_test_subconnector_type_dvi_converter - Test Subconnector type dvi converter
+ * @test: The KUnit test context
+ */
+static void dm_test_subconnector_type_dvi_converter(struct kunit *test)
+{
+ struct dc_link link = {};
+
+ link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_CONVERTER;
+ KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_DVID);
+}
+
+/**
+ * dm_test_subconnector_type_dvi_dongle - Test Subconnector type dvi dongle
+ * @test: The KUnit test context
+ */
+static void dm_test_subconnector_type_dvi_dongle(struct kunit *test)
+{
+ struct dc_link link = {};
+
+ link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_DONGLE;
+ KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_DVID);
+}
+
+/**
+ * dm_test_subconnector_type_hdmi_converter - Test Subconnector type hdmi converter
+ * @test: The KUnit test context
+ */
+static void dm_test_subconnector_type_hdmi_converter(struct kunit *test)
+{
+ struct dc_link link = {};
+
+ link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER;
+ KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_HDMIA);
+}
+
+/**
+ * dm_test_subconnector_type_hdmi_dongle - Test Subconnector type hdmi dongle
+ * @test: The KUnit test context
+ */
+static void dm_test_subconnector_type_hdmi_dongle(struct kunit *test)
+{
+ struct dc_link link = {};
+
+ link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_DONGLE;
+ KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_HDMIA);
+}
+
+/**
+ * dm_test_subconnector_type_mismatched - Test Subconnector type mismatched
+ * @test: The KUnit test context
+ */
+static void dm_test_subconnector_type_mismatched(struct kunit *test)
+{
+ struct dc_link link = {};
+
+ link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE;
+ KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Unknown);
+}
+
+/**
+ * dm_test_subconnector_type_default_unknown - Test Subconnector type default unknown
+ * @test: The KUnit test context
+ */
+static void dm_test_subconnector_type_default_unknown(struct kunit *test)
+{
+ struct dc_link link = {};
+
+ link.dpcd_caps.dongle_type = (typeof(link.dpcd_caps.dongle_type))0x7f;
+ KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Unknown);
+}
+
+/* Tests for get_output_content_type() */
+
+/**
+ * dm_test_content_type_no_data - Test Content type no data
+ * @test: The KUnit test context
+ */
+static void dm_test_content_type_no_data(struct kunit *test)
+{
+ struct drm_connector_state state = {};
+
+ state.content_type = DRM_MODE_CONTENT_TYPE_NO_DATA;
+ KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_NO_DATA);
+}
+
+/**
+ * dm_test_content_type_graphics - Test Content type graphics
+ * @test: The KUnit test context
+ */
+static void dm_test_content_type_graphics(struct kunit *test)
+{
+ struct drm_connector_state state = {};
+
+ state.content_type = DRM_MODE_CONTENT_TYPE_GRAPHICS;
+ KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_GRAPHICS);
+}
+
+/**
+ * dm_test_content_type_photo - Test Content type photo
+ * @test: The KUnit test context
+ */
+static void dm_test_content_type_photo(struct kunit *test)
+{
+ struct drm_connector_state state = {};
+
+ state.content_type = DRM_MODE_CONTENT_TYPE_PHOTO;
+ KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_PHOTO);
+}
+
+/**
+ * dm_test_content_type_cinema - Test Content type cinema
+ * @test: The KUnit test context
+ */
+static void dm_test_content_type_cinema(struct kunit *test)
+{
+ struct drm_connector_state state = {};
+
+ state.content_type = DRM_MODE_CONTENT_TYPE_CINEMA;
+ KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_CINEMA);
+}
+
+/**
+ * dm_test_content_type_game - Test Content type game
+ * @test: The KUnit test context
+ */
+static void dm_test_content_type_game(struct kunit *test)
+{
+ struct drm_connector_state state = {};
+
+ state.content_type = DRM_MODE_CONTENT_TYPE_GAME;
+ KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_GAME);
+}
+
+/**
+ * dm_test_content_type_unknown_defaults_no_data - Test unknown content type defaults to no data
+ * @test: The KUnit test context
+ */
+static void dm_test_content_type_unknown_defaults_no_data(struct kunit *test)
+{
+ struct drm_connector_state state = {};
+
+ state.content_type = 0x7f;
+ KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state),
+ (int)DISPLAY_CONTENT_TYPE_NO_DATA);
+}
+
+/* Tests for adjust_colour_depth_from_display_info() */
+
+/**
+ * dm_test_adjust_colour_depth_fits_at_888 - Test Adjust colour depth fits at 888
+ * @test: The KUnit test context
+ */
+static void dm_test_adjust_colour_depth_fits_at_888(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_display_info info = {};
+
+ /* 1080p @ 148500 KHz = 1485000 in 100Hz units */
+ timing.pix_clk_100hz = 1485000;
+ timing.display_color_depth = COLOR_DEPTH_888;
+ timing.pixel_encoding = PIXEL_ENCODING_RGB;
+ info.max_tmds_clock = 150000; /* 150 MHz */
+
+ KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info));
+ KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_888);
+}
+
+/**
+ * dm_test_adjust_colour_depth_reduces_to_888 - Test Adjust colour depth reduces to 888
+ * @test: The KUnit test context
+ */
+static void dm_test_adjust_colour_depth_reduces_to_888(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_display_info info = {};
+
+ /* Request 10bpc but TMDS limit only allows 8bpc */
+ timing.pix_clk_100hz = 1485000;
+ timing.display_color_depth = COLOR_DEPTH_101010;
+ timing.pixel_encoding = PIXEL_ENCODING_RGB;
+ /* 10bpc would need 148500*30/24 = 185625 KHz, exceeds limit */
+ info.max_tmds_clock = 160000;
+
+ KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info));
+ KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_888);
+}
+
+/**
+ * dm_test_adjust_colour_depth_10bpc_passes - Test Adjust colour depth 10bpc passes
+ * @test: The KUnit test context
+ */
+static void dm_test_adjust_colour_depth_10bpc_passes(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_display_info info = {};
+
+ timing.pix_clk_100hz = 1485000;
+ timing.display_color_depth = COLOR_DEPTH_101010;
+ timing.pixel_encoding = PIXEL_ENCODING_RGB;
+ /* 10bpc needs 185625 KHz, allow it */
+ info.max_tmds_clock = 200000;
+
+ KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info));
+ KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010);
+}
+
+/**
+ * dm_test_adjust_colour_depth_420_halves_clk - Test Adjust colour depth 420 halves clk
+ * @test: The KUnit test context
+ */
+static void dm_test_adjust_colour_depth_420_halves_clk(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_display_info info = {};
+
+ /* 4K @ 594000 KHz = 5940000 in 100Hz units */
+ timing.pix_clk_100hz = 5940000;
+ timing.display_color_depth = COLOR_DEPTH_101010;
+ timing.pixel_encoding = PIXEL_ENCODING_YCBCR420;
+ /* With 420: effective = 594000/2 = 297000, 10bpc = 297000*30/24 = 371250 */
+ info.max_tmds_clock = 400000;
+
+ KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info));
+ KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010);
+}
+
+/**
+ * dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc - Test Adjust colour
+ * depth reduces 12bpc to 10bpc
+ * @test: The KUnit test context
+ */
+static void dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_display_info info = {};
+
+ timing.pix_clk_100hz = 1485000;
+ timing.display_color_depth = COLOR_DEPTH_121212;
+ timing.pixel_encoding = PIXEL_ENCODING_RGB;
+ info.max_tmds_clock = 190000;
+
+ KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info));
+ KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010);
+}
+
+/**
+ * dm_test_adjust_colour_depth_16bpc_no_fallback - Test Adjust colour depth
+ * 16bpc cannot fall back
+ * @test: The KUnit test context
+ */
+static void dm_test_adjust_colour_depth_16bpc_no_fallback(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_display_info info = {};
+
+ /* 16bpc that exceeds limit cannot reduce because the next enum
+ * value (COLOR_DEPTH_141414) is not a valid HDMI depth.
+ */
+ timing.pix_clk_100hz = 1485000;
+ timing.display_color_depth = COLOR_DEPTH_161616;
+ timing.pixel_encoding = PIXEL_ENCODING_RGB;
+ info.max_tmds_clock = 230000;
+
+ KUNIT_EXPECT_FALSE(test, adjust_colour_depth_from_display_info(&timing, &info));
+}
+
+/**
+ * dm_test_adjust_colour_depth_none_fits - Test Adjust colour depth none fits
+ * @test: The KUnit test context
+ */
+static void dm_test_adjust_colour_depth_none_fits(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_display_info info = {};
+
+ /* Even 8bpc doesn't fit */
+ timing.pix_clk_100hz = 1485000;
+ timing.display_color_depth = COLOR_DEPTH_888;
+ timing.pixel_encoding = PIXEL_ENCODING_RGB;
+ info.max_tmds_clock = 100000; /* Too low */
+
+ KUNIT_EXPECT_FALSE(test, adjust_colour_depth_from_display_info(&timing, &info));
+}
+
+/**
+ * dm_test_adjust_colour_depth_invalid_depth - Test Adjust colour depth invalid depth
+ * @test: The KUnit test context
+ */
+static void dm_test_adjust_colour_depth_invalid_depth(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_display_info info = {};
+
+ timing.pix_clk_100hz = 1485000;
+ timing.display_color_depth = COLOR_DEPTH_141414;
+ timing.pixel_encoding = PIXEL_ENCODING_RGB;
+ info.max_tmds_clock = 400000;
+
+ KUNIT_EXPECT_FALSE(test, adjust_colour_depth_from_display_info(&timing, &info));
+ KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_141414);
+}
+
+/* Tests for amdgpu_dm_get_output_color_space() */
+
+/**
+ * dm_test_output_color_space_default_rgb_full - Test Output color space default rgb full
+ * @test: The KUnit test context
+ */
+static void dm_test_output_color_space_default_rgb_full(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_connector_state state = {};
+
+ timing.pixel_encoding = PIXEL_ENCODING_RGB;
+ state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT;
+ state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO;
+
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state),
+ (int)COLOR_SPACE_SRGB);
+}
+
+/**
+ * dm_test_output_color_space_default_rgb_limited - Test Output color space default rgb limited
+ * @test: The KUnit test context
+ */
+static void dm_test_output_color_space_default_rgb_limited(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_connector_state state = {};
+
+ timing.pixel_encoding = PIXEL_ENCODING_RGB;
+ state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT;
+ state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_LIMITED;
+
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state),
+ (int)COLOR_SPACE_SRGB_LIMITED);
+}
+
+/**
+ * dm_test_output_color_space_default_ycbcr709 - Test Output color space default ycbcr709
+ * @test: The KUnit test context
+ */
+static void dm_test_output_color_space_default_ycbcr709(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_connector_state state = {};
+
+ timing.pixel_encoding = PIXEL_ENCODING_YCBCR444;
+ timing.pix_clk_100hz = 300000;
+ timing.flags.Y_ONLY = 0;
+ state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT;
+
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state),
+ (int)COLOR_SPACE_YCBCR709);
+}
+
+/**
+ * dm_test_output_color_space_default_ycbcr601_limited - Test Output color space
+ * default ycbcr601 limited
+ * @test: The KUnit test context
+ */
+static void dm_test_output_color_space_default_ycbcr601_limited(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_connector_state state = {};
+
+ timing.pixel_encoding = PIXEL_ENCODING_YCBCR444;
+ timing.pix_clk_100hz = 270300;
+ timing.flags.Y_ONLY = 1;
+ state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT;
+
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state),
+ (int)COLOR_SPACE_YCBCR601_LIMITED);
+}
+
+/**
+ * dm_test_output_color_space_bt601_y_only - Test Output color space bt601 y only
+ * @test: The KUnit test context
+ */
+static void dm_test_output_color_space_bt601_y_only(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_connector_state state = {};
+
+ timing.flags.Y_ONLY = 1;
+ state.colorspace = DRM_MODE_COLORIMETRY_BT601_YCC;
+
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state),
+ (int)COLOR_SPACE_YCBCR601_LIMITED);
+}
+
+/**
+ * dm_test_output_color_space_bt601 - Test Output color space bt601
+ * @test: The KUnit test context
+ */
+static void dm_test_output_color_space_bt601(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_connector_state state = {};
+
+ timing.flags.Y_ONLY = 0;
+ state.colorspace = DRM_MODE_COLORIMETRY_BT601_YCC;
+
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state),
+ (int)COLOR_SPACE_YCBCR601);
+}
+
+/**
+ * dm_test_output_color_space_bt709 - Test Output color space bt709
+ * @test: The KUnit test context
+ */
+static void dm_test_output_color_space_bt709(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_connector_state state = {};
+
+ timing.flags.Y_ONLY = 0;
+ state.colorspace = DRM_MODE_COLORIMETRY_BT709_YCC;
+
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state),
+ (int)COLOR_SPACE_YCBCR709);
+}
+
+/**
+ * dm_test_output_color_space_bt709_y_only - Test Output color space bt709 y only
+ * @test: The KUnit test context
+ */
+static void dm_test_output_color_space_bt709_y_only(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_connector_state state = {};
+
+ timing.flags.Y_ONLY = 1;
+ state.colorspace = DRM_MODE_COLORIMETRY_BT709_YCC;
+
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state),
+ (int)COLOR_SPACE_YCBCR709_LIMITED);
+}
+
+/**
+ * dm_test_output_color_space_oprgb - Test Output color space oprgb
+ * @test: The KUnit test context
+ */
+static void dm_test_output_color_space_oprgb(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_connector_state state = {};
+
+ state.colorspace = DRM_MODE_COLORIMETRY_OPRGB;
+
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state),
+ (int)COLOR_SPACE_ADOBERGB);
+}
+
+/**
+ * dm_test_output_color_space_bt2020_rgb - Test Output color space bt2020 rgb
+ * @test: The KUnit test context
+ */
+static void dm_test_output_color_space_bt2020_rgb(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_connector_state state = {};
+
+ timing.pixel_encoding = PIXEL_ENCODING_RGB;
+ state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB;
+
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state),
+ (int)COLOR_SPACE_2020_RGB_FULLRANGE);
+}
+
+/**
+ * dm_test_output_color_space_bt2020_ycc - Test Output color space bt2020 ycc
+ * @test: The KUnit test context
+ */
+static void dm_test_output_color_space_bt2020_ycc(struct kunit *test)
+{
+ struct dc_crtc_timing timing = {};
+ struct drm_connector_state state = {};
+
+ timing.pixel_encoding = PIXEL_ENCODING_YCBCR422;
+ state.colorspace = DRM_MODE_COLORIMETRY_BT2020_YCC;
+
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state),
+ (int)COLOR_SPACE_2020_YCBCR_LIMITED);
+}
+
+/* Tests for amdgpu_dm_convert_dc_color_depth_into_bpc() */
+
+/**
+ * dm_test_convert_color_depth_bpc_mappings - Test Convert color depth bpc mappings
+ * @test: The KUnit test context
+ */
+static void dm_test_convert_color_depth_bpc_mappings(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_666), 6);
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_888), 8);
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_101010), 10);
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_121212), 12);
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_141414), 14);
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_161616), 16);
+}
+
+/**
+ * dm_test_convert_color_depth_bpc_unknown - Test Convert color depth bpc unknown
+ * @test: The KUnit test context
+ */
+static void dm_test_convert_color_depth_bpc_unknown(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_UNDEFINED), 0);
+}
+
+/* Tests for amdgpu_dm_convert_color_depth_from_display_info() */
+
+/**
+ * dm_test_color_depth_from_info_bpc8 - Test Color depth from info bpc8
+ * @test: The KUnit test context
+ */
+static void dm_test_color_depth_from_info_bpc8(struct kunit *test)
+{
+ struct drm_connector *connector;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+
+ connector->display_info.bpc = 8;
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 0),
+ (int)COLOR_DEPTH_888);
+}
+
+/**
+ * dm_test_color_depth_from_info_bpc10 - Test Color depth from info bpc10
+ * @test: The KUnit test context
+ */
+static void dm_test_color_depth_from_info_bpc10(struct kunit *test)
+{
+ struct drm_connector *connector;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+
+ connector->display_info.bpc = 10;
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 0),
+ (int)COLOR_DEPTH_101010);
+}
+
+/**
+ * dm_test_color_depth_from_info_zero_bpc_defaults_888 - Test Color depth from
+ * info zero bpc defaults 888
+ * @test: The KUnit test context
+ */
+static void dm_test_color_depth_from_info_zero_bpc_defaults_888(struct kunit *test)
+{
+ struct drm_connector *connector;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+
+ connector->display_info.bpc = 0;
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 0),
+ (int)COLOR_DEPTH_888);
+}
+
+/**
+ * dm_test_color_depth_from_info_requested_bpc_caps - Test Color depth from info requested bpc caps
+ * @test: The KUnit test context
+ */
+static void dm_test_color_depth_from_info_requested_bpc_caps(struct kunit *test)
+{
+ struct drm_connector *connector;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+
+ /* Display supports 12bpc but user requests max 10 */
+ connector->display_info.bpc = 12;
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 10),
+ (int)COLOR_DEPTH_101010);
+}
+
+/**
+ * dm_test_color_depth_from_info_y420_default - Test Color depth from info y420 default
+ * @test: The KUnit test context
+ */
+static void dm_test_color_depth_from_info_y420_default(struct kunit *test)
+{
+ struct drm_connector *connector;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+
+ /* No Y420 DC modes set → 8bpc */
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, true, 0),
+ (int)COLOR_DEPTH_888);
+}
+
+/**
+ * dm_test_color_depth_from_info_y420_10bpc - Test Color depth from info y420 10bpc
+ * @test: The KUnit test context
+ */
+static void dm_test_color_depth_from_info_y420_10bpc(struct kunit *test)
+{
+ struct drm_connector *connector;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+
+ connector->display_info.hdmi.y420_dc_modes = DRM_EDID_YCBCR420_DC_30;
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, true, 0),
+ (int)COLOR_DEPTH_101010);
+}
+
+/**
+ * dm_test_color_depth_from_info_y420_12bpc - Test Color depth from info y420 12bpc
+ * @test: The KUnit test context
+ */
+static void dm_test_color_depth_from_info_y420_12bpc(struct kunit *test)
+{
+ struct drm_connector *connector;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+
+ connector->display_info.hdmi.y420_dc_modes = DRM_EDID_YCBCR420_DC_36;
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, true, 0),
+ (int)COLOR_DEPTH_121212);
+}
+
+/**
+ * dm_test_color_depth_from_info_y420_16bpc - Test Color depth from info y420 16bpc
+ * @test: The KUnit test context
+ */
+static void dm_test_color_depth_from_info_y420_16bpc(struct kunit *test)
+{
+ struct drm_connector *connector;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+
+ connector->display_info.hdmi.y420_dc_modes = DRM_EDID_YCBCR420_DC_48;
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, true, 0),
+ (int)COLOR_DEPTH_161616);
+}
+
+/**
+ * dm_test_color_depth_from_info_requested_odd_bpc - Test Color depth from info requested odd bpc
+ * @test: The KUnit test context
+ */
+static void dm_test_color_depth_from_info_requested_odd_bpc(struct kunit *test)
+{
+ struct drm_connector *connector;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+
+ connector->display_info.bpc = 12;
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 11),
+ (int)COLOR_DEPTH_101010);
+}
+
+/**
+ * dm_test_color_depth_from_info_unsupported_bpc - Test Color depth from info unsupported bpc
+ * @test: The KUnit test context
+ */
+static void dm_test_color_depth_from_info_unsupported_bpc(struct kunit *test)
+{
+ struct drm_connector *connector;
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector);
+
+ connector->display_info.bpc = 9;
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 0),
+ (int)COLOR_DEPTH_UNDEFINED);
+}
+
+/* Tests for to_drm_connector_type() */
+
+/**
+ * dm_test_to_connector_type_hdmi - Test To connector type hdmi
+ * @test: The KUnit test context
+ */
+static void dm_test_to_connector_type_hdmi(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_HDMI_TYPE_A, 0),
+ DRM_MODE_CONNECTOR_HDMIA);
+}
+
+/**
+ * dm_test_to_connector_type_edp - Test To connector type edp
+ * @test: The KUnit test context
+ */
+static void dm_test_to_connector_type_edp(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_EDP, 0),
+ DRM_MODE_CONNECTOR_eDP);
+}
+
+/**
+ * dm_test_to_connector_type_lvds - Test To connector type lvds
+ * @test: The KUnit test context
+ */
+static void dm_test_to_connector_type_lvds(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_LVDS, 0),
+ DRM_MODE_CONNECTOR_LVDS);
+}
+
+/**
+ * dm_test_to_connector_type_rgb - Test To connector type rgb
+ * @test: The KUnit test context
+ */
+static void dm_test_to_connector_type_rgb(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_RGB, 0),
+ DRM_MODE_CONNECTOR_VGA);
+}
+
+/**
+ * dm_test_to_connector_type_dp - Test To connector type dp
+ * @test: The KUnit test context
+ */
+static void dm_test_to_connector_type_dp(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_DISPLAY_PORT, 0),
+ DRM_MODE_CONNECTOR_DisplayPort);
+}
+
+/**
+ * dm_test_to_connector_type_dp_mst - Test To connector type dp mst
+ * @test: The KUnit test context
+ */
+static void dm_test_to_connector_type_dp_mst(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_DISPLAY_PORT_MST, 0),
+ DRM_MODE_CONNECTOR_DisplayPort);
+}
+
+/**
+ * dm_test_to_connector_type_dvi_dvii - Test To connector type dvi dvii
+ * @test: The KUnit test context
+ */
+static void dm_test_to_connector_type_dvi_dvii(struct kunit *test)
+{
+ int type = to_drm_connector_type(SIGNAL_TYPE_DVI_SINGLE_LINK, CONNECTOR_ID_SINGLE_LINK_DVII);
+
+ KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVII);
+}
+
+/**
+ * dm_test_to_connector_type_dual_link_dvii - Test To connector type dual link dvii
+ * @test: The KUnit test context
+ */
+static void dm_test_to_connector_type_dual_link_dvii(struct kunit *test)
+{
+ int type = to_drm_connector_type(SIGNAL_TYPE_DVI_DUAL_LINK, CONNECTOR_ID_DUAL_LINK_DVII);
+
+ KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVII);
+}
+
+/**
+ * dm_test_to_connector_type_dvi_dvid - Test To connector type dvi dvid
+ * @test: The KUnit test context
+ */
+static void dm_test_to_connector_type_dvi_dvid(struct kunit *test)
+{
+ int type = to_drm_connector_type(SIGNAL_TYPE_DVI_SINGLE_LINK, CONNECTOR_ID_SINGLE_LINK_DVID);
+
+ KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVID);
+}
+
+/**
+ * dm_test_to_connector_type_virtual - Test To connector type virtual
+ * @test: The KUnit test context
+ */
+static void dm_test_to_connector_type_virtual(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_VIRTUAL, 0),
+ DRM_MODE_CONNECTOR_VIRTUAL);
+}
+
+/**
+ * dm_test_to_connector_type_unknown - Test To connector type unknown
+ * @test: The KUnit test context
+ */
+static void dm_test_to_connector_type_unknown(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_NONE, 0),
+ DRM_MODE_CONNECTOR_Unknown);
+}
+
+/* Tests for is_duplicate_mode() */
+
+/**
+ * dm_test_is_duplicate_mode_empty_list - Test Is duplicate mode empty list
+ * @test: The KUnit test context
+ */
+static void dm_test_is_duplicate_mode_empty_list(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode mode = {};
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector);
+
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
+ mode.hdisplay = 1920;
+ mode.vdisplay = 1080;
+
+ KUNIT_EXPECT_FALSE(test, is_duplicate_mode(aconnector, &mode));
+}
+
+/**
+ * dm_test_is_duplicate_mode_match - Test Is duplicate mode match
+ * @test: The KUnit test context
+ */
+static void dm_test_is_duplicate_mode_match(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode existing = {};
+ struct drm_display_mode candidate = {};
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector);
+
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
+ existing.hdisplay = 1920;
+ existing.vdisplay = 1080;
+ existing.clock = 148500;
+ list_add_tail(&existing.head, &aconnector->base.probed_modes);
+
+ candidate.hdisplay = 1920;
+ candidate.vdisplay = 1080;
+ candidate.clock = 148500;
+
+ KUNIT_EXPECT_TRUE(test, is_duplicate_mode(aconnector, &candidate));
+}
+
+/**
+ * dm_test_is_duplicate_mode_no_match - Test Is duplicate mode no match
+ * @test: The KUnit test context
+ */
+static void dm_test_is_duplicate_mode_no_match(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode existing = {};
+ struct drm_display_mode candidate = {};
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector);
+
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
+ existing.hdisplay = 1920;
+ existing.vdisplay = 1080;
+ existing.clock = 148500;
+ list_add_tail(&existing.head, &aconnector->base.probed_modes);
+
+ candidate.hdisplay = 2560;
+ candidate.vdisplay = 1440;
+ candidate.clock = 241500;
+
+ KUNIT_EXPECT_FALSE(test, is_duplicate_mode(aconnector, &candidate));
+}
+
+/**
+ * dm_test_is_duplicate_mode_same_size_different_clock - Test Is duplicate mode
+ * same size different clock
+ * @test: The KUnit test context
+ */
+static void dm_test_is_duplicate_mode_same_size_different_clock(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode existing = {};
+ struct drm_display_mode candidate = {};
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector);
+
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
+ existing.hdisplay = 1920;
+ existing.vdisplay = 1080;
+ existing.clock = 148500;
+ list_add_tail(&existing.head, &aconnector->base.probed_modes);
+
+ candidate.hdisplay = 1920;
+ candidate.vdisplay = 1080;
+ candidate.clock = 74250;
+
+ KUNIT_EXPECT_FALSE(test, is_duplicate_mode(aconnector, &candidate));
+}
+
+/* Tests for amdgpu_dm_get_encoder_crtc_mask() */
+
+/**
+ * dm_test_encoder_crtc_mask_1 - Test Encoder crtc mask 1
+ * @test: The KUnit test context
+ */
+static void dm_test_encoder_crtc_mask_1(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->mode_info.num_crtc = 1;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x1);
+}
+
+/**
+ * dm_test_encoder_crtc_mask_2 - Test Encoder crtc mask 2
+ * @test: The KUnit test context
+ */
+static void dm_test_encoder_crtc_mask_2(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->mode_info.num_crtc = 2;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x3);
+}
+
+/**
+ * dm_test_encoder_crtc_mask_3 - Test Encoder crtc mask 3
+ * @test: The KUnit test context
+ */
+static void dm_test_encoder_crtc_mask_3(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->mode_info.num_crtc = 3;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x7);
+}
+
+/**
+ * dm_test_encoder_crtc_mask_4 - Test Encoder crtc mask 4
+ * @test: The KUnit test context
+ */
+static void dm_test_encoder_crtc_mask_4(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->mode_info.num_crtc = 4;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0xf);
+}
+
+/**
+ * dm_test_encoder_crtc_mask_5 - Test Encoder crtc mask 5
+ * @test: The KUnit test context
+ */
+static void dm_test_encoder_crtc_mask_5(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->mode_info.num_crtc = 5;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x1f);
+}
+
+/**
+ * dm_test_encoder_crtc_mask_6 - Test Encoder crtc mask 6
+ * @test: The KUnit test context
+ */
+static void dm_test_encoder_crtc_mask_6(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->mode_info.num_crtc = 6;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x3f);
+}
+
+/**
+ * dm_test_encoder_crtc_mask_default - Test Encoder crtc mask default
+ * @test: The KUnit test context
+ */
+static void dm_test_encoder_crtc_mask_default(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ /* Values > 6 use the default case */
+ adev->mode_info.num_crtc = 8;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x3f);
+}
+
+/* Tests for get_aspect_ratio() */
+
+/**
+ * dm_test_aspect_ratio_no_data - Test Aspect ratio no data
+ * @test: The KUnit test context
+ */
+static void dm_test_aspect_ratio_no_data(struct kunit *test)
+{
+ struct drm_display_mode mode = {};
+
+ mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_NONE;
+ KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_NO_DATA);
+}
+
+/**
+ * dm_test_aspect_ratio_4_3 - Test Aspect ratio 4 3
+ * @test: The KUnit test context
+ */
+static void dm_test_aspect_ratio_4_3(struct kunit *test)
+{
+ struct drm_display_mode mode = {};
+
+ mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3;
+ KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_4_3);
+}
+
+/**
+ * dm_test_aspect_ratio_16_9 - Test Aspect ratio 16 9
+ * @test: The KUnit test context
+ */
+static void dm_test_aspect_ratio_16_9(struct kunit *test)
+{
+ struct drm_display_mode mode = {};
+
+ mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9;
+ KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_16_9);
+}
+
+/**
+ * dm_test_aspect_ratio_64_27 - Test Aspect ratio 64 27
+ * @test: The KUnit test context
+ */
+static void dm_test_aspect_ratio_64_27(struct kunit *test)
+{
+ struct drm_display_mode mode = {};
+
+ mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27;
+ KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_64_27);
+}
+
+/**
+ * dm_test_aspect_ratio_256_135 - Test Aspect ratio 256 135
+ * @test: The KUnit test context
+ */
+static void dm_test_aspect_ratio_256_135(struct kunit *test)
+{
+ struct drm_display_mode mode = {};
+
+ mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135;
+ KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_256_135);
+}
+
+/* Tests for decide_crtc_timing_for_drm_display_mode() */
+
+/**
+ * dm_test_decide_crtc_timing_scale_enabled - Test Decide crtc timing scale enabled
+ * @test: The KUnit test context
+ */
+static void dm_test_decide_crtc_timing_scale_enabled(struct kunit *test)
+{
+ struct drm_display_mode drm_mode = {};
+ struct drm_display_mode native_mode = {};
+
+ native_mode.crtc_clock = 148500;
+ native_mode.crtc_hdisplay = 1920;
+ native_mode.crtc_vdisplay = 1080;
+ native_mode.crtc_htotal = 2200;
+ native_mode.crtc_vtotal = 1125;
+ native_mode.crtc_hsync_start = 2008;
+ native_mode.crtc_hsync_end = 2052;
+ native_mode.crtc_vsync_start = 1084;
+ native_mode.crtc_vsync_end = 1089;
+
+ /* Different clock/htotal/vtotal, but scale_enabled forces copy */
+ drm_mode.clock = 74250;
+ drm_mode.htotal = 1650;
+ drm_mode.vtotal = 750;
+
+ decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, true);
+
+ KUNIT_EXPECT_EQ(test, drm_mode.crtc_clock, 148500);
+ KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, drm_mode.crtc_vdisplay, 1080);
+ KUNIT_EXPECT_EQ(test, drm_mode.crtc_htotal, 2200);
+ KUNIT_EXPECT_EQ(test, drm_mode.crtc_vtotal, 1125);
+}
+
+/**
+ * dm_test_decide_crtc_timing_matching_mode - Test Decide crtc timing matching mode
+ * @test: The KUnit test context
+ */
+static void dm_test_decide_crtc_timing_matching_mode(struct kunit *test)
+{
+ struct drm_display_mode drm_mode = {};
+ struct drm_display_mode native_mode = {};
+
+ native_mode.clock = 148500;
+ native_mode.htotal = 2200;
+ native_mode.vtotal = 1125;
+ native_mode.crtc_clock = 148500;
+ native_mode.crtc_hdisplay = 1920;
+ native_mode.crtc_vdisplay = 1080;
+ native_mode.crtc_htotal = 2200;
+ native_mode.crtc_vtotal = 1125;
+
+ /* Matching clock/htotal/vtotal triggers copy */
+ drm_mode.clock = 148500;
+ drm_mode.htotal = 2200;
+ drm_mode.vtotal = 1125;
+
+ decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false);
+
+ KUNIT_EXPECT_EQ(test, drm_mode.crtc_clock, 148500);
+ KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, drm_mode.crtc_vtotal, 1125);
+}
+
+/**
+ * dm_test_decide_crtc_timing_no_copy - Test Decide crtc timing no copy
+ * @test: The KUnit test context
+ */
+static void dm_test_decide_crtc_timing_no_copy(struct kunit *test)
+{
+ struct drm_display_mode drm_mode = {};
+ struct drm_display_mode native_mode = {};
+
+ native_mode.clock = 148500;
+ native_mode.htotal = 2200;
+ native_mode.vtotal = 1125;
+ native_mode.crtc_clock = 148500;
+ native_mode.crtc_hdisplay = 1920;
+
+ /* Different timings, no scaling → no copy */
+ drm_mode.clock = 74250;
+ drm_mode.htotal = 1650;
+ drm_mode.vtotal = 750;
+
+ decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false);
+
+ KUNIT_EXPECT_EQ(test, drm_mode.crtc_clock, 0);
+ KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 0);
+}
+
+/**
+ * dm_test_decide_crtc_timing_no_crtc_clock - Test Decide crtc timing no crtc clock
+ * @test: The KUnit test context
+ */
+static void dm_test_decide_crtc_timing_no_crtc_clock(struct kunit *test)
+{
+ struct drm_display_mode drm_mode = {};
+ struct drm_display_mode native_mode = {};
+
+ /* Matching timings but native crtc_clock is 0 → no copy */
+ native_mode.clock = 148500;
+ native_mode.htotal = 2200;
+ native_mode.vtotal = 1125;
+ native_mode.crtc_clock = 0;
+ native_mode.crtc_hdisplay = 1920;
+
+ drm_mode.clock = 148500;
+ drm_mode.htotal = 2200;
+ drm_mode.vtotal = 1125;
+
+ decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false);
+
+ KUNIT_EXPECT_EQ(test, drm_mode.crtc_clock, 0);
+ KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 0);
+}
+
+/* Tests for amdgpu_dm_connector_funcs_reset() */
+
+static const struct drm_connector_funcs dm_test_connector_funcs = {
+ .reset = amdgpu_dm_connector_funcs_reset,
+ .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+/**
+ * dm_test_funcs_reset_sets_defaults - Test funcs_reset sets defaults
+ * @test: The KUnit test context
+ */
+static void dm_test_funcs_reset_sets_defaults(struct kunit *test)
+{
+ struct device *dev;
+ struct drm_device *drm;
+ struct drm_connector *connector;
+ struct dm_connector_state *dm_state;
+
+ dev = drm_kunit_helper_alloc_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+ sizeof(*drm), 0,
+ DRIVER_MODESET);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, connector);
+
+ drmm_connector_init(drm, connector, &dm_test_connector_funcs,
+ DRM_MODE_CONNECTOR_DisplayPort, NULL);
+
+ amdgpu_dm_connector_funcs_reset(connector);
+
+ KUNIT_ASSERT_NOT_NULL(test, connector->state);
+ dm_state = to_dm_connector_state(connector->state);
+ KUNIT_EXPECT_EQ(test, (int)dm_state->scaling, (int)RMX_OFF);
+ KUNIT_EXPECT_FALSE(test, dm_state->underscan_enable);
+ KUNIT_EXPECT_EQ(test, (int)dm_state->underscan_hborder, 0);
+ KUNIT_EXPECT_EQ(test, (int)dm_state->underscan_vborder, 0);
+ KUNIT_EXPECT_EQ(test, (int)dm_state->base.max_requested_bpc, 8);
+ KUNIT_EXPECT_EQ(test, dm_state->vcpi_slots, 0);
+ KUNIT_EXPECT_EQ(test, (int)dm_state->pbn, 0);
+}
+
+/**
+ * dm_test_funcs_reset_edp_abm_level - Test funcs_reset eDP sets ABM
+ * @test: The KUnit test context
+ */
+static void dm_test_funcs_reset_edp_abm_level(struct kunit *test)
+{
+ struct device *dev;
+ struct drm_device *drm;
+ struct drm_connector *connector;
+ struct dm_connector_state *dm_state;
+ int saved_abm_level = amdgpu_dm_get_abm_level_param();
+
+ dev = drm_kunit_helper_alloc_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+ sizeof(*drm), 0,
+ DRIVER_MODESET);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, connector);
+
+ drmm_connector_init(drm, connector, &dm_test_connector_funcs,
+ DRM_MODE_CONNECTOR_eDP, NULL);
+
+ /* Test with abm_level > 0 */
+ amdgpu_dm_set_abm_level_param(3);
+ amdgpu_dm_connector_funcs_reset(connector);
+
+ KUNIT_ASSERT_NOT_NULL(test, connector->state);
+ dm_state = to_dm_connector_state(connector->state);
+ KUNIT_EXPECT_EQ(test, (int)dm_state->abm_level, 3);
+
+ amdgpu_dm_set_abm_level_param(saved_abm_level);
+}
+
+/**
+ * dm_test_funcs_reset_edp_abm_disabled - Test funcs_reset eDP ABM
+ * disabled
+ * @test: The KUnit test context
+ */
+static void dm_test_funcs_reset_edp_abm_disabled(struct kunit *test)
+{
+ struct device *dev;
+ struct drm_device *drm;
+ struct drm_connector *connector;
+ struct dm_connector_state *dm_state;
+ int saved_abm_level = amdgpu_dm_get_abm_level_param();
+
+ dev = drm_kunit_helper_alloc_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+ sizeof(*drm), 0,
+ DRIVER_MODESET);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, connector);
+
+ drmm_connector_init(drm, connector, &dm_test_connector_funcs,
+ DRM_MODE_CONNECTOR_eDP, NULL);
+
+ /* Test with abm_level <= 0 → immediate disable */
+ amdgpu_dm_set_abm_level_param(-1);
+ amdgpu_dm_connector_funcs_reset(connector);
+
+ KUNIT_ASSERT_NOT_NULL(test, connector->state);
+ dm_state = to_dm_connector_state(connector->state);
+ KUNIT_EXPECT_EQ(test, (int)dm_state->abm_level,
+ (int)ABM_LEVEL_IMMEDIATE_DISABLE);
+
+ amdgpu_dm_set_abm_level_param(saved_abm_level);
+}
+
+/* Tests for amdgpu_dm_connector_atomic_duplicate_state() */
+
+/**
+ * dm_test_atomic_dup_state_copies_fields - Test atomic_duplicate copies
+ * fields
+ * @test: The KUnit test context
+ */
+static void dm_test_atomic_dup_state_copies_fields(struct kunit *test)
+{
+ struct device *dev;
+ struct drm_device *drm;
+ struct drm_connector *connector;
+ struct dm_connector_state *dm_state;
+ struct dm_connector_state *new_dm_state;
+ struct drm_connector_state *new_state;
+
+ dev = drm_kunit_helper_alloc_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+ sizeof(*drm), 0,
+ DRIVER_MODESET);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+
+ connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, connector);
+
+ drmm_connector_init(drm, connector, &dm_test_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA, NULL);
+
+ amdgpu_dm_connector_funcs_reset(connector);
+ KUNIT_ASSERT_NOT_NULL(test, connector->state);
+
+ /* Modify original state fields */
+ dm_state = to_dm_connector_state(connector->state);
+ dm_state->scaling = RMX_CENTER;
+ dm_state->underscan_enable = true;
+ dm_state->underscan_hborder = 10;
+ dm_state->underscan_vborder = 20;
+ dm_state->freesync_capable = true;
+ dm_state->abm_level = 2;
+ dm_state->vcpi_slots = 4;
+ dm_state->pbn = 1234;
+
+ /* Duplicate */
+ new_state = amdgpu_dm_connector_atomic_duplicate_state(connector);
+ KUNIT_ASSERT_NOT_NULL(test, new_state);
+ new_dm_state = to_dm_connector_state(new_state);
+
+ /* Verify all fields copied */
+ KUNIT_EXPECT_EQ(test, (int)new_dm_state->scaling, (int)RMX_CENTER);
+ KUNIT_EXPECT_TRUE(test, new_dm_state->underscan_enable);
+ KUNIT_EXPECT_EQ(test, (int)new_dm_state->underscan_hborder, 10);
+ KUNIT_EXPECT_EQ(test, (int)new_dm_state->underscan_vborder, 20);
+ KUNIT_EXPECT_TRUE(test, new_dm_state->freesync_capable);
+ KUNIT_EXPECT_EQ(test, (int)new_dm_state->abm_level, 2);
+ KUNIT_EXPECT_EQ(test, new_dm_state->vcpi_slots, 4);
+ KUNIT_EXPECT_EQ(test, (int)new_dm_state->pbn, 1234);
+
+ kfree(new_dm_state);
+}
+
+/* Tests for amdgpu_dm_fill_hdr_info_packet() */
+
+/**
+ * dm_test_fill_hdr_null_metadata - Test fill_hdr returns 0 with no
+ * metadata
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_hdr_null_metadata(struct kunit *test)
+{
+ struct drm_connector_state state = {};
+ struct dc_info_packet out = {};
+
+ /* No hdr_output_metadata → early return 0, out stays zeroed */
+ state.hdr_output_metadata = NULL;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_fill_hdr_info_packet(&state, &out), 0);
+ KUNIT_EXPECT_FALSE(test, out.valid);
+}
+
+/**
+ * dm_test_fill_hdr_zeroes_output - Test fill_hdr zeroes output with no
+ * metadata
+ * @test: The KUnit test context
+ */
+static void dm_test_fill_hdr_zeroes_output(struct kunit *test)
+{
+ struct drm_connector_state state = {};
+ struct dc_info_packet out;
+
+ /* Pre-fill out with nonzero to verify memset(0) */
+ memset(&out, 0xAA, sizeof(out));
+
+ state.hdr_output_metadata = NULL;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_fill_hdr_info_packet(&state, &out), 0);
+ KUNIT_EXPECT_FALSE(test, out.valid);
+ KUNIT_EXPECT_EQ(test, (int)out.hb0, 0);
+ KUNIT_EXPECT_EQ(test, (int)out.hb1, 0);
+ KUNIT_EXPECT_EQ(test, (int)out.hb2, 0);
+ KUNIT_EXPECT_EQ(test, (int)out.hb3, 0);
+}
+
+/* Tests for amdgpu_dm_connector_atomic_set_property() */
+
+/*
+ * Build a connector wired to a kunit-allocated amdgpu_device so that
+ * drm_to_adev() resolves correctly, together with old/new dm states and
+ * the set of properties used by the get/set property handlers.
+ */
+struct dm_test_prop_ctx {
+ struct amdgpu_device *adev;
+ struct drm_connector *connector;
+ struct dm_connector_state *old_state;
+ struct dm_connector_state *new_state;
+ struct drm_property *scaling_prop;
+ struct drm_property *hborder_prop;
+ struct drm_property *vborder_prop;
+ struct drm_property *underscan_prop;
+ struct drm_property *abm_prop;
+};
+
+static struct dm_test_prop_ctx *dm_test_prop_ctx_alloc(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+ ctx->adev = kunit_kzalloc(test, sizeof(*ctx->adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->adev);
+ ctx->connector = kunit_kzalloc(test, sizeof(*ctx->connector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->connector);
+ ctx->old_state = kunit_kzalloc(test, sizeof(*ctx->old_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->old_state);
+ ctx->new_state = kunit_kzalloc(test, sizeof(*ctx->new_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->new_state);
+ ctx->scaling_prop = kunit_kzalloc(test, sizeof(*ctx->scaling_prop), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->scaling_prop);
+ ctx->hborder_prop = kunit_kzalloc(test, sizeof(*ctx->hborder_prop), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->hborder_prop);
+ ctx->vborder_prop = kunit_kzalloc(test, sizeof(*ctx->vborder_prop), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->vborder_prop);
+ ctx->underscan_prop = kunit_kzalloc(test, sizeof(*ctx->underscan_prop), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->underscan_prop);
+ ctx->abm_prop = kunit_kzalloc(test, sizeof(*ctx->abm_prop), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->abm_prop);
+
+ ctx->connector->dev = &ctx->adev->ddev;
+ ctx->connector->state = &ctx->old_state->base;
+
+ ctx->adev->ddev.mode_config.scaling_mode_property = ctx->scaling_prop;
+ ctx->adev->mode_info.underscan_hborder_property = ctx->hborder_prop;
+ ctx->adev->mode_info.underscan_vborder_property = ctx->vborder_prop;
+ ctx->adev->mode_info.underscan_property = ctx->underscan_prop;
+ ctx->adev->mode_info.abm_level_property = ctx->abm_prop;
+
+ return ctx;
+}
+
+/**
+ * dm_test_set_property_scaling_center - Test set scaling property to center
+ * @test: The KUnit test context
+ */
+static void dm_test_set_property_scaling_center(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->scaling_prop, DRM_MODE_SCALE_CENTER), 0);
+ KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_CENTER);
+}
+
+/**
+ * dm_test_set_property_scaling_aspect - Test set scaling property to aspect
+ * @test: The KUnit test context
+ */
+static void dm_test_set_property_scaling_aspect(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->scaling_prop, DRM_MODE_SCALE_ASPECT), 0);
+ KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_ASPECT);
+}
+
+/**
+ * dm_test_set_property_scaling_fullscreen - Test set scaling property to full
+ * @test: The KUnit test context
+ */
+static void dm_test_set_property_scaling_fullscreen(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->scaling_prop, DRM_MODE_SCALE_FULLSCREEN), 0);
+ KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_FULL);
+}
+
+/**
+ * dm_test_set_property_scaling_none - Test set scaling property to none
+ * @test: The KUnit test context
+ */
+static void dm_test_set_property_scaling_none(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+
+ /* old scaling is RMX_CENTER so RMX_OFF is a real change */
+ ctx->old_state->scaling = RMX_CENTER;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->scaling_prop, DRM_MODE_SCALE_NONE), 0);
+ KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_OFF);
+}
+
+/**
+ * dm_test_set_property_scaling_unchanged - Test set scaling property unchanged
+ * @test: The KUnit test context
+ */
+static void dm_test_set_property_scaling_unchanged(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+
+ /* old already RMX_OFF, requesting NONE/OFF returns 0 without write */
+ ctx->old_state->scaling = RMX_OFF;
+ ctx->new_state->scaling = RMX_CENTER;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->scaling_prop, DRM_MODE_SCALE_NONE), 0);
+ /* new_state untouched because of early return */
+ KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_CENTER);
+}
+
+/**
+ * dm_test_set_property_underscan_hborder - Test set underscan hborder
+ * @test: The KUnit test context
+ */
+static void dm_test_set_property_underscan_hborder(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->hborder_prop, 42), 0);
+ KUNIT_EXPECT_EQ(test, (int)ctx->new_state->underscan_hborder, 42);
+}
+
+/**
+ * dm_test_set_property_underscan_vborder - Test set underscan vborder
+ * @test: The KUnit test context
+ */
+static void dm_test_set_property_underscan_vborder(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->vborder_prop, 24), 0);
+ KUNIT_EXPECT_EQ(test, (int)ctx->new_state->underscan_vborder, 24);
+}
+
+/**
+ * dm_test_set_property_underscan_enable - Test set underscan enable
+ * @test: The KUnit test context
+ */
+static void dm_test_set_property_underscan_enable(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->underscan_prop, 1), 0);
+ KUNIT_EXPECT_TRUE(test, ctx->new_state->underscan_enable);
+}
+
+/**
+ * dm_test_set_property_abm_sysfs_control - Test set abm sysfs control
+ * @test: The KUnit test context
+ */
+static void dm_test_set_property_abm_sysfs_control(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+
+ ctx->new_state->abm_sysfs_forbidden = true;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->abm_prop, ABM_SYSFS_CONTROL), 0);
+ KUNIT_EXPECT_FALSE(test, ctx->new_state->abm_sysfs_forbidden);
+}
+
+/**
+ * dm_test_set_property_abm_level_off - Test set abm level off
+ * @test: The KUnit test context
+ */
+static void dm_test_set_property_abm_level_off(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->abm_prop, ABM_LEVEL_OFF), 0);
+ KUNIT_EXPECT_TRUE(test, ctx->new_state->abm_sysfs_forbidden);
+ KUNIT_EXPECT_EQ(test, (int)ctx->new_state->abm_level,
+ (int)ABM_LEVEL_IMMEDIATE_DISABLE);
+}
+
+/**
+ * dm_test_set_property_abm_level_value - Test set abm level to a value
+ * @test: The KUnit test context
+ */
+static void dm_test_set_property_abm_level_value(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->abm_prop, 3), 0);
+ KUNIT_EXPECT_TRUE(test, ctx->new_state->abm_sysfs_forbidden);
+ KUNIT_EXPECT_EQ(test, (int)ctx->new_state->abm_level, 3);
+}
+
+/**
+ * dm_test_set_property_unknown - Test set unknown property returns -EINVAL
+ * @test: The KUnit test context
+ */
+static void dm_test_set_property_unknown(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+ struct drm_property *other;
+
+ other = kunit_kzalloc(test, sizeof(*other), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, other);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property(
+ ctx->connector, &ctx->new_state->base,
+ other, 0), -EINVAL);
+}
+
+/* Tests for amdgpu_dm_connector_atomic_get_property() */
+
+/**
+ * dm_test_get_property_scaling_center - Test get scaling property center
+ * @test: The KUnit test context
+ */
+static void dm_test_get_property_scaling_center(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+ uint64_t val = 0;
+
+ ctx->new_state->scaling = RMX_CENTER;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->scaling_prop, &val), 0);
+ KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SCALE_CENTER);
+}
+
+/**
+ * dm_test_get_property_scaling_aspect - Test get scaling property aspect
+ * @test: The KUnit test context
+ */
+static void dm_test_get_property_scaling_aspect(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+ uint64_t val = 0;
+
+ ctx->new_state->scaling = RMX_ASPECT;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->scaling_prop, &val), 0);
+ KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SCALE_ASPECT);
+}
+
+/**
+ * dm_test_get_property_scaling_full - Test get scaling property fullscreen
+ * @test: The KUnit test context
+ */
+static void dm_test_get_property_scaling_full(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+ uint64_t val = 0;
+
+ ctx->new_state->scaling = RMX_FULL;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->scaling_prop, &val), 0);
+ KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SCALE_FULLSCREEN);
+}
+
+/**
+ * dm_test_get_property_scaling_off - Test get scaling property off/none
+ * @test: The KUnit test context
+ */
+static void dm_test_get_property_scaling_off(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+ uint64_t val = 0;
+
+ ctx->new_state->scaling = RMX_OFF;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->scaling_prop, &val), 0);
+ KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SCALE_NONE);
+}
+
+/**
+ * dm_test_get_property_underscan_borders - Test get underscan borders/enable
+ * @test: The KUnit test context
+ */
+static void dm_test_get_property_underscan_borders(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+ uint64_t val = 0;
+
+ ctx->new_state->underscan_hborder = 12;
+ ctx->new_state->underscan_vborder = 34;
+ ctx->new_state->underscan_enable = true;
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->hborder_prop, &val), 0);
+ KUNIT_EXPECT_EQ(test, (int)val, 12);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->vborder_prop, &val), 0);
+ KUNIT_EXPECT_EQ(test, (int)val, 34);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->underscan_prop, &val), 0);
+ KUNIT_EXPECT_EQ(test, (int)val, 1);
+}
+
+/**
+ * dm_test_get_property_abm_sysfs_allowed - Test get abm returns sysfs control
+ * @test: The KUnit test context
+ */
+static void dm_test_get_property_abm_sysfs_allowed(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+ uint64_t val = 0;
+
+ ctx->new_state->abm_sysfs_forbidden = false;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->abm_prop, &val), 0);
+ KUNIT_EXPECT_EQ(test, (int)val, (int)ABM_SYSFS_CONTROL);
+}
+
+/**
+ * dm_test_get_property_abm_level - Test get abm returns level when forbidden
+ * @test: The KUnit test context
+ */
+static void dm_test_get_property_abm_level(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+ uint64_t val = 0;
+
+ ctx->new_state->abm_sysfs_forbidden = true;
+ ctx->new_state->abm_level = 2;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->abm_prop, &val), 0);
+ KUNIT_EXPECT_EQ(test, (int)val, 2);
+}
+
+/**
+ * dm_test_get_property_abm_disabled_zero - Test get abm returns 0 when disabled
+ * @test: The KUnit test context
+ */
+static void dm_test_get_property_abm_disabled_zero(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+ uint64_t val = 0xdead;
+
+ ctx->new_state->abm_sysfs_forbidden = true;
+ ctx->new_state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE;
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property(
+ ctx->connector, &ctx->new_state->base,
+ ctx->abm_prop, &val), 0);
+ KUNIT_EXPECT_EQ(test, (int)val, 0);
+}
+
+/**
+ * dm_test_get_property_unknown - Test get unknown property returns -EINVAL
+ * @test: The KUnit test context
+ */
+static void dm_test_get_property_unknown(struct kunit *test)
+{
+ struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test);
+ struct drm_property *other;
+ uint64_t val = 0;
+
+ other = kunit_kzalloc(test, sizeof(*other), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, other);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property(
+ ctx->connector, &ctx->new_state->base,
+ other, &val), -EINVAL);
+}
+
+/* Tests for amdgpu_dm_get_highest_refresh_rate_mode() */
+
+/**
+ * dm_test_highest_refresh_writeback_null - Test writeback connector returns NULL
+ * @test: The KUnit test context
+ */
+static void dm_test_highest_refresh_writeback_null(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK;
+ KUNIT_EXPECT_NULL(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false));
+}
+
+/**
+ * dm_test_highest_refresh_cached_base - Test cached freesync_vid_base is returned
+ * @test: The KUnit test context
+ */
+static void dm_test_highest_refresh_cached_base(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ aconnector->freesync_vid_base.clock = 148500;
+
+ KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false),
+ &aconnector->freesync_vid_base);
+}
+
+/**
+ * dm_test_highest_refresh_preferred_mode - Test preferred mode is selected
+ * @test: The KUnit test context
+ */
+static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode *mode;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ INIT_LIST_HEAD(&aconnector->base.modes);
+
+ mode->type = DRM_MODE_TYPE_PREFERRED;
+ mode->clock = 148500;
+ mode->hdisplay = 1920;
+ mode->vdisplay = 1080;
+ mode->htotal = 2200;
+ mode->vtotal = 1125;
+ list_add_tail(&mode->head, &aconnector->base.modes);
+
+ KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false),
+ mode);
+}
+
+/* Tests for amdgpu_dm_is_freesync_video_mode() */
+
+/**
+ * dm_test_is_freesync_video_mode_null_mode - Test NULL mode returns false
+ * @test: The KUnit test context
+ */
+static void dm_test_is_freesync_video_mode_null_mode(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ aconnector->freesync_vid_base.clock = 148500;
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(NULL, aconnector));
+}
+
+/**
+ * dm_test_is_freesync_video_mode_match - Test matching mode returns true
+ * @test: The KUnit test context
+ */
+static void dm_test_is_freesync_video_mode_match(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode candidate = {};
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ /* Cached high mode acts as reference */
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ aconnector->freesync_vid_base.clock = 148500;
+ aconnector->freesync_vid_base.hdisplay = 1920;
+ aconnector->freesync_vid_base.vdisplay = 1080;
+ aconnector->freesync_vid_base.hsync_start = 2008;
+ aconnector->freesync_vid_base.hsync_end = 2052;
+ aconnector->freesync_vid_base.htotal = 2200;
+ aconnector->freesync_vid_base.vsync_start = 1084;
+ aconnector->freesync_vid_base.vsync_end = 1089;
+ aconnector->freesync_vid_base.vtotal = 1125;
+
+ candidate.clock = 148500;
+ candidate.hdisplay = 1920;
+ candidate.vdisplay = 1080;
+ candidate.hsync_start = 2008;
+ candidate.hsync_end = 2052;
+ candidate.htotal = 2200;
+ candidate.vsync_start = 1084;
+ candidate.vsync_end = 1089;
+ candidate.vtotal = 1125;
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector));
+}
+
+/**
+ * dm_test_is_freesync_video_mode_no_match - Test mismatched mode returns false
+ * @test: The KUnit test context
+ */
+static void dm_test_is_freesync_video_mode_no_match(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode candidate = {};
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ aconnector->freesync_vid_base.clock = 148500;
+ aconnector->freesync_vid_base.hdisplay = 1920;
+ aconnector->freesync_vid_base.vdisplay = 1080;
+ aconnector->freesync_vid_base.htotal = 2200;
+ aconnector->freesync_vid_base.vtotal = 1125;
+
+ /* Different resolution → not a freesync video mode */
+ candidate.clock = 148500;
+ candidate.hdisplay = 1280;
+ candidate.vdisplay = 720;
+ candidate.htotal = 1650;
+ candidate.vtotal = 750;
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector));
+}
+
+static struct kunit_case amdgpu_dm_connector_tests[] = {
+ /* get_subconnector_type */
+ KUNIT_CASE(dm_test_subconnector_type_none),
+ KUNIT_CASE(dm_test_subconnector_type_vga),
+ KUNIT_CASE(dm_test_subconnector_type_dvi_converter),
+ KUNIT_CASE(dm_test_subconnector_type_dvi_dongle),
+ KUNIT_CASE(dm_test_subconnector_type_hdmi_converter),
+ KUNIT_CASE(dm_test_subconnector_type_hdmi_dongle),
+ KUNIT_CASE(dm_test_subconnector_type_mismatched),
+ KUNIT_CASE(dm_test_subconnector_type_default_unknown),
+ /* get_output_content_type */
+ KUNIT_CASE(dm_test_content_type_no_data),
+ KUNIT_CASE(dm_test_content_type_graphics),
+ KUNIT_CASE(dm_test_content_type_photo),
+ KUNIT_CASE(dm_test_content_type_cinema),
+ KUNIT_CASE(dm_test_content_type_game),
+ KUNIT_CASE(dm_test_content_type_unknown_defaults_no_data),
+ /* adjust_colour_depth_from_display_info */
+ KUNIT_CASE(dm_test_adjust_colour_depth_fits_at_888),
+ KUNIT_CASE(dm_test_adjust_colour_depth_reduces_to_888),
+ KUNIT_CASE(dm_test_adjust_colour_depth_10bpc_passes),
+ KUNIT_CASE(dm_test_adjust_colour_depth_420_halves_clk),
+ KUNIT_CASE(dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc),
+ KUNIT_CASE(dm_test_adjust_colour_depth_16bpc_no_fallback),
+ KUNIT_CASE(dm_test_adjust_colour_depth_none_fits),
+ KUNIT_CASE(dm_test_adjust_colour_depth_invalid_depth),
+ /* amdgpu_dm_get_output_color_space */
+ KUNIT_CASE(dm_test_output_color_space_default_rgb_full),
+ KUNIT_CASE(dm_test_output_color_space_default_rgb_limited),
+ KUNIT_CASE(dm_test_output_color_space_default_ycbcr709),
+ KUNIT_CASE(dm_test_output_color_space_default_ycbcr601_limited),
+ KUNIT_CASE(dm_test_output_color_space_bt601_y_only),
+ KUNIT_CASE(dm_test_output_color_space_bt601),
+ KUNIT_CASE(dm_test_output_color_space_bt709),
+ KUNIT_CASE(dm_test_output_color_space_bt709_y_only),
+ KUNIT_CASE(dm_test_output_color_space_oprgb),
+ KUNIT_CASE(dm_test_output_color_space_bt2020_rgb),
+ KUNIT_CASE(dm_test_output_color_space_bt2020_ycc),
+ /* Tests for amdgpu_dm_convert_dc_color_depth_into_bpc */
+ KUNIT_CASE(dm_test_convert_color_depth_bpc_mappings),
+ KUNIT_CASE(dm_test_convert_color_depth_bpc_unknown),
+ /* amdgpu_dm_convert_color_depth_from_display_info */
+ KUNIT_CASE(dm_test_color_depth_from_info_bpc8),
+ KUNIT_CASE(dm_test_color_depth_from_info_bpc10),
+ KUNIT_CASE(dm_test_color_depth_from_info_zero_bpc_defaults_888),
+ KUNIT_CASE(dm_test_color_depth_from_info_requested_bpc_caps),
+ KUNIT_CASE(dm_test_color_depth_from_info_y420_default),
+ KUNIT_CASE(dm_test_color_depth_from_info_y420_10bpc),
+ KUNIT_CASE(dm_test_color_depth_from_info_y420_12bpc),
+ KUNIT_CASE(dm_test_color_depth_from_info_y420_16bpc),
+ KUNIT_CASE(dm_test_color_depth_from_info_requested_odd_bpc),
+ KUNIT_CASE(dm_test_color_depth_from_info_unsupported_bpc),
+ /* to_drm_connector_type */
+ KUNIT_CASE(dm_test_to_connector_type_hdmi),
+ KUNIT_CASE(dm_test_to_connector_type_edp),
+ KUNIT_CASE(dm_test_to_connector_type_lvds),
+ KUNIT_CASE(dm_test_to_connector_type_rgb),
+ KUNIT_CASE(dm_test_to_connector_type_dp),
+ KUNIT_CASE(dm_test_to_connector_type_dp_mst),
+ KUNIT_CASE(dm_test_to_connector_type_dvi_dvii),
+ KUNIT_CASE(dm_test_to_connector_type_dual_link_dvii),
+ KUNIT_CASE(dm_test_to_connector_type_dvi_dvid),
+ KUNIT_CASE(dm_test_to_connector_type_virtual),
+ KUNIT_CASE(dm_test_to_connector_type_unknown),
+ /* is_duplicate_mode */
+ KUNIT_CASE(dm_test_is_duplicate_mode_empty_list),
+ KUNIT_CASE(dm_test_is_duplicate_mode_match),
+ KUNIT_CASE(dm_test_is_duplicate_mode_no_match),
+ KUNIT_CASE(dm_test_is_duplicate_mode_same_size_different_clock),
+ /* amdgpu_dm_get_encoder_crtc_mask */
+ KUNIT_CASE(dm_test_encoder_crtc_mask_1),
+ KUNIT_CASE(dm_test_encoder_crtc_mask_2),
+ KUNIT_CASE(dm_test_encoder_crtc_mask_3),
+ KUNIT_CASE(dm_test_encoder_crtc_mask_4),
+ KUNIT_CASE(dm_test_encoder_crtc_mask_5),
+ KUNIT_CASE(dm_test_encoder_crtc_mask_6),
+ KUNIT_CASE(dm_test_encoder_crtc_mask_default),
+ /* get_aspect_ratio */
+ KUNIT_CASE(dm_test_aspect_ratio_no_data),
+ KUNIT_CASE(dm_test_aspect_ratio_4_3),
+ KUNIT_CASE(dm_test_aspect_ratio_16_9),
+ KUNIT_CASE(dm_test_aspect_ratio_64_27),
+ KUNIT_CASE(dm_test_aspect_ratio_256_135),
+ /* decide_crtc_timing_for_drm_display_mode */
+ KUNIT_CASE(dm_test_decide_crtc_timing_scale_enabled),
+ KUNIT_CASE(dm_test_decide_crtc_timing_matching_mode),
+ KUNIT_CASE(dm_test_decide_crtc_timing_no_copy),
+ KUNIT_CASE(dm_test_decide_crtc_timing_no_crtc_clock),
+ /* amdgpu_dm_connector_funcs_reset */
+ KUNIT_CASE(dm_test_funcs_reset_sets_defaults),
+ KUNIT_CASE(dm_test_funcs_reset_edp_abm_level),
+ KUNIT_CASE(dm_test_funcs_reset_edp_abm_disabled),
+ /* amdgpu_dm_connector_atomic_duplicate_state */
+ KUNIT_CASE(dm_test_atomic_dup_state_copies_fields),
+ /* amdgpu_dm_fill_hdr_info_packet */
+ KUNIT_CASE(dm_test_fill_hdr_null_metadata),
+ KUNIT_CASE(dm_test_fill_hdr_zeroes_output),
+ /* amdgpu_dm_connector_atomic_set_property */
+ KUNIT_CASE(dm_test_set_property_scaling_center),
+ KUNIT_CASE(dm_test_set_property_scaling_aspect),
+ KUNIT_CASE(dm_test_set_property_scaling_fullscreen),
+ KUNIT_CASE(dm_test_set_property_scaling_none),
+ KUNIT_CASE(dm_test_set_property_scaling_unchanged),
+ KUNIT_CASE(dm_test_set_property_underscan_hborder),
+ KUNIT_CASE(dm_test_set_property_underscan_vborder),
+ KUNIT_CASE(dm_test_set_property_underscan_enable),
+ KUNIT_CASE(dm_test_set_property_abm_sysfs_control),
+ KUNIT_CASE(dm_test_set_property_abm_level_off),
+ KUNIT_CASE(dm_test_set_property_abm_level_value),
+ KUNIT_CASE(dm_test_set_property_unknown),
+ /* amdgpu_dm_connector_atomic_get_property */
+ KUNIT_CASE(dm_test_get_property_scaling_center),
+ KUNIT_CASE(dm_test_get_property_scaling_aspect),
+ KUNIT_CASE(dm_test_get_property_scaling_full),
+ KUNIT_CASE(dm_test_get_property_scaling_off),
+ KUNIT_CASE(dm_test_get_property_underscan_borders),
+ KUNIT_CASE(dm_test_get_property_abm_sysfs_allowed),
+ KUNIT_CASE(dm_test_get_property_abm_level),
+ KUNIT_CASE(dm_test_get_property_abm_disabled_zero),
+ KUNIT_CASE(dm_test_get_property_unknown),
+ /* amdgpu_dm_get_highest_refresh_rate_mode */
+ KUNIT_CASE(dm_test_highest_refresh_writeback_null),
+ KUNIT_CASE(dm_test_highest_refresh_cached_base),
+ KUNIT_CASE(dm_test_highest_refresh_preferred_mode),
+ /* amdgpu_dm_is_freesync_video_mode */
+ KUNIT_CASE(dm_test_is_freesync_video_mode_null_mode),
+ KUNIT_CASE(dm_test_is_freesync_video_mode_match),
+ KUNIT_CASE(dm_test_is_freesync_video_mode_no_match),
+ {}
+};
+
+static struct kunit_suite amdgpu_dm_connector_test_suite = {
+ .name = "amdgpu_dm_connector",
+ .test_cases = amdgpu_dm_connector_tests,
+};
+
+kunit_test_suite(amdgpu_dm_connector_test_suite);
+
+MODULE_AUTHOR("AMD");
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_connector");
+MODULE_LICENSE("Dual MIT/GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 22/32] drm/amd/display: Add KUnit tests for amdgpu_dm_irq
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (20 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 21/32] drm/amd/display: Add KUnit tests for amdgpu_dm_connector Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 23/32] drm/amd/display: Add KUnit tests for amdgpu_dm_crtc Chenyu Chen
` (10 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Add KUnit tests for helper functions, IRQ table management paths, and
DRM mock-backed CRTC lookup in amdgpu_dm_irq.c.
Tests cover:
- amdgpu_dm_hpd_to_dal_irq_source(): all HPD types 1-6,
AMDGPU_HPD_NONE, and out-of-range values
- are_sinks_equal(): NULL inputs, signal mismatch, EDID
length mismatch, EDID data mismatch, identical sinks,
zero-length EDID, full-length identical EDID, and a
single trailing-byte difference
- dmub_notification_type_str(): notification type mappings that are
always built, plus the unknown/default case
- amdgpu_dm_irq_init(): low/high handler list initialization
- amdgpu_dm_irq_register_interrupt(): NULL input rejection,
invalid context/source rejection, low/high handler insertion,
multiple handlers on one source, and the same handler registered
in both low and high contexts
- amdgpu_dm_irq_unregister_interrupt(): invalid source and NULL
handler rejection, removal of registered low/high handlers, and
the handler-not-found path
- amdgpu_dm_irq_fini(): cleanup of registered low/high handlers and
the empty-table case
- amdgpu_dm_get_crtc_by_otg_inst(): DRM mock CRTC list match,
no-match, and empty-list paths
Assisted-by: Copilot:Claude-Opus-4
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 15 +-
.../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 8 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 1 +
.../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 934 ++++++++++++++++++
4 files changed, 955 insertions(+), 3 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c
index 2433180d014f..4c4624f3a122 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c
@@ -33,6 +33,7 @@
#include "amdgpu_display.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_irq.h"
+#include "amdgpu_dm_kunit_helpers.h"
#include "amdgpu_dm_crtc.h"
#include "amdgpu_dm_hdcp.h"
#include "amdgpu_dm_mst_types.h"
@@ -372,6 +373,7 @@ void *amdgpu_dm_irq_register_interrupt(struct amdgpu_device *adev,
return handler_data;
}
+EXPORT_IF_KUNIT(amdgpu_dm_irq_register_interrupt);
/**
* amdgpu_dm_irq_unregister_interrupt() - Remove a handler from the DM IRQ table
@@ -416,6 +418,7 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev,
ih, irq_source);
}
}
+EXPORT_IF_KUNIT(amdgpu_dm_irq_unregister_interrupt);
/**
* amdgpu_dm_irq_init() - Initialize DM IRQ management
@@ -450,6 +453,7 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev)
return 0;
}
+EXPORT_IF_KUNIT(amdgpu_dm_irq_init);
/**
* amdgpu_dm_irq_fini() - Tear down DM IRQ management
@@ -488,6 +492,7 @@ void amdgpu_dm_irq_fini(struct amdgpu_device *adev)
/* Deallocate handlers from the table. */
unregister_all_irq_handlers(adev);
}
+EXPORT_IF_KUNIT(amdgpu_dm_irq_fini);
void amdgpu_dm_irq_suspend(struct amdgpu_device *adev)
{
@@ -690,7 +695,7 @@ static int amdgpu_dm_irq_handler(struct amdgpu_device *adev,
return 0;
}
-static enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type)
+STATIC_IFN_KUNIT enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type)
{
switch (type) {
case AMDGPU_HPD_1:
@@ -709,6 +714,7 @@ static enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type)
return DC_IRQ_SOURCE_INVALID;
}
}
+EXPORT_IF_KUNIT(amdgpu_dm_hpd_to_dal_irq_source);
static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev,
struct amdgpu_irq_src *source,
@@ -1192,7 +1198,7 @@ void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm)
}
}
-static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2)
+STATIC_IFN_KUNIT bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2)
{
if (!sink1 || !sink2)
return false;
@@ -1207,6 +1213,7 @@ static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *s
return false;
return true;
}
+EXPORT_IF_KUNIT(are_sinks_equal);
/**
@@ -1693,6 +1700,7 @@ amdgpu_dm_get_crtc_by_otg_inst(struct amdgpu_device *adev,
return NULL;
}
+EXPORT_IF_KUNIT(amdgpu_dm_get_crtc_by_otg_inst);
/**
* dm_pflip_high_irq() - Handle pageflip interrupt
@@ -2067,7 +2075,7 @@ static void dm_handle_hpd_work(struct work_struct *work)
}
-static const char *dmub_notification_type_str(enum dmub_notification_type e)
+STATIC_IFN_KUNIT const char *dmub_notification_type_str(enum dmub_notification_type e)
{
switch (e) {
case DMUB_NOTIFICATION_NO_DATA:
@@ -2090,6 +2098,7 @@ static const char *dmub_notification_type_str(enum dmub_notification_type e)
return "<unknown>";
}
}
+EXPORT_IF_KUNIT(dmub_notification_type_str);
#define DMUB_TRACE_MAX_READ 64
/**
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h
index ba6968f5626f..bccb5d354a9f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h
@@ -30,8 +30,10 @@
struct amdgpu_device;
struct amdgpu_crtc;
struct amdgpu_display_manager;
+struct dc_sink;
struct hpd_rx_irq_offload_work_queue;
struct work_struct;
+enum dmub_notification_type;
/*
* Display Manager IRQ-related interfaces (for use by DAL).
@@ -120,4 +122,10 @@ int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev);
int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev);
int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev);
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type);
+bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2);
+const char *dmub_notification_type_str(enum dmub_notification_type e);
+#endif
+
#endif /* __AMDGPU_DM_IRQ_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index 422eef0bfe49..583604914753 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_dmub_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
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_irq_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c
new file mode 100644
index 000000000000..525caa0b1f6a
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c
@@ -0,0 +1,934 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_irq.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+#include <drm/drm_kunit_helpers.h>
+
+#include "dc.h"
+#include "amdgpu.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_irq.h"
+#include "dmub/dmub_srv.h"
+
+static void dm_test_irq_handler(void *arg)
+{
+}
+
+static void dm_test_irq_handler_alt(void *arg)
+{
+}
+
+static void dm_test_crtc_list_del(void *data)
+{
+ struct amdgpu_crtc *acrtc = data;
+
+ list_del_init(&acrtc->base.head);
+}
+
+/* Tests for amdgpu_dm_hpd_to_dal_irq_source() */
+
+/**
+ * dm_test_hpd_to_dal_irq_source_hpd1 - Test Hpd to dal irq source hpd1
+ * @test: The KUnit test context
+ */
+static void dm_test_hpd_to_dal_irq_source_hpd1(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_1),
+ (int)DC_IRQ_SOURCE_HPD1);
+}
+
+/**
+ * dm_test_hpd_to_dal_irq_source_hpd2 - Test Hpd to dal irq source hpd2
+ * @test: The KUnit test context
+ */
+static void dm_test_hpd_to_dal_irq_source_hpd2(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_2),
+ (int)DC_IRQ_SOURCE_HPD2);
+}
+
+/**
+ * dm_test_hpd_to_dal_irq_source_hpd3 - Test Hpd to dal irq source hpd3
+ * @test: The KUnit test context
+ */
+static void dm_test_hpd_to_dal_irq_source_hpd3(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_3),
+ (int)DC_IRQ_SOURCE_HPD3);
+}
+
+/**
+ * dm_test_hpd_to_dal_irq_source_hpd4 - Test Hpd to dal irq source hpd4
+ * @test: The KUnit test context
+ */
+static void dm_test_hpd_to_dal_irq_source_hpd4(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_4),
+ (int)DC_IRQ_SOURCE_HPD4);
+}
+
+/**
+ * dm_test_hpd_to_dal_irq_source_hpd5 - Test Hpd to dal irq source hpd5
+ * @test: The KUnit test context
+ */
+static void dm_test_hpd_to_dal_irq_source_hpd5(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_5),
+ (int)DC_IRQ_SOURCE_HPD5);
+}
+
+/**
+ * dm_test_hpd_to_dal_irq_source_hpd6 - Test Hpd to dal irq source hpd6
+ * @test: The KUnit test context
+ */
+static void dm_test_hpd_to_dal_irq_source_hpd6(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_6),
+ (int)DC_IRQ_SOURCE_HPD6);
+}
+
+/**
+ * dm_test_hpd_to_dal_irq_source_invalid - Test Hpd to dal irq source invalid
+ * @test: The KUnit test context
+ */
+static void dm_test_hpd_to_dal_irq_source_invalid(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_NONE),
+ (int)DC_IRQ_SOURCE_INVALID);
+}
+
+/**
+ * dm_test_hpd_to_dal_irq_source_out_of_range - Test Hpd to dal irq source out of range
+ * @test: The KUnit test context
+ */
+static void dm_test_hpd_to_dal_irq_source_out_of_range(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(99),
+ (int)DC_IRQ_SOURCE_INVALID);
+}
+
+/* Tests for are_sinks_equal() */
+
+/**
+ * dm_test_are_sinks_equal_both_null - Test Are sinks equal both null
+ * @test: The KUnit test context
+ */
+static void dm_test_are_sinks_equal_both_null(struct kunit *test)
+{
+ KUNIT_EXPECT_FALSE(test, are_sinks_equal(NULL, NULL));
+}
+
+/**
+ * dm_test_are_sinks_equal_first_null - Test Are sinks equal first null
+ * @test: The KUnit test context
+ */
+static void dm_test_are_sinks_equal_first_null(struct kunit *test)
+{
+ struct dc_sink *sink2;
+
+ sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2);
+
+ KUNIT_EXPECT_FALSE(test, are_sinks_equal(NULL, sink2));
+}
+
+/**
+ * dm_test_are_sinks_equal_second_null - Test Are sinks equal second null
+ * @test: The KUnit test context
+ */
+static void dm_test_are_sinks_equal_second_null(struct kunit *test)
+{
+ struct dc_sink *sink1;
+
+ sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1);
+
+ KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, NULL));
+}
+
+/**
+ * dm_test_are_sinks_equal_different_signal - Test Are sinks equal different signal
+ * @test: The KUnit test context
+ */
+static void dm_test_are_sinks_equal_different_signal(struct kunit *test)
+{
+ struct dc_sink *sink1, *sink2;
+
+ sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1);
+ sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2);
+
+ sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ sink2->sink_signal = SIGNAL_TYPE_DISPLAY_PORT;
+
+ KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, sink2));
+}
+
+/**
+ * dm_test_are_sinks_equal_different_edid_length - Test Are sinks equal different edid length
+ * @test: The KUnit test context
+ */
+static void dm_test_are_sinks_equal_different_edid_length(struct kunit *test)
+{
+ struct dc_sink *sink1, *sink2;
+
+ sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1);
+ sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2);
+
+ sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ sink1->dc_edid.length = 128;
+ sink2->dc_edid.length = 256;
+
+ KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, sink2));
+}
+
+/**
+ * dm_test_are_sinks_equal_different_edid_data - Test Are sinks equal different edid data
+ * @test: The KUnit test context
+ */
+static void dm_test_are_sinks_equal_different_edid_data(struct kunit *test)
+{
+ struct dc_sink *sink1, *sink2;
+
+ sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1);
+ sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2);
+
+ sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ sink1->dc_edid.length = 4;
+ sink2->dc_edid.length = 4;
+ memset(sink1->dc_edid.raw_edid, 0xAA, 4);
+ memset(sink2->dc_edid.raw_edid, 0xBB, 4);
+
+ KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, sink2));
+}
+
+/**
+ * dm_test_are_sinks_equal_identical - Test Are sinks equal identical
+ * @test: The KUnit test context
+ */
+static void dm_test_are_sinks_equal_identical(struct kunit *test)
+{
+ struct dc_sink *sink1, *sink2;
+
+ sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1);
+ sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2);
+
+ sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ sink1->dc_edid.length = 4;
+ sink2->dc_edid.length = 4;
+ memset(sink1->dc_edid.raw_edid, 0xAA, 4);
+ memset(sink2->dc_edid.raw_edid, 0xAA, 4);
+
+ KUNIT_EXPECT_TRUE(test, are_sinks_equal(sink1, sink2));
+}
+
+/**
+ * dm_test_are_sinks_equal_zero_length - Test Are sinks equal zero length
+ * @test: The KUnit test context
+ */
+static void dm_test_are_sinks_equal_zero_length(struct kunit *test)
+{
+ struct dc_sink *sink1, *sink2;
+
+ sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1);
+ sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2);
+
+ sink1->sink_signal = SIGNAL_TYPE_DISPLAY_PORT;
+ sink2->sink_signal = SIGNAL_TYPE_DISPLAY_PORT;
+ sink1->dc_edid.length = 0;
+ sink2->dc_edid.length = 0;
+
+ KUNIT_EXPECT_TRUE(test, are_sinks_equal(sink1, sink2));
+}
+
+/**
+ * dm_test_are_sinks_equal_full_edid_identical - Test Are sinks equal full edid identical
+ * @test: The KUnit test context
+ */
+static void dm_test_are_sinks_equal_full_edid_identical(struct kunit *test)
+{
+ struct dc_sink *sink1, *sink2;
+
+ sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1);
+ sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2);
+
+ sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ sink1->dc_edid.length = 128;
+ sink2->dc_edid.length = 128;
+ memset(sink1->dc_edid.raw_edid, 0x5A, 128);
+ memset(sink2->dc_edid.raw_edid, 0x5A, 128);
+
+ KUNIT_EXPECT_TRUE(test, are_sinks_equal(sink1, sink2));
+}
+
+/**
+ * dm_test_are_sinks_equal_full_edid_last_byte_differs - Test Are sinks equal last byte differs
+ * @test: The KUnit test context
+ */
+static void dm_test_are_sinks_equal_full_edid_last_byte_differs(struct kunit *test)
+{
+ struct dc_sink *sink1, *sink2;
+
+ sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1);
+ sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2);
+
+ sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+ sink1->dc_edid.length = 128;
+ sink2->dc_edid.length = 128;
+ memset(sink1->dc_edid.raw_edid, 0x5A, 128);
+ memset(sink2->dc_edid.raw_edid, 0x5A, 128);
+ sink2->dc_edid.raw_edid[127] = 0x5B;
+
+ KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, sink2));
+}
+
+/* Tests for dmub_notification_type_str() */
+
+/**
+ * dm_test_notification_str_no_data - Test Notification str no data
+ * @test: The KUnit test context
+ */
+static void dm_test_notification_str_no_data(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_NO_DATA), "NO_DATA");
+}
+
+/**
+ * dm_test_notification_str_aux_reply - Test Notification str aux reply
+ * @test: The KUnit test context
+ */
+static void dm_test_notification_str_aux_reply(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_AUX_REPLY), "AUX_REPLY");
+}
+
+/**
+ * dm_test_notification_str_hpd - Test Notification str hpd
+ * @test: The KUnit test context
+ */
+static void dm_test_notification_str_hpd(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_HPD), "HPD");
+}
+
+/**
+ * dm_test_notification_str_hpd_irq - Test Notification str hpd irq
+ * @test: The KUnit test context
+ */
+static void dm_test_notification_str_hpd_irq(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_HPD_IRQ), "HPD_IRQ");
+}
+
+/**
+ * dm_test_notification_str_set_config - Test Notification str set config
+ * @test: The KUnit test context
+ */
+static void dm_test_notification_str_set_config(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_SET_CONFIG_REPLY),
+ "SET_CONFIG_REPLY");
+}
+
+/**
+ * dm_test_notification_str_dpia - Test Notification str dpia
+ * @test: The KUnit test context
+ */
+static void dm_test_notification_str_dpia(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_DPIA_NOTIFICATION),
+ "DPIA_NOTIFICATION");
+}
+
+/**
+ * dm_test_notification_str_hpd_sense - Test Notification str hpd sense
+ * @test: The KUnit test context
+ */
+static void dm_test_notification_str_hpd_sense(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_HPD_SENSE_NOTIFY),
+ "HPD_SENSE_NOTIFY");
+}
+
+/**
+ * dm_test_notification_str_fused_io - Test Notification str fused io
+ * @test: The KUnit test context
+ */
+static void dm_test_notification_str_fused_io(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_FUSED_IO),
+ "FUSED_IO");
+}
+
+/**
+ * dm_test_notification_str_unknown - Test Notification str unknown
+ * @test: The KUnit test context
+ */
+static void dm_test_notification_str_unknown(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_MAX), "<unknown>");
+}
+
+/* Tests for amdgpu_dm_irq_init() */
+
+/**
+ * dm_test_irq_init_initializes_lists - Test irq init initializes list heads
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_init_initializes_lists(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ int src;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+ for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) {
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_low_tab[src]));
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_high_tab[src]));
+ }
+}
+
+/* Tests for amdgpu_dm_irq_register_interrupt() */
+
+/**
+ * dm_test_irq_register_rejects_null_params - Test register rejects null params
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_register_rejects_null_params(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dc_interrupt_params int_params = { 0 };
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
+ int_params.irq_source = DC_IRQ_SOURCE_HPD1;
+
+ KUNIT_EXPECT_NULL(test,
+ amdgpu_dm_irq_register_interrupt(adev, NULL,
+ dm_test_irq_handler, NULL));
+ KUNIT_EXPECT_NULL(test,
+ amdgpu_dm_irq_register_interrupt(adev, &int_params, NULL, NULL));
+}
+
+/**
+ * dm_test_irq_register_rejects_invalid_context - Test register rejects context
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_register_rejects_invalid_context(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dc_interrupt_params int_params = { 0 };
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ int_params.int_context = INTERRUPT_CONTEXT_NUMBER;
+ int_params.irq_source = DC_IRQ_SOURCE_HPD1;
+
+ KUNIT_EXPECT_NULL(test,
+ amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_test_irq_handler, NULL));
+}
+
+/**
+ * dm_test_irq_register_rejects_invalid_source - Test register rejects source
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_register_rejects_invalid_source(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dc_interrupt_params int_params = { 0 };
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
+ int_params.irq_source = DC_IRQ_SOURCE_INVALID;
+
+ KUNIT_EXPECT_NULL(test,
+ amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_test_irq_handler, NULL));
+}
+
+/**
+ * dm_test_irq_register_adds_low_context_handler - Test register adds low handler
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_register_adds_low_context_handler(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dc_interrupt_params int_params = { 0 };
+ void *handler;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
+ int_params.irq_source = DC_IRQ_SOURCE_HPD1;
+
+ handler = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_test_irq_handler, adev);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+ KUNIT_EXPECT_FALSE(test,
+ list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1]));
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD1]));
+
+ amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1,
+ dm_test_irq_handler);
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1]));
+}
+
+/**
+ * dm_test_irq_register_adds_high_context_handler - Test register adds high handler
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_register_adds_high_context_handler(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dc_interrupt_params int_params = { 0 };
+ void *handler;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+ int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+ int_params.irq_source = DC_IRQ_SOURCE_HPD2;
+
+ handler = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_test_irq_handler, adev);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+ KUNIT_EXPECT_FALSE(test,
+ list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD2]));
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD2]));
+
+ amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD2,
+ dm_test_irq_handler);
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD2]));
+}
+
+/**
+ * dm_test_irq_register_multiple_handlers - Test register keeps multiple handlers
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_register_multiple_handlers(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dc_interrupt_params int_params = { 0 };
+ struct list_head *hnd_list;
+ void *handler1, *handler2;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
+ int_params.irq_source = DC_IRQ_SOURCE_HPD1;
+
+ handler1 = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_test_irq_handler, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler1);
+ handler2 = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_test_irq_handler_alt, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler2);
+
+ hnd_list = &adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1];
+ KUNIT_EXPECT_EQ(test, list_count_nodes(hnd_list), 2);
+
+ amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1,
+ dm_test_irq_handler);
+ amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1,
+ dm_test_irq_handler_alt);
+ KUNIT_EXPECT_TRUE(test, list_empty(hnd_list));
+}
+
+/**
+ * dm_test_irq_register_separate_contexts - Test register same source in two contexts
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_register_separate_contexts(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dc_interrupt_params int_params = { 0 };
+ void *handler;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+ int_params.irq_source = DC_IRQ_SOURCE_HPD5;
+
+ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
+ handler = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_test_irq_handler, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+
+ int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+ handler = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_test_irq_handler, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+
+ KUNIT_EXPECT_FALSE(test,
+ list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD5]));
+ KUNIT_EXPECT_FALSE(test,
+ list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD5]));
+
+ /*
+ * A single unregister call stops at the first context where the handler
+ * is found (low context), leaving the high context handler in place.
+ */
+ amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD5,
+ dm_test_irq_handler);
+
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD5]));
+ KUNIT_EXPECT_FALSE(test,
+ list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD5]));
+
+ /* A second call removes the remaining high context handler. */
+ amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD5,
+ dm_test_irq_handler);
+
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD5]));
+}
+
+/* Tests for amdgpu_dm_irq_unregister_interrupt() */
+
+/**
+ * dm_test_irq_unregister_rejects_invalid_source - Test unregister rejects source
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_unregister_rejects_invalid_source(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+ amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_INVALID,
+ dm_test_irq_handler);
+
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1]));
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD1]));
+}
+
+/**
+ * dm_test_irq_unregister_rejects_null_handler - Test unregister rejects handler
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_unregister_rejects_null_handler(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+ amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1,
+ DAL_INVALID_IRQ_HANDLER_IDX);
+
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1]));
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD1]));
+}
+
+/**
+ * dm_test_irq_unregister_handler_not_found - Test unregister keeps unmatched handler
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_unregister_handler_not_found(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dc_interrupt_params int_params = { 0 };
+ void *handler;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
+ int_params.irq_source = DC_IRQ_SOURCE_HPD1;
+ handler = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_test_irq_handler, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+
+ /* Unregister a handler that was never registered for this source. */
+ amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1,
+ dm_test_irq_handler_alt);
+
+ /* The originally registered handler must still be present. */
+ KUNIT_EXPECT_FALSE(test,
+ list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1]));
+
+ amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1,
+ dm_test_irq_handler);
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1]));
+}
+
+/* Tests for amdgpu_dm_irq_fini() */
+
+/**
+ * dm_test_irq_fini_removes_registered_handlers - Test fini removes handlers
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_fini_removes_registered_handlers(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct dc_interrupt_params int_params = { 0 };
+ void *handler;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+ int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT;
+ int_params.irq_source = DC_IRQ_SOURCE_HPD3;
+ handler = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_test_irq_handler, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+
+ int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT;
+ int_params.irq_source = DC_IRQ_SOURCE_HPD4;
+ handler = amdgpu_dm_irq_register_interrupt(adev, &int_params,
+ dm_test_irq_handler, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler);
+
+ amdgpu_dm_irq_fini(adev);
+
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD3]));
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD4]));
+}
+
+/**
+ * dm_test_irq_fini_on_empty_tables - Test fini on tables with no handlers
+ * @test: The KUnit test context
+ */
+static void dm_test_irq_fini_on_empty_tables(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ int src;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0);
+
+ amdgpu_dm_irq_fini(adev);
+
+ for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) {
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_low_tab[src]));
+ KUNIT_EXPECT_TRUE(test,
+ list_empty(&adev->dm.irq_handler_list_high_tab[src]));
+ }
+}
+
+/* Tests for amdgpu_dm_get_crtc_by_otg_inst() */
+
+/**
+ * dm_test_get_crtc_by_otg_inst_returns_match - Test CRTC lookup by OTG instance
+ * @test: The KUnit test context
+ */
+static void dm_test_get_crtc_by_otg_inst_returns_match(struct kunit *test)
+{
+ struct amdgpu_crtc *acrtc_a, *acrtc_b;
+ struct amdgpu_device *adev;
+ struct drm_device *drm;
+ struct device *dev;
+
+ dev = drm_kunit_helper_alloc_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+ sizeof(*adev),
+ offsetof(struct amdgpu_device, ddev),
+ DRIVER_MODESET);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+ adev = drm_to_adev(drm);
+
+ acrtc_a = kunit_kzalloc(test, sizeof(*acrtc_a), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc_a);
+ acrtc_b = kunit_kzalloc(test, sizeof(*acrtc_b), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc_b);
+
+ INIT_LIST_HEAD(&acrtc_a->base.head);
+ INIT_LIST_HEAD(&acrtc_b->base.head);
+ acrtc_a->otg_inst = 1;
+ acrtc_b->otg_inst = 3;
+
+ list_add_tail(&acrtc_a->base.head, &drm->mode_config.crtc_list);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del,
+ acrtc_a), 0);
+ list_add_tail(&acrtc_b->base.head, &drm->mode_config.crtc_list);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del,
+ acrtc_b), 0);
+
+ KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 3), acrtc_b);
+}
+
+/**
+ * dm_test_get_crtc_by_otg_inst_returns_null - Test CRTC lookup misses unknown OTG
+ * @test: The KUnit test context
+ */
+static void dm_test_get_crtc_by_otg_inst_returns_null(struct kunit *test)
+{
+ struct amdgpu_crtc *acrtc;
+ struct amdgpu_device *adev;
+ struct drm_device *drm;
+ struct device *dev;
+
+ dev = drm_kunit_helper_alloc_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+ sizeof(*adev),
+ offsetof(struct amdgpu_device, ddev),
+ DRIVER_MODESET);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+ adev = drm_to_adev(drm);
+
+ acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+
+ INIT_LIST_HEAD(&acrtc->base.head);
+ acrtc->otg_inst = 2;
+
+ list_add_tail(&acrtc->base.head, &drm->mode_config.crtc_list);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del,
+ acrtc), 0);
+
+ KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 5));
+}
+
+/**
+ * dm_test_get_crtc_by_otg_inst_empty_list - Test CRTC lookup on empty CRTC list
+ * @test: The KUnit test context
+ */
+static void dm_test_get_crtc_by_otg_inst_empty_list(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct drm_device *drm;
+ struct device *dev;
+
+ dev = drm_kunit_helper_alloc_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+ sizeof(*adev),
+ offsetof(struct amdgpu_device, ddev),
+ DRIVER_MODESET);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+ adev = drm_to_adev(drm);
+
+ KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 0));
+}
+
+static struct kunit_case amdgpu_dm_irq_tests[] = {
+ /* amdgpu_dm_hpd_to_dal_irq_source */
+ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1),
+ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd2),
+ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd3),
+ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd4),
+ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd5),
+ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd6),
+ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_invalid),
+ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_out_of_range),
+ /* are_sinks_equal */
+ KUNIT_CASE(dm_test_are_sinks_equal_both_null),
+ KUNIT_CASE(dm_test_are_sinks_equal_first_null),
+ KUNIT_CASE(dm_test_are_sinks_equal_second_null),
+ KUNIT_CASE(dm_test_are_sinks_equal_different_signal),
+ KUNIT_CASE(dm_test_are_sinks_equal_different_edid_length),
+ KUNIT_CASE(dm_test_are_sinks_equal_different_edid_data),
+ KUNIT_CASE(dm_test_are_sinks_equal_identical),
+ KUNIT_CASE(dm_test_are_sinks_equal_zero_length),
+ KUNIT_CASE(dm_test_are_sinks_equal_full_edid_identical),
+ KUNIT_CASE(dm_test_are_sinks_equal_full_edid_last_byte_differs),
+ /* dmub_notification_type_str */
+ KUNIT_CASE(dm_test_notification_str_no_data),
+ KUNIT_CASE(dm_test_notification_str_aux_reply),
+ KUNIT_CASE(dm_test_notification_str_hpd),
+ KUNIT_CASE(dm_test_notification_str_hpd_irq),
+ KUNIT_CASE(dm_test_notification_str_set_config),
+ KUNIT_CASE(dm_test_notification_str_dpia),
+ KUNIT_CASE(dm_test_notification_str_hpd_sense),
+ KUNIT_CASE(dm_test_notification_str_fused_io),
+ KUNIT_CASE(dm_test_notification_str_unknown),
+ /* amdgpu_dm_irq_init */
+ KUNIT_CASE(dm_test_irq_init_initializes_lists),
+ /* amdgpu_dm_irq_register_interrupt */
+ KUNIT_CASE(dm_test_irq_register_rejects_null_params),
+ KUNIT_CASE(dm_test_irq_register_rejects_invalid_context),
+ KUNIT_CASE(dm_test_irq_register_rejects_invalid_source),
+ KUNIT_CASE(dm_test_irq_register_adds_low_context_handler),
+ KUNIT_CASE(dm_test_irq_register_adds_high_context_handler),
+ KUNIT_CASE(dm_test_irq_register_multiple_handlers),
+ KUNIT_CASE(dm_test_irq_register_separate_contexts),
+ /* amdgpu_dm_irq_unregister_interrupt */
+ KUNIT_CASE(dm_test_irq_unregister_rejects_invalid_source),
+ KUNIT_CASE(dm_test_irq_unregister_rejects_null_handler),
+ KUNIT_CASE(dm_test_irq_unregister_handler_not_found),
+ /* amdgpu_dm_irq_fini */
+ KUNIT_CASE(dm_test_irq_fini_removes_registered_handlers),
+ KUNIT_CASE(dm_test_irq_fini_on_empty_tables),
+ /* amdgpu_dm_get_crtc_by_otg_inst */
+ KUNIT_CASE(dm_test_get_crtc_by_otg_inst_returns_match),
+ KUNIT_CASE(dm_test_get_crtc_by_otg_inst_returns_null),
+ KUNIT_CASE(dm_test_get_crtc_by_otg_inst_empty_list),
+ {}
+};
+
+static struct kunit_suite amdgpu_dm_irq_test_suite = {
+ .name = "amdgpu_dm_irq",
+ .test_cases = amdgpu_dm_irq_tests,
+};
+
+kunit_test_suite(amdgpu_dm_irq_test_suite);
+
+MODULE_AUTHOR("AMD");
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_irq");
+MODULE_LICENSE("Dual MIT/GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 23/32] drm/amd/display: Add KUnit tests for amdgpu_dm_crtc
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (21 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 22/32] drm/amd/display: Add KUnit tests for amdgpu_dm_irq Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 24/32] drm/amd/display: Add KUnit tests for amdgpu_dm_services Chenyu Chen
` (9 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Add KUnit coverage for functions in amdgpu_dm_crtc.c:
- amdgpu_dm_crtc_modeset_required: verify active+needs_modeset
combinations (mode_changed, active_changed, connectors_changed)
- amdgpu_dm_crtc_vrr_active_irq: verify all VRR state enum values
- amdgpu_dm_crtc_vrr_active: verify all VRR state enum values
- amdgpu_dm_is_headless: null adev, no connectors, writeback-only,
disconnected display, connected display, and mixed connector cases
- amdgpu_dm_crtc_helper_mode_fixup: verify it accepts the mode
- amdgpu_dm_crtc_set_vupdate_irq: verify the otg_inst == -1 early
return using a DRM mock device
- idle_create_workqueue: verify the idle workqueue is allocated and
initialized in a disabled, non-running state
Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 14 +-
.../amd/display/amdgpu_dm/amdgpu_dm_crtc.h | 6 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 1 +
.../amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 532 ++++++++++++++++++
4 files changed, 550 insertions(+), 3 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
index efb19f675b0c..c9916ed0ddc1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
@@ -34,6 +34,7 @@
#include "amdgpu_dm_plane.h"
#include "amdgpu_dm_trace.h"
#include "amdgpu_dm_debugfs.h"
+#include "amdgpu_dm_kunit_helpers.h"
#include "modules/inc/mod_power.h"
#define HPD_DETECTION_PERIOD_uS 2000000
@@ -65,6 +66,7 @@ bool amdgpu_dm_crtc_modeset_required(struct drm_crtc_state *crtc_state,
{
return crtc_state->active && drm_atomic_crtc_needs_modeset(crtc_state);
}
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_modeset_required);
bool amdgpu_dm_crtc_vrr_active_irq(struct amdgpu_crtc *acrtc)
@@ -74,6 +76,7 @@ bool amdgpu_dm_crtc_vrr_active_irq(struct amdgpu_crtc *acrtc)
acrtc->dm_irq_params.freesync_config.state ==
VRR_STATE_ACTIVE_FIXED;
}
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_vrr_active_irq);
int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable)
{
@@ -93,12 +96,14 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable)
acrtc->crtc_id, enable ? "en" : "dis", rc);
return rc;
}
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_set_vupdate_irq);
bool amdgpu_dm_crtc_vrr_active(const struct dm_crtc_state *dm_state)
{
return dm_state->freesync_config.state == VRR_STATE_ACTIVE_VARIABLE ||
dm_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED;
}
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_vrr_active);
/**
* amdgpu_dm_crtc_set_static_screen_optimze() - Toggle static screen optimizations.
@@ -156,6 +161,7 @@ bool amdgpu_dm_is_headless(struct amdgpu_device *adev)
drm_connector_list_iter_end(&iter);
return is_headless;
}
+EXPORT_IF_KUNIT(amdgpu_dm_is_headless);
static void amdgpu_dm_idle_worker(struct work_struct *work)
{
@@ -207,6 +213,7 @@ struct idle_workqueue *idle_create_workqueue(struct amdgpu_device *adev)
return idle_work;
}
+EXPORT_IF_KUNIT(idle_create_workqueue);
static void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work)
{
@@ -595,12 +602,13 @@ static void amdgpu_dm_crtc_update_crtc_active_planes(struct drm_crtc *crtc,
amdgpu_dm_crtc_count_crtc_active_planes(new_crtc_state);
}
-static bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc,
- const struct drm_display_mode *mode,
- struct drm_display_mode *adjusted_mode)
+STATIC_IFN_KUNIT bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
{
return true;
}
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_helper_mode_fixup);
static int amdgpu_dm_crtc_helper_atomic_check(struct drm_crtc *crtc,
struct drm_atomic_state *state)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h
index e9fb52f0e66d..d8b004f613ab 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h
@@ -42,6 +42,12 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable);
bool amdgpu_dm_crtc_vrr_active_irq(struct amdgpu_crtc *acrtc);
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode);
+#endif
+
bool amdgpu_dm_crtc_vrr_active(const struct dm_crtc_state *dm_state);
int amdgpu_dm_crtc_enable_vblank(struct drm_crtc *crtc);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index 583604914753..cde8f7748bc5 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -27,3 +27,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
new file mode 100644
index 000000000000..c83bd3e074f1
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
@@ -0,0 +1,532 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_crtc.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_kunit_helpers.h>
+
+#include "dc.h"
+#include "amdgpu.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
+#include "amdgpu_dm_crtc.h"
+#include "amdgpu_dm_irq_params.h"
+
+/* Tests for amdgpu_dm_crtc_modeset_required() */
+
+/**
+ * dm_test_crtc_modeset_required_active_mode_changed - Test Crtc modeset required active mode changed
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_modeset_required_active_mode_changed(struct kunit *test)
+{
+ struct drm_crtc_state state = {};
+
+ state.active = true;
+ state.mode_changed = true;
+
+ KUNIT_EXPECT_TRUE(test,
+ amdgpu_dm_crtc_modeset_required(&state, NULL, NULL));
+}
+
+/**
+ * dm_test_crtc_modeset_required_active_active_changed - Test Crtc modeset required active active changed
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_modeset_required_active_active_changed(struct kunit *test)
+{
+ struct drm_crtc_state state = {};
+
+ state.active = true;
+ state.active_changed = true;
+
+ KUNIT_EXPECT_TRUE(test,
+ amdgpu_dm_crtc_modeset_required(&state, NULL, NULL));
+}
+
+/**
+ * dm_test_crtc_modeset_required_active_connectors_changed - Test Crtc modeset required active connectors changed
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_modeset_required_active_connectors_changed(struct kunit *test)
+{
+ struct drm_crtc_state state = {};
+
+ state.active = true;
+ state.connectors_changed = true;
+
+ KUNIT_EXPECT_TRUE(test,
+ amdgpu_dm_crtc_modeset_required(&state, NULL, NULL));
+}
+
+/**
+ * dm_test_crtc_modeset_required_inactive - Test Crtc modeset required inactive
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_modeset_required_inactive(struct kunit *test)
+{
+ struct drm_crtc_state state = {};
+
+ state.active = false;
+ state.mode_changed = true;
+
+ KUNIT_EXPECT_FALSE(test,
+ amdgpu_dm_crtc_modeset_required(&state, NULL, NULL));
+}
+
+/**
+ * dm_test_crtc_modeset_required_no_changes - Test Crtc modeset required no changes
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_modeset_required_no_changes(struct kunit *test)
+{
+ struct drm_crtc_state state = {};
+
+ state.active = true;
+ state.mode_changed = false;
+ state.active_changed = false;
+ state.connectors_changed = false;
+
+ KUNIT_EXPECT_FALSE(test,
+ amdgpu_dm_crtc_modeset_required(&state, NULL, NULL));
+}
+
+/* Tests for amdgpu_dm_crtc_vrr_active_irq() */
+
+/**
+ * dm_test_crtc_vrr_active_irq_variable - Test Crtc vrr active irq variable
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_vrr_active_irq_variable(struct kunit *test)
+{
+ struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc),
+ GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+
+ acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_VARIABLE;
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc));
+}
+
+/**
+ * dm_test_crtc_vrr_active_irq_fixed - Test Crtc vrr active irq fixed
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_vrr_active_irq_fixed(struct kunit *test)
+{
+ struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc),
+ GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+
+ acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_FIXED;
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc));
+}
+
+/**
+ * dm_test_crtc_vrr_active_irq_inactive - Test Crtc vrr active irq inactive
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_vrr_active_irq_inactive(struct kunit *test)
+{
+ struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc),
+ GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+
+ acrtc->dm_irq_params.freesync_config.state = VRR_STATE_INACTIVE;
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc));
+}
+
+/**
+ * dm_test_crtc_vrr_active_irq_disabled - Test Crtc vrr active irq disabled
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_vrr_active_irq_disabled(struct kunit *test)
+{
+ struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc),
+ GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+
+ acrtc->dm_irq_params.freesync_config.state = VRR_STATE_DISABLED;
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc));
+}
+
+/**
+ * dm_test_crtc_vrr_active_irq_unsupported - Test Crtc vrr active irq unsupported
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_vrr_active_irq_unsupported(struct kunit *test)
+{
+ struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc),
+ GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+
+ acrtc->dm_irq_params.freesync_config.state = VRR_STATE_UNSUPPORTED;
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc));
+}
+
+/* Tests for amdgpu_dm_crtc_vrr_active() */
+
+/**
+ * dm_test_crtc_vrr_active_variable - Test Crtc vrr active variable
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_vrr_active_variable(struct kunit *test)
+{
+ struct dm_crtc_state *dm_state = kunit_kzalloc(test,
+ sizeof(*dm_state),
+ GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state);
+
+ dm_state->freesync_config.state = VRR_STATE_ACTIVE_VARIABLE;
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_crtc_vrr_active(dm_state));
+}
+
+/**
+ * dm_test_crtc_vrr_active_fixed - Test Crtc vrr active fixed
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_vrr_active_fixed(struct kunit *test)
+{
+ struct dm_crtc_state *dm_state = kunit_kzalloc(test,
+ sizeof(*dm_state),
+ GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state);
+
+ dm_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED;
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_crtc_vrr_active(dm_state));
+}
+
+/**
+ * dm_test_crtc_vrr_active_inactive - Test Crtc vrr active inactive
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_vrr_active_inactive(struct kunit *test)
+{
+ struct dm_crtc_state *dm_state = kunit_kzalloc(test,
+ sizeof(*dm_state),
+ GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state);
+
+ dm_state->freesync_config.state = VRR_STATE_INACTIVE;
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active(dm_state));
+}
+
+/**
+ * dm_test_crtc_vrr_active_disabled - Test Crtc vrr active disabled
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_vrr_active_disabled(struct kunit *test)
+{
+ struct dm_crtc_state *dm_state = kunit_kzalloc(test,
+ sizeof(*dm_state),
+ GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state);
+
+ dm_state->freesync_config.state = VRR_STATE_DISABLED;
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active(dm_state));
+}
+
+/**
+ * dm_test_crtc_vrr_active_unsupported - Test Crtc vrr active unsupported
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_vrr_active_unsupported(struct kunit *test)
+{
+ struct dm_crtc_state *dm_state = kunit_kzalloc(test,
+ sizeof(*dm_state),
+ GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state);
+
+ dm_state->freesync_config.state = VRR_STATE_UNSUPPORTED;
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active(dm_state));
+}
+
+/* Tests for amdgpu_dm_is_headless() */
+
+static void dm_test_add_connector(struct drm_device *dev,
+ struct drm_connector *connector,
+ int connector_type,
+ enum drm_connector_status status)
+{
+ INIT_LIST_HEAD(&connector->head);
+ kref_init(&connector->base.refcount);
+ connector->connector_type = connector_type;
+ connector->status = status;
+ list_add_tail(&connector->head, &dev->mode_config.connector_list);
+}
+
+/**
+ * dm_test_crtc_is_headless_null_adev - Test Crtc is headless null adev
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_is_headless_null_adev(struct kunit *test)
+{
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(NULL));
+}
+
+/**
+ * dm_test_crtc_is_headless_no_connectors - Test Crtc is headless no connectors
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_is_headless_no_connectors(struct kunit *test)
+{
+ struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ INIT_LIST_HEAD(&dev->mode_config.connector_list);
+ spin_lock_init(&dev->mode_config.connector_list_lock);
+ adev->dm.ddev = dev;
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(adev));
+}
+
+/**
+ * dm_test_crtc_is_headless_writeback_only - Test Crtc is headless writeback only
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_is_headless_writeback_only(struct kunit *test)
+{
+ struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+ struct drm_connector *wb = kunit_kzalloc(test, sizeof(*wb), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb);
+
+ INIT_LIST_HEAD(&dev->mode_config.connector_list);
+ spin_lock_init(&dev->mode_config.connector_list_lock);
+ adev->dm.ddev = dev;
+
+ dm_test_add_connector(dev, wb, DRM_MODE_CONNECTOR_WRITEBACK,
+ connector_status_connected);
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(adev));
+}
+
+/**
+ * dm_test_crtc_is_headless_disconnected_display - Test Crtc is headless disconnected display
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_is_headless_disconnected_display(struct kunit *test)
+{
+ struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+ struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display);
+
+ INIT_LIST_HEAD(&dev->mode_config.connector_list);
+ spin_lock_init(&dev->mode_config.connector_list_lock);
+ adev->dm.ddev = dev;
+
+ dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_HDMIA,
+ connector_status_disconnected);
+
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(adev));
+}
+
+/**
+ * dm_test_crtc_is_headless_connected_display - Test Crtc is headless connected display
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_is_headless_connected_display(struct kunit *test)
+{
+ struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+ struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display);
+
+ INIT_LIST_HEAD(&dev->mode_config.connector_list);
+ spin_lock_init(&dev->mode_config.connector_list_lock);
+ adev->dm.ddev = dev;
+
+ dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_HDMIA,
+ connector_status_connected);
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_headless(adev));
+}
+
+/**
+ * dm_test_crtc_is_headless_mixed_connectors - Test headless skips WB and finds display
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_is_headless_mixed_connectors(struct kunit *test)
+{
+ struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
+ struct drm_connector *wb = kunit_kzalloc(test, sizeof(*wb), GFP_KERNEL);
+ struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display);
+
+ INIT_LIST_HEAD(&dev->mode_config.connector_list);
+ spin_lock_init(&dev->mode_config.connector_list_lock);
+ adev->dm.ddev = dev;
+
+ dm_test_add_connector(dev, wb, DRM_MODE_CONNECTOR_WRITEBACK,
+ connector_status_connected);
+ dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_DisplayPort,
+ connector_status_connected);
+
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_headless(adev));
+}
+
+/* Tests for amdgpu_dm_crtc_helper_mode_fixup() */
+
+/**
+ * dm_test_crtc_helper_mode_fixup_returns_true - Test mode_fixup accepts mode
+ * @test: The KUnit test context
+ */
+static void dm_test_crtc_helper_mode_fixup_returns_true(struct kunit *test)
+{
+ struct drm_display_mode mode = { 0 };
+ struct drm_display_mode adjusted_mode = { 0 };
+
+ KUNIT_EXPECT_TRUE(test,
+ amdgpu_dm_crtc_helper_mode_fixup(NULL, &mode, &adjusted_mode));
+}
+
+/* Tests for amdgpu_dm_crtc_set_vupdate_irq() */
+
+/**
+ * dm_test_crtc_set_vupdate_irq_no_otg - Test vupdate irq with unassigned OTG
+ * @test: The KUnit test context
+ *
+ * When the CRTC has no OTG instance assigned (otg_inst == -1) the function
+ * must return 0 immediately without touching the DC interrupt state.
+ */
+static void dm_test_crtc_set_vupdate_irq_no_otg(struct kunit *test)
+{
+ struct amdgpu_crtc *acrtc;
+ struct amdgpu_device *adev;
+ struct drm_device *drm;
+ struct device *dev;
+
+ dev = drm_kunit_helper_alloc_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+ sizeof(*adev),
+ offsetof(struct amdgpu_device, ddev),
+ DRIVER_MODESET);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+ adev = drm_to_adev(drm);
+
+ acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+
+ acrtc->base.dev = drm;
+ acrtc->otg_inst = -1;
+
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, true), 0);
+ KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, false), 0);
+}
+
+/* Tests for idle_create_workqueue() */
+
+/**
+ * dm_test_idle_create_workqueue - Test idle workqueue creation
+ * @test: The KUnit test context
+ *
+ * Verify that idle_create_workqueue() allocates an idle workqueue tied to the
+ * device's display manager and initializes it in a disabled, non-running state.
+ */
+static void dm_test_idle_create_workqueue(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct idle_workqueue *idle_work;
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ idle_work = idle_create_workqueue(adev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, idle_work);
+
+ KUNIT_EXPECT_PTR_EQ(test, idle_work->dm, &adev->dm);
+ KUNIT_EXPECT_FALSE(test, idle_work->enable);
+ KUNIT_EXPECT_FALSE(test, idle_work->running);
+
+ kfree(idle_work);
+}
+
+static struct kunit_case amdgpu_dm_crtc_tests[] = {
+ /* amdgpu_dm_crtc_modeset_required */
+ KUNIT_CASE(dm_test_crtc_modeset_required_active_mode_changed),
+ KUNIT_CASE(dm_test_crtc_modeset_required_active_active_changed),
+ KUNIT_CASE(dm_test_crtc_modeset_required_active_connectors_changed),
+ KUNIT_CASE(dm_test_crtc_modeset_required_inactive),
+ KUNIT_CASE(dm_test_crtc_modeset_required_no_changes),
+ /* amdgpu_dm_crtc_vrr_active_irq */
+ KUNIT_CASE(dm_test_crtc_vrr_active_irq_variable),
+ KUNIT_CASE(dm_test_crtc_vrr_active_irq_fixed),
+ KUNIT_CASE(dm_test_crtc_vrr_active_irq_inactive),
+ KUNIT_CASE(dm_test_crtc_vrr_active_irq_disabled),
+ KUNIT_CASE(dm_test_crtc_vrr_active_irq_unsupported),
+ /* amdgpu_dm_crtc_vrr_active */
+ KUNIT_CASE(dm_test_crtc_vrr_active_variable),
+ KUNIT_CASE(dm_test_crtc_vrr_active_fixed),
+ KUNIT_CASE(dm_test_crtc_vrr_active_inactive),
+ KUNIT_CASE(dm_test_crtc_vrr_active_disabled),
+ KUNIT_CASE(dm_test_crtc_vrr_active_unsupported),
+ /* amdgpu_dm_is_headless */
+ KUNIT_CASE(dm_test_crtc_is_headless_null_adev),
+ KUNIT_CASE(dm_test_crtc_is_headless_no_connectors),
+ KUNIT_CASE(dm_test_crtc_is_headless_writeback_only),
+ KUNIT_CASE(dm_test_crtc_is_headless_disconnected_display),
+ KUNIT_CASE(dm_test_crtc_is_headless_connected_display),
+ KUNIT_CASE(dm_test_crtc_is_headless_mixed_connectors),
+ /* amdgpu_dm_crtc_helper_mode_fixup */
+ KUNIT_CASE(dm_test_crtc_helper_mode_fixup_returns_true),
+ /* amdgpu_dm_crtc_set_vupdate_irq */
+ KUNIT_CASE(dm_test_crtc_set_vupdate_irq_no_otg),
+ /* idle_create_workqueue */
+ KUNIT_CASE(dm_test_idle_create_workqueue),
+ {}
+};
+
+static struct kunit_suite amdgpu_dm_crtc_test_suite = {
+ .name = "amdgpu_dm_crtc",
+ .test_cases = amdgpu_dm_crtc_tests,
+};
+
+kunit_test_suite(amdgpu_dm_crtc_test_suite);
+
+MODULE_AUTHOR("AMD");
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_crtc");
+MODULE_LICENSE("Dual MIT/GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 24/32] drm/amd/display: Add KUnit tests for amdgpu_dm_services
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (22 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 23/32] drm/amd/display: Add KUnit tests for amdgpu_dm_crtc Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 25/32] drm/amd/display: Add KUnit tests for amdgpu_dm_helpers Chenyu Chen
` (8 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Add amdgpu_dm_services_test.c with KUnit coverage for five
functions in amdgpu_dm_services.c:
- dm_get_elapse_time_in_ns(): four arithmetic cases covering
zero delta, positive delta, ULLONG_MAX span, and unsigned
wraparound.
- dm_perf_trace_timestamp(): one case verifying the function
dereferences ctx->perf_trace safely (the tracepoint is a
no-op without an attached probe).
- dm_trace_smu_enter(): two cases for the empty stub with NULL
ctx and with non-zero parameters.
- dm_trace_smu_exit(): three cases for the empty stub covering
success, failure, and a non-zero response value.
- dm_query_extended_brightness_caps(): four guard-clause cases
(NULL ctx, NULL caps, NULL ctx->driver_context, NULL ctx with
LCD2) plus two success cases covering the LCD1 slot with
luminance data copy and a non-LCD1 display using the second
backlight slot with zero data points.
Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../display/amdgpu_dm/amdgpu_dm_services.c | 6 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 1 +
.../amdgpu_dm/tests/amdgpu_dm_services_test.c | 313 ++++++++++++++++++
3 files changed, 320 insertions(+)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c
index 0fdcf70256cc..6c0464754ed8 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c
@@ -36,6 +36,7 @@
#include "amdgpu_dm_irq.h"
#include "amdgpu_pm.h"
#include "amdgpu_dm_trace.h"
+#include "amdgpu_dm_kunit_helpers.h"
unsigned long long
dm_get_elapse_time_in_ns(struct dc_context *ctx,
@@ -44,6 +45,7 @@
{
return current_time_stamp - last_time_stamp;
}
+EXPORT_IF_KUNIT(dm_get_elapse_time_in_ns);
void dm_perf_trace_timestamp(const char *func_name, unsigned int line, struct dc_context *ctx)
{
@@ -53,14 +55,17 @@ void dm_perf_trace_timestamp(const char *func_name, unsigned int line, struct dc
&ctx->perf_trace->last_entry_write,
func_name, line);
}
+EXPORT_IF_KUNIT(dm_perf_trace_timestamp);
void dm_trace_smu_enter(uint32_t msg_id, uint32_t param_in, unsigned int delay, struct dc_context *ctx)
{
}
+EXPORT_IF_KUNIT(dm_trace_smu_enter);
void dm_trace_smu_exit(bool success, uint32_t response, struct dc_context *ctx)
{
}
+EXPORT_IF_KUNIT(dm_trace_smu_exit);
/**** power component interfaces ****/
@@ -90,3 +95,4 @@ bool dm_query_extended_brightness_caps(struct dc_context *ctx,
sizeof(struct dm_bl_data_point) * pCaps->num_data_points);
return true;
}
+EXPORT_IF_KUNIT(dm_query_extended_brightness_caps);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index cde8f7748bc5..364b4f3c783f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -28,3 +28,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c
new file mode 100644
index 000000000000..e48bac7fb024
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_services.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+
+#include "dc.h"
+#include "amdgpu.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
+#include "dm_services.h"
+#include "dm_services_types.h"
+
+/* Tests for dm_get_elapse_time_in_ns() */
+
+/**
+ * dm_test_get_elapse_time_zero_delta - Test Get elapse time zero delta
+ * @test: The KUnit test context
+ */
+static void dm_test_get_elapse_time_zero_delta(struct kunit *test)
+{
+ unsigned long long ts = 1000000ULL;
+
+ KUNIT_EXPECT_EQ(test, dm_get_elapse_time_in_ns(NULL, ts, ts), 0ULL);
+}
+
+/**
+ * dm_test_get_elapse_time_positive_delta - Test Get elapse time positive delta
+ * @test: The KUnit test context
+ */
+static void dm_test_get_elapse_time_positive_delta(struct kunit *test)
+{
+ unsigned long long current_ts = 5000000ULL;
+ unsigned long long last_ts = 1000000ULL;
+
+ KUNIT_EXPECT_EQ(test, dm_get_elapse_time_in_ns(NULL, current_ts, last_ts),
+ 4000000ULL);
+}
+
+/**
+ * dm_test_get_elapse_time_large_delta - Test Get elapse time large delta
+ * @test: The KUnit test context
+ */
+static void dm_test_get_elapse_time_large_delta(struct kunit *test)
+{
+ unsigned long long current_ts = ULLONG_MAX;
+ unsigned long long last_ts = 0ULL;
+
+ KUNIT_EXPECT_EQ(test, dm_get_elapse_time_in_ns(NULL, current_ts, last_ts),
+ ULLONG_MAX);
+}
+
+/**
+ * dm_test_get_elapse_time_wraparound - Test Get elapse time wraparound
+ * @test: The KUnit test context
+ */
+static void dm_test_get_elapse_time_wraparound(struct kunit *test)
+{
+ /* Unsigned wraparound: result = ULLONG_MAX - last + current + 1 */
+ unsigned long long current_ts = 5ULL;
+ unsigned long long last_ts = ULLONG_MAX - 4ULL;
+
+ KUNIT_EXPECT_EQ(test, dm_get_elapse_time_in_ns(NULL, current_ts, last_ts),
+ 10ULL);
+}
+
+/* Tests for dm_perf_trace_timestamp() */
+
+/**
+ * dm_test_perf_trace_timestamp_basic - Test Perf trace timestamp basic
+ * @test: The KUnit test context
+ *
+ * The tracepoint is a no-op without an attached probe, so this verifies the
+ * function dereferences ctx->perf_trace safely and does not crash.
+ */
+static void dm_test_perf_trace_timestamp_basic(struct kunit *test)
+{
+ struct dc_context *ctx;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ ctx->perf_trace = kunit_kzalloc(test, sizeof(*ctx->perf_trace), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->perf_trace);
+
+ ctx->perf_trace->read_count = 10;
+ ctx->perf_trace->write_count = 20;
+
+ dm_perf_trace_timestamp(__func__, __LINE__, ctx);
+}
+
+/* Tests for dm_trace_smu_enter() */
+
+/**
+ * dm_test_trace_smu_enter_null_ctx - Test Trace smu enter null ctx
+ * @test: The KUnit test context
+ */
+static void dm_test_trace_smu_enter_null_ctx(struct kunit *test)
+{
+ /* Empty stub — must not crash with NULL ctx */
+ dm_trace_smu_enter(0, 0, 0, NULL);
+}
+
+/**
+ * dm_test_trace_smu_enter_with_params - Test Trace smu enter with params
+ * @test: The KUnit test context
+ */
+static void dm_test_trace_smu_enter_with_params(struct kunit *test)
+{
+ /* Exercise non-zero msg_id, param_in, and delay */
+ dm_trace_smu_enter(0xFF, 0x12345678, 1000, NULL);
+}
+
+/* Tests for dm_trace_smu_exit() */
+
+/**
+ * dm_test_trace_smu_exit_success_null_ctx - Test Trace smu exit success null ctx
+ * @test: The KUnit test context
+ */
+static void dm_test_trace_smu_exit_success_null_ctx(struct kunit *test)
+{
+ /* Empty stub — must not crash on success path with NULL ctx */
+ dm_trace_smu_exit(true, 0x0, NULL);
+}
+
+/**
+ * dm_test_trace_smu_exit_failure_null_ctx - Test Trace smu exit failure null ctx
+ * @test: The KUnit test context
+ */
+static void dm_test_trace_smu_exit_failure_null_ctx(struct kunit *test)
+{
+ /* Empty stub — must not crash on failure path with NULL ctx */
+ dm_trace_smu_exit(false, 0x0, NULL);
+}
+
+/**
+ * dm_test_trace_smu_exit_with_response - Test Trace smu exit with response
+ * @test: The KUnit test context
+ */
+static void dm_test_trace_smu_exit_with_response(struct kunit *test)
+{
+ /* Exercise non-zero response value */
+ dm_trace_smu_exit(true, 0xDEADBEEF, NULL);
+}
+
+/* Tests for dm_query_extended_brightness_caps() */
+
+/**
+ * dm_test_query_brightness_caps_null_ctx - Test Query brightness caps null ctx
+ * @test: The KUnit test context
+ */
+static void dm_test_query_brightness_caps_null_ctx(struct kunit *test)
+{
+ struct dm_acpi_atif_backlight_caps caps = {};
+
+ KUNIT_EXPECT_FALSE(test,
+ dm_query_extended_brightness_caps(NULL, AcpiDisplayType_LCD1, &caps));
+}
+
+/**
+ * dm_test_query_brightness_caps_null_caps - Test Query brightness caps null caps
+ * @test: The KUnit test context
+ */
+static void dm_test_query_brightness_caps_null_caps(struct kunit *test)
+{
+ struct dc_context ctx = {};
+
+ ctx.driver_context = (void *)0x1; /* non-NULL sentinel */
+
+ KUNIT_EXPECT_FALSE(test,
+ dm_query_extended_brightness_caps(&ctx, AcpiDisplayType_LCD1, NULL));
+}
+
+/**
+ * dm_test_query_brightness_caps_null_driver_ctx - Test Query brightness caps null driver ctx
+ * @test: The KUnit test context
+ */
+static void dm_test_query_brightness_caps_null_driver_ctx(struct kunit *test)
+{
+ struct dc_context ctx = {};
+ struct dm_acpi_atif_backlight_caps caps = {};
+
+ ctx.driver_context = NULL;
+
+ KUNIT_EXPECT_FALSE(test,
+ dm_query_extended_brightness_caps(&ctx, AcpiDisplayType_LCD1, &caps));
+}
+
+/**
+ * dm_test_query_brightness_caps_lcd2_null_ctx - Test Query brightness caps lcd2 null ctx
+ * @test: The KUnit test context
+ */
+static void dm_test_query_brightness_caps_lcd2_null_ctx(struct kunit *test)
+{
+ struct dm_acpi_atif_backlight_caps caps = {};
+
+ KUNIT_EXPECT_FALSE(test,
+ dm_query_extended_brightness_caps(NULL, AcpiDisplayType_LCD2, &caps));
+}
+
+/**
+ * dm_test_query_brightness_caps_lcd1_success - Test Query brightness caps lcd1 success
+ * @test: The KUnit test context
+ */
+static void dm_test_query_brightness_caps_lcd1_success(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct amdgpu_dm_backlight_caps *source_caps;
+ struct dc_context ctx = {};
+ struct dm_acpi_atif_backlight_caps caps = {};
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, adev);
+
+ source_caps = &adev->dm.backlight_caps[0];
+ source_caps->caps_valid = true;
+ source_caps->min_input_signal = 12;
+ source_caps->max_input_signal = 240;
+ source_caps->ac_level = 80;
+ source_caps->dc_level = 40;
+ source_caps->data_points = 2;
+ source_caps->luminance_data[0].luminance = 10;
+ source_caps->luminance_data[0].input_signal = 22;
+ source_caps->luminance_data[1].luminance = 90;
+ source_caps->luminance_data[1].input_signal = 200;
+ ctx.driver_context = adev;
+
+ KUNIT_EXPECT_TRUE(test,
+ dm_query_extended_brightness_caps(&ctx, AcpiDisplayType_LCD1, &caps));
+ KUNIT_EXPECT_EQ(test, caps.num_data_points, 2);
+ KUNIT_EXPECT_EQ(test, caps.max_input_signal, 240);
+ KUNIT_EXPECT_EQ(test, caps.min_input_signal, 12);
+ KUNIT_EXPECT_EQ(test, caps.ac_level_percentage, 80);
+ KUNIT_EXPECT_EQ(test, caps.dc_level_percentage, 40);
+ KUNIT_EXPECT_EQ(test, caps.data_points[0].luminance, 10);
+ KUNIT_EXPECT_EQ(test, caps.data_points[0].signal_level, 22);
+ KUNIT_EXPECT_EQ(test, caps.data_points[1].luminance, 90);
+ KUNIT_EXPECT_EQ(test, caps.data_points[1].signal_level, 200);
+}
+
+/**
+ * dm_test_query_brightness_caps_non_lcd1_uses_second_slot - Test Query brightness caps non lcd1 uses second slot
+ * @test: The KUnit test context
+ */
+static void dm_test_query_brightness_caps_non_lcd1_uses_second_slot(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct amdgpu_dm_backlight_caps *source_caps;
+ struct dc_context ctx = {};
+ struct dm_acpi_atif_backlight_caps caps = {};
+
+ adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, adev);
+
+ adev->dm.backlight_caps[0].caps_valid = true;
+ adev->dm.backlight_caps[0].min_input_signal = 1;
+ adev->dm.backlight_caps[0].max_input_signal = 2;
+ source_caps = &adev->dm.backlight_caps[1];
+ source_caps->caps_valid = true;
+ source_caps->min_input_signal = 33;
+ source_caps->max_input_signal = 199;
+ source_caps->ac_level = 70;
+ source_caps->dc_level = 30;
+ source_caps->data_points = 0;
+ ctx.driver_context = adev;
+
+ KUNIT_EXPECT_TRUE(test,
+ dm_query_extended_brightness_caps(&ctx, AcpiDisplayType_DFP1, &caps));
+ KUNIT_EXPECT_EQ(test, caps.num_data_points, 0);
+ KUNIT_EXPECT_EQ(test, caps.max_input_signal, 199);
+ KUNIT_EXPECT_EQ(test, caps.min_input_signal, 33);
+ KUNIT_EXPECT_EQ(test, caps.ac_level_percentage, 70);
+ KUNIT_EXPECT_EQ(test, caps.dc_level_percentage, 30);
+ KUNIT_EXPECT_EQ(test, caps.data_points[0].luminance, 0);
+ KUNIT_EXPECT_EQ(test, caps.data_points[0].signal_level, 0);
+}
+
+static struct kunit_case amdgpu_dm_services_test_cases[] = {
+ /* dm_get_elapse_time_in_ns */
+ KUNIT_CASE(dm_test_get_elapse_time_zero_delta),
+ KUNIT_CASE(dm_test_get_elapse_time_positive_delta),
+ KUNIT_CASE(dm_test_get_elapse_time_large_delta),
+ KUNIT_CASE(dm_test_get_elapse_time_wraparound),
+ /* dm_perf_trace_timestamp */
+ KUNIT_CASE(dm_test_perf_trace_timestamp_basic),
+ /* dm_trace_smu_enter */
+ KUNIT_CASE(dm_test_trace_smu_enter_null_ctx),
+ KUNIT_CASE(dm_test_trace_smu_enter_with_params),
+ /* dm_trace_smu_exit */
+ KUNIT_CASE(dm_test_trace_smu_exit_success_null_ctx),
+ KUNIT_CASE(dm_test_trace_smu_exit_failure_null_ctx),
+ KUNIT_CASE(dm_test_trace_smu_exit_with_response),
+ /* dm_query_extended_brightness_caps */
+ KUNIT_CASE(dm_test_query_brightness_caps_null_ctx),
+ KUNIT_CASE(dm_test_query_brightness_caps_null_caps),
+ KUNIT_CASE(dm_test_query_brightness_caps_null_driver_ctx),
+ KUNIT_CASE(dm_test_query_brightness_caps_lcd2_null_ctx),
+ KUNIT_CASE(dm_test_query_brightness_caps_lcd1_success),
+ KUNIT_CASE(dm_test_query_brightness_caps_non_lcd1_uses_second_slot),
+ {}
+};
+
+static struct kunit_suite amdgpu_dm_services_test_suite = {
+ .name = "amdgpu_dm_services",
+ .test_cases = amdgpu_dm_services_test_cases,
+};
+
+kunit_test_suite(amdgpu_dm_services_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_services");
+MODULE_LICENSE("Dual MIT/GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 25/32] drm/amd/display: Add KUnit tests for amdgpu_dm_helpers
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (23 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 24/32] drm/amd/display: Add KUnit tests for amdgpu_dm_services Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 26/32] drm/amd/display: Add KUnit tests for amdgpu_dm_quirks Chenyu Chen
` (7 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Add amdgpu_dm_helpers_test.c with 32 KUnit test cases covering the
following functions in amdgpu_dm_helpers.c:
- edid_extract_panel_id(): basic extraction with known mfg_id and
prod_code; zero inputs produce zero output.
- dm_is_freesync_pcon_whitelist(): every entry in the whitelist
table returns true; an unknown ID and a zero ID return false.
- populate_hdmi_info_from_connector(): scdc_present is copied from
hdmi->scdc.supported for both true and false; FRL DSC fields map
10bpc and 12bpc correctly and ignore unknown values.
- dm_get_adaptive_sync_support_type(): five cases covering the
default non-converter path, HDMI converter without conditions,
partial conditions, all conditions met with a whitelist device
(FREESYNC_TYPE_PCON_IN_WHITELIST), and all conditions met with a
non-whitelisted device.
- dm_helpers_is_fullscreen() / dm_helpers_is_hdr_on(): stubs always
return false.
- get_max_frl_rate(): all six valid lane/rate combinations plus the
unknown combination returning 0.
- dm_dtn_log_begin()/dm_dtn_log_append_v()/dm_dtn_log_end(): buffer
accumulation and NULL-context handling without crashing.
- dm_helpers_dp_read_dpcd()/dm_helpers_dp_write_dpcd(): NULL link
private data returns false.
- dm_helpers_dp_mst_start_top_mgr()/dm_helpers_dp_mst_stop_top_mgr():
NULL link private data and the boot path.
- dm_helpers_dp_write_hblank_reduction(): stub returns false.
Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 58 +-
.../amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 20 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 1 +
.../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 645 ++++++++++++++++++
4 files changed, 707 insertions(+), 17 deletions(-)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
index 6350212b9a66..9c4e0a4e251c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
@@ -48,6 +48,8 @@
#include "dm_helpers.h"
#include "ddc_service_types.h"
#include "clk_mgr.h"
+#include "amdgpu_dm_kunit_helpers.h"
+#include "amdgpu_dm_helpers.h"
#define MCCS_DEST_ADDR (0x6E >> 1)
#define MCCS_SRC_ADDR 0x51
@@ -88,12 +90,13 @@ union vcp_reply {
unsigned char raw[11];
};
-static u32 edid_extract_panel_id(struct edid *edid)
+STATIC_IFN_KUNIT u32 edid_extract_panel_id(struct edid *edid)
{
return (u32)edid->mfg_id[0] << 24 |
(u32)edid->mfg_id[1] << 16 |
(u32)EDID_PRODUCT_ID(edid);
}
+EXPORT_IF_KUNIT(edid_extract_panel_id);
static void apply_edid_quirks(struct dc_link *link, struct edid *edid,
struct dc_edid_caps *edid_caps)
@@ -495,6 +498,7 @@ void dm_dtn_log_begin(struct dc_context *ctx,
dm_dtn_log_append_v(ctx, log_ctx, "%s", msg);
}
+EXPORT_IF_KUNIT(dm_dtn_log_begin);
__printf(3, 4)
void dm_dtn_log_append_v(struct dc_context *ctx,
@@ -557,6 +561,7 @@ void dm_dtn_log_append_v(struct dc_context *ctx,
if (n > 0)
log_ctx->pos += n;
}
+EXPORT_IF_KUNIT(dm_dtn_log_append_v);
void dm_dtn_log_end(struct dc_context *ctx,
struct dc_log_buffer_ctx *log_ctx)
@@ -570,6 +575,7 @@ void dm_dtn_log_end(struct dc_context *ctx,
dm_dtn_log_append_v(ctx, log_ctx, "%s", msg);
}
+EXPORT_IF_KUNIT(dm_dtn_log_end);
bool dm_helpers_dp_mst_start_top_mgr(
struct dc_context *ctx,
@@ -604,6 +610,7 @@ bool dm_helpers_dp_mst_start_top_mgr(
return true;
}
+EXPORT_IF_KUNIT(dm_helpers_dp_mst_start_top_mgr);
bool dm_helpers_dp_mst_stop_top_mgr(
struct dc_context *ctx,
@@ -626,6 +633,7 @@ bool dm_helpers_dp_mst_stop_top_mgr(
return false;
}
+EXPORT_IF_KUNIT(dm_helpers_dp_mst_stop_top_mgr);
bool dm_helpers_dp_read_dpcd(
struct dc_context *ctx,
@@ -643,6 +651,7 @@ bool dm_helpers_dp_read_dpcd(
return drm_dp_dpcd_read(&aconnector->dm_dp_aux.aux, address, data,
size) == size;
}
+EXPORT_IF_KUNIT(dm_helpers_dp_read_dpcd);
bool dm_helpers_dp_write_dpcd(
struct dc_context *ctx,
@@ -659,6 +668,7 @@ bool dm_helpers_dp_write_dpcd(
return drm_dp_dpcd_write(&aconnector->dm_dp_aux.aux,
address, (uint8_t *)data, size) > 0;
}
+EXPORT_IF_KUNIT(dm_helpers_dp_write_dpcd);
bool dm_helpers_submit_i2c(
struct dc_context *ctx,
@@ -974,6 +984,7 @@ bool dm_helpers_dp_write_hblank_reduction(struct dc_context *ctx, const struct d
// TODO
return false;
}
+EXPORT_IF_KUNIT(dm_helpers_dp_write_hblank_reduction);
bool dm_helpers_is_dp_sink_present(struct dc_link *link)
{
@@ -1091,7 +1102,7 @@ dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, struct amdgpu_dm_conn
return edid;
}
-static uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane)
+STATIC_IFN_KUNIT uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane)
{
uint8_t max_frl_rate;
@@ -1112,6 +1123,7 @@ static uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane)
return max_frl_rate;
}
+EXPORT_IF_KUNIT(get_max_frl_rate);
static uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice)
{
@@ -1156,6 +1168,7 @@ void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *hd
}
}
}
+EXPORT_IF_KUNIT(populate_hdmi_info_from_connector);
enum dc_edid_status dm_helpers_read_local_edid(
struct dc_context *ctx,
@@ -1556,24 +1569,32 @@ void dm_helpers_dp_mst_update_branch_bandwidth(
// TODO
}
-static bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id)
+STATIC_IFN_KUNIT const uint32_t dm_freesync_pcon_whitelist[] = {
+ DP_BRANCH_DEVICE_ID_0060AD,
+ DP_BRANCH_DEVICE_ID_00E04C,
+ DP_BRANCH_DEVICE_ID_90CC24,
+ DP_BRANCH_DEVICE_ID_001CF8,
+ DP_BRANCH_DEVICE_ID_001FF2,
+};
+EXPORT_IF_KUNIT(dm_freesync_pcon_whitelist);
+
+STATIC_IFN_KUNIT uint32_t dm_freesync_pcon_whitelist_count(void)
{
- bool ret_val = false;
-
- switch (branch_dev_id) {
- case DP_BRANCH_DEVICE_ID_0060AD:
- case DP_BRANCH_DEVICE_ID_00E04C:
- case DP_BRANCH_DEVICE_ID_90CC24:
- case DP_BRANCH_DEVICE_ID_001CF8:
- case DP_BRANCH_DEVICE_ID_001FF2:
- ret_val = true;
- break;
- default:
- break;
- }
+ return ARRAY_SIZE(dm_freesync_pcon_whitelist);
+}
+EXPORT_IF_KUNIT(dm_freesync_pcon_whitelist_count);
+
+STATIC_IFN_KUNIT bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id)
+{
+ u32 i;
- return ret_val;
+ for (i = 0; i < dm_freesync_pcon_whitelist_count(); i++)
+ if (dm_freesync_pcon_whitelist[i] == branch_dev_id)
+ return true;
+
+ return false;
}
+EXPORT_IF_KUNIT(dm_is_freesync_pcon_whitelist);
enum adaptive_sync_type dm_get_adaptive_sync_support_type(struct dc_link *link)
{
@@ -1593,18 +1614,21 @@ enum adaptive_sync_type dm_get_adaptive_sync_support_type(struct dc_link *link)
return as_type;
}
+EXPORT_IF_KUNIT(dm_get_adaptive_sync_support_type);
bool dm_helpers_is_fullscreen(struct dc_context *ctx, struct dc_stream_state *stream)
{
// TODO
return false;
}
+EXPORT_IF_KUNIT(dm_helpers_is_fullscreen);
bool dm_helpers_is_hdr_on(struct dc_context *ctx, struct dc_stream_state *stream)
{
// TODO
return false;
}
+EXPORT_IF_KUNIT(dm_helpers_is_hdr_on);
static int mccs_operation_vcp_request(unsigned int vcp_code, struct dc_link *link,
union vcp_reply *reply)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h
new file mode 100644
index 000000000000..2ac9762895ec
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#ifndef __AMDGPU_DM_HELPERS_H__
+#define __AMDGPU_DM_HELPERS_H__
+
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+#include <drm/drm_edid.h>
+
+/* Exported for KUnit testing */
+u32 edid_extract_panel_id(struct edid *edid);
+uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane);
+bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id);
+extern const uint32_t dm_freesync_pcon_whitelist[];
+uint32_t dm_freesync_pcon_whitelist_count(void);
+#endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */
+
+#endif /* __AMDGPU_DM_HELPERS_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index 364b4f3c783f..a067332f9f41 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -29,3 +29,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_helpers_test.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c
new file mode 100644
index 000000000000..14004ff87c9b
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c
@@ -0,0 +1,645 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_helpers.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_kunit_helpers.h>
+
+#include "dc.h"
+#include "amdgpu.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
+#include "dm_helpers.h"
+#include "ddc_service_types.h"
+#include "amdgpu_dm_helpers.h"
+
+/* Tests for edid_extract_panel_id() */
+
+/**
+ * dm_test_edid_extract_panel_id_basic - Test Edid extract panel id basic
+ * @test: The KUnit test context
+ */
+static void dm_test_edid_extract_panel_id_basic(struct kunit *test)
+{
+ struct edid *edid;
+ u32 panel_id;
+
+ edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, edid);
+
+ edid->mfg_id[0] = 0x12;
+ edid->mfg_id[1] = 0x34;
+ edid->prod_code[0] = 0xAB;
+ edid->prod_code[1] = 0xCD;
+
+ panel_id = edid_extract_panel_id(edid);
+
+ /*
+ * Expected: (0x12 << 24) | (0x34 << 16) | EDID_PRODUCT_ID(edid)
+ * EDID_PRODUCT_ID = prod_code[0] | (prod_code[1] << 8) = 0xAB | 0xCD00 = 0xCDAB
+ * Result: 0x12340000 | 0x0000CDAB = 0x1234CDAB
+ */
+ KUNIT_EXPECT_EQ(test, panel_id, (u32)0x1234CDAB);
+}
+
+/**
+ * dm_test_edid_extract_panel_id_zeros - Test Edid extract panel id zeros
+ * @test: The KUnit test context
+ */
+static void dm_test_edid_extract_panel_id_zeros(struct kunit *test)
+{
+ struct edid *edid;
+
+ edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, edid);
+
+ KUNIT_EXPECT_EQ(test, edid_extract_panel_id(edid), 0U);
+}
+
+/* Tests for dm_is_freesync_pcon_whitelist() */
+
+/**
+ * dm_test_freesync_pcon_whitelist_all_known - Test all known Freesync Pcon whitelist entries
+ * @test: The KUnit test context
+ *
+ * Iterates over the driver's whitelist table directly so that any ID added
+ * to dm_freesync_pcon_whitelist[] is automatically covered by this test.
+ */
+static void dm_test_freesync_pcon_whitelist_all_known(struct kunit *test)
+{
+ u32 i;
+
+ for (i = 0; i < dm_freesync_pcon_whitelist_count(); i++)
+ KUNIT_EXPECT_TRUE(test,
+ dm_is_freesync_pcon_whitelist(dm_freesync_pcon_whitelist[i]));
+}
+
+/**
+ * dm_test_freesync_pcon_whitelist_not_in_list - Test Freesync pcon whitelist not in list
+ * @test: The KUnit test context
+ */
+static void dm_test_freesync_pcon_whitelist_not_in_list(struct kunit *test)
+{
+ /* 0xFFFFFF is not a known whitelist device */
+ KUNIT_EXPECT_FALSE(test, dm_is_freesync_pcon_whitelist(0xFFFFFF));
+}
+
+/**
+ * dm_test_freesync_pcon_whitelist_zero - Test Freesync pcon whitelist zero
+ * @test: The KUnit test context
+ */
+static void dm_test_freesync_pcon_whitelist_zero(struct kunit *test)
+{
+ KUNIT_EXPECT_FALSE(test, dm_is_freesync_pcon_whitelist(0));
+}
+
+/* Tests for populate_hdmi_info_from_connector() */
+
+/**
+ * dm_test_populate_hdmi_scdc_present_true - Test Populate hdmi scdc present true
+ * @test: The KUnit test context
+ */
+static void dm_test_populate_hdmi_scdc_present_true(struct kunit *test)
+{
+ struct drm_hdmi_info *hdmi;
+ struct dc_edid_caps *caps;
+
+ hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, hdmi);
+ caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, caps);
+
+ hdmi->scdc.supported = true;
+
+ populate_hdmi_info_from_connector(true, hdmi, caps);
+
+ KUNIT_EXPECT_TRUE(test, caps->scdc_present);
+}
+
+/**
+ * dm_test_populate_hdmi_scdc_present_false - Test Populate hdmi scdc present false
+ * @test: The KUnit test context
+ */
+static void dm_test_populate_hdmi_scdc_present_false(struct kunit *test)
+{
+ struct drm_hdmi_info *hdmi;
+ struct dc_edid_caps *caps;
+
+ hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, hdmi);
+ caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, caps);
+
+ hdmi->scdc.supported = false;
+ caps->scdc_present = true; /* pre-set to confirm it gets cleared */
+
+ populate_hdmi_info_from_connector(true, hdmi, caps);
+
+ KUNIT_EXPECT_FALSE(test, caps->scdc_present);
+}
+
+/**
+ * dm_test_populate_hdmi_frl_dsc_10bpc - Test HDMI FRL DSC 10 bpc caps
+ * @test: The KUnit test context
+ */
+static void dm_test_populate_hdmi_frl_dsc_10bpc(struct kunit *test)
+{
+ struct drm_hdmi_info *hdmi;
+ struct dc_edid_caps *caps;
+
+ hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, hdmi);
+ caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, caps);
+
+ hdmi->max_lanes = 4;
+ hdmi->max_frl_rate_per_lane = 12;
+ hdmi->dsc_cap.v_1p2 = true;
+ hdmi->dsc_cap.bpc_supported = 10;
+ hdmi->dsc_cap.all_bpp = true;
+ hdmi->dsc_cap.native_420 = true;
+ hdmi->dsc_cap.max_slices = 8;
+ hdmi->dsc_cap.clk_per_slice = 400;
+ hdmi->dsc_cap.max_lanes = 4;
+ hdmi->dsc_cap.max_frl_rate_per_lane = 10;
+ hdmi->dsc_cap.total_chunk_kbytes = 7;
+
+ populate_hdmi_info_from_connector(true, hdmi, caps);
+
+ KUNIT_EXPECT_EQ(test, caps->max_frl_rate, 6);
+ KUNIT_EXPECT_TRUE(test, caps->frl_dsc_support);
+ KUNIT_EXPECT_TRUE(test, caps->frl_dsc_10bpc);
+ KUNIT_EXPECT_FALSE(test, caps->frl_dsc_12bpc);
+ KUNIT_EXPECT_TRUE(test, caps->frl_dsc_all_bpp);
+ KUNIT_EXPECT_TRUE(test, caps->frl_dsc_native_420);
+ KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_slices, 5);
+ KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_frl_rate, 5);
+ KUNIT_EXPECT_EQ(test, caps->frl_dsc_total_chunk_kbytes, 7);
+}
+
+/**
+ * dm_test_populate_hdmi_frl_dsc_12bpc - Test HDMI FRL DSC 12 bpc caps
+ * @test: The KUnit test context
+ */
+static void dm_test_populate_hdmi_frl_dsc_12bpc(struct kunit *test)
+{
+ struct drm_hdmi_info *hdmi;
+ struct dc_edid_caps *caps;
+
+ hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, hdmi);
+ caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, caps);
+
+ hdmi->max_lanes = 3;
+ hdmi->max_frl_rate_per_lane = 6;
+ hdmi->dsc_cap.v_1p2 = true;
+ hdmi->dsc_cap.bpc_supported = 12;
+ hdmi->dsc_cap.max_slices = 16;
+ hdmi->dsc_cap.clk_per_slice = 400;
+ hdmi->dsc_cap.max_lanes = 3;
+ hdmi->dsc_cap.max_frl_rate_per_lane = 3;
+
+ populate_hdmi_info_from_connector(true, hdmi, caps);
+
+ KUNIT_EXPECT_EQ(test, caps->max_frl_rate, 2);
+ KUNIT_EXPECT_TRUE(test, caps->frl_dsc_support);
+ KUNIT_EXPECT_FALSE(test, caps->frl_dsc_10bpc);
+ KUNIT_EXPECT_TRUE(test, caps->frl_dsc_12bpc);
+ KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_slices, 7);
+ KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_frl_rate, 1);
+}
+
+/**
+ * dm_test_populate_hdmi_frl_dsc_unknown_values - Test HDMI FRL DSC unknown values
+ * @test: The KUnit test context
+ */
+static void dm_test_populate_hdmi_frl_dsc_unknown_values(struct kunit *test)
+{
+ struct drm_hdmi_info *hdmi;
+ struct dc_edid_caps *caps;
+
+ hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, hdmi);
+ caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, caps);
+
+ hdmi->max_lanes = 2;
+ hdmi->max_frl_rate_per_lane = 3;
+ hdmi->dsc_cap.v_1p2 = true;
+ hdmi->dsc_cap.bpc_supported = 8;
+ hdmi->dsc_cap.max_slices = 3;
+ hdmi->dsc_cap.clk_per_slice = 340;
+ hdmi->dsc_cap.max_lanes = 2;
+ hdmi->dsc_cap.max_frl_rate_per_lane = 12;
+
+ populate_hdmi_info_from_connector(true, hdmi, caps);
+
+ KUNIT_EXPECT_EQ(test, caps->max_frl_rate, 0);
+ KUNIT_EXPECT_TRUE(test, caps->frl_dsc_support);
+ KUNIT_EXPECT_FALSE(test, caps->frl_dsc_10bpc);
+ KUNIT_EXPECT_FALSE(test, caps->frl_dsc_12bpc);
+ KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_slices, 0);
+ KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_frl_rate, 0);
+}
+
+/* Tests for dm_get_adaptive_sync_support_type() */
+
+/**
+ * dm_test_adaptive_sync_type_none_default - Test Adaptive sync type none default
+ * @test: The KUnit test context
+ */
+static void dm_test_adaptive_sync_type_none_default(struct kunit *test)
+{
+ struct dc_link *link;
+
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+
+ /* dongle_type = 0 (DISPLAY_DONGLE_NONE) → default case → TYPE_NONE */
+ KUNIT_EXPECT_EQ(test,
+ (int)dm_get_adaptive_sync_support_type(link),
+ (int)ADAPTIVE_SYNC_TYPE_NONE);
+}
+
+/**
+ * dm_test_adaptive_sync_type_converter_no_conditions - Converter without caps
+ * @test: The KUnit test context
+ */
+static void dm_test_adaptive_sync_type_converter_no_conditions(struct kunit *test)
+{
+ struct dc_link *link;
+
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+
+ /* HDMI converter but no adaptive sync cap → still NONE */
+ link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER;
+
+ KUNIT_EXPECT_EQ(test,
+ (int)dm_get_adaptive_sync_support_type(link),
+ (int)ADAPTIVE_SYNC_TYPE_NONE);
+}
+
+/**
+ * dm_test_adaptive_sync_type_converter_partial_conditions - Partial caps
+ * @test: The KUnit test context
+ */
+static void dm_test_adaptive_sync_type_converter_partial_conditions(struct kunit *test)
+{
+ struct dc_link *link;
+
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+
+ /* Cap set and whitelist ID, but allow_invalid_MSA_timing_param = false */
+ link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER;
+ link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1;
+ link->dpcd_caps.allow_invalid_MSA_timing_param = false;
+ link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_0060AD;
+
+ KUNIT_EXPECT_EQ(test,
+ (int)dm_get_adaptive_sync_support_type(link),
+ (int)ADAPTIVE_SYNC_TYPE_NONE);
+}
+
+/**
+ * dm_test_adaptive_sync_type_pcon_whitelist - Test Adaptive sync type pcon whitelist
+ * @test: The KUnit test context
+ */
+static void dm_test_adaptive_sync_type_pcon_whitelist(struct kunit *test)
+{
+ struct dc_link *link;
+
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+
+ /* All conditions met → FREESYNC_TYPE_PCON_IN_WHITELIST */
+ link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER;
+ link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1;
+ link->dpcd_caps.allow_invalid_MSA_timing_param = true;
+ link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_0060AD;
+
+ KUNIT_EXPECT_EQ(test,
+ (int)dm_get_adaptive_sync_support_type(link),
+ (int)FREESYNC_TYPE_PCON_IN_WHITELIST);
+}
+
+/**
+ * dm_test_adaptive_sync_type_converter_nonwhitelist - Converter not whitelisted
+ * @test: The KUnit test context
+ */
+static void dm_test_adaptive_sync_type_converter_nonwhitelist(struct kunit *test)
+{
+ struct dc_link *link;
+
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+
+ /* All conditions met but branch_dev_id not in whitelist → NONE */
+ link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER;
+ link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1;
+ link->dpcd_caps.allow_invalid_MSA_timing_param = true;
+ link->dpcd_caps.branch_dev_id = 0xFFFFFF;
+
+ KUNIT_EXPECT_EQ(test,
+ (int)dm_get_adaptive_sync_support_type(link),
+ (int)ADAPTIVE_SYNC_TYPE_NONE);
+}
+
+/* Tests for dm_helpers_is_fullscreen() and dm_helpers_is_hdr_on() */
+
+/**
+ * dm_test_helpers_is_fullscreen_returns_false - Test Helpers is fullscreen returns false
+ * @test: The KUnit test context
+ */
+static void dm_test_helpers_is_fullscreen_returns_false(struct kunit *test)
+{
+ /* Stub — always returns false */
+ KUNIT_EXPECT_FALSE(test, dm_helpers_is_fullscreen(NULL, NULL));
+}
+
+/**
+ * dm_test_helpers_is_hdr_on_returns_false - Test Helpers is hdr on returns false
+ * @test: The KUnit test context
+ */
+static void dm_test_helpers_is_hdr_on_returns_false(struct kunit *test)
+{
+ /* Stub — always returns false */
+ KUNIT_EXPECT_FALSE(test, dm_helpers_is_hdr_on(NULL, NULL));
+}
+
+/* Tests for get_max_frl_rate() */
+
+/**
+ * dm_test_get_max_frl_rate_3lanes_3gbps - Test Get max frl rate 3lanes 3gbps
+ * @test: The KUnit test context
+ */
+static void dm_test_get_max_frl_rate_3lanes_3gbps(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, get_max_frl_rate(3, 3), 1);
+}
+
+/**
+ * dm_test_get_max_frl_rate_3lanes_6gbps - Test Get max frl rate 3lanes 6gbps
+ * @test: The KUnit test context
+ */
+static void dm_test_get_max_frl_rate_3lanes_6gbps(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, get_max_frl_rate(3, 6), 2);
+}
+
+/**
+ * dm_test_get_max_frl_rate_4lanes_6gbps - Test Get max frl rate 4lanes 6gbps
+ * @test: The KUnit test context
+ */
+static void dm_test_get_max_frl_rate_4lanes_6gbps(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, get_max_frl_rate(4, 6), 3);
+}
+
+/**
+ * dm_test_get_max_frl_rate_4lanes_8gbps - Test Get max frl rate 4lanes 8gbps
+ * @test: The KUnit test context
+ */
+static void dm_test_get_max_frl_rate_4lanes_8gbps(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, get_max_frl_rate(4, 8), 4);
+}
+
+/**
+ * dm_test_get_max_frl_rate_4lanes_10gbps - Test Get max frl rate 4lanes 10gbps
+ * @test: The KUnit test context
+ */
+static void dm_test_get_max_frl_rate_4lanes_10gbps(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, get_max_frl_rate(4, 10), 5);
+}
+
+/**
+ * dm_test_get_max_frl_rate_4lanes_12gbps - Test Get max frl rate 4lanes 12gbps
+ * @test: The KUnit test context
+ */
+static void dm_test_get_max_frl_rate_4lanes_12gbps(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, get_max_frl_rate(4, 12), 6);
+}
+
+/**
+ * dm_test_get_max_frl_rate_unknown - Test Get max frl rate unknown
+ * @test: The KUnit test context
+ */
+static void dm_test_get_max_frl_rate_unknown(struct kunit *test)
+{
+ /* Unknown lane/rate combination → 0 */
+ KUNIT_EXPECT_EQ(test, get_max_frl_rate(2, 3), 0);
+}
+
+/* Tests for dm_dtn_log_begin() / dm_dtn_log_append_v() / dm_dtn_log_end() */
+
+/**
+ * dm_test_dtn_log_buffer_accumulates - Test DTN log buffer accumulation
+ * @test: The KUnit test context
+ */
+static void dm_test_dtn_log_buffer_accumulates(struct kunit *test)
+{
+ struct dc_log_buffer_ctx log_ctx = {0};
+
+ dm_dtn_log_begin(NULL, &log_ctx);
+ dm_dtn_log_append_v(NULL, &log_ctx, "x=%d\n", 7);
+ dm_dtn_log_end(NULL, &log_ctx);
+
+ KUNIT_ASSERT_NOT_NULL(test, log_ctx.buf);
+ KUNIT_EXPECT_STREQ(test, log_ctx.buf, "[dtn begin]\nx=7\n[dtn end]\n");
+ KUNIT_EXPECT_EQ(test, log_ctx.pos, strlen("[dtn begin]\nx=7\n[dtn end]\n"));
+
+ kvfree(log_ctx.buf);
+}
+
+/**
+ * dm_test_dtn_log_null_ctx_no_crash - Test DTN log helpers with NULL log buffer
+ * @test: The KUnit test context
+ */
+static void dm_test_dtn_log_null_ctx_no_crash(struct kunit *test)
+{
+ /* NULL log_ctx redirects to dmesg and must not dereference a buffer */
+ dm_dtn_log_begin(NULL, NULL);
+ dm_dtn_log_append_v(NULL, NULL, "value %d\n", 1);
+ dm_dtn_log_end(NULL, NULL);
+
+ KUNIT_EXPECT_TRUE(test, true);
+}
+
+/* Tests for dm_helpers_dp_read_dpcd() / dm_helpers_dp_write_dpcd() */
+
+/**
+ * dm_test_dp_read_dpcd_null_priv - Test DPCD read returns false without connector
+ * @test: The KUnit test context
+ */
+static void dm_test_dp_read_dpcd_null_priv(struct kunit *test)
+{
+ struct dc_link *link;
+ uint8_t data = 0;
+
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+
+ /* link->priv (aconnector) is NULL → early return false */
+ KUNIT_EXPECT_FALSE(test,
+ dm_helpers_dp_read_dpcd(NULL, link, 0, &data, sizeof(data)));
+}
+
+/**
+ * dm_test_dp_write_dpcd_null_priv - Test DPCD write returns false without connector
+ * @test: The KUnit test context
+ */
+static void dm_test_dp_write_dpcd_null_priv(struct kunit *test)
+{
+ struct dc_link *link;
+ uint8_t data = 0;
+
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+
+ /* link->priv (aconnector) is NULL → early return false */
+ KUNIT_EXPECT_FALSE(test,
+ dm_helpers_dp_write_dpcd(NULL, link, 0, &data, sizeof(data)));
+}
+
+/* Tests for dm_helpers_dp_mst_start_top_mgr() / dm_helpers_dp_mst_stop_top_mgr() */
+
+/**
+ * dm_test_mst_start_top_mgr_null_priv - Test MST start returns false without connector
+ * @test: The KUnit test context
+ */
+static void dm_test_mst_start_top_mgr_null_priv(struct kunit *test)
+{
+ struct dc_link *link;
+
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+
+ KUNIT_EXPECT_FALSE(test, dm_helpers_dp_mst_start_top_mgr(NULL, link, false));
+}
+
+/**
+ * dm_test_mst_stop_top_mgr_null_priv - Test MST stop returns false without connector
+ * @test: The KUnit test context
+ */
+static void dm_test_mst_stop_top_mgr_null_priv(struct kunit *test)
+{
+ struct dc_link *link;
+
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+
+ KUNIT_EXPECT_FALSE(test, dm_helpers_dp_mst_stop_top_mgr(NULL, link));
+}
+
+/**
+ * dm_test_mst_start_top_mgr_boot - Test MST start boot path on a connector-backed link
+ * @test: The KUnit test context
+ *
+ * Uses the DRM KUnit mock device to back the connector so the link is a
+ * realistic connector-backed link. The boot path short-circuits and returns
+ * true without touching the MST topology manager.
+ */
+static void dm_test_mst_start_top_mgr_boot(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct amdgpu_device *adev;
+ struct drm_device *drm;
+ struct device *dev;
+ struct dc_link *link;
+
+ dev = drm_kunit_helper_alloc_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ drm = __drm_kunit_helper_alloc_drm_device(test, dev,
+ sizeof(*adev),
+ offsetof(struct amdgpu_device, ddev),
+ DRIVER_MODESET);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+ adev = drm_to_adev(drm);
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ aconnector->base.dev = drm;
+
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+ link->priv = aconnector;
+
+ KUNIT_EXPECT_TRUE(test, dm_helpers_dp_mst_start_top_mgr(NULL, link, true));
+}
+
+/* Tests for dm_helpers_dp_write_hblank_reduction() */
+
+/**
+ * dm_test_dp_write_hblank_reduction_false - Test hblank reduction stub returns false
+ * @test: The KUnit test context
+ */
+static void dm_test_dp_write_hblank_reduction_false(struct kunit *test)
+{
+ KUNIT_EXPECT_FALSE(test, dm_helpers_dp_write_hblank_reduction(NULL, NULL));
+}
+
+static struct kunit_case amdgpu_dm_helpers_test_cases[] = {
+ /* edid_extract_panel_id */
+ KUNIT_CASE(dm_test_edid_extract_panel_id_basic),
+ KUNIT_CASE(dm_test_edid_extract_panel_id_zeros),
+ /* dm_is_freesync_pcon_whitelist */
+ KUNIT_CASE(dm_test_freesync_pcon_whitelist_all_known),
+ KUNIT_CASE(dm_test_freesync_pcon_whitelist_not_in_list),
+ KUNIT_CASE(dm_test_freesync_pcon_whitelist_zero),
+ /* populate_hdmi_info_from_connector */
+ KUNIT_CASE(dm_test_populate_hdmi_scdc_present_true),
+ KUNIT_CASE(dm_test_populate_hdmi_scdc_present_false),
+ KUNIT_CASE(dm_test_populate_hdmi_frl_dsc_10bpc),
+ KUNIT_CASE(dm_test_populate_hdmi_frl_dsc_12bpc),
+ KUNIT_CASE(dm_test_populate_hdmi_frl_dsc_unknown_values),
+ /* dm_get_adaptive_sync_support_type */
+ KUNIT_CASE(dm_test_adaptive_sync_type_none_default),
+ KUNIT_CASE(dm_test_adaptive_sync_type_converter_no_conditions),
+ KUNIT_CASE(dm_test_adaptive_sync_type_converter_partial_conditions),
+ KUNIT_CASE(dm_test_adaptive_sync_type_pcon_whitelist),
+ KUNIT_CASE(dm_test_adaptive_sync_type_converter_nonwhitelist),
+ /* dm_helpers_is_fullscreen / dm_helpers_is_hdr_on */
+ KUNIT_CASE(dm_test_helpers_is_fullscreen_returns_false),
+ KUNIT_CASE(dm_test_helpers_is_hdr_on_returns_false),
+ /* get_max_frl_rate */
+ KUNIT_CASE(dm_test_get_max_frl_rate_3lanes_3gbps),
+ KUNIT_CASE(dm_test_get_max_frl_rate_3lanes_6gbps),
+ KUNIT_CASE(dm_test_get_max_frl_rate_4lanes_6gbps),
+ KUNIT_CASE(dm_test_get_max_frl_rate_4lanes_8gbps),
+ KUNIT_CASE(dm_test_get_max_frl_rate_4lanes_10gbps),
+ KUNIT_CASE(dm_test_get_max_frl_rate_4lanes_12gbps),
+ KUNIT_CASE(dm_test_get_max_frl_rate_unknown),
+ /* dm_dtn_log_begin / dm_dtn_log_append_v / dm_dtn_log_end */
+ KUNIT_CASE(dm_test_dtn_log_buffer_accumulates),
+ KUNIT_CASE(dm_test_dtn_log_null_ctx_no_crash),
+ /* dm_helpers_dp_read_dpcd / dm_helpers_dp_write_dpcd */
+ KUNIT_CASE(dm_test_dp_read_dpcd_null_priv),
+ KUNIT_CASE(dm_test_dp_write_dpcd_null_priv),
+ /* dm_helpers_dp_mst_start_top_mgr / dm_helpers_dp_mst_stop_top_mgr */
+ KUNIT_CASE(dm_test_mst_start_top_mgr_null_priv),
+ KUNIT_CASE(dm_test_mst_stop_top_mgr_null_priv),
+ KUNIT_CASE(dm_test_mst_start_top_mgr_boot),
+ /* dm_helpers_dp_write_hblank_reduction */
+ KUNIT_CASE(dm_test_dp_write_hblank_reduction_false),
+ {}
+};
+
+static struct kunit_suite amdgpu_dm_helpers_test_suite = {
+ .name = "amdgpu_dm_helpers",
+ .test_cases = amdgpu_dm_helpers_test_cases,
+};
+
+kunit_test_suite(amdgpu_dm_helpers_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_helpers");
+MODULE_LICENSE("Dual MIT/GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 26/32] drm/amd/display: Add KUnit tests for amdgpu_dm_quirks
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (24 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 25/32] drm/amd/display: Add KUnit tests for amdgpu_dm_helpers Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 27/32] drm/amd/display: Add more KUnit tests for amdgpu_dm_pp_smu Chenyu Chen
` (6 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Add KUnit test file amdgpu_dm_quirks_test.c covering retrieve_dmi_info().
Three test cases are provided:
- Verify aux_hpd_discon_quirk is reset to false even when previously true
- Verify edp0_on_dp1_quirk is reset to false even when previously true
- Verify both quirks remain false on a zero-initialised dm when no
DMI match is found (expected in UML/KUnit environment)
Register the new test object in the tests/Makefile under
CONFIG_DRM_AMD_DC_KUNIT_TEST.
Assisted-by: Copilot:Claude-Sonnet-4.6
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_quirks.c | 2 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 1 +
.../amdgpu_dm/tests/amdgpu_dm_quirks_test.c | 103 ++++++++++++++++++
3 files changed, 106 insertions(+)
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c
index 1da07ebf9217..cf28d50c3b5e 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c
@@ -28,6 +28,7 @@
#include "amdgpu.h"
#include "amdgpu_dm.h"
+#include "amdgpu_dm_kunit_helpers.h"
struct amdgpu_dm_quirks {
bool aux_hpd_discon;
@@ -176,3 +177,4 @@ void retrieve_dmi_info(struct amdgpu_display_manager *dm)
drm_info(dev, "support_edp0_on_dp1 attached\n");
}
}
+EXPORT_IF_KUNIT(retrieve_dmi_info);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
index a067332f9f41..168ad064e7cb 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile
@@ -30,3 +30,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_helpers_test.o
+obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_quirks_test.o
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c
new file mode 100644
index 000000000000..a09f31ee0a2a
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * KUnit tests for amdgpu_dm_quirks.c
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <kunit/test.h>
+
+#include "dc.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
+
+/* Tests for retrieve_dmi_info() */
+
+/*
+ * Verify that retrieve_dmi_info() always initialises aux_hpd_discon_quirk to
+ * false, even when the caller had previously set it to true.
+ */
+/**
+ * dm_test_quirks_aux_hpd_discon_reset - Test Quirks aux hpd discon reset
+ * @test: The KUnit test context
+ */
+static void dm_test_quirks_aux_hpd_discon_reset(struct kunit *test)
+{
+ struct amdgpu_display_manager *dm;
+
+ dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm);
+
+ dm->aux_hpd_discon_quirk = true;
+
+ retrieve_dmi_info(dm);
+
+ /*
+ * In a KUnit / UML environment no real DMI table is present, so
+ * dmi_check_system() returns 0 and retrieve_dmi_info() leaves the
+ * quirk at its initialised-to-false value.
+ */
+ KUNIT_EXPECT_FALSE(test, dm->aux_hpd_discon_quirk);
+}
+
+/*
+ * Verify that retrieve_dmi_info() always initialises edp0_on_dp1_quirk to
+ * false, even when the caller had previously set it to true.
+ */
+/**
+ * dm_test_quirks_edp0_on_dp1_reset - Test Quirks edp0 on dp1 reset
+ * @test: The KUnit test context
+ */
+static void dm_test_quirks_edp0_on_dp1_reset(struct kunit *test)
+{
+ struct amdgpu_display_manager *dm;
+
+ dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm);
+
+ dm->edp0_on_dp1_quirk = true;
+
+ retrieve_dmi_info(dm);
+
+ KUNIT_EXPECT_FALSE(test, dm->edp0_on_dp1_quirk);
+}
+
+/*
+ * Verify that when no DMI match is found both quirks remain false after a
+ * fresh (zero-initialised) dm is passed to retrieve_dmi_info().
+ */
+/**
+ * dm_test_quirks_no_dmi_match_both_false - Test Quirks no dmi match both false
+ * @test: The KUnit test context
+ */
+static void dm_test_quirks_no_dmi_match_both_false(struct kunit *test)
+{
+ struct amdgpu_display_manager *dm;
+
+ dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm);
+
+ retrieve_dmi_info(dm);
+
+ KUNIT_EXPECT_FALSE(test, dm->aux_hpd_discon_quirk);
+ KUNIT_EXPECT_FALSE(test, dm->edp0_on_dp1_quirk);
+}
+
+static struct kunit_case amdgpu_dm_quirks_tests[] = {
+ /* retrieve_dmi_info */
+ KUNIT_CASE(dm_test_quirks_aux_hpd_discon_reset),
+ KUNIT_CASE(dm_test_quirks_edp0_on_dp1_reset),
+ KUNIT_CASE(dm_test_quirks_no_dmi_match_both_false),
+ {}
+};
+
+static struct kunit_suite amdgpu_dm_quirks_test_suite = {
+ .name = "amdgpu_dm_quirks",
+ .test_cases = amdgpu_dm_quirks_tests,
+};
+
+kunit_test_suite(amdgpu_dm_quirks_test_suite);
+
+MODULE_AUTHOR("AMD");
+MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_quirks");
+MODULE_LICENSE("Dual MIT/GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 27/32] drm/amd/display: Add more KUnit tests for amdgpu_dm_pp_smu
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (25 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 26/32] drm/amd/display: Add KUnit tests for amdgpu_dm_quirks Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 28/32] drm/amd/display: Add more KUnit tests for amdgpu_dm_mst_types Chenyu Chen
` (5 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
Expand KUnit coverage of amdgpu_dm_pp_smu.c and extract several pure
translation helpers so they can be unit tested in isolation.
Extract pure logic into testable helpers:
- build_pm_display_cfg() from dm_pp_apply_display_requirements()
- build_wm_clock_ranges_soc15() from pp_rv_set_wm_ranges()
- cap_clock_levels_to_validation() from dm_pp_get_clock_levels_by_type()
- pp_smu_nv_clock_id_to_pp() from pp_nv_set_voltage_by_freq()
Tests cover:
- pp_to_dc_clock_levels: within-limit copy and count capping
- pp_to_dc_clock_levels_with_latency: field copy and count capping
- pp_to_dc_clock_levels_with_voltage: field copy and count capping
- dm_pp_get_funcs: RV, RV 1.01, NV, RN, and unsupported versions
- dm_pp_apply_display_requirements: DPM-disabled early-return path
- dm_pp_apply_clock_for_voltage_request: invalid clock type path
- build_pm_display_cfg: scalar field scaling and per-display mapping
- build_wm_clock_ranges_soc15: DMIF and MCIF range translation
- cap_clock_levels_to_validation: engine/memory capping and floor
- pp_smu_nv_clock_id_to_pp: valid ids and invalid-id rejection
Assisted-by: Copilot:Claude-Opus-4.8
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 234 +++---
.../amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 22 +
.../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 736 ++++++++++++++++++
3 files changed, 889 insertions(+), 103 deletions(-)
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 ca7141dbdf6a..e0fe4cb97f31 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
@@ -36,72 +36,64 @@
#include "amdgpu_dm_kunit_helpers.h"
#include "amdgpu_dm_pp_smu.h"
-bool dm_pp_apply_display_requirements(
- const struct dc_context *ctx,
+STATIC_IFN_KUNIT void build_pm_display_cfg(
+ struct amd_pp_display_configuration *pm_display_cfg,
const struct dm_pp_display_configuration *pp_display_cfg)
{
- struct amdgpu_device *adev = ctx->driver_context;
int i;
- if (adev->pm.dpm_enabled) {
+ memset(pm_display_cfg, 0, sizeof(*pm_display_cfg));
- memset(&adev->pm.pm_display_cfg, 0,
- sizeof(adev->pm.pm_display_cfg));
+ pm_display_cfg->cpu_cc6_disable = pp_display_cfg->cpu_cc6_disable;
+ pm_display_cfg->cpu_pstate_disable = pp_display_cfg->cpu_pstate_disable;
+ pm_display_cfg->cpu_pstate_separation_time = pp_display_cfg->cpu_pstate_separation_time;
+ pm_display_cfg->nb_pstate_switch_disable = pp_display_cfg->nb_pstate_switch_disable;
- adev->pm.pm_display_cfg.cpu_cc6_disable =
- pp_display_cfg->cpu_cc6_disable;
+ pm_display_cfg->num_display = pp_display_cfg->display_count;
+ pm_display_cfg->num_path_including_non_display = pp_display_cfg->display_count;
- adev->pm.pm_display_cfg.cpu_pstate_disable =
- pp_display_cfg->cpu_pstate_disable;
+ pm_display_cfg->min_core_set_clock = pp_display_cfg->min_engine_clock_khz/10;
+ pm_display_cfg->min_core_set_clock_in_sr =
+ pp_display_cfg->min_engine_clock_deep_sleep_khz/10;
+ pm_display_cfg->min_mem_set_clock = pp_display_cfg->min_memory_clock_khz/10;
- adev->pm.pm_display_cfg.cpu_pstate_separation_time =
- pp_display_cfg->cpu_pstate_separation_time;
+ pm_display_cfg->min_dcef_deep_sleep_set_clk =
+ pp_display_cfg->min_engine_clock_deep_sleep_khz/10;
+ pm_display_cfg->min_dcef_set_clk = pp_display_cfg->min_dcfclock_khz/10;
- adev->pm.pm_display_cfg.nb_pstate_switch_disable =
- pp_display_cfg->nb_pstate_switch_disable;
+ pm_display_cfg->multi_monitor_in_sync = pp_display_cfg->all_displays_in_sync;
+ pm_display_cfg->min_vblank_time = pp_display_cfg->avail_mclk_switch_time_us;
- adev->pm.pm_display_cfg.num_display =
- pp_display_cfg->display_count;
- adev->pm.pm_display_cfg.num_path_including_non_display =
- pp_display_cfg->display_count;
+ pm_display_cfg->display_clk = pp_display_cfg->disp_clk_khz/10;
- adev->pm.pm_display_cfg.min_core_set_clock =
- pp_display_cfg->min_engine_clock_khz/10;
- adev->pm.pm_display_cfg.min_core_set_clock_in_sr =
- pp_display_cfg->min_engine_clock_deep_sleep_khz/10;
- adev->pm.pm_display_cfg.min_mem_set_clock =
- pp_display_cfg->min_memory_clock_khz/10;
+ pm_display_cfg->dce_tolerable_mclk_in_active_latency =
+ pp_display_cfg->avail_mclk_switch_time_in_disp_active_us;
- adev->pm.pm_display_cfg.min_dcef_deep_sleep_set_clk =
- pp_display_cfg->min_engine_clock_deep_sleep_khz/10;
- adev->pm.pm_display_cfg.min_dcef_set_clk =
- pp_display_cfg->min_dcfclock_khz/10;
+ pm_display_cfg->crtc_index = pp_display_cfg->crtc_index;
+ pm_display_cfg->line_time_in_us = pp_display_cfg->line_time_in_us;
- adev->pm.pm_display_cfg.multi_monitor_in_sync =
- pp_display_cfg->all_displays_in_sync;
- adev->pm.pm_display_cfg.min_vblank_time =
- pp_display_cfg->avail_mclk_switch_time_us;
+ pm_display_cfg->vrefresh = pp_display_cfg->disp_configs[0].v_refresh;
+ pm_display_cfg->crossfire_display_index = -1;
+ pm_display_cfg->min_bus_bandwidth = 0;
- adev->pm.pm_display_cfg.display_clk =
- pp_display_cfg->disp_clk_khz/10;
-
- adev->pm.pm_display_cfg.dce_tolerable_mclk_in_active_latency =
- pp_display_cfg->avail_mclk_switch_time_in_disp_active_us;
+ for (i = 0; i < pp_display_cfg->display_count; i++) {
+ const struct dm_pp_single_disp_config *dc_cfg =
+ &pp_display_cfg->disp_configs[i];
+ pm_display_cfg->displays[i].controller_id = dc_cfg->pipe_idx + 1;
+ pm_display_cfg->displays[i].pixel_clock = dc_cfg->pixel_clock;
+ }
+}
+EXPORT_IF_KUNIT(build_pm_display_cfg);
- adev->pm.pm_display_cfg.crtc_index = pp_display_cfg->crtc_index;
- adev->pm.pm_display_cfg.line_time_in_us =
- pp_display_cfg->line_time_in_us;
+bool dm_pp_apply_display_requirements(
+ const struct dc_context *ctx,
+ const struct dm_pp_display_configuration *pp_display_cfg)
+{
+ struct amdgpu_device *adev = ctx->driver_context;
- adev->pm.pm_display_cfg.vrefresh = pp_display_cfg->disp_configs[0].v_refresh;
- adev->pm.pm_display_cfg.crossfire_display_index = -1;
- adev->pm.pm_display_cfg.min_bus_bandwidth = 0;
+ if (adev->pm.dpm_enabled) {
- for (i = 0; i < pp_display_cfg->display_count; i++) {
- const struct dm_pp_single_disp_config *dc_cfg =
- &pp_display_cfg->disp_configs[i];
- adev->pm.pm_display_cfg.displays[i].controller_id = dc_cfg->pipe_idx + 1;
- adev->pm.pm_display_cfg.displays[i].pixel_clock = dc_cfg->pixel_clock;
- }
+ build_pm_display_cfg(&adev->pm.pm_display_cfg, pp_display_cfg);
amdgpu_dpm_display_configuration_change(adev, &adev->pm.pm_display_cfg);
@@ -110,6 +102,7 @@ bool dm_pp_apply_display_requirements(
return true;
}
+EXPORT_IF_KUNIT(dm_pp_apply_display_requirements);
STATIC_IFN_KUNIT void get_default_clock_levels(
enum dm_pp_clock_type clk_type,
@@ -187,7 +180,7 @@ STATIC_IFN_KUNIT enum amd_pp_clock_type dc_to_pp_clock_type(
}
EXPORT_IF_KUNIT(dc_to_pp_clock_type);
-static void pp_to_dc_clock_levels(
+STATIC_IFN_KUNIT void pp_to_dc_clock_levels(
const struct amd_pp_clocks *pp_clks,
struct dm_pp_clock_levels *dc_clks,
enum dm_pp_clock_type dc_clk_type)
@@ -212,8 +205,9 @@ static void pp_to_dc_clock_levels(
dc_clks->clocks_in_khz[i] = pp_clks->clock[i];
}
}
+EXPORT_IF_KUNIT(pp_to_dc_clock_levels);
-static void pp_to_dc_clock_levels_with_latency(
+STATIC_IFN_KUNIT void pp_to_dc_clock_levels_with_latency(
const struct pp_clock_levels_with_latency *pp_clks,
struct dm_pp_clock_levels_with_latency *clk_level_info,
enum dm_pp_clock_type dc_clk_type)
@@ -239,8 +233,9 @@ static void pp_to_dc_clock_levels_with_latency(
clk_level_info->data[i].latency_in_us = pp_clks->data[i].latency_in_us;
}
}
+EXPORT_IF_KUNIT(pp_to_dc_clock_levels_with_latency);
-static void pp_to_dc_clock_levels_with_voltage(
+STATIC_IFN_KUNIT void pp_to_dc_clock_levels_with_voltage(
const struct pp_clock_levels_with_voltage *pp_clks,
struct dm_pp_clock_levels_with_voltage *clk_level_info,
enum dm_pp_clock_type dc_clk_type)
@@ -267,6 +262,41 @@ static void pp_to_dc_clock_levels_with_voltage(
clk_level_info->data[i].voltage_in_mv = pp_clks->data[i].voltage_in_mv;
}
}
+EXPORT_IF_KUNIT(pp_to_dc_clock_levels_with_voltage);
+
+STATIC_IFN_KUNIT void cap_clock_levels_to_validation(
+ struct dm_pp_clock_levels *dc_clks,
+ enum dm_pp_clock_type clk_type,
+ const struct amd_pp_simple_clock_info *validation_clks)
+{
+ uint32_t i;
+
+ /* Determine the highest non-boosted level from the Validation Clocks */
+ if (clk_type == DM_PP_CLOCK_TYPE_ENGINE_CLK) {
+ for (i = 0; i < dc_clks->num_levels; i++) {
+ if (dc_clks->clocks_in_khz[i] > validation_clks->engine_max_clock) {
+ /* This clock is higher the validation clock.
+ * Than means the previous one is the highest
+ * non-boosted one.
+ */
+ DRM_INFO("DM_PPLIB: reducing engine clock level from %d to %d\n",
+ dc_clks->num_levels, i);
+ dc_clks->num_levels = i > 0 ? i : 1;
+ break;
+ }
+ }
+ } else if (clk_type == DM_PP_CLOCK_TYPE_MEMORY_CLK) {
+ for (i = 0; i < dc_clks->num_levels; i++) {
+ if (dc_clks->clocks_in_khz[i] > validation_clks->memory_max_clock) {
+ DRM_INFO("DM_PPLIB: reducing memory clock level from %d to %d\n",
+ dc_clks->num_levels, i);
+ dc_clks->num_levels = i > 0 ? i : 1;
+ break;
+ }
+ }
+ }
+}
+EXPORT_IF_KUNIT(cap_clock_levels_to_validation);
bool dm_pp_get_clock_levels_by_type(
const struct dc_context *ctx,
@@ -276,7 +306,6 @@ bool dm_pp_get_clock_levels_by_type(
struct amdgpu_device *adev = ctx->driver_context;
struct amd_pp_clocks pp_clks = { 0 };
struct amd_pp_simple_clock_info validation_clks = { 0 };
- uint32_t i;
if (amdgpu_dpm_get_clock_by_type(adev,
dc_to_pp_clock_type(clk_type), &pp_clks)) {
@@ -304,30 +333,7 @@ bool dm_pp_get_clock_levels_by_type(
validation_clks.engine_max_clock *= 10;
validation_clks.memory_max_clock *= 10;
- /* Determine the highest non-boosted level from the Validation Clocks */
- if (clk_type == DM_PP_CLOCK_TYPE_ENGINE_CLK) {
- for (i = 0; i < dc_clks->num_levels; i++) {
- if (dc_clks->clocks_in_khz[i] > validation_clks.engine_max_clock) {
- /* This clock is higher the validation clock.
- * Than means the previous one is the highest
- * non-boosted one.
- */
- DRM_INFO("DM_PPLIB: reducing engine clock level from %d to %d\n",
- dc_clks->num_levels, i);
- dc_clks->num_levels = i > 0 ? i : 1;
- break;
- }
- }
- } else if (clk_type == DM_PP_CLOCK_TYPE_MEMORY_CLK) {
- for (i = 0; i < dc_clks->num_levels; i++) {
- if (dc_clks->clocks_in_khz[i] > validation_clks.memory_max_clock) {
- DRM_INFO("DM_PPLIB: reducing memory clock level from %d to %d\n",
- dc_clks->num_levels, i);
- dc_clks->num_levels = i > 0 ? i : 1;
- break;
- }
- }
- }
+ cap_clock_levels_to_validation(dc_clks, clk_type, &validation_clks);
return true;
}
@@ -411,26 +417,26 @@ bool dm_pp_apply_clock_for_voltage_request(
return true;
}
+EXPORT_IF_KUNIT(dm_pp_apply_clock_for_voltage_request);
-static void pp_rv_set_wm_ranges(struct pp_smu *pp,
- struct pp_smu_wm_range_sets *ranges)
+STATIC_IFN_KUNIT void build_wm_clock_ranges_soc15(
+ const struct pp_smu_wm_range_sets *ranges,
+ struct dm_pp_wm_sets_with_clock_ranges_soc15 *wm_with_clock_ranges)
{
- const struct dc_context *ctx = pp->dm;
- struct amdgpu_device *adev = ctx->driver_context;
- struct dm_pp_wm_sets_with_clock_ranges_soc15 wm_with_clock_ranges;
- struct dm_pp_clock_range_for_dmif_wm_set_soc15 *wm_dce_clocks = wm_with_clock_ranges.wm_dmif_clocks_ranges;
- struct dm_pp_clock_range_for_mcif_wm_set_soc15 *wm_soc_clocks = wm_with_clock_ranges.wm_mcif_clocks_ranges;
+ struct dm_pp_clock_range_for_dmif_wm_set_soc15 *wm_dce_clocks =
+ wm_with_clock_ranges->wm_dmif_clocks_ranges;
+ struct dm_pp_clock_range_for_mcif_wm_set_soc15 *wm_soc_clocks =
+ wm_with_clock_ranges->wm_mcif_clocks_ranges;
int32_t i;
- wm_with_clock_ranges.num_wm_dmif_sets = ranges->num_reader_wm_sets;
- wm_with_clock_ranges.num_wm_mcif_sets = ranges->num_writer_wm_sets;
+ wm_with_clock_ranges->num_wm_dmif_sets = ranges->num_reader_wm_sets;
+ wm_with_clock_ranges->num_wm_mcif_sets = ranges->num_writer_wm_sets;
- for (i = 0; i < wm_with_clock_ranges.num_wm_dmif_sets; i++) {
+ for (i = 0; i < wm_with_clock_ranges->num_wm_dmif_sets; i++) {
if (ranges->reader_wm_sets[i].wm_inst > 3)
wm_dce_clocks[i].wm_set_id = WM_SET_A;
else
- wm_dce_clocks[i].wm_set_id =
- ranges->reader_wm_sets[i].wm_inst;
+ wm_dce_clocks[i].wm_set_id = ranges->reader_wm_sets[i].wm_inst;
wm_dce_clocks[i].wm_max_dcfclk_clk_in_khz =
ranges->reader_wm_sets[i].max_drain_clk_mhz * 1000;
wm_dce_clocks[i].wm_min_dcfclk_clk_in_khz =
@@ -441,12 +447,11 @@ static void pp_rv_set_wm_ranges(struct pp_smu *pp,
ranges->reader_wm_sets[i].min_fill_clk_mhz * 1000;
}
- for (i = 0; i < wm_with_clock_ranges.num_wm_mcif_sets; i++) {
+ for (i = 0; i < wm_with_clock_ranges->num_wm_mcif_sets; i++) {
if (ranges->writer_wm_sets[i].wm_inst > 3)
wm_soc_clocks[i].wm_set_id = WM_SET_A;
else
- wm_soc_clocks[i].wm_set_id =
- ranges->writer_wm_sets[i].wm_inst;
+ wm_soc_clocks[i].wm_set_id = ranges->writer_wm_sets[i].wm_inst;
wm_soc_clocks[i].wm_max_socclk_clk_in_khz =
ranges->writer_wm_sets[i].max_fill_clk_mhz * 1000;
wm_soc_clocks[i].wm_min_socclk_clk_in_khz =
@@ -456,6 +461,17 @@ static void pp_rv_set_wm_ranges(struct pp_smu *pp,
wm_soc_clocks[i].wm_min_mem_clk_in_khz =
ranges->writer_wm_sets[i].min_drain_clk_mhz * 1000;
}
+}
+EXPORT_IF_KUNIT(build_wm_clock_ranges_soc15);
+
+static void pp_rv_set_wm_ranges(struct pp_smu *pp,
+ struct pp_smu_wm_range_sets *ranges)
+{
+ const struct dc_context *ctx = pp->dm;
+ struct amdgpu_device *adev = ctx->driver_context;
+ struct dm_pp_wm_sets_with_clock_ranges_soc15 wm_with_clock_ranges;
+
+ build_wm_clock_ranges_soc15(ranges, &wm_with_clock_ranges);
amdgpu_dpm_set_watermarks_for_clocks_ranges(adev,
&wm_with_clock_ranges);
@@ -604,27 +620,38 @@ static enum pp_smu_status pp_nv_set_pstate_handshake_support(
return PP_SMU_RESULT_OK;
}
-static enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp,
- enum pp_smu_nv_clock_id clock_id, int mhz)
+STATIC_IFN_KUNIT bool pp_smu_nv_clock_id_to_pp(enum pp_smu_nv_clock_id clock_id,
+ enum amd_pp_clock_type *clock_type)
{
- const struct dc_context *ctx = pp->dm;
- struct amdgpu_device *adev = ctx->driver_context;
- struct pp_display_clock_request clock_req;
- int ret = 0;
-
switch (clock_id) {
case PP_SMU_NV_DISPCLK:
- clock_req.clock_type = amd_pp_disp_clock;
+ *clock_type = amd_pp_disp_clock;
break;
case PP_SMU_NV_PHYCLK:
- clock_req.clock_type = amd_pp_phy_clock;
+ *clock_type = amd_pp_phy_clock;
break;
case PP_SMU_NV_PIXELCLK:
- clock_req.clock_type = amd_pp_pixel_clock;
+ *clock_type = amd_pp_pixel_clock;
break;
default:
- break;
+ return false;
}
+
+ return true;
+}
+EXPORT_IF_KUNIT(pp_smu_nv_clock_id_to_pp);
+
+static enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp,
+ enum pp_smu_nv_clock_id clock_id, int mhz)
+{
+ const struct dc_context *ctx = pp->dm;
+ struct amdgpu_device *adev = ctx->driver_context;
+ struct pp_display_clock_request clock_req;
+ int ret = 0;
+
+ if (!pp_smu_nv_clock_id_to_pp(clock_id, &clock_req.clock_type))
+ return PP_SMU_RESULT_FAIL;
+
clock_req.clock_freq_in_khz = mhz * 1000;
/* 0: successful or smu.ppt_funcs->display_clock_voltage_request = NULL
@@ -744,3 +771,4 @@ void dm_pp_get_funcs(
break;
}
}
+EXPORT_IF_KUNIT(dm_pp_get_funcs);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h
index 827b60d5affe..e851e3ee5b63 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h
@@ -8,9 +8,31 @@
#include "dm_pp_interface.h"
+struct amd_pp_display_configuration;
+struct pp_smu_wm_range_sets;
+struct dm_pp_wm_sets_with_clock_ranges_soc15;
+
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+void build_pm_display_cfg(struct amd_pp_display_configuration *pm_display_cfg,
+ const struct dm_pp_display_configuration *pp_display_cfg);
+void build_wm_clock_ranges_soc15(const struct pp_smu_wm_range_sets *ranges,
+ struct dm_pp_wm_sets_with_clock_ranges_soc15 *wm_with_clock_ranges);
void get_default_clock_levels(enum dm_pp_clock_type clk_type, struct dm_pp_clock_levels *clks);
enum amd_pp_clock_type dc_to_pp_clock_type(enum dm_pp_clock_type dm_pp_clk_type);
+void pp_to_dc_clock_levels(const struct amd_pp_clocks *pp_clks,
+ struct dm_pp_clock_levels *dc_clks,
+ enum dm_pp_clock_type dc_clk_type);
+void pp_to_dc_clock_levels_with_latency(const struct pp_clock_levels_with_latency *pp_clks,
+ struct dm_pp_clock_levels_with_latency *clk_level_info,
+ enum dm_pp_clock_type dc_clk_type);
+void pp_to_dc_clock_levels_with_voltage(const struct pp_clock_levels_with_voltage *pp_clks,
+ struct dm_pp_clock_levels_with_voltage *clk_level_info,
+ enum dm_pp_clock_type dc_clk_type);
+void cap_clock_levels_to_validation(struct dm_pp_clock_levels *dc_clks,
+ enum dm_pp_clock_type clk_type,
+ const struct amd_pp_simple_clock_info *validation_clks);
+bool pp_smu_nv_clock_id_to_pp(enum pp_smu_nv_clock_id clock_id,
+ enum amd_pp_clock_type *clock_type);
#endif
#endif /* __AMDGPU_DM_PP_SMU_H__ */
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c
index 556473f55ebe..dbb6dfd5c284 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c
@@ -9,6 +9,9 @@
#include <linux/types.h>
#include "dc.h"
+#include "dm_services.h"
+#include "dm_pp_smu.h"
+#include "amdgpu.h"
#include "amdgpu_mode.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_pp_smu.h"
@@ -210,6 +213,704 @@ static void dm_test_dc_to_pp_clock_type_invalid(struct kunit *test)
KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(0), 0);
}
+/* ---- Tests for pp_to_dc_clock_levels ---- */
+
+/**
+ * dm_test_pp_to_dc_clock_levels_within_limit - Test normal copy within limit
+ * @test: KUnit test context
+ *
+ * Verify that pp_to_dc_clock_levels correctly copies clock values when the
+ * count is within DM_PP_MAX_CLOCK_LEVELS.
+ */
+static void dm_test_pp_to_dc_clock_levels_within_limit(struct kunit *test)
+{
+ struct amd_pp_clocks pp_clks = {};
+ struct dm_pp_clock_levels dc_clks = {};
+
+ pp_clks.count = 3;
+ pp_clks.clock[0] = 300000;
+ pp_clks.clock[1] = 500000;
+ pp_clks.clock[2] = 700000;
+
+ pp_to_dc_clock_levels(&pp_clks, &dc_clks, DM_PP_CLOCK_TYPE_ENGINE_CLK);
+
+ KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 3U);
+ KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[0], 300000U);
+ KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[1], 500000U);
+ KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[2], 700000U);
+}
+
+/**
+ * dm_test_pp_to_dc_clock_levels_caps_at_max - Test count capping at max
+ * @test: KUnit test context
+ *
+ * Verify that pp_to_dc_clock_levels caps num_levels at DM_PP_MAX_CLOCK_LEVELS
+ * when the input count exceeds the maximum.
+ */
+static void dm_test_pp_to_dc_clock_levels_caps_at_max(struct kunit *test)
+{
+ struct amd_pp_clocks pp_clks = {};
+ struct dm_pp_clock_levels dc_clks = {};
+ uint32_t i;
+
+ pp_clks.count = DM_PP_MAX_CLOCK_LEVELS + 1;
+ for (i = 0; i < DM_PP_MAX_CLOCK_LEVELS; i++)
+ pp_clks.clock[i] = (i + 1) * 100000;
+
+ pp_to_dc_clock_levels(&pp_clks, &dc_clks, DM_PP_CLOCK_TYPE_ENGINE_CLK);
+
+ KUNIT_EXPECT_EQ(test, dc_clks.num_levels, (uint32_t)DM_PP_MAX_CLOCK_LEVELS);
+}
+
+/* ---- Tests for pp_to_dc_clock_levels_with_latency ---- */
+
+/**
+ * dm_test_pp_to_dc_clock_levels_latency_within_limit - Test normal copy
+ * @test: KUnit test context
+ *
+ * Verify that pp_to_dc_clock_levels_with_latency correctly copies clock
+ * and latency values when count is within limits.
+ */
+static void dm_test_pp_to_dc_clock_levels_latency_within_limit(struct kunit *test)
+{
+ struct pp_clock_levels_with_latency pp_clks = {};
+ struct dm_pp_clock_levels_with_latency dc_clks = {};
+
+ pp_clks.num_levels = 2;
+ pp_clks.data[0].clocks_in_khz = 400000;
+ pp_clks.data[0].latency_in_us = 10;
+ pp_clks.data[1].clocks_in_khz = 800000;
+ pp_clks.data[1].latency_in_us = 20;
+
+ pp_to_dc_clock_levels_with_latency(&pp_clks, &dc_clks,
+ DM_PP_CLOCK_TYPE_ENGINE_CLK);
+
+ KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 2U);
+ KUNIT_EXPECT_EQ(test, dc_clks.data[0].clocks_in_khz, 400000U);
+ KUNIT_EXPECT_EQ(test, dc_clks.data[0].latency_in_us, 10U);
+ KUNIT_EXPECT_EQ(test, dc_clks.data[1].clocks_in_khz, 800000U);
+ KUNIT_EXPECT_EQ(test, dc_clks.data[1].latency_in_us, 20U);
+}
+
+/**
+ * dm_test_pp_to_dc_clock_levels_latency_caps_at_max - Test count capping
+ * @test: KUnit test context
+ *
+ * Verify that pp_to_dc_clock_levels_with_latency caps num_levels at
+ * DM_PP_MAX_CLOCK_LEVELS when input exceeds the maximum.
+ */
+static void dm_test_pp_to_dc_clock_levels_latency_caps_at_max(struct kunit *test)
+{
+ struct pp_clock_levels_with_latency pp_clks = {};
+ struct dm_pp_clock_levels_with_latency dc_clks = {};
+
+ pp_clks.num_levels = DM_PP_MAX_CLOCK_LEVELS + 1;
+
+ pp_to_dc_clock_levels_with_latency(&pp_clks, &dc_clks,
+ DM_PP_CLOCK_TYPE_ENGINE_CLK);
+
+ KUNIT_EXPECT_EQ(test, dc_clks.num_levels, (uint32_t)DM_PP_MAX_CLOCK_LEVELS);
+}
+
+/* ---- Tests for pp_to_dc_clock_levels_with_voltage ---- */
+
+/**
+ * dm_test_pp_to_dc_clock_levels_voltage_within_limit - Test normal copy
+ * @test: KUnit test context
+ *
+ * Verify that pp_to_dc_clock_levels_with_voltage correctly copies clock
+ * and voltage values when count is within limits.
+ */
+static void dm_test_pp_to_dc_clock_levels_voltage_within_limit(struct kunit *test)
+{
+ struct pp_clock_levels_with_voltage pp_clks = {};
+ struct dm_pp_clock_levels_with_voltage dc_clks = {};
+
+ pp_clks.num_levels = 2;
+ pp_clks.data[0].clocks_in_khz = 300000;
+ pp_clks.data[0].voltage_in_mv = 800;
+ pp_clks.data[1].clocks_in_khz = 600000;
+ pp_clks.data[1].voltage_in_mv = 950;
+
+ pp_to_dc_clock_levels_with_voltage(&pp_clks, &dc_clks,
+ DM_PP_CLOCK_TYPE_MEMORY_CLK);
+
+ KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 2U);
+ KUNIT_EXPECT_EQ(test, dc_clks.data[0].clocks_in_khz, 300000U);
+ KUNIT_EXPECT_EQ(test, dc_clks.data[0].voltage_in_mv, 800U);
+ KUNIT_EXPECT_EQ(test, dc_clks.data[1].clocks_in_khz, 600000U);
+ KUNIT_EXPECT_EQ(test, dc_clks.data[1].voltage_in_mv, 950U);
+}
+
+/**
+ * dm_test_pp_to_dc_clock_levels_voltage_caps_at_max - Test count capping
+ * @test: KUnit test context
+ *
+ * Verify that pp_to_dc_clock_levels_with_voltage caps num_levels at
+ * DM_PP_MAX_CLOCK_LEVELS when input exceeds the maximum.
+ */
+static void dm_test_pp_to_dc_clock_levels_voltage_caps_at_max(struct kunit *test)
+{
+ struct pp_clock_levels_with_voltage pp_clks = {};
+ struct dm_pp_clock_levels_with_voltage dc_clks = {};
+
+ pp_clks.num_levels = DM_PP_MAX_CLOCK_LEVELS + 1;
+
+ pp_to_dc_clock_levels_with_voltage(&pp_clks, &dc_clks,
+ DM_PP_CLOCK_TYPE_MEMORY_CLK);
+
+ KUNIT_EXPECT_EQ(test, dc_clks.num_levels, (uint32_t)DM_PP_MAX_CLOCK_LEVELS);
+}
+
+/* ---- Tests for dm_pp_get_funcs ---- */
+
+/**
+ * dm_test_get_funcs_rv - Test Raven PP SMU function table setup
+ * @test: KUnit test context
+ *
+ * Verify that DCN 1.0 initializes the Raven SMU function table and stores
+ * the DC context in the PP SMU handle.
+ */
+static void dm_test_get_funcs_rv(struct kunit *test)
+{
+ struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ KUNIT_ASSERT_NOT_NULL(test, funcs);
+
+ ctx->dce_version = DCN_VERSION_1_0;
+
+ dm_pp_get_funcs(ctx, funcs);
+
+ KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_VER_RV);
+ KUNIT_EXPECT_PTR_EQ(test, funcs->rv_funcs.pp_smu.dm, ctx);
+ KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_wm_ranges != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_pme_wa_enable != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_display_count != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_min_deep_sleep_dcfclk != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_hard_min_dcfclk_by_freq != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_hard_min_fclk_by_freq != NULL);
+ KUNIT_EXPECT_FALSE(test, funcs->rv_funcs.set_hard_min_socclk_by_freq != NULL);
+}
+
+/**
+ * dm_test_get_funcs_rv_101 - Test DCN 1.01 Raven PP SMU setup
+ * @test: KUnit test context
+ *
+ * Verify that DCN 1.01 uses the same Raven SMU function table as DCN 1.0.
+ */
+static void dm_test_get_funcs_rv_101(struct kunit *test)
+{
+ struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ KUNIT_ASSERT_NOT_NULL(test, funcs);
+
+ ctx->dce_version = DCN_VERSION_1_01;
+
+ dm_pp_get_funcs(ctx, funcs);
+
+ KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_VER_RV);
+ KUNIT_EXPECT_PTR_EQ(test, funcs->rv_funcs.pp_smu.dm, ctx);
+ KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_display_count != NULL);
+}
+
+/**
+ * dm_test_get_funcs_nv - Test Navi PP SMU function table setup
+ * @test: KUnit test context
+ *
+ * Verify that DCN 2.0 initializes the Navi SMU function table and leaves the
+ * unsupported PME workaround callback unset.
+ */
+static void dm_test_get_funcs_nv(struct kunit *test)
+{
+ struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ KUNIT_ASSERT_NOT_NULL(test, funcs);
+
+ ctx->dce_version = DCN_VERSION_2_0;
+
+ dm_pp_get_funcs(ctx, funcs);
+
+ KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_VER_NV);
+ KUNIT_EXPECT_PTR_EQ(test, funcs->nv_funcs.pp_smu.dm, ctx);
+ KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_display_count != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_hard_min_dcfclk_by_freq != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_min_deep_sleep_dcfclk != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_voltage_by_freq != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_wm_ranges != NULL);
+ KUNIT_EXPECT_FALSE(test, funcs->nv_funcs.set_pme_wa_enable != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_hard_min_uclk_by_freq != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.get_maximum_sustainable_clocks != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.get_uclk_dpm_states != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_pstate_handshake_support != NULL);
+}
+
+/**
+ * dm_test_get_funcs_rn - Test Renoir PP SMU function table setup
+ * @test: KUnit test context
+ *
+ * Verify that DCN 2.1 initializes the Renoir SMU function table.
+ */
+static void dm_test_get_funcs_rn(struct kunit *test)
+{
+ struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ KUNIT_ASSERT_NOT_NULL(test, funcs);
+
+ ctx->dce_version = DCN_VERSION_2_1;
+
+ dm_pp_get_funcs(ctx, funcs);
+
+ KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_VER_RN);
+ KUNIT_EXPECT_PTR_EQ(test, funcs->rn_funcs.pp_smu.dm, ctx);
+ KUNIT_EXPECT_TRUE(test, funcs->rn_funcs.set_wm_ranges != NULL);
+ KUNIT_EXPECT_TRUE(test, funcs->rn_funcs.get_dpm_clock_table != NULL);
+}
+
+/**
+ * dm_test_get_funcs_unsupported - Test unsupported DCE version handling
+ * @test: KUnit test context
+ *
+ * Verify that unsupported DCE versions do not initialize a PP SMU version or
+ * function table callbacks.
+ */
+static void dm_test_get_funcs_unsupported(struct kunit *test)
+{
+ struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ KUNIT_ASSERT_NOT_NULL(test, funcs);
+
+ ctx->dce_version = DCE_VERSION_MAX;
+
+ dm_pp_get_funcs(ctx, funcs);
+
+ KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_UNSUPPORTED);
+ KUNIT_EXPECT_FALSE(test, funcs->rv_funcs.set_wm_ranges != NULL);
+}
+
+/* ---- Tests for amdgpu_device-backed entry points ---- */
+
+/**
+ * dm_test_apply_display_requirements_dpm_disabled - Test DPM-disabled path
+ * @test: KUnit test context
+ *
+ * Verify that dm_pp_apply_display_requirements returns true without touching
+ * the display configuration when DPM is disabled.
+ */
+static void dm_test_apply_display_requirements_dpm_disabled(struct kunit *test)
+{
+ struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ struct dm_pp_display_configuration cfg = {};
+
+ KUNIT_ASSERT_NOT_NULL(test, adev);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+ adev->pm.dpm_enabled = false;
+ ctx->driver_context = adev;
+
+ KUNIT_EXPECT_TRUE(test, dm_pp_apply_display_requirements(ctx, &cfg));
+}
+
+/**
+ * dm_test_apply_clock_for_voltage_invalid_type - Test invalid clock type path
+ * @test: KUnit test context
+ *
+ * Verify that dm_pp_apply_clock_for_voltage_request returns false for a clock
+ * type that does not map to a valid PP clock type, taking the early-return
+ * path before any SMU request is issued.
+ */
+static void dm_test_apply_clock_for_voltage_invalid_type(struct kunit *test)
+{
+ struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL);
+ struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ struct dm_pp_clock_for_voltage_req req = {};
+
+ KUNIT_ASSERT_NOT_NULL(test, adev);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+ ctx->driver_context = adev;
+ req.clk_type = (enum dm_pp_clock_type)0xffff;
+ req.clocks_in_khz = 500000;
+
+ KUNIT_EXPECT_FALSE(test, dm_pp_apply_clock_for_voltage_request(ctx, &req));
+}
+
+/* ---- Tests for build_pm_display_cfg ---- */
+
+/**
+ * dm_test_build_pm_display_cfg_scalar_fields - Test scalar field translation
+ * @test: KUnit test context
+ *
+ * Verify that build_pm_display_cfg copies the pass-through fields and applies
+ * the /10 (10 kHz) scaling, and sets the fixed constants.
+ */
+static void dm_test_build_pm_display_cfg_scalar_fields(struct kunit *test)
+{
+ struct amd_pp_display_configuration *pm =
+ kunit_kzalloc(test, sizeof(*pm), GFP_KERNEL);
+ struct dm_pp_display_configuration *pp =
+ kunit_kzalloc(test, sizeof(*pp), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, pm);
+ KUNIT_ASSERT_NOT_NULL(test, pp);
+
+ pp->cpu_cc6_disable = true;
+ pp->cpu_pstate_disable = true;
+ pp->cpu_pstate_separation_time = 7;
+ pp->nb_pstate_switch_disable = true;
+ pp->display_count = 2;
+ pp->min_engine_clock_khz = 300000;
+ pp->min_engine_clock_deep_sleep_khz = 50000;
+ pp->min_memory_clock_khz = 800000;
+ pp->min_dcfclock_khz = 600000;
+ pp->all_displays_in_sync = true;
+ pp->avail_mclk_switch_time_us = 11;
+ pp->disp_clk_khz = 400000;
+ pp->avail_mclk_switch_time_in_disp_active_us = 13;
+ pp->crtc_index = 3;
+ pp->line_time_in_us = 17;
+ pp->disp_configs[0].v_refresh = 60;
+
+ build_pm_display_cfg(pm, pp);
+
+ KUNIT_EXPECT_TRUE(test, pm->cpu_cc6_disable);
+ KUNIT_EXPECT_TRUE(test, pm->cpu_pstate_disable);
+ KUNIT_EXPECT_EQ(test, pm->cpu_pstate_separation_time, 7);
+ KUNIT_EXPECT_TRUE(test, pm->nb_pstate_switch_disable);
+ KUNIT_EXPECT_EQ(test, pm->num_display, 2);
+ KUNIT_EXPECT_EQ(test, pm->num_path_including_non_display, 2);
+ KUNIT_EXPECT_EQ(test, pm->min_core_set_clock, 30000);
+ KUNIT_EXPECT_EQ(test, pm->min_core_set_clock_in_sr, 5000);
+ KUNIT_EXPECT_EQ(test, pm->min_mem_set_clock, 80000);
+ KUNIT_EXPECT_EQ(test, pm->min_dcef_deep_sleep_set_clk, 5000);
+ KUNIT_EXPECT_EQ(test, pm->min_dcef_set_clk, 60000);
+ KUNIT_EXPECT_TRUE(test, pm->multi_monitor_in_sync);
+ KUNIT_EXPECT_EQ(test, pm->min_vblank_time, 11);
+ KUNIT_EXPECT_EQ(test, pm->display_clk, 40000);
+ KUNIT_EXPECT_EQ(test, pm->dce_tolerable_mclk_in_active_latency, 13);
+ KUNIT_EXPECT_EQ(test, pm->crtc_index, 3);
+ KUNIT_EXPECT_EQ(test, pm->line_time_in_us, 17);
+ KUNIT_EXPECT_EQ(test, pm->vrefresh, 60);
+ KUNIT_EXPECT_EQ(test, pm->crossfire_display_index, -1);
+ KUNIT_EXPECT_EQ(test, pm->min_bus_bandwidth, 0);
+}
+
+/**
+ * dm_test_build_pm_display_cfg_per_display - Test per-display translation
+ * @test: KUnit test context
+ *
+ * Verify that build_pm_display_cfg maps each display config, applying the
+ * controller_id = pipe_idx + 1 offset and copying the pixel clock.
+ */
+static void dm_test_build_pm_display_cfg_per_display(struct kunit *test)
+{
+ struct amd_pp_display_configuration *pm =
+ kunit_kzalloc(test, sizeof(*pm), GFP_KERNEL);
+ struct dm_pp_display_configuration *pp =
+ kunit_kzalloc(test, sizeof(*pp), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, pm);
+ KUNIT_ASSERT_NOT_NULL(test, pp);
+
+ pp->display_count = 2;
+ pp->disp_configs[0].pipe_idx = 0;
+ pp->disp_configs[0].pixel_clock = 148500;
+ pp->disp_configs[1].pipe_idx = 4;
+ pp->disp_configs[1].pixel_clock = 297000;
+
+ build_pm_display_cfg(pm, pp);
+
+ KUNIT_EXPECT_EQ(test, pm->displays[0].controller_id, 1);
+ KUNIT_EXPECT_EQ(test, pm->displays[0].pixel_clock, 148500);
+ KUNIT_EXPECT_EQ(test, pm->displays[1].controller_id, 5);
+ KUNIT_EXPECT_EQ(test, pm->displays[1].pixel_clock, 297000);
+}
+
+/* ---- Tests for build_wm_clock_ranges_soc15 ---- */
+
+/**
+ * dm_test_build_wm_clock_ranges_dmif - Test reader (DMIF) watermark sets
+ * @test: KUnit test context
+ *
+ * Verify that build_wm_clock_ranges_soc15 copies the reader set count,
+ * maps wm_inst to wm_set_id (clamping instances > 3 to WM_SET_A), and
+ * converts every clock from MHz to kHz (x1000) into the DMIF clock ranges.
+ */
+static void dm_test_build_wm_clock_ranges_dmif(struct kunit *test)
+{
+ struct pp_smu_wm_range_sets *ranges =
+ kunit_kzalloc(test, sizeof(*ranges), GFP_KERNEL);
+ struct dm_pp_wm_sets_with_clock_ranges_soc15 *wm =
+ kunit_kzalloc(test, sizeof(*wm), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, ranges);
+ KUNIT_ASSERT_NOT_NULL(test, wm);
+
+ ranges->num_reader_wm_sets = 2;
+ /* set 0: wm_inst within range -> preserved */
+ ranges->reader_wm_sets[0].wm_inst = 2;
+ ranges->reader_wm_sets[0].max_drain_clk_mhz = 600;
+ ranges->reader_wm_sets[0].min_drain_clk_mhz = 300;
+ ranges->reader_wm_sets[0].max_fill_clk_mhz = 800;
+ ranges->reader_wm_sets[0].min_fill_clk_mhz = 400;
+ /* set 1: wm_inst > 3 -> clamped to WM_SET_A */
+ ranges->reader_wm_sets[1].wm_inst = 5;
+ ranges->reader_wm_sets[1].max_drain_clk_mhz = 700;
+ ranges->reader_wm_sets[1].min_drain_clk_mhz = 350;
+ ranges->reader_wm_sets[1].max_fill_clk_mhz = 900;
+ ranges->reader_wm_sets[1].min_fill_clk_mhz = 450;
+
+ build_wm_clock_ranges_soc15(ranges, wm);
+
+ KUNIT_EXPECT_EQ(test, wm->num_wm_dmif_sets, 2U);
+ KUNIT_EXPECT_EQ(test, wm->num_wm_mcif_sets, 0U);
+
+ KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_set_id, WM_SET_C);
+ KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_max_dcfclk_clk_in_khz, 600000U);
+ KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_min_dcfclk_clk_in_khz, 300000U);
+ KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_max_mem_clk_in_khz, 800000U);
+ KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_min_mem_clk_in_khz, 400000U);
+
+ KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_set_id, WM_SET_A);
+ KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_max_dcfclk_clk_in_khz, 700000U);
+ KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_min_dcfclk_clk_in_khz, 350000U);
+ KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_max_mem_clk_in_khz, 900000U);
+ KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_min_mem_clk_in_khz, 450000U);
+}
+
+/**
+ * dm_test_build_wm_clock_ranges_mcif - Test writer (MCIF) watermark sets
+ * @test: KUnit test context
+ *
+ * Verify that build_wm_clock_ranges_soc15 copies the writer set count and
+ * maps the writer clocks into the MCIF ranges: fill clocks become socclk
+ * and drain clocks become mem clk, each converted from MHz to kHz.
+ */
+static void dm_test_build_wm_clock_ranges_mcif(struct kunit *test)
+{
+ struct pp_smu_wm_range_sets *ranges =
+ kunit_kzalloc(test, sizeof(*ranges), GFP_KERNEL);
+ struct dm_pp_wm_sets_with_clock_ranges_soc15 *wm =
+ kunit_kzalloc(test, sizeof(*wm), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, ranges);
+ KUNIT_ASSERT_NOT_NULL(test, wm);
+
+ ranges->num_writer_wm_sets = 1;
+ ranges->writer_wm_sets[0].wm_inst = 1;
+ ranges->writer_wm_sets[0].max_fill_clk_mhz = 1200;
+ ranges->writer_wm_sets[0].min_fill_clk_mhz = 600;
+ ranges->writer_wm_sets[0].max_drain_clk_mhz = 1000;
+ ranges->writer_wm_sets[0].min_drain_clk_mhz = 500;
+
+ build_wm_clock_ranges_soc15(ranges, wm);
+
+ KUNIT_EXPECT_EQ(test, wm->num_wm_dmif_sets, 0U);
+ KUNIT_EXPECT_EQ(test, wm->num_wm_mcif_sets, 1U);
+
+ KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_set_id, WM_SET_B);
+ KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_max_socclk_clk_in_khz, 1200000U);
+ KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_min_socclk_clk_in_khz, 600000U);
+ KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_max_mem_clk_in_khz, 1000000U);
+ KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_min_mem_clk_in_khz, 500000U);
+}
+
+/* ---- Tests for cap_clock_levels_to_validation ---- */
+
+/**
+ * dm_test_cap_clock_levels_engine_caps - Test engine clock level capping
+ * @test: KUnit test context
+ *
+ * Verify that for engine clocks, num_levels is reduced to the index of the
+ * first level whose frequency exceeds the engine validation clock.
+ */
+static void dm_test_cap_clock_levels_engine_caps(struct kunit *test)
+{
+ struct dm_pp_clock_levels clks = { 0 };
+ struct amd_pp_simple_clock_info validation = {
+ .engine_max_clock = 450000,
+ .memory_max_clock = 800000,
+ };
+
+ clks.num_levels = 3;
+ clks.clocks_in_khz[0] = 300000;
+ clks.clocks_in_khz[1] = 400000;
+ clks.clocks_in_khz[2] = 500000;
+
+ cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_ENGINE_CLK, &validation);
+
+ KUNIT_EXPECT_EQ(test, clks.num_levels, 2U);
+}
+
+/**
+ * dm_test_cap_clock_levels_engine_first_exceeds - Test floor of one level
+ * @test: KUnit test context
+ *
+ * Verify that when the very first engine clock level already exceeds the
+ * validation clock, num_levels is clamped to 1 rather than 0.
+ */
+static void dm_test_cap_clock_levels_engine_first_exceeds(struct kunit *test)
+{
+ struct dm_pp_clock_levels clks = { 0 };
+ struct amd_pp_simple_clock_info validation = {
+ .engine_max_clock = 100000,
+ .memory_max_clock = 800000,
+ };
+
+ clks.num_levels = 3;
+ clks.clocks_in_khz[0] = 300000;
+ clks.clocks_in_khz[1] = 400000;
+ clks.clocks_in_khz[2] = 500000;
+
+ cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_ENGINE_CLK, &validation);
+
+ KUNIT_EXPECT_EQ(test, clks.num_levels, 1U);
+}
+
+/**
+ * dm_test_cap_clock_levels_memory_caps - Test memory clock level capping
+ * @test: KUnit test context
+ *
+ * Verify that for memory clocks, num_levels is reduced based on the memory
+ * validation clock (and is unaffected by the engine validation clock).
+ */
+static void dm_test_cap_clock_levels_memory_caps(struct kunit *test)
+{
+ struct dm_pp_clock_levels clks = { 0 };
+ struct amd_pp_simple_clock_info validation = {
+ .engine_max_clock = 100000,
+ .memory_max_clock = 700000,
+ };
+
+ clks.num_levels = 2;
+ clks.clocks_in_khz[0] = 333000;
+ clks.clocks_in_khz[1] = 800000;
+
+ cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_MEMORY_CLK, &validation);
+
+ KUNIT_EXPECT_EQ(test, clks.num_levels, 1U);
+}
+
+/**
+ * dm_test_cap_clock_levels_within_limit - Test no capping when within limit
+ * @test: KUnit test context
+ *
+ * Verify that num_levels is left unchanged when no level exceeds the
+ * validation clock.
+ */
+static void dm_test_cap_clock_levels_within_limit(struct kunit *test)
+{
+ struct dm_pp_clock_levels clks = { 0 };
+ struct amd_pp_simple_clock_info validation = {
+ .engine_max_clock = 999000,
+ .memory_max_clock = 999000,
+ };
+
+ clks.num_levels = 3;
+ clks.clocks_in_khz[0] = 300000;
+ clks.clocks_in_khz[1] = 400000;
+ clks.clocks_in_khz[2] = 500000;
+
+ cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_ENGINE_CLK, &validation);
+
+ KUNIT_EXPECT_EQ(test, clks.num_levels, 3U);
+}
+
+/**
+ * dm_test_cap_clock_levels_other_type - Test non-engine/memory types ignored
+ * @test: KUnit test context
+ *
+ * Verify that for clock types other than engine or memory, num_levels is
+ * left unchanged regardless of the validation clocks.
+ */
+static void dm_test_cap_clock_levels_other_type(struct kunit *test)
+{
+ struct dm_pp_clock_levels clks = { 0 };
+ struct amd_pp_simple_clock_info validation = {
+ .engine_max_clock = 1,
+ .memory_max_clock = 1,
+ };
+
+ clks.num_levels = 3;
+ clks.clocks_in_khz[0] = 300000;
+ clks.clocks_in_khz[1] = 400000;
+ clks.clocks_in_khz[2] = 500000;
+
+ cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_DISPLAY_CLK, &validation);
+
+ KUNIT_EXPECT_EQ(test, clks.num_levels, 3U);
+}
+
+/* ---- Tests for pp_smu_nv_clock_id_to_pp ---- */
+
+/**
+ * dm_test_nv_clock_id_dispclk - Test DISPCLK id mapping
+ * @test: KUnit test context
+ *
+ * Verify that PP_SMU_NV_DISPCLK maps to amd_pp_disp_clock and returns true.
+ */
+static void dm_test_nv_clock_id_dispclk(struct kunit *test)
+{
+ enum amd_pp_clock_type clock_type = amd_pp_mem_clock;
+
+ KUNIT_EXPECT_TRUE(test, pp_smu_nv_clock_id_to_pp(PP_SMU_NV_DISPCLK, &clock_type));
+ KUNIT_EXPECT_EQ(test, clock_type, amd_pp_disp_clock);
+}
+
+/**
+ * dm_test_nv_clock_id_phyclk - Test PHYCLK id mapping
+ * @test: KUnit test context
+ *
+ * Verify that PP_SMU_NV_PHYCLK maps to amd_pp_phy_clock and returns true.
+ */
+static void dm_test_nv_clock_id_phyclk(struct kunit *test)
+{
+ enum amd_pp_clock_type clock_type = amd_pp_mem_clock;
+
+ KUNIT_EXPECT_TRUE(test, pp_smu_nv_clock_id_to_pp(PP_SMU_NV_PHYCLK, &clock_type));
+ KUNIT_EXPECT_EQ(test, clock_type, amd_pp_phy_clock);
+}
+
+/**
+ * dm_test_nv_clock_id_pixelclk - Test PIXELCLK id mapping
+ * @test: KUnit test context
+ *
+ * Verify that PP_SMU_NV_PIXELCLK maps to amd_pp_pixel_clock and returns true.
+ */
+static void dm_test_nv_clock_id_pixelclk(struct kunit *test)
+{
+ enum amd_pp_clock_type clock_type = amd_pp_mem_clock;
+
+ KUNIT_EXPECT_TRUE(test, pp_smu_nv_clock_id_to_pp(PP_SMU_NV_PIXELCLK, &clock_type));
+ KUNIT_EXPECT_EQ(test, clock_type, amd_pp_pixel_clock);
+}
+
+/**
+ * dm_test_nv_clock_id_invalid - Test unknown id is rejected
+ * @test: KUnit test context
+ *
+ * Verify that an unknown clock id returns false and leaves the output
+ * clock_type untouched, guarding against the previously uninitialized path.
+ */
+static void dm_test_nv_clock_id_invalid(struct kunit *test)
+{
+ enum amd_pp_clock_type clock_type = amd_pp_dcef_clock;
+
+ KUNIT_EXPECT_FALSE(test, pp_smu_nv_clock_id_to_pp((enum pp_smu_nv_clock_id)0xff,
+ &clock_type));
+ KUNIT_EXPECT_EQ(test, clock_type, amd_pp_dcef_clock);
+}
+
static struct kunit_case dm_pp_smu_test_cases[] = {
/* get_default_clock_levels */
KUNIT_CASE(dm_test_default_clock_levels_display),
@@ -227,6 +928,41 @@ static struct kunit_case dm_pp_smu_test_cases[] = {
KUNIT_CASE(dm_test_dc_to_pp_clock_type_phyclk),
KUNIT_CASE(dm_test_dc_to_pp_clock_type_dppclk),
KUNIT_CASE(dm_test_dc_to_pp_clock_type_invalid),
+ /* pp_to_dc_clock_levels */
+ KUNIT_CASE(dm_test_pp_to_dc_clock_levels_within_limit),
+ KUNIT_CASE(dm_test_pp_to_dc_clock_levels_caps_at_max),
+ /* pp_to_dc_clock_levels_with_latency */
+ KUNIT_CASE(dm_test_pp_to_dc_clock_levels_latency_within_limit),
+ KUNIT_CASE(dm_test_pp_to_dc_clock_levels_latency_caps_at_max),
+ /* pp_to_dc_clock_levels_with_voltage */
+ KUNIT_CASE(dm_test_pp_to_dc_clock_levels_voltage_within_limit),
+ KUNIT_CASE(dm_test_pp_to_dc_clock_levels_voltage_caps_at_max),
+ /* dm_pp_get_funcs */
+ KUNIT_CASE(dm_test_get_funcs_rv),
+ KUNIT_CASE(dm_test_get_funcs_rv_101),
+ KUNIT_CASE(dm_test_get_funcs_nv),
+ KUNIT_CASE(dm_test_get_funcs_rn),
+ KUNIT_CASE(dm_test_get_funcs_unsupported),
+ /* amdgpu_device-backed entry points */
+ KUNIT_CASE(dm_test_apply_display_requirements_dpm_disabled),
+ KUNIT_CASE(dm_test_apply_clock_for_voltage_invalid_type),
+ /* build_pm_display_cfg */
+ KUNIT_CASE(dm_test_build_pm_display_cfg_scalar_fields),
+ KUNIT_CASE(dm_test_build_pm_display_cfg_per_display),
+ /* build_wm_clock_ranges_soc15 */
+ KUNIT_CASE(dm_test_build_wm_clock_ranges_dmif),
+ KUNIT_CASE(dm_test_build_wm_clock_ranges_mcif),
+ /* cap_clock_levels_to_validation */
+ KUNIT_CASE(dm_test_cap_clock_levels_engine_caps),
+ KUNIT_CASE(dm_test_cap_clock_levels_engine_first_exceeds),
+ KUNIT_CASE(dm_test_cap_clock_levels_memory_caps),
+ KUNIT_CASE(dm_test_cap_clock_levels_within_limit),
+ KUNIT_CASE(dm_test_cap_clock_levels_other_type),
+ /* pp_smu_nv_clock_id_to_pp */
+ KUNIT_CASE(dm_test_nv_clock_id_dispclk),
+ KUNIT_CASE(dm_test_nv_clock_id_phyclk),
+ KUNIT_CASE(dm_test_nv_clock_id_pixelclk),
+ KUNIT_CASE(dm_test_nv_clock_id_invalid),
{}
};
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 28/32] drm/amd/display: Add more KUnit tests for amdgpu_dm_mst_types
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (26 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 27/32] drm/amd/display: Add more KUnit tests for amdgpu_dm_pp_smu Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 29/32] drm/amd/display: Set default backlight without ACPI support Chenyu Chen
` (4 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
The following existing functions are also exported for the test module:
- needs_dsc_aux_workaround: detect branches needing the DSC AUX workaround
- dm_mst_get_pbn_divider: compute the PBN divider from link bandwidth
- amdgpu_dm_mst_reset_mst_connector_setting: reset per-connector MST state
- retrieve_downstream_port_device: read downstream port presence from DPCD
- retrieve_branch_specific_data: read branch OUI from the upstream device
Several self-contained pieces of logic are extracted from larger functions
into small testable helpers.
- dm_dp_aux_transfer_result: AUX return-code to errno mapping
- dm_dp_aux_fill_payload_flags: AUX request bit decode
- dm_mst_msg_ready_mask: MST sideband ESI mask selection
- dm_mst_select_esi_dpcd: DPCD ESI address/length selection
Assisted-by: Copilot:Claude-Opus-4.6
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../display/amdgpu_dm/amdgpu_dm_mst_types.c | 143 ++++---
.../display/amdgpu_dm/amdgpu_dm_mst_types.h | 12 +
.../tests/amdgpu_dm_mst_types_test.c | 385 ++++++++++++++++++
3 files changed, 491 insertions(+), 49 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
index 09a6a1deb5db..83d0e1403e54 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
@@ -34,6 +34,7 @@
#include "dm_services.h"
#include "amdgpu.h"
#include "amdgpu_dm.h"
+#include "dmub_cmd.h"
#include "amdgpu_dm_mst_types.h"
#include "amdgpu_dm_hdcp.h"
@@ -44,7 +45,6 @@
#include "ddc_service_types.h"
#include "dpcd_defs.h"
-#include "dmub_cmd.h"
#if defined(CONFIG_DEBUG_FS)
#include "amdgpu_dm_debugfs.h"
#endif
@@ -53,6 +53,49 @@
#define PEAK_FACTOR_X1000 1006
+/*
+ * Translate a failed AUX transaction's operation result into an errno-style
+ * return value. @result is returned unchanged for AUX_RET_SUCCESS.
+ */
+STATIC_IFN_KUNIT ssize_t dm_dp_aux_transfer_result(ssize_t result,
+ enum aux_return_code_type operation_result)
+{
+ switch (operation_result) {
+ case AUX_RET_SUCCESS:
+ break;
+ case AUX_RET_ERROR_HPD_DISCON:
+ case AUX_RET_ERROR_UNKNOWN:
+ case AUX_RET_ERROR_INVALID_OPERATION:
+ case AUX_RET_ERROR_PROTOCOL_ERROR:
+ result = -EIO;
+ break;
+ case AUX_RET_ERROR_INVALID_REPLY:
+ case AUX_RET_ERROR_ENGINE_ACQUIRE:
+ result = -EBUSY;
+ break;
+ case AUX_RET_ERROR_TIMEOUT:
+ result = -ETIMEDOUT;
+ break;
+ }
+
+ return result;
+}
+EXPORT_IF_KUNIT(dm_dp_aux_transfer_result);
+
+/*
+ * Derive the AUX payload transaction flags from a DP AUX request field.
+ */
+STATIC_IFN_KUNIT void dm_dp_aux_fill_payload_flags(u8 request,
+ struct aux_payload *payload)
+{
+ payload->i2c_over_aux = (request & DP_AUX_NATIVE_WRITE) == 0;
+ payload->write = (request & DP_AUX_I2C_READ) == 0;
+ payload->mot = (request & DP_AUX_I2C_MOT) != 0;
+ payload->write_status_update =
+ (request & DP_AUX_I2C_WRITE_STATUS_UPDATE) != 0;
+}
+EXPORT_IF_KUNIT(dm_dp_aux_fill_payload_flags);
+
/*
* This function handles both native AUX and I2C-Over-AUX transactions.
*/
@@ -73,11 +116,7 @@ static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux,
payload.data = msg->buffer;
payload.length = msg->size;
payload.reply = &msg->reply;
- payload.i2c_over_aux = (msg->request & DP_AUX_NATIVE_WRITE) == 0;
- payload.write = (msg->request & DP_AUX_I2C_READ) == 0;
- payload.mot = (msg->request & DP_AUX_I2C_MOT) != 0;
- payload.write_status_update =
- (msg->request & DP_AUX_I2C_WRITE_STATUS_UPDATE) != 0;
+ dm_dp_aux_fill_payload_flags(msg->request, &payload);
payload.defer_delay = 0;
if (payload.write) {
@@ -117,23 +156,7 @@ static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux,
}
if (result < 0) {
- switch (operation_result) {
- case AUX_RET_SUCCESS:
- break;
- case AUX_RET_ERROR_HPD_DISCON:
- case AUX_RET_ERROR_UNKNOWN:
- case AUX_RET_ERROR_INVALID_OPERATION:
- case AUX_RET_ERROR_PROTOCOL_ERROR:
- result = -EIO;
- break;
- case AUX_RET_ERROR_INVALID_REPLY:
- case AUX_RET_ERROR_ENGINE_ACQUIRE:
- result = -EBUSY;
- break;
- case AUX_RET_ERROR_TIMEOUT:
- result = -ETIMEDOUT;
- break;
- }
+ result = dm_dp_aux_transfer_result(result, operation_result);
drm_dbg_dp(adev_to_drm(adev), "DP AUX transfer fail:%d\n", operation_result);
}
@@ -184,7 +207,7 @@ amdgpu_dm_mst_connector_late_register(struct drm_connector *connector)
}
-static inline void
+STATIC_IFN_KUNIT void
amdgpu_dm_mst_reset_mst_connector_setting(struct amdgpu_dm_connector *aconnector)
{
aconnector->drm_edid = NULL;
@@ -193,6 +216,7 @@ amdgpu_dm_mst_reset_mst_connector_setting(struct amdgpu_dm_connector *aconnector
aconnector->mst_local_bw = 0;
aconnector->vc_full_pbn = 0;
}
+EXPORT_IF_KUNIT(amdgpu_dm_mst_reset_mst_connector_setting);
static void
amdgpu_dm_mst_connector_early_unregister(struct drm_connector *connector)
@@ -313,7 +337,7 @@ static bool validate_dsc_caps_on_connector(struct amdgpu_dm_connector *aconnecto
}
#endif
-static bool retrieve_downstream_port_device(struct amdgpu_dm_connector *aconnector)
+STATIC_IFN_KUNIT bool retrieve_downstream_port_device(struct amdgpu_dm_connector *aconnector)
{
union dp_downstream_port_present ds_port_present;
@@ -331,8 +355,9 @@ static bool retrieve_downstream_port_device(struct amdgpu_dm_connector *aconnect
return true;
}
+EXPORT_IF_KUNIT(retrieve_downstream_port_device);
-static bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector)
+STATIC_IFN_KUNIT bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector)
{
struct drm_connector *connector = &aconnector->base;
struct drm_dp_mst_port *port = aconnector->mst_output_port;
@@ -359,6 +384,7 @@ static bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector
return true;
}
+EXPORT_IF_KUNIT(retrieve_branch_specific_data);
static int dm_dp_mst_get_modes(struct drm_connector *connector)
{
@@ -708,6 +734,44 @@ dm_dp_add_mst_connector(struct drm_dp_mst_topology_mgr *mgr,
return connector;
}
+/*
+ * Select the ESI[1] mask used to filter the MST sideband ready bits for a
+ * given message-ready event type.
+ */
+STATIC_IFN_KUNIT u8 dm_mst_msg_ready_mask(enum mst_msg_ready_type msg_rdy_type)
+{
+ switch (msg_rdy_type) {
+ case DOWN_REP_MSG_RDY_EVENT:
+ /* Only handle DOWN_REP_MSG_RDY case*/
+ return DP_DOWN_REP_MSG_RDY;
+ case UP_REQ_MSG_RDY_EVENT:
+ /* Only handle UP_REQ_MSG_RDY case*/
+ return DP_UP_REQ_MSG_RDY;
+ default:
+ /* Handle both cases*/
+ return DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY;
+ }
+}
+EXPORT_IF_KUNIT(dm_mst_msg_ready_mask);
+
+/*
+ * Select the DPCD ESI address and read length based on the DPCD revision.
+ */
+STATIC_IFN_KUNIT void dm_mst_select_esi_dpcd(u8 dpcd_rev, int *dpcd_addr,
+ u8 *dpcd_bytes_to_read)
+{
+ if (dpcd_rev < 0x12) {
+ *dpcd_bytes_to_read = DP_LANE0_1_STATUS - DP_SINK_COUNT;
+ /* DPCD 0x200 - 0x201 for downstream IRQ */
+ *dpcd_addr = DP_SINK_COUNT;
+ } else {
+ *dpcd_bytes_to_read = DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI;
+ /* DPCD 0x2002 - 0x2005 for downstream IRQ */
+ *dpcd_addr = DP_SINK_COUNT_ESI;
+ }
+}
+EXPORT_IF_KUNIT(dm_mst_select_esi_dpcd);
+
void dm_handle_mst_sideband_msg_ready_event(
struct drm_dp_mst_topology_mgr *mgr,
enum mst_msg_ready_type msg_rdy_type)
@@ -726,15 +790,8 @@ void dm_handle_mst_sideband_msg_ready_event(
const struct dc_link_status *link_status = dc_link_get_status(aconnector->dc_link);
- if (link_status->dpcd_caps->dpcd_rev.raw < 0x12) {
- dpcd_bytes_to_read = DP_LANE0_1_STATUS - DP_SINK_COUNT;
- /* DPCD 0x200 - 0x201 for downstream IRQ */
- dpcd_addr = DP_SINK_COUNT;
- } else {
- dpcd_bytes_to_read = DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI;
- /* DPCD 0x2002 - 0x2005 for downstream IRQ */
- dpcd_addr = DP_SINK_COUNT_ESI;
- }
+ dm_mst_select_esi_dpcd(link_status->dpcd_caps->dpcd_rev.raw, &dpcd_addr,
+ &dpcd_bytes_to_read);
mutex_lock(&aconnector->handle_mst_msg_ready);
@@ -756,20 +813,7 @@ void dm_handle_mst_sideband_msg_ready_event(
DRM_DEBUG_DRIVER("ESI %02x %02x %02x\n", esi[0], esi[1], esi[2]);
- switch (msg_rdy_type) {
- case DOWN_REP_MSG_RDY_EVENT:
- /* Only handle DOWN_REP_MSG_RDY case*/
- esi[1] &= DP_DOWN_REP_MSG_RDY;
- break;
- case UP_REQ_MSG_RDY_EVENT:
- /* Only handle UP_REQ_MSG_RDY case*/
- esi[1] &= DP_UP_REQ_MSG_RDY;
- break;
- default:
- /* Handle both cases*/
- esi[1] &= (DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY);
- break;
- }
+ esi[1] &= dm_mst_msg_ready_mask(msg_rdy_type);
if (!esi[1])
break;
@@ -866,6 +910,7 @@ uint32_t dm_mst_get_pbn_divider(struct dc_link *link)
return dfixed_const(pbn_div_x100) / 100;
}
+EXPORT_IF_KUNIT(dm_mst_get_pbn_divider);
struct dsc_mst_fairness_params {
struct dc_crtc_timing *timing;
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h
index 5a7065e53645..5e5190671923 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h
@@ -60,6 +60,7 @@ enum mst_msg_ready_type {
struct amdgpu_device;
struct amdgpu_display_manager;
struct amdgpu_dm_connector;
+struct aux_payload;
struct dc_state;
struct dc_stream_state;
struct dm_atomic_state;
@@ -100,4 +101,15 @@ enum dc_status dm_dp_mst_is_port_support_mode(
struct amdgpu_dm_connector *aconnector,
struct dc_stream_state *stream);
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+void amdgpu_dm_mst_reset_mst_connector_setting(struct amdgpu_dm_connector *aconnector);
+bool retrieve_downstream_port_device(struct amdgpu_dm_connector *aconnector);
+bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector);
+ssize_t dm_dp_aux_transfer_result(ssize_t result,
+ enum aux_return_code_type operation_result);
+void dm_dp_aux_fill_payload_flags(u8 request, struct aux_payload *payload);
+u8 dm_mst_msg_ready_mask(enum mst_msg_ready_type msg_rdy_type);
+void dm_mst_select_esi_dpcd(u8 dpcd_rev, int *dpcd_addr, u8 *dpcd_bytes_to_read);
+#endif
+
#endif
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c
index e21386819ea1..e3b171992be1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c
@@ -7,10 +7,44 @@
#include <kunit/test.h>
+#include <drm/display/drm_dp.h>
+#include <drm/display/drm_dp_helper.h>
+#include <drm/display/drm_dp_mst_helper.h>
+
#include "dc.h"
#include "dpcd_defs.h"
+#include "dmub_cmd.h"
+#include "amdgpu.h"
+#include "amdgpu_mode.h"
+#include "amdgpu_dm.h"
#include "amdgpu_dm_mst_types.h"
+/*
+ * Minimal mock DPCD backing store and AUX transfer callback used to exercise
+ * the DPCD read paths without real hardware.
+ */
+static u8 dm_mst_test_dpcd[0x10];
+
+static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux,
+ struct drm_dp_aux_msg *msg)
+{
+ size_t i;
+
+ switch (msg->request & ~DP_AUX_I2C_MOT) {
+ case DP_AUX_NATIVE_READ:
+ for (i = 0; i < msg->size; i++)
+ ((u8 *)msg->buffer)[i] =
+ dm_mst_test_dpcd[(msg->address + i) & 0xf];
+ msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+ return msg->size;
+ case DP_AUX_NATIVE_WRITE:
+ msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+ return msg->size;
+ default:
+ return -EINVAL;
+ }
+}
+
/* Tests for needs_dsc_aux_workaround */
/**
@@ -103,6 +137,332 @@ static void dm_mst_test_needs_dsc_aux_workaround_low_sink_count(struct kunit *te
KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link));
}
+/**
+ * dm_mst_test_needs_dsc_aux_workaround_zero_sink_count - Test workaround skipped for zero sinks
+ * @test: KUnit test context
+ *
+ * Verify that needs_dsc_aux_workaround() returns false when the sink
+ * count is zero, even if device ID and DPCD rev match.
+ */
+static void dm_mst_test_needs_dsc_aux_workaround_zero_sink_count(struct kunit *test)
+{
+ struct dc_link link = {0};
+
+ link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24;
+ link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14;
+ link.dpcd_caps.sink_count.bits.SINK_COUNT = 0;
+
+ KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link));
+}
+
+/* Tests for dm_mst_get_pbn_divider */
+
+/**
+ * dm_mst_test_pbn_divider_null_link - Test pbn_divider with NULL link
+ * @test: KUnit test context
+ *
+ * Verify that dm_mst_get_pbn_divider() returns 0 when passed a NULL
+ * link pointer without crashing.
+ */
+static void dm_mst_test_pbn_divider_null_link(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, dm_mst_get_pbn_divider(NULL), 0U);
+}
+
+/* Tests for amdgpu_dm_mst_reset_mst_connector_setting */
+
+/**
+ * dm_mst_test_reset_connector_setting - Test MST connector setting reset
+ * @test: KUnit test context
+ *
+ * Verify that amdgpu_dm_mst_reset_mst_connector_setting() clears the cached
+ * EDID, DSC AUX, passthrough AUX, local bandwidth, and VC PBN state.
+ */
+static void dm_mst_test_reset_connector_setting(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_dp_mst_port *port;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, port);
+
+ aconnector->drm_edid = (const struct drm_edid *)test;
+ aconnector->dsc_aux = (struct drm_dp_aux *)test;
+ aconnector->mst_output_port = port;
+ aconnector->mst_output_port->passthrough_aux = (struct drm_dp_aux *)test;
+ aconnector->mst_local_bw = 12345;
+ aconnector->vc_full_pbn = 678;
+
+ amdgpu_dm_mst_reset_mst_connector_setting(aconnector);
+
+ KUNIT_EXPECT_TRUE(test, aconnector->drm_edid == NULL);
+ KUNIT_EXPECT_TRUE(test, aconnector->dsc_aux == NULL);
+ KUNIT_EXPECT_TRUE(test, aconnector->mst_output_port->passthrough_aux == NULL);
+ KUNIT_EXPECT_EQ(test, aconnector->mst_local_bw, 0U);
+ KUNIT_EXPECT_EQ(test, aconnector->vc_full_pbn, 0U);
+}
+
+/* Tests for retrieve_downstream_port_device */
+
+/**
+ * dm_mst_test_retrieve_downstream_no_aux - Test retrieval bails out without AUX
+ * @test: KUnit test context
+ *
+ * Verify that retrieve_downstream_port_device() returns false when the
+ * connector has no DSC AUX channel and therefore cannot read DPCD.
+ */
+static void dm_mst_test_retrieve_downstream_no_aux(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+
+ aconnector->dsc_aux = NULL;
+
+ KUNIT_EXPECT_FALSE(test, retrieve_downstream_port_device(aconnector));
+}
+
+/**
+ * dm_mst_test_retrieve_downstream_present - Test retrieval parses DPCD 0x05
+ * @test: KUnit test context
+ *
+ * Verify that retrieve_downstream_port_device() reads DP_DOWNSTREAMPORT_PRESENT
+ * over a mock AUX channel and caches the parsed downstream port fields.
+ */
+static void dm_mst_test_retrieve_downstream_present(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_dp_aux *aux;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, aux);
+
+ memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd));
+ /* PORT_PRESENT = 1, PORT_TYPE = 2 (0b101) */
+ dm_mst_test_dpcd[DP_DOWNSTREAMPORT_PRESENT] = 0x05;
+
+ aux->name = "dm_mst_test_aux";
+ aux->transfer = dm_mst_test_aux_transfer;
+ drm_dp_aux_init(aux);
+ drm_dp_dpcd_set_probe(aux, false);
+ aconnector->dsc_aux = aux;
+
+ KUNIT_EXPECT_TRUE(test, retrieve_downstream_port_device(aconnector));
+ KUNIT_EXPECT_EQ(test,
+ (int)aconnector->mst_downstream_port_present.fields.PORT_PRESENT, 1);
+ KUNIT_EXPECT_EQ(test,
+ (int)aconnector->mst_downstream_port_present.fields.PORT_TYPE, 2);
+}
+
+/* Tests for retrieve_branch_specific_data */
+
+/**
+ * dm_mst_test_retrieve_branch_no_parent - Test branch lookup needs a parent port
+ * @test: KUnit test context
+ *
+ * Verify that retrieve_branch_specific_data() returns false when the MST
+ * output port has no parent branch device to query.
+ */
+static void dm_mst_test_retrieve_branch_no_parent(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_dp_mst_port *port;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ KUNIT_ASSERT_NOT_NULL(test, port);
+
+ port->parent = NULL;
+ aconnector->mst_output_port = port;
+
+ KUNIT_EXPECT_FALSE(test, retrieve_branch_specific_data(aconnector));
+}
+
+/**
+ * dm_mst_test_aux_result_success - AUX_RET_SUCCESS preserves the input result.
+ * @test: KUnit test context.
+ *
+ * On success the original (negative) transfer result must be returned unchanged.
+ */
+static void dm_mst_test_aux_result_success(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-5, AUX_RET_SUCCESS), (ssize_t)-5);
+ KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(3, AUX_RET_SUCCESS), (ssize_t)3);
+}
+
+/**
+ * dm_mst_test_aux_result_eio - HPD/unknown/protocol errors map to -EIO.
+ * @test: KUnit test context.
+ *
+ * AUX_RET_ERROR_HPD_DISCON, AUX_RET_ERROR_UNKNOWN,
+ * AUX_RET_ERROR_INVALID_OPERATION and AUX_RET_ERROR_PROTOCOL_ERROR all map to -EIO.
+ */
+static void dm_mst_test_aux_result_eio(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_HPD_DISCON),
+ (ssize_t)-EIO);
+ KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_UNKNOWN),
+ (ssize_t)-EIO);
+ KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_INVALID_OPERATION),
+ (ssize_t)-EIO);
+ KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_PROTOCOL_ERROR),
+ (ssize_t)-EIO);
+}
+
+/**
+ * dm_mst_test_aux_result_ebusy - invalid reply / engine acquire map to -EBUSY.
+ * @test: KUnit test context.
+ *
+ * AUX_RET_ERROR_INVALID_REPLY and AUX_RET_ERROR_ENGINE_ACQUIRE map to -EBUSY.
+ */
+static void dm_mst_test_aux_result_ebusy(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_INVALID_REPLY),
+ (ssize_t)-EBUSY);
+ KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_ENGINE_ACQUIRE),
+ (ssize_t)-EBUSY);
+}
+
+/**
+ * dm_mst_test_aux_result_timeout - AUX_RET_ERROR_TIMEOUT maps to -ETIMEDOUT.
+ * @test: KUnit test context.
+ */
+static void dm_mst_test_aux_result_timeout(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_TIMEOUT),
+ (ssize_t)-ETIMEDOUT);
+}
+
+/**
+ * dm_mst_test_fill_payload_flags_native_write - native write request decode.
+ * @test: KUnit test context.
+ *
+ * DP_AUX_NATIVE_WRITE clears i2c_over_aux and sets write; no I2C bits set.
+ */
+static void dm_mst_test_fill_payload_flags_native_write(struct kunit *test)
+{
+ struct aux_payload payload = { 0 };
+
+ dm_dp_aux_fill_payload_flags(DP_AUX_NATIVE_WRITE, &payload);
+
+ KUNIT_EXPECT_FALSE(test, payload.i2c_over_aux);
+ KUNIT_EXPECT_TRUE(test, payload.write);
+ KUNIT_EXPECT_FALSE(test, payload.mot);
+ KUNIT_EXPECT_FALSE(test, payload.write_status_update);
+}
+
+/**
+ * dm_mst_test_fill_payload_flags_native_read - native read request decode.
+ * @test: KUnit test context.
+ *
+ * DP_AUX_NATIVE_READ keeps i2c_over_aux clear; the I2C_READ bit clears write.
+ */
+static void dm_mst_test_fill_payload_flags_native_read(struct kunit *test)
+{
+ struct aux_payload payload = { 0 };
+
+ dm_dp_aux_fill_payload_flags(DP_AUX_NATIVE_READ, &payload);
+
+ KUNIT_EXPECT_FALSE(test, payload.i2c_over_aux);
+ KUNIT_EXPECT_FALSE(test, payload.write);
+ KUNIT_EXPECT_FALSE(test, payload.mot);
+}
+
+/**
+ * dm_mst_test_fill_payload_flags_i2c_read_mot - I2C read with MOT request decode.
+ * @test: KUnit test context.
+ *
+ * DP_AUX_I2C_READ sets i2c_over_aux and clears write; DP_AUX_I2C_MOT sets mot.
+ */
+static void dm_mst_test_fill_payload_flags_i2c_read_mot(struct kunit *test)
+{
+ struct aux_payload payload = { 0 };
+
+ dm_dp_aux_fill_payload_flags(DP_AUX_I2C_READ | DP_AUX_I2C_MOT, &payload);
+
+ KUNIT_EXPECT_TRUE(test, payload.i2c_over_aux);
+ KUNIT_EXPECT_FALSE(test, payload.write);
+ KUNIT_EXPECT_TRUE(test, payload.mot);
+}
+
+/**
+ * dm_mst_test_fill_payload_flags_write_status - write status update decode.
+ * @test: KUnit test context.
+ *
+ * DP_AUX_I2C_WRITE_STATUS_UPDATE sets write_status_update.
+ */
+static void dm_mst_test_fill_payload_flags_write_status(struct kunit *test)
+{
+ struct aux_payload payload = { 0 };
+
+ dm_dp_aux_fill_payload_flags(DP_AUX_I2C_WRITE | DP_AUX_I2C_WRITE_STATUS_UPDATE,
+ &payload);
+
+ KUNIT_EXPECT_TRUE(test, payload.i2c_over_aux);
+ KUNIT_EXPECT_TRUE(test, payload.write_status_update);
+}
+
+/**
+ * dm_mst_test_msg_ready_mask - ESI mask selection per message-ready type.
+ * @test: KUnit test context.
+ *
+ * DOWN_REP and UP_REQ each select their single bit; other types select both.
+ */
+static void dm_mst_test_msg_ready_mask(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, dm_mst_msg_ready_mask(DOWN_REP_MSG_RDY_EVENT),
+ (u8)DP_DOWN_REP_MSG_RDY);
+ KUNIT_EXPECT_EQ(test, dm_mst_msg_ready_mask(UP_REQ_MSG_RDY_EVENT),
+ (u8)DP_UP_REQ_MSG_RDY);
+ KUNIT_EXPECT_EQ(test, dm_mst_msg_ready_mask(DOWN_OR_UP_MSG_RDY_EVENT),
+ (u8)(DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY));
+ KUNIT_EXPECT_EQ(test, dm_mst_msg_ready_mask(NONE_MSG_RDY_EVENT),
+ (u8)(DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY));
+}
+
+/**
+ * dm_mst_test_select_esi_dpcd_legacy - pre-1.2 DPCD ESI address/length.
+ * @test: KUnit test context.
+ *
+ * For DPCD rev < 0x12 the legacy DP_SINK_COUNT address/length pair is selected.
+ */
+static void dm_mst_test_select_esi_dpcd_legacy(struct kunit *test)
+{
+ int dpcd_addr = -1;
+ u8 dpcd_bytes_to_read = 0;
+
+ dm_mst_select_esi_dpcd(0x11, &dpcd_addr, &dpcd_bytes_to_read);
+
+ KUNIT_EXPECT_EQ(test, dpcd_addr, DP_SINK_COUNT);
+ KUNIT_EXPECT_EQ(test, (int)dpcd_bytes_to_read,
+ (int)(DP_LANE0_1_STATUS - DP_SINK_COUNT));
+}
+
+/**
+ * dm_mst_test_select_esi_dpcd_esi - 1.2+ DPCD ESI address/length.
+ * @test: KUnit test context.
+ *
+ * For DPCD rev >= 0x12 the ESI DP_SINK_COUNT_ESI address/length pair is selected.
+ */
+static void dm_mst_test_select_esi_dpcd_esi(struct kunit *test)
+{
+ int dpcd_addr = -1;
+ u8 dpcd_bytes_to_read = 0;
+
+ dm_mst_select_esi_dpcd(0x14, &dpcd_addr, &dpcd_bytes_to_read);
+
+ KUNIT_EXPECT_EQ(test, dpcd_addr, DP_SINK_COUNT_ESI);
+ KUNIT_EXPECT_EQ(test, (int)dpcd_bytes_to_read,
+ (int)(DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI));
+}
+
static struct kunit_case dm_mst_types_test_cases[] = {
/* needs_dsc_aux_workaround tests */
KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_match),
@@ -110,6 +470,31 @@ static struct kunit_case dm_mst_types_test_cases[] = {
KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id),
KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_wrong_rev),
KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_low_sink_count),
+ KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_zero_sink_count),
+ /* dm_mst_get_pbn_divider tests */
+ KUNIT_CASE(dm_mst_test_pbn_divider_null_link),
+ /* amdgpu_dm_mst_reset_mst_connector_setting tests */
+ KUNIT_CASE(dm_mst_test_reset_connector_setting),
+ /* retrieve_downstream_port_device tests */
+ KUNIT_CASE(dm_mst_test_retrieve_downstream_no_aux),
+ KUNIT_CASE(dm_mst_test_retrieve_downstream_present),
+ /* retrieve_branch_specific_data tests */
+ KUNIT_CASE(dm_mst_test_retrieve_branch_no_parent),
+ /* dm_dp_aux_transfer_result tests */
+ KUNIT_CASE(dm_mst_test_aux_result_success),
+ KUNIT_CASE(dm_mst_test_aux_result_eio),
+ KUNIT_CASE(dm_mst_test_aux_result_ebusy),
+ KUNIT_CASE(dm_mst_test_aux_result_timeout),
+ /* dm_dp_aux_fill_payload_flags tests */
+ KUNIT_CASE(dm_mst_test_fill_payload_flags_native_write),
+ KUNIT_CASE(dm_mst_test_fill_payload_flags_native_read),
+ KUNIT_CASE(dm_mst_test_fill_payload_flags_i2c_read_mot),
+ KUNIT_CASE(dm_mst_test_fill_payload_flags_write_status),
+ /* dm_mst_msg_ready_mask tests */
+ KUNIT_CASE(dm_mst_test_msg_ready_mask),
+ /* dm_mst_select_esi_dpcd tests */
+ KUNIT_CASE(dm_mst_test_select_esi_dpcd_legacy),
+ KUNIT_CASE(dm_mst_test_select_esi_dpcd_esi),
{}
};
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 29/32] drm/amd/display: Set default backlight without ACPI support
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (27 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 28/32] drm/amd/display: Add more KUnit tests for amdgpu_dm_mst_types Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 30/32] drm/amd/display: Move backlight macros to backlight header Chenyu Chen
` (3 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Mario Limonciello,
Edson Juliano Drosdeck, Chenyu Chen
From: Mario Limonciello <mario.limonciello@amd.com>
[Why]
If BIOS doesn't include ATIF method it will not specify default AC or DC
levels. This means that backlight will always start at 0%, which isn't
expected behavior.
[How]
Set default AC and DC level when no valid caps found. Also reduce code
duplication for ACPI and non-ACPI cases.
Reported-by: Edson Juliano Drosdeck <edson.drosdeck@gmail.com>
Closes: https://lore.kernel.org/dri-devel/20260526210048.1162477-1-edson.drosdeck@gmail.com/
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
index f101aed75bb3..0a861d846677 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
@@ -78,20 +78,16 @@ void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm,
caps->caps_valid = false;
}
}
-
+#else
+ if (caps->aux_support)
+ return;
+#endif
if (!caps->caps_valid) {
caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
+ caps->ac_level = caps->dc_level = 50;
caps->caps_valid = true;
}
-#else
- if (caps->aux_support)
- return;
-
- caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
- caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- caps->caps_valid = true;
-#endif
}
EXPORT_IF_KUNIT(amdgpu_dm_update_backlight_caps);
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 30/32] drm/amd/display: Move backlight macros to backlight header
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (28 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 29/32] drm/amd/display: Set default backlight without ACPI support Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 31/32] drm/amd/display: [FW Promotion] Release 0.1.63.0 Chenyu Chen
` (2 subsequent siblings)
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Bhawanpreet Lakha, Chenyu Chen
From: Alex Hung <alex.hung@amd.com>
[WHAT]
Move AMDGPU_DM_DEFAULT_MIN_BACKLIGHT, AMDGPU_DM_DEFAULT_MAX_BACKLIGHT,
AMDGPU_DM_MIN_SPREAD, and AUX_BL_DEFAULT_TRANSITION_TIME_MS from
amdgpu_dm_backlight.c to amdgpu_dm_backlight.h so they can be reused
by KUnit tests. Update the test file to use these macros instead of
hardcoded literal values.
Assisted-by: Copilot:Claude-Opus-4.6
Reviewed-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
---
.../display/amdgpu_dm/amdgpu_dm_backlight.c | 5 --
.../display/amdgpu_dm/amdgpu_dm_backlight.h | 5 ++
.../tests/amdgpu_dm_backlight_test.c | 62 +++++++++----------
3 files changed, 36 insertions(+), 36 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
index 0a861d846677..f19092a3237e 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
@@ -49,11 +49,6 @@
#include "amd_shared.h"
#include "amdgpu_dm_kunit_helpers.h"
-#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12
-#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255
-#define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2)
-#define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50
-
void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm,
int bl_idx)
{
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
index 5234da6ae484..a6c01b7ccab3 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
@@ -29,6 +29,11 @@ struct amdgpu_dm_connector;
struct drm_connector;
struct attribute_group;
+#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12
+#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255
+#define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2)
+#define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50
+
void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm,
int bl_idx);
void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
index 2f4293cfd478..8763cd635ae1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
@@ -110,8 +110,8 @@ static void dm_test_backlight_caps_non_aux_sets_defaults(struct kunit *test)
amdgpu_dm_update_backlight_caps(dm, 0);
KUNIT_EXPECT_TRUE(test, caps->caps_valid);
- KUNIT_EXPECT_EQ(test, caps->min_input_signal, 12);
- KUNIT_EXPECT_EQ(test, caps->max_input_signal, 255);
+ KUNIT_EXPECT_EQ(test, caps->min_input_signal, AMDGPU_DM_DEFAULT_MIN_BACKLIGHT);
+ KUNIT_EXPECT_EQ(test, caps->max_input_signal, AMDGPU_DM_DEFAULT_MAX_BACKLIGHT);
}
#endif
@@ -141,13 +141,13 @@ static void dm_test_brightness_range_pwm(struct kunit *test)
unsigned int min, max;
caps.aux_support = false;
- caps.min_input_signal = 12;
- caps.max_input_signal = 255;
+ caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1);
- /* 0x101 * 12 = 3084, 0x101 * 255 = 65535 */
- KUNIT_EXPECT_EQ(test, min, 0x101U * 12);
- KUNIT_EXPECT_EQ(test, max, 0x101U * 255);
+ /* 0x101 * AMDGPU_DM_DEFAULT_MIN_BACKLIGHT, 0x101 * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT */
+ KUNIT_EXPECT_EQ(test, min, 0x101U * AMDGPU_DM_DEFAULT_MIN_BACKLIGHT);
+ KUNIT_EXPECT_EQ(test, max, 0x101U * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT);
}
/**
@@ -195,10 +195,10 @@ static void dm_test_brightness_to_user_below_min(struct kunit *test)
struct amdgpu_dm_backlight_caps caps = {};
caps.aux_support = false;
- caps.min_input_signal = 12;
- caps.max_input_signal = 255;
+ caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- /* brightness < min (0x101*12 = 3084), should return 0 */
+ /* brightness < min (0x101*AMDGPU_DM_DEFAULT_MIN_BACKLIGHT), should return 0 */
KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, 100), 0U);
}
@@ -212,8 +212,8 @@ static void dm_test_brightness_to_user_at_max(struct kunit *test)
unsigned int min, max;
caps.aux_support = false;
- caps.min_input_signal = 12;
- caps.max_input_signal = 255;
+ caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
get_brightness_range(&caps, &min, &max);
@@ -231,8 +231,8 @@ static void dm_test_brightness_to_user_at_min(struct kunit *test)
unsigned int min, max;
caps.aux_support = false;
- caps.min_input_signal = 12;
- caps.max_input_signal = 255;
+ caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
get_brightness_range(&caps, &min, &max);
@@ -251,8 +251,8 @@ static void dm_test_brightness_to_user_midpoint_pwm(struct kunit *test)
u64 expected;
caps.aux_support = false;
- caps.min_input_signal = 12;
- caps.max_input_signal = 255;
+ caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
get_brightness_range(&caps, &min, &max);
@@ -286,8 +286,8 @@ static void dm_test_brightness_from_user_zero(struct kunit *test)
unsigned int min, max;
caps.aux_support = false;
- caps.min_input_signal = 12;
- caps.max_input_signal = 255;
+ caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
/* no custom curve */
caps.data_points = 0;
@@ -307,8 +307,8 @@ static void dm_test_brightness_from_user_max(struct kunit *test)
unsigned int min, max;
caps.aux_support = false;
- caps.min_input_signal = 12;
- caps.max_input_signal = 255;
+ caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps.data_points = 0;
get_brightness_range(&caps, &min, &max);
@@ -403,7 +403,7 @@ static void dm_test_custom_brightness_exact_match(struct kunit *test)
caps.aux_support = false;
caps.min_input_signal = 0;
- caps.max_input_signal = 255;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps.data_points = 3;
caps.luminance_data[0].input_signal = 50;
caps.luminance_data[0].luminance = 20;
@@ -453,7 +453,7 @@ static void dm_test_custom_brightness_below_first(struct kunit *test)
caps.aux_support = false;
caps.min_input_signal = 0;
- caps.max_input_signal = 255;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps.data_points = 2;
caps.luminance_data[0].input_signal = 100;
caps.luminance_data[0].luminance = 40;
@@ -498,7 +498,7 @@ static void dm_test_custom_brightness_interpolation(struct kunit *test)
caps.aux_support = false;
caps.min_input_signal = 0;
- caps.max_input_signal = 255;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps.data_points = 2;
caps.luminance_data[0].input_signal = 50;
caps.luminance_data[0].luminance = 20;
@@ -539,7 +539,7 @@ static void dm_test_custom_brightness_above_last(struct kunit *test)
caps.aux_support = false;
caps.min_input_signal = 0;
- caps.max_input_signal = 255;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps.data_points = 2;
caps.luminance_data[0].input_signal = 50;
caps.luminance_data[0].luminance = 20;
@@ -580,7 +580,7 @@ static void dm_test_custom_brightness_single_data_point(struct kunit *test)
caps.aux_support = false;
caps.min_input_signal = 0;
- caps.max_input_signal = 255;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps.data_points = 1;
caps.luminance_data[0].input_signal = 128;
caps.luminance_data[0].luminance = 50;
@@ -616,7 +616,7 @@ static void dm_test_custom_brightness_lower_lum_zero(struct kunit *test)
caps.aux_support = false;
caps.min_input_signal = 0;
- caps.max_input_signal = 255;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps.data_points = 2;
caps.luminance_data[0].input_signal = 50;
caps.luminance_data[0].luminance = 0; /* zero lower luminance */
@@ -650,8 +650,8 @@ static void dm_test_brightness_to_user_above_max(struct kunit *test)
unsigned int min, max, result;
caps.aux_support = false;
- caps.min_input_signal = 12;
- caps.max_input_signal = 255;
+ caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
get_brightness_range(&caps, &min, &max);
@@ -672,8 +672,8 @@ static void dm_test_brightness_from_user_midrange(struct kunit *test)
u32 result;
caps.aux_support = false;
- caps.min_input_signal = 12;
- caps.max_input_signal = 255;
+ caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps.data_points = 0;
get_brightness_range(&caps, &min, &max);
@@ -700,7 +700,7 @@ static void dm_test_brightness_from_user_with_curve(struct kunit *test)
caps.aux_support = false;
caps.min_input_signal = 0;
- caps.max_input_signal = 255;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps.data_points = 2;
caps.luminance_data[0].input_signal = 50;
caps.luminance_data[0].luminance = 20;
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 31/32] drm/amd/display: [FW Promotion] Release 0.1.63.0
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (29 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 30/32] drm/amd/display: Move backlight macros to backlight header Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-10 9:45 ` [PATCH 32/32] drm/amd/display: Promote DC to 3.2.386 Chenyu Chen
2026-06-15 13:11 ` [PATCH 00/32] DC Patches Jun 15 2026 Wheeler, Daniel
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Taimur Hassan, Chenyu Chen
From: Taimur Hassan <Syed.Hassan@amd.com>
[Why & How]
Add some CACP command and remove some unused struct and enum.
Signed-off-by: Taimur Hassan <Syed.Hassan@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
Acked-by: Tom Chung <chiahsuan.chung@amd.com>
---
.../gpu/drm/amd/display/dmub/inc/dmub_cmd.h | 178 ++++--------------
1 file changed, 35 insertions(+), 143 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h b/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h
index 6f6a59a23495..57f30be6bc9c 100644
--- a/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h
+++ b/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h
@@ -1800,28 +1800,13 @@ enum dmub_inbox0_command {
*
* Command IDs should be treated as stable ABI.
* Do not reuse or modify IDs.
+ * Note that command IDs 1-4 have been deprecated.
*/
enum dmub_cmd_type {
/**
* Invalid command.
*/
DMUB_CMD__NULL = 0,
- /**
- * Read modify write register sequence offload.
- */
- DMUB_CMD__REG_SEQ_READ_MODIFY_WRITE = 1,
- /**
- * Field update register sequence offload.
- */
- DMUB_CMD__REG_SEQ_FIELD_UPDATE_SEQ = 2,
- /**
- * Burst write sequence offload.
- */
- DMUB_CMD__REG_SEQ_BURST_WRITE = 3,
- /**
- * Reg wait sequence offload.
- */
- DMUB_CMD__REG_REG_WAIT = 4,
/**
* Workaround to avoid HUBP underflow during NV12 playback.
*/
@@ -2041,98 +2026,6 @@ struct dmub_cmd_header {
unsigned int reserved1 : 2; /**< reserved bits */
};
-/*
- * struct dmub_cmd_read_modify_write_sequence - Read modify write
- *
- * 60 payload bytes can hold up to 5 sets of read modify writes,
- * each take 3 dwords.
- *
- * number of sequences = header.payload_bytes / sizeof(struct dmub_cmd_read_modify_write_sequence)
- *
- * modify_mask = 0xffff'ffff means all fields are going to be updated. in this case
- * command parser will skip the read and we can use modify_mask = 0xffff'ffff as reg write
- */
-struct dmub_cmd_read_modify_write_sequence {
- uint32_t addr; /**< register address */
- uint32_t modify_mask; /**< modify mask */
- uint32_t modify_value; /**< modify value */
-};
-
-/**
- * Maximum number of ops in read modify write sequence.
- */
-#define DMUB_READ_MODIFY_WRITE_SEQ__MAX 5
-
-/**
- * struct dmub_cmd_read_modify_write_sequence - Read modify write command.
- */
-struct dmub_rb_cmd_read_modify_write {
- struct dmub_cmd_header header; /**< command header */
- /**
- * Read modify write sequence.
- */
- struct dmub_cmd_read_modify_write_sequence seq[DMUB_READ_MODIFY_WRITE_SEQ__MAX];
-};
-
-/*
- * Update a register with specified masks and values sequeunce
- *
- * 60 payload bytes can hold address + up to 7 sets of mask/value combo, each take 2 dword
- *
- * number of field update sequence = (header.payload_bytes - sizeof(addr)) / sizeof(struct read_modify_write_sequence)
- *
- *
- * USE CASE:
- * 1. auto-increment register where additional read would update pointer and produce wrong result
- * 2. toggle a bit without read in the middle
- */
-
-struct dmub_cmd_reg_field_update_sequence {
- uint32_t modify_mask; /**< 0xffff'ffff to skip initial read */
- uint32_t modify_value; /**< value to update with */
-};
-
-/**
- * Maximum number of ops in field update sequence.
- */
-#define DMUB_REG_FIELD_UPDATE_SEQ__MAX 7
-
-/**
- * struct dmub_rb_cmd_reg_field_update_sequence - Field update command.
- */
-struct dmub_rb_cmd_reg_field_update_sequence {
- struct dmub_cmd_header header; /**< command header */
- uint32_t addr; /**< register address */
- /**
- * Field update sequence.
- */
- struct dmub_cmd_reg_field_update_sequence seq[DMUB_REG_FIELD_UPDATE_SEQ__MAX];
-};
-
-
-/**
- * Maximum number of burst write values.
- */
-#define DMUB_BURST_WRITE_VALUES__MAX 14
-
-/*
- * struct dmub_rb_cmd_burst_write - Burst write
- *
- * support use case such as writing out LUTs.
- *
- * 60 payload bytes can hold up to 14 values to write to given address
- *
- * number of payload = header.payload_bytes / sizeof(struct read_modify_write_sequence)
- */
-struct dmub_rb_cmd_burst_write {
- struct dmub_cmd_header header; /**< command header */
- uint32_t addr; /**< register start address */
- /**
- * Burst write register values.
- */
- uint32_t write_values[DMUB_BURST_WRITE_VALUES__MAX];
-};
-
/**
* struct dmub_rb_cmd_common - Common command header
*/
@@ -2144,24 +2037,6 @@ struct dmub_rb_cmd_common {
uint8_t cmd_buffer[DMUB_RB_CMD_SIZE - sizeof(struct dmub_cmd_header)];
};
-/**
- * struct dmub_cmd_reg_wait_data - Register wait data
- */
-struct dmub_cmd_reg_wait_data {
- uint32_t addr; /**< Register address */
- uint32_t mask; /**< Mask for register bits */
- uint32_t condition_field_value; /**< Value to wait for */
- uint32_t time_out_us; /**< Time out for reg wait in microseconds */
-};
-
-/**
- * struct dmub_rb_cmd_reg_wait - Register wait command
- */
-struct dmub_rb_cmd_reg_wait {
- struct dmub_cmd_header header; /**< Command header */
- struct dmub_cmd_reg_wait_data reg_wait; /**< Register wait data */
-};
-
/**
* struct dmub_cmd_PLAT_54186_wa - Underflow workaround
*
@@ -6439,10 +6314,43 @@ struct dmub_cmd_cacp_set_backlight_data {
*/
uint8_t panel_mask;
+ /**
+ * AUX HW Instance.
+ */
+ uint8_t aux_inst;
+
/**
* Explicit padding to 4 byte boundary.
*/
- uint8_t pad[2];
+ uint8_t pad[1];
+
+ /**
+ * Backlight control type.
+ * Value 0 is PWM backlight control.
+ * Value 1 is VAUX backlight control.
+ * Value 2 is AMD DPCD AUX backlight control.
+ */
+ enum dmub_backlight_control_type backlight_control_type;
+
+ /**
+ * Minimum luminance in nits.
+ */
+ uint32_t min_luminance;
+
+ /**
+ * Maximum luminance in nits.
+ */
+ uint32_t max_luminance;
+
+ /**
+ * Minimum backlight in pwm.
+ */
+ uint32_t min_backlight_pwm;
+
+ /**
+ * Maximum backlight in pwm.
+ */
+ uint32_t max_backlight_pwm;
};
/**
@@ -7362,22 +7270,6 @@ union dmub_rb_cmd {
* Elements shared with all commands.
*/
struct dmub_rb_cmd_common cmd_common;
- /**
- * Definition of a DMUB_CMD__REG_SEQ_READ_MODIFY_WRITE command.
- */
- struct dmub_rb_cmd_read_modify_write read_modify_write;
- /**
- * Definition of a DMUB_CMD__REG_SEQ_FIELD_UPDATE_SEQ command.
- */
- struct dmub_rb_cmd_reg_field_update_sequence reg_field_update_seq;
- /**
- * Definition of a DMUB_CMD__REG_SEQ_BURST_WRITE command.
- */
- struct dmub_rb_cmd_burst_write burst_write;
- /**
- * Definition of a DMUB_CMD__REG_REG_WAIT command.
- */
- struct dmub_rb_cmd_reg_wait reg_wait;
/**
* Definition of a DMUB_CMD__VBIOS_DIGX_ENCODER_CONTROL command.
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 32/32] drm/amd/display: Promote DC to 3.2.386
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (30 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 31/32] drm/amd/display: [FW Promotion] Release 0.1.63.0 Chenyu Chen
@ 2026-06-10 9:45 ` Chenyu Chen
2026-06-15 13:11 ` [PATCH 00/32] DC Patches Jun 15 2026 Wheeler, Daniel
32 siblings, 0 replies; 38+ messages in thread
From: Chenyu Chen @ 2026-06-10 9:45 UTC (permalink / raw)
To: amd-gfx
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Taimur Hassan, Chenyu Chen
From: Taimur Hassan <Syed.Hassan@amd.com>
This version brings along the following updates:
- Increase dcn42b uclk value.
- Add a new interface to set idle opts in clock manager.
- Revert dmub_cmd updates for HDMI.
- Add utm_qos_model pointer to clk_bw_params.
- Remove get_utm_qos_model from soc_and_ip_translator.
- Rename hdmi_frl_borrow_mode.
- Remove unused project_id from DML2 core instance.
- Drop HDMI2_1 guards.
- Introduce dc_plane_cm and migrate surface update color path.
- Extract backlight code to amdgpu_dm_backlight.
- Extract audio code to amdgpu_dm_audio.
- Extract DMUB code to amdgpu_dm_dmub.
- Move HPD and IRQ handler code to amdgpu_dm_irq.
- Extract connector and encoder code to amdgpu_dm_connector.
- Fix conflicting types for dc_plane_cm functions.
- Add PSR Active VTotal Control capability.
- Enable pstate for DCN4 non-emulation builds.
- Refactor surface_update_flags to flat struct with helpers.
- Add support for HDMI Compliance Automation.
- Add KUnit tests for amdgpu_dm and its components.
- Set default backlight without ACPI support.
- Move backlight macros to backlight header.
- FW Promotion Release 0.1.63.0.
Signed-off-by: Taimur Hassan <Syed.Hassan@amd.com>
Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
Acked-by: Tom Chung <chiahsuan.chung@amd.com>
---
drivers/gpu/drm/amd/display/dc/dc.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h
index 2202c8669bf8..2de0f9cf8264 100644
--- a/drivers/gpu/drm/amd/display/dc/dc.h
+++ b/drivers/gpu/drm/amd/display/dc/dc.h
@@ -65,7 +65,7 @@ struct dcn_dsc_reg_state;
struct dcn_optc_reg_state;
struct dcn_dccg_reg_state;
-#define DC_VER "3.2.385"
+#define DC_VER "3.2.386"
/**
* MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH 16/32] drm/amd/display: Add Support for HDMI Compliance Automation
2026-06-10 9:45 ` [PATCH 16/32] drm/amd/display: Add Support for HDMI Compliance Automation Chenyu Chen
@ 2026-06-10 14:43 ` Nicolas Frattaroli
2026-07-20 13:14 ` Nicolas Frattaroli
2026-07-21 2:09 ` Chen, Chen-Yu
0 siblings, 2 replies; 38+ messages in thread
From: Nicolas Frattaroli @ 2026-06-10 14:43 UTC (permalink / raw)
To: amd-gfx, Chenyu Chen
Cc: Harry Wentland, Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin,
Tom Chung, Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski,
Alex Hung, James Lin, Chenyu Chen, Fangzhi Zuo, Chenyu Chen
On Wednesday, 10 June 2026 11:45:00 Central European Summer Time Chenyu Chen wrote:
> From: Fangzhi Zuo <Jerry.Zuo@amd.com>
>
> Add support to get DUT trained at FRL link rate when working with
> Teledyne M41h compliance automation.
>
> Reviewed-by: Alex Hung <alex.hung@amd.com>
> Signed-off-by: Fangzhi Zuo <Jerry.Zuo@amd.com>
> Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
> ---
> .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 3 +
> .../display/amdgpu_dm/amdgpu_dm_connector.c | 5 ++
> .../amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 67 ++++++++++++++++++-
> .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 6 ++
> 4 files changed, 80 insertions(+), 1 deletion(-)
>
> 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 2940dd5b7348..ba1e11e144f2 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
> @@ -877,6 +877,9 @@ struct amdgpu_dm_connector {
> unsigned int hdmi_hpd_debounce_delay_ms;
> struct delayed_work hdmi_hpd_debounce_work;
> struct dc_sink *hdmi_prev_sink;
> +
> + /* HDMI compliance automation */
> + bool hdmi_comp_auto;
> };
>
> static inline void amdgpu_dm_set_mst_status(uint8_t *status,
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
> index 6ef257622f1a..59091ee32099 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
> @@ -573,6 +573,11 @@ void amdgpu_dm_update_connector_after_detect(
> amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true);
> amdgpu_dm_update_connector_ext_caps(aconnector);
> dm_set_panel_type(aconnector);
> +
> + if (aconnector->hdmi_comp_auto) {
> + if (sink->sink_signal != SIGNAL_TYPE_HDMI_FRL)
> + sink->sink_signal = SIGNAL_TYPE_HDMI_FRL;
> + }
> } else {
> hdmi_cec_unset_edid(aconnector);
> drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux);
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
> index 096a855a7304..95a56e39f452 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
> @@ -2982,6 +2982,64 @@ static ssize_t hdmi_cec_state_write(struct file *f, const char __user *buf,
> return size;
> }
>
> +/**
> + * hdmi_automation_enable - Enable/Disable HDMI automation feature
> + * @f: file structure.
> + * @buf: userspace buffer. set to '1' to enable; '0' to disable automation feature.
> + * @size: size of buffer from userpsace.
> + * @pos: unused.
> + *
> + * Return size on success, error code on failure
> + */
> +static ssize_t hdmi_automation_enable(struct file *f, const char __user *buf,
> + size_t size, loff_t *pos)
> +{
> + struct amdgpu_dm_connector *aconnector = file_inode(f)->i_private;
> + char *wr_buf = NULL;
> + const uint32_t wr_buf_size = 40;
> + int max_param_num = 1;
> + uint8_t param_nums = 0;
> + long param[2];
> + bool hdmi_comp_auto;
> +
> + if (size == 0)
> + return -EINVAL;
> +
> + wr_buf = kcalloc(wr_buf_size, sizeof(char), GFP_KERNEL);
> + if (!wr_buf)
> + return -ENOSPC;
> +
> + if (parse_write_buffer_into_params(wr_buf, wr_buf_size,
> + (long *)param, buf,
> + max_param_num,
> + ¶m_nums)) {
> + kfree(wr_buf);
> + return -EINVAL;
> + }
> +
> + if (param_nums <= 0) {
> + kfree(wr_buf);
> + DRM_DEBUG_DRIVER("user data not be read\n");
> + return -EINVAL;
> + }
> +
> + switch (param[0]) {
> + case 0:
> + hdmi_comp_auto = false;
> + break;
> + case 1:
> + default:
> + hdmi_comp_auto = true;
> + break;
> + }
> +
> + /* Persist setting across sink re-detection/hotplug. */
> + aconnector->hdmi_comp_auto = hdmi_comp_auto;
> +
> + kfree(wr_buf);
> + return size;
> +}
> +
> DEFINE_SHOW_ATTRIBUTE(dp_dsc_fec_support);
> DEFINE_SHOW_ATTRIBUTE(dmub_fw_state);
> DEFINE_SHOW_ATTRIBUTE(dmub_tracebuffer);
> @@ -3099,6 +3157,12 @@ static const struct file_operations dp_mst_link_settings_debugfs_fops = {
> .llseek = default_llseek
> };
>
> +static const struct file_operations hdmi_automation_debugfs_fops = {
> + .owner = THIS_MODULE,
> + .write = hdmi_automation_enable,
> + .llseek = default_llseek
> +};
> +
I really don't understand why this can't just be a DEFINE_DEBUGFS_ATTRIBUTE,
and then you can replace the overcomplicated hdmi_automation_enable() with
just simple setter and getter functions that already receive the parameter
of the right type.
> static const struct {
> char *name;
> const struct file_operations *fops;
> @@ -3131,7 +3195,8 @@ static const struct {
> const struct file_operations *fops;
> } hdmi_debugfs_entries[] = {
> {"hdcp_sink_capability", &hdcp_sink_capability_fops},
> - {"hdmi_cec_state", &hdmi_cec_state_fops}
> + {"hdmi_cec_state", &hdmi_cec_state_fops},
> + {"hdmi_automation", &hdmi_automation_debugfs_fops}
> };
>
> /*
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
> index a2d0bb34e639..6350212b9a66 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
> @@ -193,6 +193,12 @@ enum dc_edid_status dm_helpers_parse_edid_caps(
> __func__, connector->name, edid_caps->frl_dsc_10bpc, edid_caps->frl_dsc_12bpc, \
> edid_caps->frl_dsc_all_bpp, edid_caps->frl_dsc_native_420, edid_caps->frl_dsc_max_slices, \
> edid_caps->frl_dsc_max_frl_rate, edid_caps->frl_dsc_total_chunk_kbytes);
> + if (aconnector->hdmi_comp_auto) {
> + edid_caps->panel_patch.hdmi_comp_auto = true;
> + link->ctx->dc->debug.force_frl_max = true;
> + link->ctx->dc->debug.force_frl_dsc = true;
> + drm_dbg_driver(connector->dev, "%s: HDMI_FRL [%s] hdmi_comp_auto --> enabled\n", __func__, connector->name);
> + }
> }
>
> apply_edid_quirks(link, edid_buf, edid_caps);
>
^ permalink raw reply [flat|nested] 38+ messages in thread
* RE: [PATCH 00/32] DC Patches Jun 15 2026
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
` (31 preceding siblings ...)
2026-06-10 9:45 ` [PATCH 32/32] drm/amd/display: Promote DC to 3.2.386 Chenyu Chen
@ 2026-06-15 13:11 ` Wheeler, Daniel
32 siblings, 0 replies; 38+ messages in thread
From: Wheeler, Daniel @ 2026-06-15 13:11 UTC (permalink / raw)
To: Chen, Chen-Yu, amd-gfx@lists.freedesktop.org
Cc: Wentland, Harry, Li, Sun peng (Leo), Pillai, Aurabindo, Li, Roman,
Lin, Wayne, Chung, ChiaHsuan (Tom), Zuo, Jerry, Wu, Ray,
LIPSKI, IVAN, Hung, Alex, Lin, Ping Lei, Chen, Chen-Yu,
Chen, Chen-Yu
Public
Hi all,
This week this patchset was tested on 4 systems, two dGPU and two APU based, and tested across multiple display and connection types. This week was also the last promotion test ran on Ubuntu 24.04. There won't be any testing this week as we had a holiday this past Monday, so the first test on Ubuntu 26.04 will begin next week.
APU
* Single Display eDP -> 1080p 60hz, 1920x1200 165hz, 3840x2400 60hz
* Single Display DP (SST DSC) -> 4k144hz, 4k240hz
* Multi display -> eDP + DP/HDMI/USB-C -> 1080p 60hz eDP + 4k 144hz, 4k 240hz (Includes USB-C to DP/HDMI adapters)
* Thunderbolt -> LG Ultrafine 5k
* MST DSC -> Cable Matters 101075 (DP to 3x DP) with 3x 4k60hz displays, HP Hook G2 with 2x 4k60hz displays
* USB 4 -> HP Hook G4, Lenovo Thunderbolt Dock, both with 2x 4k60hz DP and 1x 4k60hz HDMI displays
* SST PCON -> Club3D CAC-1085 + 1x 4k 144hz, FRL3, at a max resolution supported by the dongle of 4k 120hz YUV420 12bpc.
* MST PCON -> 1x 4k 144hz, FRL3, at a max resolution supported by the adapter of 4k 120hz RGB 8bpc.
DGPU
* Single Display DP (SST DSC) -> 4k144hz, 4k240hz
* Multiple Display DP -> 4k240hz + 4k144hz
* MST (Startech MST14DP123DP [DP to 3x DP] and 2x 4k 60hz displays)
* MST DSC (with Cable Matters 101075 [DP to 3x DP] with 3x 4k60hz displays)
The testing is a mix of automated and manual tests. Manual testing includes (but is not limited to)
* Changing display configurations and settings
* Video/Audio playback
* Benchmark testing
* Suspend/Resume testing
* Feature testing (Freesync, HDCP, etc.)
Automated testing includes (but is not limited to)
* Script testing (scripts to automate some of the manual checks)
* IGT testing
The testing is mainly tested on the following displays, but occasionally there are tests with other displays
* Samsung G8 Neo 4k240hz
* Samsung QN55QN95B 4k 120hz
* Acer XV322QKKV 4k144hz
* HP U27 4k Wireless 4k60hz
* LG 27UD58B 4k60hz
* LG 32UN650WA 4k60hz
* LG Ultrafine 5k 5k60hz
* AU Optronics B140HAN01.1 1080p 60hz eDP
* AU Optronics B160UAN01.J 1920x1200 165hz eDP
* Samsung ATNA60YV02-0 3840x2400 60Hz OLED eDP
The patchset consists of the amd-staging-drm-next branch (Head commit -> fda768acb2a1d6dd59212bb06b94142dd80f361c -> drm/amd/display: Promote DC to 3.2.385) with new patches added on top of it.
Tested on Ubuntu 26.04.4, on Wayland and X11, using Gnome.
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
Thank you,
Dan Wheeler
Sr. Technologist | AMD
SW Display
------------------------------------------------------------------------------------------------------------------
1 Commerce Valley Dr E, Thornhill, ON L3T 7X6
amd.com
-----Original Message-----
From: Chenyu Chen <chen-yu.chen@amd.com>
Sent: Wednesday, June 10, 2026 5:45 AM
To: amd-gfx@lists.freedesktop.org
Cc: Wentland, Harry <Harry.Wentland@amd.com>; Li, Sun peng (Leo) <Sunpeng.Li@amd.com>; Pillai, Aurabindo <Aurabindo.Pillai@amd.com>; Li, Roman <Roman.Li@amd.com>; Lin, Wayne <Wayne.Lin@amd.com>; Chung, ChiaHsuan (Tom) <ChiaHsuan.Chung@amd.com>; Zuo, Jerry <Jerry.Zuo@amd.com>; Wheeler, Daniel <Daniel.Wheeler@amd.com>; Wu, Ray <Ray.Wu@amd.com>; LIPSKI, IVAN <IVAN.LIPSKI@amd.com>; Hung, Alex <Alex.Hung@amd.com>; Lin, Ping Lei <PingLei.Lin@amd.com>; Chen, Chen-Yu <Chen-Yu.Chen@amd.com>; Chen, Chen-Yu <Chen-Yu.Chen@amd.com>
Subject: [PATCH 00/32] DC Patches Jun 15 2026
This DC patchset brings improvements in multiple areas. In summary, we highlight:
- Major amdgpu_dm refactoring into modular components
(backlight, audio, DMUB, IRQ, connector)
- Enhancements in clock management and DCN4 pstate handling
- HDMI pipeline cleanup and compliance automation support
- Introduction of dc_plane_cm and surface update refactor
- Backlight handling improvements without ACPI dependency
- Addition of KUnit tests to improve test coverage
- Firmware update to version 0.1.63.0
Cc: Daniel Wheeler <daniel.wheeler@amd.com>
Alex Hung (19):
drm/amd/display: Extract backlight code to amdgpu_dm_backlight
drm/amd/display: Extract audio code to amdgpu_dm_audio
drm/amd/display: Extract DMUB code to amdgpu_dm_dmub
drm/amd/display: Move HPD and IRQ handler code to amdgpu_dm_irq
drm/amd/display: Extract connector and encoder code to
amdgpu_dm_connector
drm/amd/display: Fix conflicting types for dc_plane_cm functions
drm/amd/display: Add KUnit tests for amdgpu_dm
drm/amd/display: Add KUnit tests for amdgpu_dm_backlight
drm/amd/display: Add KUnit tests for amdgpu_dm_audio
drm/amd/display: Add KUnit tests for amdgpu_dm_dmub
drm/amd/display: Add KUnit tests for amdgpu_dm_connector
drm/amd/display: Add KUnit tests for amdgpu_dm_irq
drm/amd/display: Add KUnit tests for amdgpu_dm_crtc
drm/amd/display: Add KUnit tests for amdgpu_dm_services
drm/amd/display: Add KUnit tests for amdgpu_dm_helpers
drm/amd/display: Add KUnit tests for amdgpu_dm_quirks
drm/amd/display: Add more KUnit tests for amdgpu_dm_pp_smu
drm/amd/display: Add more KUnit tests for amdgpu_dm_mst_types
drm/amd/display: Move backlight macros to backlight header
Fangzhi Zuo (1):
drm/amd/display: Add Support for HDMI Compliance Automation
Gabe Teeger (2):
drm/amd/display: Increase dcn42b uclk value
drm/amd/display: Enable pstate for DCN4 non-emulation builds
Mario Limonciello (1):
drm/amd/display: Set default backlight without ACPI support
Nicholas Kazlauskas (1):
drm/amd/display: Add a new interface to set idle opts in clock manager
Rafal Ostrowski (2):
drm/amd/display: Introduce dc_plane_cm and migrate surface update
color path
drm/amd/display: Refactor surface_update_flags to flat struct with
helpers
Robin Chen (1):
drm/amd/display: Add PSR Active VTotal Control capability
Taimur Hassan (2):
drm/amd/display: [FW Promotion] Release 0.1.63.0
drm/amd/display: Promote DC to 3.2.386
Wenjing Liu (3):
drm/amd/display: Add utm_qos_model pointer to clk_bw_params
drm/amd/display: Remove get_utm_qos_model from soc_and_ip_translator
drm/amd/display: Remove unused project_id from DML2 core instance
.../gpu/drm/amd/display/amdgpu_dm/Makefile | 6 +-
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 11981 ++++------------
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 79 +-
.../amd/display/amdgpu_dm/amdgpu_dm_audio.c | 323 +
.../amd/display/amdgpu_dm/amdgpu_dm_audio.h | 56 +
.../display/amdgpu_dm/amdgpu_dm_backlight.c | 704 +
.../display/amdgpu_dm/amdgpu_dm_backlight.h | 67 +
.../amd/display/amdgpu_dm/amdgpu_dm_color.c | 69 +-
.../amd/display/amdgpu_dm/amdgpu_dm_color.h | 8 +-
.../display/amdgpu_dm/amdgpu_dm_connector.c | 3581 +++++
.../display/amdgpu_dm/amdgpu_dm_connector.h | 162 +
.../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 14 +-
.../amd/display/amdgpu_dm/amdgpu_dm_crtc.h | 6 +
.../amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 69 +-
.../amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 934 ++
.../amd/display/amdgpu_dm/amdgpu_dm_dmub.h | 68 +
.../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 64 +-
.../amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 20 +
.../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 1511 +-
.../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 27 +
.../amdgpu_dm/amdgpu_dm_kunit_helpers.h | 1 +
.../display/amdgpu_dm/amdgpu_dm_mst_types.c | 145 +-
.../display/amdgpu_dm/amdgpu_dm_mst_types.h | 12 +
.../amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 234 +-
.../amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 22 +
.../amd/display/amdgpu_dm/amdgpu_dm_quirks.c | 2 +
.../display/amdgpu_dm/amdgpu_dm_services.c | 7 +
.../drm/amd/display/amdgpu_dm/tests/Makefile | 11 +
.../amdgpu_dm/tests/amdgpu_dm_audio_test.c | 490 +
.../tests/amdgpu_dm_backlight_test.c | 1128 ++
.../amdgpu_dm/tests/amdgpu_dm_color_test.c | 64 +-
.../tests/amdgpu_dm_connector_test.c | 2142 +++
.../amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 532 +
.../amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 600 +
.../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 645 +
.../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 934 ++
.../tests/amdgpu_dm_mst_types_test.c | 385 +
.../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 736 +
.../amdgpu_dm/tests/amdgpu_dm_quirks_test.c | 103 +
.../amdgpu_dm/tests/amdgpu_dm_services_test.c | 313 +
.../display/amdgpu_dm/tests/amdgpu_dm_test.c | 929 ++
drivers/gpu/drm/amd/display/dc/core/dc.c | 287 +-
.../drm/amd/display/dc/core/dc_hw_sequencer.c | 20 +-
.../gpu/drm/amd/display/dc/core/dc_surface.c | 44 +-
drivers/gpu/drm/amd/display/dc/dc.h | 227 +-
drivers/gpu/drm/amd/display/dc/dc_dp_types.h | 1 +
drivers/gpu/drm/amd/display/dc/dc_stream.h | 29 +
drivers/gpu/drm/amd/display/dc/dc_types.h | 66 +-
.../drm/amd/display/dc/dml/calcs/dcn_calcs.c | 2 +-
.../dml21/inc/bounding_boxes/dcn42b_soc_bb.h | 2 +-
.../dml21/src/dml2_core/dml2_core_factory.c | 2 -
.../src/dml2_core/dml2_core_shared_types.h | 2 -
.../src/inc/dml2_internal_shared_types.h | 1 -
.../amd/display/dc/hubp/dcn401/dcn401_hubp.c | 2 +-
.../amd/display/dc/hwss/dce110/dce110_hwseq.c | 8 +-
.../amd/display/dc/hwss/dce60/dce60_hwseq.c | 8 +-
.../amd/display/dc/hwss/dcn10/dcn10_hwseq.c | 62 +-
.../amd/display/dc/hwss/dcn20/dcn20_hwseq.c | 76 +-
.../amd/display/dc/hwss/dcn201/dcn201_hwseq.c | 2 +-
.../amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 10 +-
.../amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 33 +-
.../amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 244 +-
.../amd/display/dc/hwss/dcn401/dcn401_hwseq.h | 2 +-
.../amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 318 +-
.../amd/display/dc/hwss/dcn42/dcn42_hwseq.h | 5 +-
.../display/dc/hwss/hw_sequencer_private.h | 3 +-
.../gpu/drm/amd/display/dc/inc/hw/clk_mgr.h | 4 +
drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h | 2 +-
.../display/dc/inc/soc_and_ip_translator.h | 14 -
.../dc/link/protocols/link_dp_capability.c | 6 +-
.../dc/resource/dcn42b/dcn42b_resource.c | 2 +-
.../gpu/drm/amd/display/dmub/inc/dmub_cmd.h | 178 +-
.../amd/display/include/ddc_service_types.h | 1 +
73 files changed, 20422 insertions(+), 10425 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c
create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
--
2.43.0
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH 16/32] drm/amd/display: Add Support for HDMI Compliance Automation
2026-06-10 14:43 ` Nicolas Frattaroli
@ 2026-07-20 13:14 ` Nicolas Frattaroli
2026-07-21 2:27 ` Chen, Chen-Yu
2026-07-21 2:09 ` Chen, Chen-Yu
1 sibling, 1 reply; 38+ messages in thread
From: Nicolas Frattaroli @ 2026-07-20 13:14 UTC (permalink / raw)
To: amd-gfx, Chenyu Chen, Harry Wentland
Cc: Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin, Tom Chung,
Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski, Alex Hung,
James Lin, Chenyu Chen, Fangzhi Zuo, Chenyu Chen
On Wednesday, 10 June 2026 16:43:37 Central European Summer Time Nicolas Frattaroli wrote:
> On Wednesday, 10 June 2026 11:45:00 Central European Summer Time Chenyu Chen wrote:
> > From: Fangzhi Zuo <Jerry.Zuo@amd.com>
> >
> > Add support to get DUT trained at FRL link rate when working with
> > Teledyne M41h compliance automation.
> >
> > Reviewed-by: Alex Hung <alex.hung@amd.com>
> > Signed-off-by: Fangzhi Zuo <Jerry.Zuo@amd.com>
> > Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
> > ---
> > .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 3 +
> > .../display/amdgpu_dm/amdgpu_dm_connector.c | 5 ++
> > .../amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 67 ++++++++++++++++++-
> > .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 6 ++
> > 4 files changed, 80 insertions(+), 1 deletion(-)
> >
> > [... snip ...]
> > --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
> > +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
> > @@ -2982,6 +2982,64 @@ static ssize_t hdmi_cec_state_write(struct file *f, const char __user *buf,
> > return size;
> > }
> >
> > +/**
> > + * hdmi_automation_enable - Enable/Disable HDMI automation feature
> > + * @f: file structure.
> > + * @buf: userspace buffer. set to '1' to enable; '0' to disable automation feature.
> > + * @size: size of buffer from userpsace.
> > + * @pos: unused.
> > + *
> > + * Return size on success, error code on failure
> > + */
> > +static ssize_t hdmi_automation_enable(struct file *f, const char __user *buf,
> > + size_t size, loff_t *pos)
> > +{
> > + struct amdgpu_dm_connector *aconnector = file_inode(f)->i_private;
> > + char *wr_buf = NULL;
> > + const uint32_t wr_buf_size = 40;
> > + int max_param_num = 1;
> > + uint8_t param_nums = 0;
> > + long param[2];
> > + bool hdmi_comp_auto;
> > +
> > + if (size == 0)
> > + return -EINVAL;
> > +
> > + wr_buf = kcalloc(wr_buf_size, sizeof(char), GFP_KERNEL);
> > + if (!wr_buf)
> > + return -ENOSPC;
> > +
> > + if (parse_write_buffer_into_params(wr_buf, wr_buf_size,
> > + (long *)param, buf,
> > + max_param_num,
> > + ¶m_nums)) {
> > + kfree(wr_buf);
> > + return -EINVAL;
> > + }
> > +
> > + if (param_nums <= 0) {
> > + kfree(wr_buf);
> > + DRM_DEBUG_DRIVER("user data not be read\n");
> > + return -EINVAL;
> > + }
> > +
> > + switch (param[0]) {
> > + case 0:
> > + hdmi_comp_auto = false;
> > + break;
> > + case 1:
> > + default:
> > + hdmi_comp_auto = true;
> > + break;
> > + }
> > +
> > + /* Persist setting across sink re-detection/hotplug. */
> > + aconnector->hdmi_comp_auto = hdmi_comp_auto;
> > +
> > + kfree(wr_buf);
> > + return size;
> > +}
> > +
> > DEFINE_SHOW_ATTRIBUTE(dp_dsc_fec_support);
> > DEFINE_SHOW_ATTRIBUTE(dmub_fw_state);
> > DEFINE_SHOW_ATTRIBUTE(dmub_tracebuffer);
> > @@ -3099,6 +3157,12 @@ static const struct file_operations dp_mst_link_settings_debugfs_fops = {
> > .llseek = default_llseek
> > };
> >
> > +static const struct file_operations hdmi_automation_debugfs_fops = {
> > + .owner = THIS_MODULE,
> > + .write = hdmi_automation_enable,
> > + .llseek = default_llseek
> > +};
> > +
>
> I really don't understand why this can't just be a DEFINE_DEBUGFS_ATTRIBUTE,
> and then you can replace the overcomplicated hdmi_automation_enable() with
> just simple setter and getter functions that already receive the parameter
> of the right type.
Aaaand this was applied without the review comment being addressed.
Along with the incredible other things in this series, like
link.dpcd_caps.dongle_type = (typeof(link.dpcd_caps.dongle_type))0x7f;
in "[PATCH 21/32] drm/amd/display: Add KUnit tests for amdgpu_dm_connector",
I'm feeling like AMD is treating mainline as a vendor BSP to just dump code
into.
> > static const struct {
> > char *name;
> > const struct file_operations *fops;
> > @@ -3131,7 +3195,8 @@ static const struct {
> > const struct file_operations *fops;
> > } hdmi_debugfs_entries[] = {
> > {"hdcp_sink_capability", &hdcp_sink_capability_fops},
> > - {"hdmi_cec_state", &hdmi_cec_state_fops}
> > + {"hdmi_cec_state", &hdmi_cec_state_fops},
> > + {"hdmi_automation", &hdmi_automation_debugfs_fops}
> > };
> >
> > /*
> > diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
> > index a2d0bb34e639..6350212b9a66 100644
> > --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
> > +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
> > @@ -193,6 +193,12 @@ enum dc_edid_status dm_helpers_parse_edid_caps(
> > __func__, connector->name, edid_caps->frl_dsc_10bpc, edid_caps->frl_dsc_12bpc, \
> > edid_caps->frl_dsc_all_bpp, edid_caps->frl_dsc_native_420, edid_caps->frl_dsc_max_slices, \
> > edid_caps->frl_dsc_max_frl_rate, edid_caps->frl_dsc_total_chunk_kbytes);
> > + if (aconnector->hdmi_comp_auto) {
> > + edid_caps->panel_patch.hdmi_comp_auto = true;
> > + link->ctx->dc->debug.force_frl_max = true;
> > + link->ctx->dc->debug.force_frl_dsc = true;
> > + drm_dbg_driver(connector->dev, "%s: HDMI_FRL [%s] hdmi_comp_auto --> enabled\n", __func__, connector->name);
> > + }
> > }
> >
> > apply_edid_quirks(link, edid_buf, edid_caps);
> >
>
>
^ permalink raw reply [flat|nested] 38+ messages in thread
* RE: [PATCH 16/32] drm/amd/display: Add Support for HDMI Compliance Automation
2026-06-10 14:43 ` Nicolas Frattaroli
2026-07-20 13:14 ` Nicolas Frattaroli
@ 2026-07-21 2:09 ` Chen, Chen-Yu
1 sibling, 0 replies; 38+ messages in thread
From: Chen, Chen-Yu @ 2026-07-21 2:09 UTC (permalink / raw)
To: Nicolas Frattaroli, amd-gfx@lists.freedesktop.org, Zuo, Jerry
Cc: Wentland, Harry, Li, Sun peng (Leo), Pillai, Aurabindo, Li, Roman,
Lin, Wayne, Chung, ChiaHsuan (Tom), Wheeler, Daniel, Wu, Ray,
LIPSKI, IVAN, Hung, Alex, Lin, Ping Lei, Zuo, Jerry
AMD General
Hi @Zuo, Jerry,
This is my mistake. I missed a review comment from when promoting the series, and the patches were applied before the feedback was addressed.
Could you please review the comment below and determine whether a follow-up patch is required?
Apologies for the oversight.
Regards,
Chenyu
-----Original Message-----
From: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
Sent: Wednesday, June 10, 2026 10:44 PM
To: amd-gfx@lists.freedesktop.org; Chen, Chen-Yu <Chen-Yu.Chen@amd.com>
Cc: Wentland, Harry <Harry.Wentland@amd.com>; Li, Sun peng (Leo) <Sunpeng.Li@amd.com>; Pillai, Aurabindo <Aurabindo.Pillai@amd.com>; Li, Roman <Roman.Li@amd.com>; Lin, Wayne <Wayne.Lin@amd.com>; Chung, ChiaHsuan (Tom) <ChiaHsuan.Chung@amd.com>; Zuo, Jerry <Jerry.Zuo@amd.com>; Wheeler, Daniel <Daniel.Wheeler@amd.com>; Wu, Ray <Ray.Wu@amd.com>; LIPSKI, IVAN <IVAN.LIPSKI@amd.com>; Hung, Alex <Alex.Hung@amd.com>; Lin, Ping Lei <PingLei.Lin@amd.com>; Chen, Chen-Yu <Chen-Yu.Chen@amd.com>; Zuo, Jerry <Jerry.Zuo@amd.com>; Chen, Chen-Yu <Chen-Yu.Chen@amd.com>
Subject: Re: [PATCH 16/32] drm/amd/display: Add Support for HDMI Compliance Automation
On Wednesday, 10 June 2026 11:45:00 Central European Summer Time Chenyu Chen wrote:
> From: Fangzhi Zuo <Jerry.Zuo@amd.com>
>
> Add support to get DUT trained at FRL link rate when working with
> Teledyne M41h compliance automation.
>
> Reviewed-by: Alex Hung <alex.hung@amd.com>
> Signed-off-by: Fangzhi Zuo <Jerry.Zuo@amd.com>
> Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
> ---
> .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 3 +
> .../display/amdgpu_dm/amdgpu_dm_connector.c | 5 ++
> .../amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 67
> ++++++++++++++++++- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c |
> 6 ++
> 4 files changed, 80 insertions(+), 1 deletion(-)
>
> 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 2940dd5b7348..ba1e11e144f2 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
> @@ -877,6 +877,9 @@ struct amdgpu_dm_connector {
> unsigned int hdmi_hpd_debounce_delay_ms;
> struct delayed_work hdmi_hpd_debounce_work;
> struct dc_sink *hdmi_prev_sink;
> +
> + /* HDMI compliance automation */
> + bool hdmi_comp_auto;
> };
>
> static inline void amdgpu_dm_set_mst_status(uint8_t *status, diff
> --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
> b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
> index 6ef257622f1a..59091ee32099 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
> @@ -573,6 +573,11 @@ void amdgpu_dm_update_connector_after_detect(
> amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true);
> amdgpu_dm_update_connector_ext_caps(aconnector);
> dm_set_panel_type(aconnector);
> +
> + if (aconnector->hdmi_comp_auto) {
> + if (sink->sink_signal != SIGNAL_TYPE_HDMI_FRL)
> + sink->sink_signal = SIGNAL_TYPE_HDMI_FRL;
> + }
> } else {
> hdmi_cec_unset_edid(aconnector);
> drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux);
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
> b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
> index 096a855a7304..95a56e39f452 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
> @@ -2982,6 +2982,64 @@ static ssize_t hdmi_cec_state_write(struct file *f, const char __user *buf,
> return size;
> }
>
> +/**
> + * hdmi_automation_enable - Enable/Disable HDMI automation feature
> + * @f: file structure.
> + * @buf: userspace buffer. set to '1' to enable; '0' to disable automation feature.
> + * @size: size of buffer from userpsace.
> + * @pos: unused.
> + *
> + * Return size on success, error code on failure */ static ssize_t
> +hdmi_automation_enable(struct file *f, const char __user *buf,
> + size_t size, loff_t *pos)
> +{
> + struct amdgpu_dm_connector *aconnector = file_inode(f)->i_private;
> + char *wr_buf = NULL;
> + const uint32_t wr_buf_size = 40;
> + int max_param_num = 1;
> + uint8_t param_nums = 0;
> + long param[2];
> + bool hdmi_comp_auto;
> +
> + if (size == 0)
> + return -EINVAL;
> +
> + wr_buf = kcalloc(wr_buf_size, sizeof(char), GFP_KERNEL);
> + if (!wr_buf)
> + return -ENOSPC;
> +
> + if (parse_write_buffer_into_params(wr_buf, wr_buf_size,
> + (long *)param, buf,
> + max_param_num,
> + ¶m_nums)) {
> + kfree(wr_buf);
> + return -EINVAL;
> + }
> +
> + if (param_nums <= 0) {
> + kfree(wr_buf);
> + DRM_DEBUG_DRIVER("user data not be read\n");
> + return -EINVAL;
> + }
> +
> + switch (param[0]) {
> + case 0:
> + hdmi_comp_auto = false;
> + break;
> + case 1:
> + default:
> + hdmi_comp_auto = true;
> + break;
> + }
> +
> + /* Persist setting across sink re-detection/hotplug. */
> + aconnector->hdmi_comp_auto = hdmi_comp_auto;
> +
> + kfree(wr_buf);
> + return size;
> +}
> +
> DEFINE_SHOW_ATTRIBUTE(dp_dsc_fec_support);
> DEFINE_SHOW_ATTRIBUTE(dmub_fw_state);
> DEFINE_SHOW_ATTRIBUTE(dmub_tracebuffer);
> @@ -3099,6 +3157,12 @@ static const struct file_operations dp_mst_link_settings_debugfs_fops = {
> .llseek = default_llseek
> };
>
> +static const struct file_operations hdmi_automation_debugfs_fops = {
> + .owner = THIS_MODULE,
> + .write = hdmi_automation_enable,
> + .llseek = default_llseek
> +};
> +
I really don't understand why this can't just be a DEFINE_DEBUGFS_ATTRIBUTE, and then you can replace the overcomplicated hdmi_automation_enable() with just simple setter and getter functions that already receive the parameter of the right type.
> static const struct {
> char *name;
> const struct file_operations *fops;
> @@ -3131,7 +3195,8 @@ static const struct {
> const struct file_operations *fops;
> } hdmi_debugfs_entries[] = {
> {"hdcp_sink_capability", &hdcp_sink_capability_fops},
> - {"hdmi_cec_state", &hdmi_cec_state_fops}
> + {"hdmi_cec_state", &hdmi_cec_state_fops},
> + {"hdmi_automation", &hdmi_automation_debugfs_fops}
> };
>
> /*
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
> b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
> index a2d0bb34e639..6350212b9a66 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
> @@ -193,6 +193,12 @@ enum dc_edid_status dm_helpers_parse_edid_caps(
> __func__, connector->name, edid_caps->frl_dsc_10bpc, edid_caps->frl_dsc_12bpc, \
> edid_caps->frl_dsc_all_bpp, edid_caps->frl_dsc_native_420, edid_caps->frl_dsc_max_slices, \
> edid_caps->frl_dsc_max_frl_rate,
> edid_caps->frl_dsc_total_chunk_kbytes);
> + if (aconnector->hdmi_comp_auto) {
> + edid_caps->panel_patch.hdmi_comp_auto = true;
> + link->ctx->dc->debug.force_frl_max = true;
> + link->ctx->dc->debug.force_frl_dsc = true;
> + drm_dbg_driver(connector->dev, "%s: HDMI_FRL [%s] hdmi_comp_auto --> enabled\n", __func__, connector->name);
> + }
> }
>
> apply_edid_quirks(link, edid_buf, edid_caps);
>
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH 16/32] drm/amd/display: Add Support for HDMI Compliance Automation
2026-07-20 13:14 ` Nicolas Frattaroli
@ 2026-07-21 2:27 ` Chen, Chen-Yu
0 siblings, 0 replies; 38+ messages in thread
From: Chen, Chen-Yu @ 2026-07-21 2:27 UTC (permalink / raw)
To: Nicolas Frattaroli, amd-gfx, Harry Wentland, Alex Hung
Cc: Leo Li, Aurabindo Pillai, Roman Li, Wayne Lin, Tom Chung,
Fangzhi Zuo, Dan Wheeler, Ray Wu, Ivan Lipski, James Lin
On 7/20/2026 9:14 PM, Nicolas Frattaroli wrote:
> On Wednesday, 10 June 2026 16:43:37 Central European Summer Time Nicolas Frattaroli wrote:
>> On Wednesday, 10 June 2026 11:45:00 Central European Summer Time Chenyu Chen wrote:
>>> From: Fangzhi Zuo <Jerry.Zuo@amd.com>
>>>
>>> Add support to get DUT trained at FRL link rate when working with
>>> Teledyne M41h compliance automation.
>>>
>>> Reviewed-by: Alex Hung <alex.hung@amd.com>
>>> Signed-off-by: Fangzhi Zuo <Jerry.Zuo@amd.com>
>>> Signed-off-by: Chenyu Chen <chen-yu.chen@amd.com>
>>> ---
>>> .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 3 +
>>> .../display/amdgpu_dm/amdgpu_dm_connector.c | 5 ++
>>> .../amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 67 ++++++++++++++++++-
>>> .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 6 ++
>>> 4 files changed, 80 insertions(+), 1 deletion(-)
>>>
>>> [... snip ...]
>>> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
>>> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
>>> @@ -2982,6 +2982,64 @@ static ssize_t hdmi_cec_state_write(struct file *f, const char __user *buf,
>>> return size;
>>> }
>>>
>>> +/**
>>> + * hdmi_automation_enable - Enable/Disable HDMI automation feature
>>> + * @f: file structure.
>>> + * @buf: userspace buffer. set to '1' to enable; '0' to disable automation feature.
>>> + * @size: size of buffer from userpsace.
>>> + * @pos: unused.
>>> + *
>>> + * Return size on success, error code on failure
>>> + */
>>> +static ssize_t hdmi_automation_enable(struct file *f, const char __user *buf,
>>> + size_t size, loff_t *pos)
>>> +{
>>> + struct amdgpu_dm_connector *aconnector = file_inode(f)->i_private;
>>> + char *wr_buf = NULL;
>>> + const uint32_t wr_buf_size = 40;
>>> + int max_param_num = 1;
>>> + uint8_t param_nums = 0;
>>> + long param[2];
>>> + bool hdmi_comp_auto;
>>> +
>>> + if (size == 0)
>>> + return -EINVAL;
>>> +
>>> + wr_buf = kcalloc(wr_buf_size, sizeof(char), GFP_KERNEL);
>>> + if (!wr_buf)
>>> + return -ENOSPC;
>>> +
>>> + if (parse_write_buffer_into_params(wr_buf, wr_buf_size,
>>> + (long *)param, buf,
>>> + max_param_num,
>>> + ¶m_nums)) {
>>> + kfree(wr_buf);
>>> + return -EINVAL;
>>> + }
>>> +
>>> + if (param_nums <= 0) {
>>> + kfree(wr_buf);
>>> + DRM_DEBUG_DRIVER("user data not be read\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + switch (param[0]) {
>>> + case 0:
>>> + hdmi_comp_auto = false;
>>> + break;
>>> + case 1:
>>> + default:
>>> + hdmi_comp_auto = true;
>>> + break;
>>> + }
>>> +
>>> + /* Persist setting across sink re-detection/hotplug. */
>>> + aconnector->hdmi_comp_auto = hdmi_comp_auto;
>>> +
>>> + kfree(wr_buf);
>>> + return size;
>>> +}
>>> +
>>> DEFINE_SHOW_ATTRIBUTE(dp_dsc_fec_support);
>>> DEFINE_SHOW_ATTRIBUTE(dmub_fw_state);
>>> DEFINE_SHOW_ATTRIBUTE(dmub_tracebuffer);
>>> @@ -3099,6 +3157,12 @@ static const struct file_operations dp_mst_link_settings_debugfs_fops = {
>>> .llseek = default_llseek
>>> };
>>>
>>> +static const struct file_operations hdmi_automation_debugfs_fops = {
>>> + .owner = THIS_MODULE,
>>> + .write = hdmi_automation_enable,
>>> + .llseek = default_llseek
>>> +};
>>> +
>>
>> I really don't understand why this can't just be a DEFINE_DEBUGFS_ATTRIBUTE,
>> and then you can replace the overcomplicated hdmi_automation_enable() with
>> just simple setter and getter functions that already receive the parameter
>> of the right type.
>
> Aaaand this was applied without the review comment being addressed.
>
> Along with the incredible other things in this series, like
>
> link.dpcd_caps.dongle_type = (typeof(link.dpcd_caps.dongle_type))0x7f;
>
> in "[PATCH 21/32] drm/amd/display: Add KUnit tests for amdgpu_dm_connector",
> I'm feeling like AMD is treating mainline as a vendor BSP to just dump code
> into.
>
Hi Nicolas,
Sorry, that's on me. I missed your review comment when I promoted the series. Thanks for pointing it out.
I'll take a look at your feedback and follow up with the author to make sure it's addressed.
Hi Alex,
Could you please take a look at the feedback above? It also looks like pointed out another issue in the patch below that may need to be addressed.
Please let us know whether a follow-up fix is needed.
Apologies for the oversight on my side.
Regards,
Chenyu
>>> static const struct {
>>> char *name;
>>> const struct file_operations *fops;
>>> @@ -3131,7 +3195,8 @@ static const struct {
>>> const struct file_operations *fops;
>>> } hdmi_debugfs_entries[] = {
>>> {"hdcp_sink_capability", &hdcp_sink_capability_fops},
>>> - {"hdmi_cec_state", &hdmi_cec_state_fops}
>>> + {"hdmi_cec_state", &hdmi_cec_state_fops},
>>> + {"hdmi_automation", &hdmi_automation_debugfs_fops}
>>> };
>>>
>>> /*
>>> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
>>> index a2d0bb34e639..6350212b9a66 100644
>>> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
>>> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
>>> @@ -193,6 +193,12 @@ enum dc_edid_status dm_helpers_parse_edid_caps(
>>> __func__, connector->name, edid_caps->frl_dsc_10bpc, edid_caps->frl_dsc_12bpc, \
>>> edid_caps->frl_dsc_all_bpp, edid_caps->frl_dsc_native_420, edid_caps->frl_dsc_max_slices, \
>>> edid_caps->frl_dsc_max_frl_rate, edid_caps->frl_dsc_total_chunk_kbytes);
>>> + if (aconnector->hdmi_comp_auto) {
>>> + edid_caps->panel_patch.hdmi_comp_auto = true;
>>> + link->ctx->dc->debug.force_frl_max = true;
>>> + link->ctx->dc->debug.force_frl_dsc = true;
>>> + drm_dbg_driver(connector->dev, "%s: HDMI_FRL [%s] hdmi_comp_auto --> enabled\n", __func__, connector->name);
>>> + }
>>> }
>>>
>>> apply_edid_quirks(link, edid_buf, edid_caps);
>>>
>>
>>
>
>
>
>
^ permalink raw reply [flat|nested] 38+ messages in thread
end of thread, other threads:[~2026-07-21 7:22 UTC | newest]
Thread overview: 38+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-10 9:44 [PATCH 00/32] DC Patches Jun 15 2026 Chenyu Chen
2026-06-10 9:44 ` [PATCH 01/32] drm/amd/display: Increase dcn42b uclk value Chenyu Chen
2026-06-10 9:44 ` [PATCH 02/32] drm/amd/display: Add a new interface to set idle opts in clock manager Chenyu Chen
2026-06-10 9:44 ` [PATCH 03/32] drm/amd/display: Add utm_qos_model pointer to clk_bw_params Chenyu Chen
2026-06-10 9:44 ` [PATCH 04/32] drm/amd/display: Remove get_utm_qos_model from soc_and_ip_translator Chenyu Chen
2026-06-10 9:44 ` [PATCH 05/32] drm/amd/display: Remove unused project_id from DML2 core instance Chenyu Chen
2026-06-10 9:44 ` [PATCH 06/32] drm/amd/display: Introduce dc_plane_cm and migrate surface update color path Chenyu Chen
2026-06-10 9:44 ` [PATCH 07/32] drm/amd/display: Extract backlight code to amdgpu_dm_backlight Chenyu Chen
2026-06-10 9:44 ` [PATCH 08/32] drm/amd/display: Extract audio code to amdgpu_dm_audio Chenyu Chen
2026-06-10 9:44 ` [PATCH 09/32] drm/amd/display: Extract DMUB code to amdgpu_dm_dmub Chenyu Chen
2026-06-10 9:44 ` [PATCH 10/32] drm/amd/display: Move HPD and IRQ handler code to amdgpu_dm_irq Chenyu Chen
2026-06-10 9:44 ` [PATCH 11/32] drm/amd/display: Extract connector and encoder code to amdgpu_dm_connector Chenyu Chen
2026-06-10 9:44 ` [PATCH 12/32] drm/amd/display: Fix conflicting types for dc_plane_cm functions Chenyu Chen
2026-06-10 9:44 ` [PATCH 13/32] drm/amd/display: Add PSR Active VTotal Control capability Chenyu Chen
2026-06-10 9:44 ` [PATCH 14/32] drm/amd/display: Enable pstate for DCN4 non-emulation builds Chenyu Chen
2026-06-10 9:44 ` [PATCH 15/32] drm/amd/display: Refactor surface_update_flags to flat struct with helpers Chenyu Chen
2026-06-10 9:45 ` [PATCH 16/32] drm/amd/display: Add Support for HDMI Compliance Automation Chenyu Chen
2026-06-10 14:43 ` Nicolas Frattaroli
2026-07-20 13:14 ` Nicolas Frattaroli
2026-07-21 2:27 ` Chen, Chen-Yu
2026-07-21 2:09 ` Chen, Chen-Yu
2026-06-10 9:45 ` [PATCH 17/32] drm/amd/display: Add KUnit tests for amdgpu_dm Chenyu Chen
2026-06-10 9:45 ` [PATCH 18/32] drm/amd/display: Add KUnit tests for amdgpu_dm_backlight Chenyu Chen
2026-06-10 9:45 ` [PATCH 19/32] drm/amd/display: Add KUnit tests for amdgpu_dm_audio Chenyu Chen
2026-06-10 9:45 ` [PATCH 20/32] drm/amd/display: Add KUnit tests for amdgpu_dm_dmub Chenyu Chen
2026-06-10 9:45 ` [PATCH 21/32] drm/amd/display: Add KUnit tests for amdgpu_dm_connector Chenyu Chen
2026-06-10 9:45 ` [PATCH 22/32] drm/amd/display: Add KUnit tests for amdgpu_dm_irq Chenyu Chen
2026-06-10 9:45 ` [PATCH 23/32] drm/amd/display: Add KUnit tests for amdgpu_dm_crtc Chenyu Chen
2026-06-10 9:45 ` [PATCH 24/32] drm/amd/display: Add KUnit tests for amdgpu_dm_services Chenyu Chen
2026-06-10 9:45 ` [PATCH 25/32] drm/amd/display: Add KUnit tests for amdgpu_dm_helpers Chenyu Chen
2026-06-10 9:45 ` [PATCH 26/32] drm/amd/display: Add KUnit tests for amdgpu_dm_quirks Chenyu Chen
2026-06-10 9:45 ` [PATCH 27/32] drm/amd/display: Add more KUnit tests for amdgpu_dm_pp_smu Chenyu Chen
2026-06-10 9:45 ` [PATCH 28/32] drm/amd/display: Add more KUnit tests for amdgpu_dm_mst_types Chenyu Chen
2026-06-10 9:45 ` [PATCH 29/32] drm/amd/display: Set default backlight without ACPI support Chenyu Chen
2026-06-10 9:45 ` [PATCH 30/32] drm/amd/display: Move backlight macros to backlight header Chenyu Chen
2026-06-10 9:45 ` [PATCH 31/32] drm/amd/display: [FW Promotion] Release 0.1.63.0 Chenyu Chen
2026-06-10 9:45 ` [PATCH 32/32] drm/amd/display: Promote DC to 3.2.386 Chenyu Chen
2026-06-15 13:11 ` [PATCH 00/32] DC Patches Jun 15 2026 Wheeler, Daniel
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.